Rollup merge of #123924 - compiler-errors:tuple-sugg, r=estebank

Fix various bugs in `ty_kind_suggestion`

Consolidates two implementations of `ty_kind_suggestion`
Fixes some misuse of the empty param-env
Fixes a problem where we suggested `(42)` instead of `(42,)` for tuple suggestions
Suggest a value when `return;`, making it consistent with `break;`
Fixes #123906
This commit is contained in:
Michael Goulet 2024-04-15 15:18:06 -04:00 committed by GitHub
commit 20a5fb3b4a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 139 additions and 130 deletions

View File

@ -671,68 +671,6 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
err
}
fn ty_kind_suggestion(&self, ty: Ty<'tcx>) -> Option<String> {
// Keep in sync with `rustc_hir_analysis/src/check/mod.rs:ty_kind_suggestion`.
// FIXME: deduplicate the above.
let tcx = self.infcx.tcx;
let implements_default = |ty| {
let Some(default_trait) = tcx.get_diagnostic_item(sym::Default) else {
return false;
};
self.infcx
.type_implements_trait(default_trait, [ty], self.param_env)
.must_apply_modulo_regions()
};
Some(match ty.kind() {
ty::Never | ty::Error(_) => return None,
ty::Bool => "false".to_string(),
ty::Char => "\'x\'".to_string(),
ty::Int(_) | ty::Uint(_) => "42".into(),
ty::Float(_) => "3.14159".into(),
ty::Slice(_) => "[]".to_string(),
ty::Adt(def, _) if Some(def.did()) == tcx.get_diagnostic_item(sym::Vec) => {
"vec![]".to_string()
}
ty::Adt(def, _) if Some(def.did()) == tcx.get_diagnostic_item(sym::String) => {
"String::new()".to_string()
}
ty::Adt(def, args) if def.is_box() => {
format!("Box::new({})", self.ty_kind_suggestion(args[0].expect_ty())?)
}
ty::Adt(def, _) if Some(def.did()) == tcx.get_diagnostic_item(sym::Option) => {
"None".to_string()
}
ty::Adt(def, args) if Some(def.did()) == tcx.get_diagnostic_item(sym::Result) => {
format!("Ok({})", self.ty_kind_suggestion(args[0].expect_ty())?)
}
ty::Adt(_, _) if implements_default(ty) => "Default::default()".to_string(),
ty::Ref(_, ty, mutability) => {
if let (ty::Str, hir::Mutability::Not) = (ty.kind(), mutability) {
"\"\"".to_string()
} else {
let Some(ty) = self.ty_kind_suggestion(*ty) else {
return None;
};
format!("&{}{ty}", mutability.prefix_str())
}
}
ty::Array(ty, len) => format!(
"[{}; {}]",
self.ty_kind_suggestion(*ty)?,
len.eval_target_usize(tcx, ty::ParamEnv::reveal_all()),
),
ty::Tuple(tys) => format!(
"({})",
tys.iter()
.map(|ty| self.ty_kind_suggestion(ty))
.collect::<Option<Vec<String>>>()?
.join(", ")
),
_ => "value".to_string(),
})
}
fn suggest_assign_value(
&self,
err: &mut Diag<'_>,
@ -742,7 +680,8 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
let ty = moved_place.ty(self.body, self.infcx.tcx).ty;
debug!("ty: {:?}, kind: {:?}", ty, ty.kind());
let Some(assign_value) = self.ty_kind_suggestion(ty) else {
let Some(assign_value) = self.infcx.err_ctxt().ty_kind_suggestion(self.param_env, ty)
else {
return;
};

View File

@ -81,7 +81,6 @@ use rustc_errors::ErrorGuaranteed;
use rustc_errors::{pluralize, struct_span_code_err, Diag};
use rustc_hir::def_id::{DefId, LocalDefId};
use rustc_hir::intravisit::Visitor;
use rustc_hir::Mutability;
use rustc_index::bit_set::BitSet;
use rustc_infer::infer::error_reporting::ObligationCauseExt as _;
use rustc_infer::infer::outlives::env::OutlivesEnvironment;
@ -96,8 +95,9 @@ use rustc_span::symbol::{kw, sym, Ident};
use rustc_span::{def_id::CRATE_DEF_ID, BytePos, Span, Symbol, DUMMY_SP};
use rustc_target::abi::VariantIdx;
use rustc_target::spec::abi::Abi;
use rustc_trait_selection::infer::InferCtxtExt;
use rustc_trait_selection::traits::error_reporting::suggestions::ReturnsVisitor;
use rustc_trait_selection::traits::error_reporting::suggestions::{
ReturnsVisitor, TypeErrCtxtExt as _,
};
use rustc_trait_selection::traits::error_reporting::TypeErrCtxtExt as _;
use rustc_trait_selection::traits::ObligationCtxt;
@ -467,67 +467,6 @@ fn fn_sig_suggestion<'tcx>(
)
}
pub fn ty_kind_suggestion<'tcx>(ty: Ty<'tcx>, tcx: TyCtxt<'tcx>) -> Option<String> {
// Keep in sync with `rustc_borrowck/src/diagnostics/conflict_errors.rs:ty_kind_suggestion`.
// FIXME: deduplicate the above.
let implements_default = |ty| {
let Some(default_trait) = tcx.get_diagnostic_item(sym::Default) else {
return false;
};
let infcx = tcx.infer_ctxt().build();
infcx
.type_implements_trait(default_trait, [ty], ty::ParamEnv::reveal_all())
.must_apply_modulo_regions()
};
Some(match ty.kind() {
ty::Never | ty::Error(_) => return None,
ty::Bool => "false".to_string(),
ty::Char => "\'x\'".to_string(),
ty::Int(_) | ty::Uint(_) => "42".into(),
ty::Float(_) => "3.14159".into(),
ty::Slice(_) => "[]".to_string(),
ty::Adt(def, _) if Some(def.did()) == tcx.get_diagnostic_item(sym::Vec) => {
"vec![]".to_string()
}
ty::Adt(def, _) if Some(def.did()) == tcx.get_diagnostic_item(sym::String) => {
"String::new()".to_string()
}
ty::Adt(def, args) if def.is_box() => {
format!("Box::new({})", ty_kind_suggestion(args[0].expect_ty(), tcx)?)
}
ty::Adt(def, _) if Some(def.did()) == tcx.get_diagnostic_item(sym::Option) => {
"None".to_string()
}
ty::Adt(def, args) if Some(def.did()) == tcx.get_diagnostic_item(sym::Result) => {
format!("Ok({})", ty_kind_suggestion(args[0].expect_ty(), tcx)?)
}
ty::Adt(_, _) if implements_default(ty) => "Default::default()".to_string(),
ty::Ref(_, ty, mutability) => {
if let (ty::Str, Mutability::Not) = (ty.kind(), mutability) {
"\"\"".to_string()
} else {
let Some(ty) = ty_kind_suggestion(*ty, tcx) else {
return None;
};
format!("&{}{ty}", mutability.prefix_str())
}
}
ty::Array(ty, len) => format!(
"[{}; {}]",
ty_kind_suggestion(*ty, tcx)?,
len.eval_target_usize(tcx, ty::ParamEnv::reveal_all()),
),
ty::Tuple(tys) => format!(
"({})",
tys.iter()
.map(|ty| ty_kind_suggestion(ty, tcx))
.collect::<Option<Vec<String>>>()?
.join(", ")
),
_ => "value".to_string(),
})
}
/// Return placeholder code for the given associated item.
/// Similar to `ty::AssocItem::suggestion`, but appropriate for use as the code snippet of a
/// structured suggestion.
@ -562,7 +501,12 @@ fn suggestion_signature<'tcx>(
}
ty::AssocKind::Const => {
let ty = tcx.type_of(assoc.def_id).instantiate_identity();
let val = ty_kind_suggestion(ty, tcx).unwrap_or_else(|| "value".to_string());
let val = tcx
.infer_ctxt()
.build()
.err_ctxt()
.ty_kind_suggestion(tcx.param_env(assoc.def_id), ty)
.unwrap_or_else(|| "value".to_string());
format!("const {}: {} = {};", assoc.name, ty, val)
}
}

View File

@ -63,6 +63,7 @@ use rustc_span::DesugaringKind;
use rustc_span::{BytePos, Span};
use rustc_target::spec::abi::Abi;
use rustc_trait_selection::infer::InferCtxtExt as _;
use rustc_trait_selection::traits::error_reporting::suggestions::TypeErrCtxtExt;
use rustc_trait_selection::traits::error_reporting::TypeErrCtxtExt as _;
use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt;
use rustc_trait_selection::traits::TraitEngineExt as _;
@ -1616,6 +1617,15 @@ impl<'tcx, 'exprs, E: AsCoercionSite> CoerceMany<'tcx, 'exprs, E> {
E0069,
"`return;` in a function whose return type is not `()`"
);
if let Some(value) = fcx.err_ctxt().ty_kind_suggestion(fcx.param_env, found)
{
err.span_suggestion_verbose(
cause.span.shrink_to_hi(),
"give the `return` a value of the expected type",
format!(" {value}"),
Applicability::HasPlaceholders,
);
}
err.span_label(cause.span, "return type is not `()`");
}
ObligationCauseCode::BlockTailExpression(blk_id, ..) => {

View File

@ -35,7 +35,6 @@ use rustc_hir::def_id::DefId;
use rustc_hir::intravisit::Visitor;
use rustc_hir::lang_items::LangItem;
use rustc_hir::{ExprKind, HirId, QPath};
use rustc_hir_analysis::check::ty_kind_suggestion;
use rustc_hir_analysis::hir_ty_lowering::HirTyLowerer as _;
use rustc_infer::infer;
use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
@ -57,6 +56,7 @@ use rustc_span::Span;
use rustc_target::abi::{FieldIdx, FIRST_VARIANT};
use rustc_target::spec::abi::Abi::RustIntrinsic;
use rustc_trait_selection::infer::InferCtxtExt;
use rustc_trait_selection::traits::error_reporting::suggestions::TypeErrCtxtExt as _;
use rustc_trait_selection::traits::error_reporting::TypeErrCtxtExt;
use rustc_trait_selection::traits::ObligationCtxt;
use rustc_trait_selection::traits::{self, ObligationCauseCode};
@ -694,7 +694,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
);
let error = Some(Sorts(ExpectedFound { expected: ty, found: e_ty }));
self.annotate_loop_expected_due_to_inference(err, expr, error);
if let Some(val) = ty_kind_suggestion(ty, tcx) {
if let Some(val) =
self.err_ctxt().ty_kind_suggestion(self.param_env, ty)
{
err.span_suggestion_verbose(
expr.span.shrink_to_hi(),
"give the `break` a value of the expected type",

View File

@ -22,6 +22,7 @@
#![feature(box_patterns)]
#![feature(control_flow_enum)]
#![feature(extract_if)]
#![feature(if_let_guard)]
#![feature(let_chains)]
#![feature(option_take_if)]
#![feature(never_type)]

View File

@ -4540,6 +4540,61 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
Applicability::MachineApplicable,
);
}
fn ty_kind_suggestion(&self, param_env: ty::ParamEnv<'tcx>, ty: Ty<'tcx>) -> Option<String> {
let tcx = self.infcx.tcx;
let implements_default = |ty| {
let Some(default_trait) = tcx.get_diagnostic_item(sym::Default) else {
return false;
};
self.type_implements_trait(default_trait, [ty], param_env).must_apply_modulo_regions()
};
Some(match ty.kind() {
ty::Never | ty::Error(_) => return None,
ty::Bool => "false".to_string(),
ty::Char => "\'x\'".to_string(),
ty::Int(_) | ty::Uint(_) => "42".into(),
ty::Float(_) => "3.14159".into(),
ty::Slice(_) => "[]".to_string(),
ty::Adt(def, _) if Some(def.did()) == tcx.get_diagnostic_item(sym::Vec) => {
"vec![]".to_string()
}
ty::Adt(def, _) if Some(def.did()) == tcx.get_diagnostic_item(sym::String) => {
"String::new()".to_string()
}
ty::Adt(def, args) if def.is_box() => {
format!("Box::new({})", self.ty_kind_suggestion(param_env, args[0].expect_ty())?)
}
ty::Adt(def, _) if Some(def.did()) == tcx.get_diagnostic_item(sym::Option) => {
"None".to_string()
}
ty::Adt(def, args) if Some(def.did()) == tcx.get_diagnostic_item(sym::Result) => {
format!("Ok({})", self.ty_kind_suggestion(param_env, args[0].expect_ty())?)
}
ty::Adt(_, _) if implements_default(ty) => "Default::default()".to_string(),
ty::Ref(_, ty, mutability) => {
if let (ty::Str, hir::Mutability::Not) = (ty.kind(), mutability) {
"\"\"".to_string()
} else {
let ty = self.ty_kind_suggestion(param_env, *ty)?;
format!("&{}{ty}", mutability.prefix_str())
}
}
ty::Array(ty, len) if let Some(len) = len.try_eval_target_usize(tcx, param_env) => {
format!("[{}; {}]", self.ty_kind_suggestion(param_env, *ty)?, len)
}
ty::Tuple(tys) => format!(
"({}{})",
tys.iter()
.map(|ty| self.ty_kind_suggestion(param_env, ty))
.collect::<Option<Vec<String>>>()?
.join(", "),
if tys.len() == 1 { "," } else { "" }
),
_ => "value".to_string(),
})
}
}
/// Add a hint to add a missing borrow or remove an unnecessary one.

View File

@ -0,0 +1,8 @@
fn as_chunks<const N: usize>() -> [u8; N] {
loop {
break;
//~^ ERROR mismatched types
}
}
fn main() {}

View File

@ -0,0 +1,18 @@
error[E0308]: mismatched types
--> $DIR/value-suggestion-ice-123906.rs:3:9
|
LL | fn as_chunks<const N: usize>() -> [u8; N] {
| ------- expected `[u8; ]` because of this return type
LL | loop {
| ---- this loop is expected to be of type `[u8; N]`
LL | break;
| ^^^^^ expected `[u8; N]`, found `()`
|
help: give the `break` a value of the expected type
|
LL | break value;
| +++++
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0308`.

View File

@ -5,6 +5,11 @@ LL | fn foo() -> u8 {
| -- expected `u8` because of this return type
LL | return;
| ^^^^^^ return type is not `()`
|
help: give the `return` a value of the expected type
|
LL | return 42;
| ++
error: aborting due to 1 previous error

View File

@ -5,6 +5,11 @@ LL | fn g() -> isize { return; }
| ----- ^^^^^^ return type is not `()`
| |
| expected `isize` because of this return type
|
help: give the `return` a value of the expected type
|
LL | fn g() -> isize { return 42; }
| ++
error: aborting due to 1 previous error

View File

@ -0,0 +1,6 @@
fn test() -> (i32,) {
return;
//~^ ERROR `return;` in a function whose return type is not `()`
}
fn main() {}

View File

@ -0,0 +1,16 @@
error[E0069]: `return;` in a function whose return type is not `()`
--> $DIR/suggest-a-value.rs:2:5
|
LL | fn test() -> (i32,) {
| ------ expected `(i32,)` because of this return type
LL | return;
| ^^^^^^ return type is not `()`
|
help: give the `return` a value of the expected type
|
LL | return (42,);
| +++++
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0069`.