diff options
Diffstat (limited to 'compiler/rustc_infer/src/infer/error_reporting')
12 files changed, 1395 insertions, 783 deletions
diff --git a/compiler/rustc_infer/src/infer/error_reporting/mod.rs b/compiler/rustc_infer/src/infer/error_reporting/mod.rs index 9ff703e52..987559d7e 100644 --- a/compiler/rustc_infer/src/infer/error_reporting/mod.rs +++ b/compiler/rustc_infer/src/infer/error_reporting/mod.rs @@ -1,3 +1,4 @@ +// ignore-tidy-filelength //! Error Reporting Code for the inference engine //! //! Because of the way inference, and in particular region inference, @@ -55,10 +56,9 @@ use crate::infer::ExpectedFound; use crate::traits::error_reporting::report_object_safety_error; use crate::traits::{ IfExpressionCause, MatchExpressionArmCause, ObligationCause, ObligationCauseCode, - StatementAsExpression, }; -use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_data_structures::fx::{FxIndexMap, FxIndexSet}; use rustc_errors::{pluralize, struct_span_err, Diagnostic, ErrorGuaranteed, IntoDiagnosticArg}; use rustc_errors::{Applicability, DiagnosticBuilder, DiagnosticStyledString, MultiSpan}; use rustc_hir as hir; @@ -67,18 +67,19 @@ use rustc_hir::def_id::{DefId, LocalDefId}; use rustc_hir::lang_items::LangItem; use rustc_hir::Node; use rustc_middle::dep_graph::DepContext; -use rustc_middle::ty::print::with_no_trimmed_paths; use rustc_middle::ty::relate::{self, RelateResult, TypeRelation}; use rustc_middle::ty::{ - self, error::TypeError, Binder, List, Region, Ty, TyCtxt, TypeFoldable, TypeSuperVisitable, + self, error::TypeError, List, Region, Ty, TyCtxt, TypeFoldable, TypeSuperVisitable, TypeVisitable, }; use rustc_span::{sym, symbol::kw, BytePos, DesugaringKind, Pos, Span}; use rustc_target::spec::abi; use std::ops::{ControlFlow, Deref}; +use std::path::PathBuf; use std::{cmp, fmt, iter}; mod note; +mod suggest; pub(crate) mod need_type_info; pub use need_type_info::TypeAnnotationNeeded; @@ -91,6 +92,8 @@ pub mod nice_region_error; pub struct TypeErrCtxt<'a, 'tcx> { pub infcx: &'a InferCtxt<'tcx>, pub typeck_results: Option<std::cell::Ref<'a, ty::TypeckResults<'tcx>>>, + pub normalize_fn_sig: Box<dyn Fn(ty::PolyFnSig<'tcx>) -> ty::PolyFnSig<'tcx> + 'a>, + pub fallback_has_occurred: bool, } impl TypeErrCtxt<'_, '_> { @@ -206,9 +209,12 @@ fn msg_span_from_early_bound_and_free_regions<'tcx>( }; (text, sp) } - ty::BrAnon(idx) => ( + ty::BrAnon(idx, span) => ( format!("the anonymous lifetime #{} defined here", idx + 1), - tcx.def_span(scope) + match span { + Some(span) => span, + None => tcx.def_span(scope) + } ), _ => ( format!("the lifetime `{}` as defined here", region), @@ -253,6 +259,7 @@ fn label_msg_span( } } +#[instrument(level = "trace", skip(tcx))] pub fn unexpected_hidden_region_diagnostic<'tcx>( tcx: TyCtxt<'tcx>, span: Span, @@ -330,33 +337,36 @@ pub fn unexpected_hidden_region_diagnostic<'tcx>( } impl<'tcx> InferCtxt<'tcx> { - pub fn get_impl_future_output_ty(&self, ty: Ty<'tcx>) -> Option<Binder<'tcx, Ty<'tcx>>> { - if let ty::Opaque(def_id, substs) = ty.kind() { - let future_trait = self.tcx.require_lang_item(LangItem::Future, None); - // Future::Output - let item_def_id = self.tcx.associated_item_def_ids(future_trait)[0]; + pub fn get_impl_future_output_ty(&self, ty: Ty<'tcx>) -> Option<Ty<'tcx>> { + let (def_id, substs) = match *ty.kind() { + ty::Opaque(def_id, substs) => (def_id, substs), + ty::Projection(data) + if self.tcx.def_kind(data.item_def_id) == DefKind::ImplTraitPlaceholder => + { + (data.item_def_id, data.substs) + } + _ => return None, + }; - let bounds = self.tcx.bound_explicit_item_bounds(*def_id); + let future_trait = self.tcx.require_lang_item(LangItem::Future, None); + let item_def_id = self.tcx.associated_item_def_ids(future_trait)[0]; - for (predicate, _) in bounds.subst_iter_copied(self.tcx, substs) { - let output = predicate + self.tcx.bound_explicit_item_bounds(def_id).subst_iter_copied(self.tcx, substs).find_map( + |(predicate, _)| { + predicate .kind() .map_bound(|kind| match kind { - ty::PredicateKind::Projection(projection_predicate) + ty::PredicateKind::Clause(ty::Clause::Projection(projection_predicate)) if projection_predicate.projection_ty.item_def_id == item_def_id => { projection_predicate.term.ty() } _ => None, }) - .transpose(); - if output.is_some() { - // We don't account for multiple `Future::Output = Ty` constraints. - return output; - } - } - } - None + .no_bound_vars() + .flatten() + }, + ) } } @@ -534,7 +544,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { fn print_dyn_existential( self, - _predicates: &'tcx ty::List<ty::Binder<'tcx, ty::ExistentialPredicate<'tcx>>>, + _predicates: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>, ) -> Result<Self::DynExistential, Self::Error> { Err(NonTrivialPath) } @@ -793,87 +803,6 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { } } - fn suggest_remove_semi_or_return_binding( - &self, - err: &mut Diagnostic, - first_id: Option<hir::HirId>, - first_ty: Ty<'tcx>, - first_span: Span, - second_id: Option<hir::HirId>, - second_ty: Ty<'tcx>, - second_span: Span, - ) { - let remove_semicolon = [ - (first_id, self.resolve_vars_if_possible(second_ty)), - (second_id, self.resolve_vars_if_possible(first_ty)), - ] - .into_iter() - .find_map(|(id, ty)| { - let hir::Node::Block(blk) = self.tcx.hir().get(id?) else { return None }; - self.could_remove_semicolon(blk, ty) - }); - match remove_semicolon { - Some((sp, StatementAsExpression::NeedsBoxing)) => { - err.multipart_suggestion( - "consider removing this semicolon and boxing the expressions", - vec![ - (first_span.shrink_to_lo(), "Box::new(".to_string()), - (first_span.shrink_to_hi(), ")".to_string()), - (second_span.shrink_to_lo(), "Box::new(".to_string()), - (second_span.shrink_to_hi(), ")".to_string()), - (sp, String::new()), - ], - Applicability::MachineApplicable, - ); - } - Some((sp, StatementAsExpression::CorrectType)) => { - err.span_suggestion_short( - sp, - "consider removing this semicolon", - "", - Applicability::MachineApplicable, - ); - } - None => { - for (id, ty) in [(first_id, second_ty), (second_id, first_ty)] { - if let Some(id) = id - && let hir::Node::Block(blk) = self.tcx.hir().get(id) - && self.consider_returning_binding(blk, ty, err) - { - break; - } - } - } - } - } - - fn suggest_boxing_for_return_impl_trait( - &self, - err: &mut Diagnostic, - return_sp: Span, - arm_spans: impl Iterator<Item = Span>, - ) { - err.multipart_suggestion( - "you could change the return type to be a boxed trait object", - vec![ - (return_sp.with_hi(return_sp.lo() + BytePos(4)), "Box<dyn".to_string()), - (return_sp.shrink_to_hi(), ">".to_string()), - ], - Applicability::MaybeIncorrect, - ); - let sugg = arm_spans - .flat_map(|sp| { - [(sp.shrink_to_lo(), "Box::new(".to_string()), (sp.shrink_to_hi(), ")".to_string())] - .into_iter() - }) - .collect::<Vec<_>>(); - err.multipart_suggestion( - "if you change the return type to expect trait objects, box the returned expressions", - sugg, - Applicability::MaybeIncorrect, - ); - } - /// Given that `other_ty` is the same as a type argument for `name` in `sub`, populate `value` /// highlighting `name` and every type argument that isn't at `pos` (which is `other_ty`), and /// populate `other_value` with `other_ty`. @@ -1003,22 +932,14 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { } } - fn normalize_fn_sig_for_diagnostic(&self, sig: ty::PolyFnSig<'tcx>) -> ty::PolyFnSig<'tcx> { - if let Some(normalize) = &self.normalize_fn_sig_for_diagnostic { - normalize(self, sig) - } else { - sig - } - } - /// Given two `fn` signatures highlight only sub-parts that are different. fn cmp_fn_sig( &self, sig1: &ty::PolyFnSig<'tcx>, sig2: &ty::PolyFnSig<'tcx>, ) -> (DiagnosticStyledString, DiagnosticStyledString) { - let sig1 = &self.normalize_fn_sig_for_diagnostic(*sig1); - let sig2 = &self.normalize_fn_sig_for_diagnostic(*sig2); + let sig1 = &(self.normalize_fn_sig)(*sig1); + let sig2 = &(self.normalize_fn_sig)(*sig2); let get_lifetimes = |sig| { use rustc_hir::def::Namespace; @@ -1258,7 +1179,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { let num_display_types = consts_offset - regions_len; for (i, (ta1, ta2)) in type_arguments.take(num_display_types).enumerate() { let i = i + regions_len; - if ta1 == ta2 { + if ta1 == ta2 && !self.tcx.sess.verbose() { values.0.push_normal("_"); values.1.push_normal("_"); } else { @@ -1274,7 +1195,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { let const_arguments = sub1.consts().zip(sub2.consts()); for (i, (ca1, ca2)) in const_arguments.enumerate() { let i = i + consts_offset; - if ca1 == ca2 { + if ca1 == ca2 && !self.tcx.sess.verbose() { values.0.push_normal("_"); values.1.push_normal("_"); } else { @@ -1347,10 +1268,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { .map(|(mod_str, _)| mod_str.len() + separator_len) .sum(); - debug!( - "cmp: separator_len={}, split_idx={}, min_len={}", - separator_len, split_idx, min_len - ); + debug!(?separator_len, ?split_idx, ?min_len, "cmp"); if split_idx >= min_len { // paths are identical, highlight everything @@ -1361,7 +1279,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { } else { let (common, uniq1) = t1_str.split_at(split_idx); let (_, uniq2) = t2_str.split_at(split_idx); - debug!("cmp: common={}, uniq1={}, uniq2={}", common, uniq1, uniq2); + debug!(?common, ?uniq1, ?uniq2, "cmp"); values.0.push_normal(common); values.0.push_highlighted(uniq1); @@ -1453,7 +1371,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { (ty::FnPtr(sig1), ty::FnPtr(sig2)) => self.cmp_fn_sig(sig1, sig2), _ => { - if t1 == t2 { + if t1 == t2 && !self.tcx.sess.verbose() { // The two types are the same, elide and don't highlight. (DiagnosticStyledString::normal("_"), DiagnosticStyledString::normal("_")) } else { @@ -1498,9 +1416,9 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { values = None; } struct OpaqueTypesVisitor<'tcx> { - types: FxHashMap<TyCategory, FxHashSet<Span>>, - expected: FxHashMap<TyCategory, FxHashSet<Span>>, - found: FxHashMap<TyCategory, FxHashSet<Span>>, + types: FxIndexMap<TyCategory, FxIndexSet<Span>>, + expected: FxIndexMap<TyCategory, FxIndexSet<Span>>, + found: FxIndexMap<TyCategory, FxIndexSet<Span>>, ignore_span: Span, tcx: TyCtxt<'tcx>, } @@ -1538,7 +1456,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { &self, err: &mut Diagnostic, target: &str, - types: &FxHashMap<TyCategory, FxHashSet<Span>>, + types: &FxIndexMap<TyCategory, FxIndexSet<Span>>, ) { for (key, values) in types.iter() { let count = values.len(); @@ -1654,55 +1572,46 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { } ValuePairs::Regions(_) => (false, Mismatch::Fixed("lifetime")), }; - let vals = match self.values_str(values) { - Some((expected, found)) => Some((expected, found)), - None => { - // Derived error. Cancel the emitter. - // NOTE(eddyb) this was `.cancel()`, but `diag` - // is borrowed, so we can't fully defuse it. - diag.downgrade_to_delayed_bug(); - return; - } + let Some(vals) = self.values_str(values) else { + // Derived error. Cancel the emitter. + // NOTE(eddyb) this was `.cancel()`, but `diag` + // is borrowed, so we can't fully defuse it. + diag.downgrade_to_delayed_bug(); + return; }; - (vals, exp_found, is_simple_error, Some(values)) + (Some(vals), exp_found, is_simple_error, Some(values)) } }; - match terr { - // Ignore msg for object safe coercion - // since E0038 message will be printed - TypeError::ObjectUnsafeCoercion(_) => {} - _ => { - let mut label_or_note = |span: Span, msg: &str| { - if (prefer_label && is_simple_error) || &[span] == diag.span.primary_spans() { - diag.span_label(span, msg); - } else { - diag.span_note(span, msg); - } - }; - if let Some((sp, msg)) = secondary_span { - if swap_secondary_and_primary { - let terr = if let Some(infer::ValuePairs::Terms(infer::ExpectedFound { - expected, - .. - })) = values - { - format!("expected this to be `{}`", expected) - } else { - terr.to_string() - }; - label_or_note(sp, &terr); - label_or_note(span, &msg); - } else { - label_or_note(span, &terr.to_string()); - label_or_note(sp, &msg); - } - } else { - label_or_note(span, &terr.to_string()); - } + let mut label_or_note = |span: Span, msg: &str| { + if (prefer_label && is_simple_error) || &[span] == diag.span.primary_spans() { + diag.span_label(span, msg); + } else { + diag.span_note(span, msg); } }; - if let Some((expected, found)) = expected_found { + if let Some((sp, msg)) = secondary_span { + if swap_secondary_and_primary { + let terr = if let Some(infer::ValuePairs::Terms(infer::ExpectedFound { + expected, + .. + })) = values + { + format!("expected this to be `{}`", expected) + } else { + terr.to_string() + }; + label_or_note(sp, &terr); + label_or_note(span, &msg); + } else { + label_or_note(span, &terr.to_string()); + label_or_note(sp, &msg); + } + } else { + label_or_note(span, &terr.to_string()); + } + + if let Some((expected, found, exp_p, found_p)) = expected_found { let (expected_label, found_label, exp_found) = match exp_found { Mismatch::Variable(ef) => ( ef.expected.prefix_string(self.tcx), @@ -1819,32 +1728,41 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { } TypeError::Sorts(values) => { let extra = expected == found; - let sort_string = |ty: Ty<'tcx>| match (extra, ty.kind()) { - (true, ty::Opaque(def_id, _)) => { - let sm = self.tcx.sess.source_map(); - let pos = sm.lookup_char_pos(self.tcx.def_span(*def_id).lo()); - format!( - " (opaque type at <{}:{}:{}>)", - sm.filename_for_diagnostics(&pos.file.name), - pos.line, - pos.col.to_usize() + 1, - ) - } - (true, ty::Projection(proj)) - if self.tcx.def_kind(proj.item_def_id) - == DefKind::ImplTraitPlaceholder => - { - let sm = self.tcx.sess.source_map(); - let pos = sm.lookup_char_pos(self.tcx.def_span(proj.item_def_id).lo()); - format!( - " (trait associated opaque type at <{}:{}:{}>)", - sm.filename_for_diagnostics(&pos.file.name), - pos.line, - pos.col.to_usize() + 1, - ) + let sort_string = |ty: Ty<'tcx>, path: Option<PathBuf>| { + let mut s = match (extra, ty.kind()) { + (true, ty::Opaque(def_id, _)) => { + let sm = self.tcx.sess.source_map(); + let pos = sm.lookup_char_pos(self.tcx.def_span(*def_id).lo()); + format!( + " (opaque type at <{}:{}:{}>)", + sm.filename_for_diagnostics(&pos.file.name), + pos.line, + pos.col.to_usize() + 1, + ) + } + (true, ty::Projection(proj)) + if self.tcx.def_kind(proj.item_def_id) + == DefKind::ImplTraitPlaceholder => + { + let sm = self.tcx.sess.source_map(); + let pos = sm.lookup_char_pos(self.tcx.def_span(proj.item_def_id).lo()); + format!( + " (trait associated opaque type at <{}:{}:{}>)", + sm.filename_for_diagnostics(&pos.file.name), + pos.line, + pos.col.to_usize() + 1, + ) + } + (true, _) => format!(" ({})", ty.sort_string(self.tcx)), + (false, _) => "".to_string(), + }; + if let Some(path) = path { + s.push_str(&format!( + "\nthe full type name has been written to '{}'", + path.display(), + )); } - (true, _) => format!(" ({})", ty.sort_string(self.tcx)), - (false, _) => "".to_string(), + s }; if !(values.expected.is_simple_text() && values.found.is_simple_text()) || (exp_found.map_or(false, |ef| { @@ -1866,14 +1784,11 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { expected, &found_label, found, - &sort_string(values.expected), - &sort_string(values.found), + &sort_string(values.expected, exp_p), + &sort_string(values.found, found_p), ); } } - TypeError::ObjectUnsafeCoercion(_) => { - diag.note_unsuccessful_coercion(found, expected); - } _ => { debug!( "note_type_err: exp_found={:?}, expected={:?} found={:?}", @@ -1944,310 +1859,6 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { debug!(?diag); } - fn suggest_tuple_pattern( - &self, - cause: &ObligationCause<'tcx>, - exp_found: &ty::error::ExpectedFound<Ty<'tcx>>, - diag: &mut Diagnostic, - ) { - // Heavily inspired by `FnCtxt::suggest_compatible_variants`, with - // some modifications due to that being in typeck and this being in infer. - if let ObligationCauseCode::Pattern { .. } = cause.code() { - if let ty::Adt(expected_adt, substs) = exp_found.expected.kind() { - let compatible_variants: Vec<_> = expected_adt - .variants() - .iter() - .filter(|variant| { - variant.fields.len() == 1 && variant.ctor_kind == hir::def::CtorKind::Fn - }) - .filter_map(|variant| { - let sole_field = &variant.fields[0]; - let sole_field_ty = sole_field.ty(self.tcx, substs); - if self.same_type_modulo_infer(sole_field_ty, exp_found.found) { - let variant_path = - with_no_trimmed_paths!(self.tcx.def_path_str(variant.def_id)); - // FIXME #56861: DRYer prelude filtering - if let Some(path) = variant_path.strip_prefix("std::prelude::") { - if let Some((_, path)) = path.split_once("::") { - return Some(path.to_string()); - } - } - Some(variant_path) - } else { - None - } - }) - .collect(); - match &compatible_variants[..] { - [] => {} - [variant] => { - diag.multipart_suggestion_verbose( - &format!("try wrapping the pattern in `{}`", variant), - vec![ - (cause.span.shrink_to_lo(), format!("{}(", variant)), - (cause.span.shrink_to_hi(), ")".to_string()), - ], - Applicability::MaybeIncorrect, - ); - } - _ => { - // More than one matching variant. - diag.multipart_suggestions( - &format!( - "try wrapping the pattern in a variant of `{}`", - self.tcx.def_path_str(expected_adt.did()) - ), - compatible_variants.into_iter().map(|variant| { - vec![ - (cause.span.shrink_to_lo(), format!("{}(", variant)), - (cause.span.shrink_to_hi(), ")".to_string()), - ] - }), - Applicability::MaybeIncorrect, - ); - } - } - } - } - } - - /// A possible error is to forget to add `.await` when using futures: - /// - /// ```compile_fail,E0308 - /// async fn make_u32() -> u32 { - /// 22 - /// } - /// - /// fn take_u32(x: u32) {} - /// - /// async fn foo() { - /// let x = make_u32(); - /// take_u32(x); - /// } - /// ``` - /// - /// This routine checks if the found type `T` implements `Future<Output=U>` where `U` is the - /// expected type. If this is the case, and we are inside of an async body, it suggests adding - /// `.await` to the tail of the expression. - fn suggest_await_on_expect_found( - &self, - cause: &ObligationCause<'tcx>, - exp_span: Span, - exp_found: &ty::error::ExpectedFound<Ty<'tcx>>, - diag: &mut Diagnostic, - ) { - debug!( - "suggest_await_on_expect_found: exp_span={:?}, expected_ty={:?}, found_ty={:?}", - exp_span, exp_found.expected, exp_found.found, - ); - - if let ObligationCauseCode::CompareImplItemObligation { .. } = cause.code() { - return; - } - - match ( - self.get_impl_future_output_ty(exp_found.expected).map(Binder::skip_binder), - self.get_impl_future_output_ty(exp_found.found).map(Binder::skip_binder), - ) { - (Some(exp), Some(found)) if self.same_type_modulo_infer(exp, found) => match cause - .code() - { - ObligationCauseCode::IfExpression(box IfExpressionCause { then_id, .. }) => { - let then_span = self.find_block_span_from_hir_id(*then_id); - diag.multipart_suggestion( - "consider `await`ing on both `Future`s", - vec![ - (then_span.shrink_to_hi(), ".await".to_string()), - (exp_span.shrink_to_hi(), ".await".to_string()), - ], - Applicability::MaybeIncorrect, - ); - } - ObligationCauseCode::MatchExpressionArm(box MatchExpressionArmCause { - prior_arms, - .. - }) => { - if let [.., arm_span] = &prior_arms[..] { - diag.multipart_suggestion( - "consider `await`ing on both `Future`s", - vec![ - (arm_span.shrink_to_hi(), ".await".to_string()), - (exp_span.shrink_to_hi(), ".await".to_string()), - ], - Applicability::MaybeIncorrect, - ); - } else { - diag.help("consider `await`ing on both `Future`s"); - } - } - _ => { - diag.help("consider `await`ing on both `Future`s"); - } - }, - (_, Some(ty)) if self.same_type_modulo_infer(exp_found.expected, ty) => { - diag.span_suggestion_verbose( - exp_span.shrink_to_hi(), - "consider `await`ing on the `Future`", - ".await", - Applicability::MaybeIncorrect, - ); - } - (Some(ty), _) if self.same_type_modulo_infer(ty, exp_found.found) => match cause.code() - { - ObligationCauseCode::Pattern { span: Some(then_span), .. } => { - diag.span_suggestion_verbose( - then_span.shrink_to_hi(), - "consider `await`ing on the `Future`", - ".await", - Applicability::MaybeIncorrect, - ); - } - ObligationCauseCode::IfExpression(box IfExpressionCause { then_id, .. }) => { - let then_span = self.find_block_span_from_hir_id(*then_id); - diag.span_suggestion_verbose( - then_span.shrink_to_hi(), - "consider `await`ing on the `Future`", - ".await", - Applicability::MaybeIncorrect, - ); - } - ObligationCauseCode::MatchExpressionArm(box MatchExpressionArmCause { - ref prior_arms, - .. - }) => { - diag.multipart_suggestion_verbose( - "consider `await`ing on the `Future`", - prior_arms - .iter() - .map(|arm| (arm.shrink_to_hi(), ".await".to_string())) - .collect(), - Applicability::MaybeIncorrect, - ); - } - _ => {} - }, - _ => {} - } - } - - fn suggest_accessing_field_where_appropriate( - &self, - cause: &ObligationCause<'tcx>, - exp_found: &ty::error::ExpectedFound<Ty<'tcx>>, - diag: &mut Diagnostic, - ) { - debug!( - "suggest_accessing_field_where_appropriate(cause={:?}, exp_found={:?})", - cause, exp_found - ); - if let ty::Adt(expected_def, expected_substs) = exp_found.expected.kind() { - if expected_def.is_enum() { - return; - } - - if let Some((name, ty)) = expected_def - .non_enum_variant() - .fields - .iter() - .filter(|field| field.vis.is_accessible_from(field.did, self.tcx)) - .map(|field| (field.name, field.ty(self.tcx, expected_substs))) - .find(|(_, ty)| self.same_type_modulo_infer(*ty, exp_found.found)) - { - if let ObligationCauseCode::Pattern { span: Some(span), .. } = *cause.code() { - if let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(span) { - let suggestion = if expected_def.is_struct() { - format!("{}.{}", snippet, name) - } else if expected_def.is_union() { - format!("unsafe {{ {}.{} }}", snippet, name) - } else { - return; - }; - diag.span_suggestion( - span, - &format!( - "you might have meant to use field `{}` whose type is `{}`", - name, ty - ), - suggestion, - Applicability::MaybeIncorrect, - ); - } - } - } - } - } - - /// When encountering a case where `.as_ref()` on a `Result` or `Option` would be appropriate, - /// suggests it. - fn suggest_as_ref_where_appropriate( - &self, - span: Span, - exp_found: &ty::error::ExpectedFound<Ty<'tcx>>, - diag: &mut Diagnostic, - ) { - if let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(span) - && let Some(msg) = self.should_suggest_as_ref(exp_found.expected, exp_found.found) - { - diag.span_suggestion( - span, - msg, - // HACK: fix issue# 100605, suggesting convert from &Option<T> to Option<&T>, remove the extra `&` - format!("{}.as_ref()", snippet.trim_start_matches('&')), - Applicability::MachineApplicable, - ); - } - } - - pub fn should_suggest_as_ref(&self, expected: Ty<'tcx>, found: Ty<'tcx>) -> Option<&str> { - if let (ty::Adt(exp_def, exp_substs), ty::Ref(_, found_ty, _)) = - (expected.kind(), found.kind()) - { - if let ty::Adt(found_def, found_substs) = *found_ty.kind() { - if exp_def == &found_def { - let have_as_ref = &[ - ( - sym::Option, - "you can convert from `&Option<T>` to `Option<&T>` using \ - `.as_ref()`", - ), - ( - sym::Result, - "you can convert from `&Result<T, E>` to \ - `Result<&T, &E>` using `.as_ref()`", - ), - ]; - if let Some(msg) = have_as_ref.iter().find_map(|(name, msg)| { - self.tcx.is_diagnostic_item(*name, exp_def.did()).then_some(msg) - }) { - let mut show_suggestion = true; - for (exp_ty, found_ty) in - iter::zip(exp_substs.types(), found_substs.types()) - { - match *exp_ty.kind() { - ty::Ref(_, exp_ty, _) => { - match (exp_ty.kind(), found_ty.kind()) { - (_, ty::Param(_)) - | (_, ty::Infer(_)) - | (ty::Param(_), _) - | (ty::Infer(_), _) => {} - _ if self.same_type_modulo_infer(exp_ty, found_ty) => {} - _ => show_suggestion = false, - }; - } - ty::Param(_) | ty::Infer(_) => {} - _ => show_suggestion = false, - } - } - if show_suggestion { - return Some(*msg); - } - } - } - } - } - None - } - pub fn report_and_explain_type_error( &self, trace: TypeTrace<'tcx>, @@ -2332,13 +1943,18 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { } } } + // For code `if Some(..) = expr `, the type mismatch may be expected `bool` but found `()`, + // we try to suggest to add the missing `let` for `if let Some(..) = expr` + (ty::Bool, ty::Tuple(list)) => if list.len() == 0 { + self.suggest_let_for_letchains(&mut err, &trace.cause, span); + } _ => {} } } let code = trace.cause.code(); if let &MatchExpressionArm(box MatchExpressionArmCause { source, .. }) = code && let hir::MatchSource::TryDesugar = source - && let Some((expected_ty, found_ty)) = self.values_str(trace.values) + && let Some((expected_ty, found_ty, _, _)) = self.values_str(trace.values) { err.note(&format!( "`?` operator cannot convert from `{}` to `{}`", @@ -2393,7 +2009,8 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { fn values_str( &self, values: ValuePairs<'tcx>, - ) -> Option<(DiagnosticStyledString, DiagnosticStyledString)> { + ) -> Option<(DiagnosticStyledString, DiagnosticStyledString, Option<PathBuf>, Option<PathBuf>)> + { match values { infer::Regions(exp_found) => self.expected_found_str(exp_found), infer::Terms(exp_found) => self.expected_found_str_term(exp_found), @@ -2403,7 +2020,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { found: exp_found.found.print_only_trait_path(), }; match self.expected_found_str(pretty_exp_found) { - Some((expected, found)) if expected == found => { + Some((expected, found, _, _)) if expected == found => { self.expected_found_str(exp_found) } ret => ret, @@ -2415,7 +2032,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { found: exp_found.found.print_only_trait_path(), }; match self.expected_found_str(pretty_exp_found) { - Some((expected, found)) if expected == found => { + Some((expected, found, _, _)) if expected == found => { self.expected_found_str(exp_found) } ret => ret, @@ -2427,17 +2044,41 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { fn expected_found_str_term( &self, exp_found: ty::error::ExpectedFound<ty::Term<'tcx>>, - ) -> Option<(DiagnosticStyledString, DiagnosticStyledString)> { + ) -> Option<(DiagnosticStyledString, DiagnosticStyledString, Option<PathBuf>, Option<PathBuf>)> + { let exp_found = self.resolve_vars_if_possible(exp_found); if exp_found.references_error() { return None; } Some(match (exp_found.expected.unpack(), exp_found.found.unpack()) { - (ty::TermKind::Ty(expected), ty::TermKind::Ty(found)) => self.cmp(expected, found), + (ty::TermKind::Ty(expected), ty::TermKind::Ty(found)) => { + let (mut exp, mut fnd) = self.cmp(expected, found); + // Use the terminal width as the basis to determine when to compress the printed + // out type, but give ourselves some leeway to avoid ending up creating a file for + // a type that is somewhat shorter than the path we'd write to. + let len = self.tcx.sess().diagnostic_width() + 40; + let exp_s = exp.content(); + let fnd_s = fnd.content(); + let mut exp_p = None; + let mut fnd_p = None; + if exp_s.len() > len { + let (exp_s, exp_path) = self.tcx.short_ty_string(expected); + exp = DiagnosticStyledString::highlighted(exp_s); + exp_p = exp_path; + } + if fnd_s.len() > len { + let (fnd_s, fnd_path) = self.tcx.short_ty_string(found); + fnd = DiagnosticStyledString::highlighted(fnd_s); + fnd_p = fnd_path; + } + (exp, fnd, exp_p, fnd_p) + } _ => ( DiagnosticStyledString::highlighted(exp_found.expected.to_string()), DiagnosticStyledString::highlighted(exp_found.found.to_string()), + None, + None, ), }) } @@ -2446,7 +2087,8 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { fn expected_found_str<T: fmt::Display + TypeFoldable<'tcx>>( &self, exp_found: ty::error::ExpectedFound<T>, - ) -> Option<(DiagnosticStyledString, DiagnosticStyledString)> { + ) -> Option<(DiagnosticStyledString, DiagnosticStyledString, Option<PathBuf>, Option<PathBuf>)> + { let exp_found = self.resolve_vars_if_possible(exp_found); if exp_found.references_error() { return None; @@ -2455,6 +2097,8 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { Some(( DiagnosticStyledString::highlighted(exp_found.expected.to_string()), DiagnosticStyledString::highlighted(exp_found.found.to_string()), + None, + None, )) } @@ -2788,36 +2432,29 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { debug!("report_sub_sup_conflict: sup_region={:?}", sup_region); debug!("report_sub_sup_conflict: sup_origin={:?}", sup_origin); - if let (&infer::Subtype(ref sup_trace), &infer::Subtype(ref sub_trace)) = - (&sup_origin, &sub_origin) + if let infer::Subtype(ref sup_trace) = sup_origin + && let infer::Subtype(ref sub_trace) = sub_origin + && let Some((sup_expected, sup_found, _, _)) = self.values_str(sup_trace.values) + && let Some((sub_expected, sub_found, _, _)) = self.values_str(sub_trace.values) + && sub_expected == sup_expected + && sub_found == sup_found { - debug!("report_sub_sup_conflict: sup_trace={:?}", sup_trace); - debug!("report_sub_sup_conflict: sub_trace={:?}", sub_trace); - debug!("report_sub_sup_conflict: sup_trace.values={:?}", sup_trace.values); - debug!("report_sub_sup_conflict: sub_trace.values={:?}", sub_trace.values); - - if let (Some((sup_expected, sup_found)), Some((sub_expected, sub_found))) = - (self.values_str(sup_trace.values), self.values_str(sub_trace.values)) - { - if sub_expected == sup_expected && sub_found == sup_found { - note_and_explain_region( - self.tcx, - &mut err, - "...but the lifetime must also be valid for ", - sub_region, - "...", - None, - ); - err.span_note( - sup_trace.cause.span, - &format!("...so that the {}", sup_trace.cause.as_requirement_str()), - ); + note_and_explain_region( + self.tcx, + &mut err, + "...but the lifetime must also be valid for ", + sub_region, + "...", + None, + ); + err.span_note( + sup_trace.cause.span, + &format!("...so that the {}", sup_trace.cause.as_requirement_str()), + ); - err.note_expected_found(&"", sup_expected, &"", sup_found); - err.emit(); - return; - } - } + err.note_expected_found(&"", sup_expected, &"", sup_found); + err.emit(); + return; } self.note_region_origin(&mut err, &sup_origin); @@ -2862,6 +2499,11 @@ impl<'tcx> TypeRelation<'tcx> for SameTypeModuloInfer<'_, 'tcx> { self.0.tcx } + fn intercrate(&self) -> bool { + assert!(!self.0.intercrate); + false + } + fn param_env(&self) -> ty::ParamEnv<'tcx> { // Unused, only for consts which we treat as always equal ty::ParamEnv::empty() @@ -2875,6 +2517,10 @@ impl<'tcx> TypeRelation<'tcx> for SameTypeModuloInfer<'_, 'tcx> { true } + fn mark_ambiguous(&mut self) { + bug!() + } + fn relate_with_variance<T: relate::Relate<'tcx>>( &mut self, _variance: ty::Variance, @@ -3043,7 +2689,6 @@ impl<'tcx> ObligationCauseExt<'tcx> for ObligationCause<'tcx> { TypeError::IntrinsicCast => { Error0308("cannot coerce intrinsics to function pointers") } - TypeError::ObjectUnsafeCoercion(did) => Error0038(did), _ => Error0308("mismatched types"), }, } @@ -3158,211 +2803,3 @@ impl<'tcx> InferCtxt<'tcx> { } } } - -impl<'tcx> TypeErrCtxt<'_, 'tcx> { - /// Be helpful when the user wrote `{... expr; }` and taking the `;` off - /// is enough to fix the error. - pub fn could_remove_semicolon( - &self, - blk: &'tcx hir::Block<'tcx>, - expected_ty: Ty<'tcx>, - ) -> Option<(Span, StatementAsExpression)> { - let blk = blk.innermost_block(); - // Do not suggest if we have a tail expr. - if blk.expr.is_some() { - return None; - } - let last_stmt = blk.stmts.last()?; - let hir::StmtKind::Semi(ref last_expr) = last_stmt.kind else { - return None; - }; - let last_expr_ty = self.typeck_results.as_ref()?.expr_ty_opt(*last_expr)?; - let needs_box = match (last_expr_ty.kind(), expected_ty.kind()) { - _ if last_expr_ty.references_error() => return None, - _ if self.same_type_modulo_infer(last_expr_ty, expected_ty) => { - StatementAsExpression::CorrectType - } - (ty::Opaque(last_def_id, _), ty::Opaque(exp_def_id, _)) - if last_def_id == exp_def_id => - { - StatementAsExpression::CorrectType - } - (ty::Opaque(last_def_id, last_bounds), ty::Opaque(exp_def_id, exp_bounds)) => { - debug!( - "both opaque, likely future {:?} {:?} {:?} {:?}", - last_def_id, last_bounds, exp_def_id, exp_bounds - ); - - let last_local_id = last_def_id.as_local()?; - let exp_local_id = exp_def_id.as_local()?; - - match ( - &self.tcx.hir().expect_item(last_local_id).kind, - &self.tcx.hir().expect_item(exp_local_id).kind, - ) { - ( - hir::ItemKind::OpaqueTy(hir::OpaqueTy { bounds: last_bounds, .. }), - hir::ItemKind::OpaqueTy(hir::OpaqueTy { bounds: exp_bounds, .. }), - ) if iter::zip(*last_bounds, *exp_bounds).all(|(left, right)| { - match (left, right) { - ( - hir::GenericBound::Trait(tl, ml), - hir::GenericBound::Trait(tr, mr), - ) if tl.trait_ref.trait_def_id() == tr.trait_ref.trait_def_id() - && ml == mr => - { - true - } - ( - hir::GenericBound::LangItemTrait(langl, _, _, argsl), - hir::GenericBound::LangItemTrait(langr, _, _, argsr), - ) if langl == langr => { - // FIXME: consider the bounds! - debug!("{:?} {:?}", argsl, argsr); - true - } - _ => false, - } - }) => - { - StatementAsExpression::NeedsBoxing - } - _ => StatementAsExpression::CorrectType, - } - } - _ => return None, - }; - let span = if last_stmt.span.from_expansion() { - let mac_call = rustc_span::source_map::original_sp(last_stmt.span, blk.span); - self.tcx.sess.source_map().mac_call_stmt_semi_span(mac_call)? - } else { - last_stmt.span.with_lo(last_stmt.span.hi() - BytePos(1)) - }; - Some((span, needs_box)) - } - - /// Suggest returning a local binding with a compatible type if the block - /// has no return expression. - pub fn consider_returning_binding( - &self, - blk: &'tcx hir::Block<'tcx>, - expected_ty: Ty<'tcx>, - err: &mut Diagnostic, - ) -> bool { - let blk = blk.innermost_block(); - // Do not suggest if we have a tail expr. - if blk.expr.is_some() { - return false; - } - let mut shadowed = FxHashSet::default(); - let mut candidate_idents = vec![]; - let mut find_compatible_candidates = |pat: &hir::Pat<'_>| { - if let hir::PatKind::Binding(_, hir_id, ident, _) = &pat.kind - && let Some(pat_ty) = self - .typeck_results - .as_ref() - .and_then(|typeck_results| typeck_results.node_type_opt(*hir_id)) - { - let pat_ty = self.resolve_vars_if_possible(pat_ty); - if self.same_type_modulo_infer(pat_ty, expected_ty) - && !(pat_ty, expected_ty).references_error() - && shadowed.insert(ident.name) - { - candidate_idents.push((*ident, pat_ty)); - } - } - true - }; - - let hir = self.tcx.hir(); - for stmt in blk.stmts.iter().rev() { - let hir::StmtKind::Local(local) = &stmt.kind else { continue; }; - local.pat.walk(&mut find_compatible_candidates); - } - match hir.find(hir.get_parent_node(blk.hir_id)) { - Some(hir::Node::Expr(hir::Expr { hir_id, .. })) => { - match hir.find(hir.get_parent_node(*hir_id)) { - Some(hir::Node::Arm(hir::Arm { pat, .. })) => { - pat.walk(&mut find_compatible_candidates); - } - Some( - hir::Node::Item(hir::Item { kind: hir::ItemKind::Fn(_, _, body), .. }) - | hir::Node::ImplItem(hir::ImplItem { - kind: hir::ImplItemKind::Fn(_, body), - .. - }) - | hir::Node::TraitItem(hir::TraitItem { - kind: hir::TraitItemKind::Fn(_, hir::TraitFn::Provided(body)), - .. - }) - | hir::Node::Expr(hir::Expr { - kind: hir::ExprKind::Closure(hir::Closure { body, .. }), - .. - }), - ) => { - for param in hir.body(*body).params { - param.pat.walk(&mut find_compatible_candidates); - } - } - Some(hir::Node::Expr(hir::Expr { - kind: - hir::ExprKind::If( - hir::Expr { kind: hir::ExprKind::Let(let_), .. }, - then_block, - _, - ), - .. - })) if then_block.hir_id == *hir_id => { - let_.pat.walk(&mut find_compatible_candidates); - } - _ => {} - } - } - _ => {} - } - - match &candidate_idents[..] { - [(ident, _ty)] => { - let sm = self.tcx.sess.source_map(); - if let Some(stmt) = blk.stmts.last() { - let stmt_span = sm.stmt_span(stmt.span, blk.span); - let sugg = if sm.is_multiline(blk.span) - && let Some(spacing) = sm.indentation_before(stmt_span) - { - format!("\n{spacing}{ident}") - } else { - format!(" {ident}") - }; - err.span_suggestion_verbose( - stmt_span.shrink_to_hi(), - format!("consider returning the local binding `{ident}`"), - sugg, - Applicability::MaybeIncorrect, - ); - } else { - let sugg = if sm.is_multiline(blk.span) - && let Some(spacing) = sm.indentation_before(blk.span.shrink_to_lo()) - { - format!("\n{spacing} {ident}\n{spacing}") - } else { - format!(" {ident} ") - }; - let left_span = sm.span_through_char(blk.span, '{').shrink_to_hi(); - err.span_suggestion_verbose( - sm.span_extend_while(left_span, |c| c.is_whitespace()).unwrap_or(left_span), - format!("consider returning the local binding `{ident}`"), - sugg, - Applicability::MaybeIncorrect, - ); - } - true - } - values if (1..3).contains(&values.len()) => { - let spans = values.iter().map(|(ident, _)| ident.span).collect::<Vec<_>>(); - err.span_note(spans, "consider returning one of these bindings"); - true - } - _ => false, - } - } -} diff --git a/compiler/rustc_infer/src/infer/error_reporting/need_type_info.rs b/compiler/rustc_infer/src/infer/error_reporting/need_type_info.rs index 7b3178e61..8ff1639a3 100644 --- a/compiler/rustc_infer/src/infer/error_reporting/need_type_info.rs +++ b/compiler/rustc_infer/src/infer/error_reporting/need_type_info.rs @@ -15,7 +15,7 @@ use rustc_hir::intravisit::{self, Visitor}; use rustc_hir::{Body, Closure, Expr, ExprKind, FnRetTy, HirId, Local, LocalSource}; use rustc_middle::hir::nested_filter; use rustc_middle::infer::unify_key::{ConstVariableOrigin, ConstVariableOriginKind}; -use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, AutoBorrowMutability}; +use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow}; use rustc_middle::ty::print::{FmtPrinter, PrettyPrinter, Print, Printer}; use rustc_middle::ty::{self, DefIdTree, InferConst}; use rustc_middle::ty::{GenericArg, GenericArgKind, SubstsRef}; @@ -508,10 +508,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { [ .., Adjustment { kind: Adjust::Borrow(AutoBorrow::Ref(_, mut_)), target: _ }, - ] => match mut_ { - AutoBorrowMutability::Mut { .. } => "&mut ", - AutoBorrowMutability::Not => "&", - }, + ] => hir::Mutability::from(*mut_).ref_prefix_str(), _ => "", }; @@ -571,7 +568,7 @@ impl<'tcx> InferCtxt<'tcx> { &self, kind: hir::GeneratorKind, span: Span, - ty: Ty<'tcx>, + ty: ty::Term<'tcx>, ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> { let ty = self.resolve_vars_if_possible(ty); let data = self.extract_inference_diagnostics_data(ty.into(), None); diff --git a/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/mismatched_static_lifetime.rs b/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/mismatched_static_lifetime.rs index c5f2a1a3f..1067ccda2 100644 --- a/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/mismatched_static_lifetime.rs +++ b/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/mismatched_static_lifetime.rs @@ -9,7 +9,7 @@ use crate::infer::error_reporting::nice_region_error::NiceRegionError; use crate::infer::lexical_region_resolve::RegionResolutionError; use crate::infer::{SubregionOrigin, TypeTrace}; use crate::traits::ObligationCauseCode; -use rustc_data_structures::fx::FxHashSet; +use rustc_data_structures::fx::FxIndexSet; use rustc_errors::{ErrorGuaranteed, MultiSpan}; use rustc_hir as hir; use rustc_hir::intravisit::Visitor; @@ -73,7 +73,7 @@ impl<'a, 'tcx> NiceRegionError<'a, 'tcx> { // Next, let's figure out the set of trait objects with implicit static bounds let ty = self.tcx().type_of(*impl_def_id); - let mut v = super::static_impl_trait::TraitObjectVisitor(FxHashSet::default()); + let mut v = super::static_impl_trait::TraitObjectVisitor(FxIndexSet::default()); v.visit_ty(ty); let mut traits = vec![]; for matching_def_id in v.0 { diff --git a/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/mod.rs b/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/mod.rs index aaf5a7af0..8a0e332f9 100644 --- a/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/mod.rs +++ b/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/mod.rs @@ -10,6 +10,7 @@ pub mod find_anon_type; mod mismatched_static_lifetime; mod named_anon_conflict; mod placeholder_error; +mod placeholder_relation; mod static_impl_trait; mod trait_impl_difference; mod util; @@ -52,7 +53,9 @@ impl<'cx, 'tcx> NiceRegionError<'cx, 'tcx> { pub fn try_report_from_nll(&self) -> Option<DiagnosticBuilder<'tcx, ErrorGuaranteed>> { // Due to the improved diagnostics returned by the MIR borrow checker, only a subset of // the nice region errors are required when running under the MIR borrow checker. - self.try_report_named_anon_conflict().or_else(|| self.try_report_placeholder_conflict()) + self.try_report_named_anon_conflict() + .or_else(|| self.try_report_placeholder_conflict()) + .or_else(|| self.try_report_placeholder_relation()) } pub fn try_report(&self) -> Option<ErrorGuaranteed> { diff --git a/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/named_anon_conflict.rs b/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/named_anon_conflict.rs index 76cb76d9f..3fe7c1598 100644 --- a/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/named_anon_conflict.rs +++ b/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/named_anon_conflict.rs @@ -68,7 +68,7 @@ impl<'a, 'tcx> NiceRegionError<'a, 'tcx> { let is_impl_item = region_info.is_impl_item; match br { - ty::BrNamed(_, kw::UnderscoreLifetime) | ty::BrAnon(_) => {} + ty::BrNamed(_, kw::UnderscoreLifetime) | ty::BrAnon(..) => {} _ => { /* not an anonymous region */ debug!("try_report_named_anon_conflict: not an anonymous region"); diff --git a/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/placeholder_error.rs b/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/placeholder_error.rs index a58516829..1f554c81e 100644 --- a/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/placeholder_error.rs +++ b/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/placeholder_error.rs @@ -399,10 +399,7 @@ impl<'tcx> NiceRegionError<'_, 'tcx> { self_ty.highlight.maybe_highlighting_region(vid, actual_has_vid); if self_ty.value.is_closure() - && self - .tcx() - .fn_trait_kind_from_lang_item(expected_trait_ref.value.def_id) - .is_some() + && self.tcx().is_fn_trait(expected_trait_ref.value.def_id) { let closure_sig = self_ty.map(|closure| { if let ty::Closure(_, substs) = closure.kind() { diff --git a/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/placeholder_relation.rs b/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/placeholder_relation.rs new file mode 100644 index 000000000..c42240f21 --- /dev/null +++ b/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/placeholder_relation.rs @@ -0,0 +1,79 @@ +use crate::infer::{ + error_reporting::nice_region_error::NiceRegionError, RegionResolutionError, SubregionOrigin, +}; +use rustc_data_structures::intern::Interned; +use rustc_errors::{DiagnosticBuilder, ErrorGuaranteed}; +use rustc_middle::ty::{self, RePlaceholder, Region}; + +impl<'tcx> NiceRegionError<'_, 'tcx> { + /// Emitted wwhen given a `ConcreteFailure` when relating two placeholders. + pub(super) fn try_report_placeholder_relation( + &self, + ) -> Option<DiagnosticBuilder<'tcx, ErrorGuaranteed>> { + match &self.error { + Some(RegionResolutionError::ConcreteFailure( + SubregionOrigin::RelateRegionParamBound(span), + Region(Interned(RePlaceholder(ty::Placeholder { name: sub_name, .. }), _)), + Region(Interned(RePlaceholder(ty::Placeholder { name: sup_name, .. }), _)), + )) => { + let msg = "lifetime bound not satisfied"; + let mut err = self.tcx().sess.struct_span_err(*span, msg); + let (sub_span, sub_symbol) = match sub_name { + ty::BrNamed(def_id, symbol) => { + (Some(self.tcx().def_span(def_id)), Some(symbol)) + } + ty::BrAnon(_, span) => (*span, None), + ty::BrEnv => (None, None), + }; + let (sup_span, sup_symbol) = match sup_name { + ty::BrNamed(def_id, symbol) => { + (Some(self.tcx().def_span(def_id)), Some(symbol)) + } + ty::BrAnon(_, span) => (*span, None), + ty::BrEnv => (None, None), + }; + match (sub_span, sup_span, sub_symbol, sup_symbol) { + (Some(sub_span), Some(sup_span), Some(sub_symbol), Some(sup_symbol)) => { + err.span_note( + sub_span, + format!("the lifetime `{sub_symbol}` defined here..."), + ); + err.span_note( + sup_span, + format!("...must outlive the lifetime `{sup_symbol}` defined here"), + ); + } + (Some(sub_span), Some(sup_span), _, Some(sup_symbol)) => { + err.span_note(sub_span, format!("the lifetime defined here...")); + err.span_note( + sup_span, + format!("...must outlive the lifetime `{sup_symbol}` defined here"), + ); + } + (Some(sub_span), Some(sup_span), Some(sub_symbol), _) => { + err.span_note( + sub_span, + format!("the lifetime `{sub_symbol}` defined here..."), + ); + err.span_note( + sup_span, + format!("...must outlive the lifetime defined here"), + ); + } + (Some(sub_span), Some(sup_span), _, _) => { + err.span_note(sub_span, format!("the lifetime defined here...")); + err.span_note( + sup_span, + format!("...must outlive the lifetime defined here"), + ); + } + _ => {} + } + err.note("this is a known limitation that will be removed in the future (see issue #100013 <https://github.com/rust-lang/rust/issues/100013> for more information)"); + Some(err) + } + + _ => None, + } + } +} diff --git a/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/static_impl_trait.rs b/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/static_impl_trait.rs index 9bf755d7f..09f9aa3c8 100644 --- a/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/static_impl_trait.rs +++ b/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/static_impl_trait.rs @@ -4,7 +4,7 @@ use crate::infer::error_reporting::nice_region_error::NiceRegionError; use crate::infer::lexical_region_resolve::RegionResolutionError; use crate::infer::{SubregionOrigin, TypeTrace}; use crate::traits::{ObligationCauseCode, UnifyReceiverContext}; -use rustc_data_structures::fx::FxHashSet; +use rustc_data_structures::fx::FxIndexSet; use rustc_errors::{struct_span_err, Applicability, Diagnostic, ErrorGuaranteed, MultiSpan}; use rustc_hir::def_id::DefId; use rustc_hir::intravisit::{walk_ty, Visitor}; @@ -236,7 +236,7 @@ impl<'a, 'tcx> NiceRegionError<'a, 'tcx> { // Same case of `impl Foo for dyn Bar { fn qux(&self) {} }` introducing a `'static` // lifetime as above, but called using a fully-qualified path to the method: // `Foo::qux(bar)`. - let mut v = TraitObjectVisitor(FxHashSet::default()); + let mut v = TraitObjectVisitor(FxIndexSet::default()); v.visit_ty(param.param_ty); if let Some((ident, self_ty)) = self.get_impl_ident_and_self_ty_from_trait(item_def_id, &v.0) @@ -314,10 +314,10 @@ pub fn suggest_new_region_bound( .iter() .filter_map(|arg| match arg { GenericBound::Outlives(Lifetime { - name: LifetimeName::Static, - span, + res: LifetimeName::Static, + ident, .. - }) => Some(*span), + }) => Some(ident.span), _ => None, }) .next() @@ -342,10 +342,10 @@ pub fn suggest_new_region_bound( .bounds .iter() .filter_map(|arg| match arg { - GenericBound::Outlives(Lifetime { name, span, .. }) - if name.ident().to_string() == lifetime_name => + GenericBound::Outlives(Lifetime { ident, .. }) + if ident.name.to_string() == lifetime_name => { - Some(*span) + Some(ident.span) } _ => None, }) @@ -361,8 +361,8 @@ pub fn suggest_new_region_bound( ); } } - TyKind::TraitObject(_, lt, _) => match lt.name { - LifetimeName::ImplicitObjectLifetimeDefault => { + TyKind::TraitObject(_, lt, _) => { + if let LifetimeName::ImplicitObjectLifetimeDefault = lt.res { err.span_suggestion_verbose( fn_return.span.shrink_to_hi(), &format!( @@ -374,15 +374,14 @@ pub fn suggest_new_region_bound( &plus_lt, Applicability::MaybeIncorrect, ); - } - name if name.ident().to_string() != lifetime_name => { + } else if lt.ident.name.to_string() != lifetime_name { // With this check we avoid suggesting redundant bounds. This // would happen if there are nested impl/dyn traits and only // one of them has the bound we'd suggest already there, like // in `impl Foo<X = dyn Bar> + '_`. if let Some(explicit_static) = &explicit_static { err.span_suggestion_verbose( - lt.span, + lt.ident.span, &format!("{} the trait object's {}", consider, explicit_static), &lifetime_name, Applicability::MaybeIncorrect, @@ -397,8 +396,7 @@ pub fn suggest_new_region_bound( ); } } - _ => {} - }, + } _ => {} } } @@ -408,7 +406,7 @@ impl<'a, 'tcx> NiceRegionError<'a, 'tcx> { fn get_impl_ident_and_self_ty_from_trait( &self, def_id: DefId, - trait_objects: &FxHashSet<DefId>, + trait_objects: &FxIndexSet<DefId>, ) -> Option<(Ident, &'tcx hir::Ty<'tcx>)> { let tcx = self.tcx(); match tcx.hir().get_if_local(def_id) { @@ -490,7 +488,7 @@ impl<'a, 'tcx> NiceRegionError<'a, 'tcx> { return false; }; - let mut v = TraitObjectVisitor(FxHashSet::default()); + let mut v = TraitObjectVisitor(FxIndexSet::default()); v.visit_ty(ty); // Get the `Ident` of the method being called and the corresponding `impl` (to point at @@ -506,7 +504,7 @@ impl<'a, 'tcx> NiceRegionError<'a, 'tcx> { fn suggest_constrain_dyn_trait_in_impl( &self, err: &mut Diagnostic, - found_dids: &FxHashSet<DefId>, + found_dids: &FxIndexSet<DefId>, ident: Ident, self_ty: &hir::Ty<'_>, ) -> bool { @@ -538,7 +536,7 @@ impl<'a, 'tcx> NiceRegionError<'a, 'tcx> { } /// Collect all the trait objects in a type that could have received an implicit `'static` lifetime. -pub struct TraitObjectVisitor(pub FxHashSet<DefId>); +pub struct TraitObjectVisitor(pub FxIndexSet<DefId>); impl<'tcx> TypeVisitor<'tcx> for TraitObjectVisitor { fn visit_ty(&mut self, t: Ty<'tcx>) -> ControlFlow<Self::BreakTy> { @@ -561,7 +559,7 @@ impl<'a, 'tcx> Visitor<'tcx> for HirTraitObjectVisitor<'a> { fn visit_ty(&mut self, t: &'tcx hir::Ty<'tcx>) { if let TyKind::TraitObject( poly_trait_refs, - Lifetime { name: LifetimeName::ImplicitObjectLifetimeDefault, .. }, + Lifetime { res: LifetimeName::ImplicitObjectLifetimeDefault, .. }, _, ) = t.kind { diff --git a/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/util.rs b/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/util.rs index f1461d701..fd26d7d29 100644 --- a/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/util.rs +++ b/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/util.rs @@ -149,6 +149,8 @@ impl<'a, 'tcx> NiceRegionError<'a, 'tcx> { region: ty::BoundRegionKind, ) -> bool { let late_bound_regions = self.tcx().collect_referenced_late_bound_regions(&ty); + // We are only checking is any region meets the condition so order doesn't matter + #[allow(rustc::potential_query_instability)] late_bound_regions.iter().any(|r| *r == region) } diff --git a/compiler/rustc_infer/src/infer/error_reporting/note.rs b/compiler/rustc_infer/src/infer/error_reporting/note.rs index 41b115f33..d2dffa4a0 100644 --- a/compiler/rustc_infer/src/infer/error_reporting/note.rs +++ b/compiler/rustc_infer/src/infer/error_reporting/note.rs @@ -16,7 +16,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { infer::Subtype(ref trace) => RegionOriginNote::WithRequirement { span: trace.cause.span, requirement: ObligationCauseAsDiagArg(trace.cause.clone()), - expected_found: self.values_str(trace.values), + expected_found: self.values_str(trace.values).map(|(e, f, _, _)| (e, f)), } .add_to_diagnostic(err), infer::Reborrow(span) => { diff --git a/compiler/rustc_infer/src/infer/error_reporting/note_region.rs b/compiler/rustc_infer/src/infer/error_reporting/note_region.rs new file mode 100644 index 000000000..41b115f33 --- /dev/null +++ b/compiler/rustc_infer/src/infer/error_reporting/note_region.rs @@ -0,0 +1,427 @@ +use crate::errors::RegionOriginNote; +use crate::infer::error_reporting::{note_and_explain_region, TypeErrCtxt}; +use crate::infer::{self, SubregionOrigin}; +use rustc_errors::{ + fluent, struct_span_err, AddToDiagnostic, Diagnostic, DiagnosticBuilder, ErrorGuaranteed, +}; +use rustc_middle::traits::ObligationCauseCode; +use rustc_middle::ty::error::TypeError; +use rustc_middle::ty::{self, Region}; + +use super::ObligationCauseAsDiagArg; + +impl<'tcx> TypeErrCtxt<'_, 'tcx> { + pub(super) fn note_region_origin(&self, err: &mut Diagnostic, origin: &SubregionOrigin<'tcx>) { + match *origin { + infer::Subtype(ref trace) => RegionOriginNote::WithRequirement { + span: trace.cause.span, + requirement: ObligationCauseAsDiagArg(trace.cause.clone()), + expected_found: self.values_str(trace.values), + } + .add_to_diagnostic(err), + infer::Reborrow(span) => { + RegionOriginNote::Plain { span, msg: fluent::infer_reborrow }.add_to_diagnostic(err) + } + infer::ReborrowUpvar(span, ref upvar_id) => { + let var_name = self.tcx.hir().name(upvar_id.var_path.hir_id); + RegionOriginNote::WithName { + span, + msg: fluent::infer_reborrow, + name: &var_name.to_string(), + continues: false, + } + .add_to_diagnostic(err); + } + infer::RelateObjectBound(span) => { + RegionOriginNote::Plain { span, msg: fluent::infer_relate_object_bound } + .add_to_diagnostic(err); + } + infer::DataBorrowed(ty, span) => { + RegionOriginNote::WithName { + span, + msg: fluent::infer_data_borrowed, + name: &self.ty_to_string(ty), + continues: false, + } + .add_to_diagnostic(err); + } + infer::ReferenceOutlivesReferent(ty, span) => { + RegionOriginNote::WithName { + span, + msg: fluent::infer_reference_outlives_referent, + name: &self.ty_to_string(ty), + continues: false, + } + .add_to_diagnostic(err); + } + infer::RelateParamBound(span, ty, opt_span) => { + RegionOriginNote::WithName { + span, + msg: fluent::infer_relate_param_bound, + name: &self.ty_to_string(ty), + continues: opt_span.is_some(), + } + .add_to_diagnostic(err); + if let Some(span) = opt_span { + RegionOriginNote::Plain { span, msg: fluent::infer_relate_param_bound_2 } + .add_to_diagnostic(err); + } + } + infer::RelateRegionParamBound(span) => { + RegionOriginNote::Plain { span, msg: fluent::infer_relate_region_param_bound } + .add_to_diagnostic(err); + } + infer::CompareImplItemObligation { span, .. } => { + RegionOriginNote::Plain { span, msg: fluent::infer_compare_impl_item_obligation } + .add_to_diagnostic(err); + } + infer::CheckAssociatedTypeBounds { ref parent, .. } => { + self.note_region_origin(err, &parent); + } + infer::AscribeUserTypeProvePredicate(span) => { + RegionOriginNote::Plain { + span, + msg: fluent::infer_ascribe_user_type_prove_predicate, + } + .add_to_diagnostic(err); + } + } + } + + pub(super) fn report_concrete_failure( + &self, + origin: SubregionOrigin<'tcx>, + sub: Region<'tcx>, + sup: Region<'tcx>, + ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> { + match origin { + infer::Subtype(box trace) => { + let terr = TypeError::RegionsDoesNotOutlive(sup, sub); + let mut err = self.report_and_explain_type_error(trace, terr); + match (*sub, *sup) { + (ty::RePlaceholder(_), ty::RePlaceholder(_)) => {} + (ty::RePlaceholder(_), _) => { + note_and_explain_region( + self.tcx, + &mut err, + "", + sup, + " doesn't meet the lifetime requirements", + None, + ); + } + (_, ty::RePlaceholder(_)) => { + note_and_explain_region( + self.tcx, + &mut err, + "the required lifetime does not necessarily outlive ", + sub, + "", + None, + ); + } + _ => { + note_and_explain_region(self.tcx, &mut err, "", sup, "...", None); + note_and_explain_region( + self.tcx, + &mut err, + "...does not necessarily outlive ", + sub, + "", + None, + ); + } + } + err + } + infer::Reborrow(span) => { + let mut err = struct_span_err!( + self.tcx.sess, + span, + E0312, + "lifetime of reference outlives lifetime of borrowed content..." + ); + note_and_explain_region( + self.tcx, + &mut err, + "...the reference is valid for ", + sub, + "...", + None, + ); + note_and_explain_region( + self.tcx, + &mut err, + "...but the borrowed content is only valid for ", + sup, + "", + None, + ); + err + } + infer::ReborrowUpvar(span, ref upvar_id) => { + let var_name = self.tcx.hir().name(upvar_id.var_path.hir_id); + let mut err = struct_span_err!( + self.tcx.sess, + span, + E0313, + "lifetime of borrowed pointer outlives lifetime of captured variable `{}`...", + var_name + ); + note_and_explain_region( + self.tcx, + &mut err, + "...the borrowed pointer is valid for ", + sub, + "...", + None, + ); + note_and_explain_region( + self.tcx, + &mut err, + &format!("...but `{}` is only valid for ", var_name), + sup, + "", + None, + ); + err + } + infer::RelateObjectBound(span) => { + let mut err = struct_span_err!( + self.tcx.sess, + span, + E0476, + "lifetime of the source pointer does not outlive lifetime bound of the \ + object type" + ); + note_and_explain_region( + self.tcx, + &mut err, + "object type is valid for ", + sub, + "", + None, + ); + note_and_explain_region( + self.tcx, + &mut err, + "source pointer is only valid for ", + sup, + "", + None, + ); + err + } + infer::RelateParamBound(span, ty, opt_span) => { + let mut err = struct_span_err!( + self.tcx.sess, + span, + E0477, + "the type `{}` does not fulfill the required lifetime", + self.ty_to_string(ty) + ); + match *sub { + ty::ReStatic => note_and_explain_region( + self.tcx, + &mut err, + "type must satisfy ", + sub, + if opt_span.is_some() { " as required by this binding" } else { "" }, + opt_span, + ), + _ => note_and_explain_region( + self.tcx, + &mut err, + "type must outlive ", + sub, + if opt_span.is_some() { " as required by this binding" } else { "" }, + opt_span, + ), + } + err + } + infer::RelateRegionParamBound(span) => { + let mut err = + struct_span_err!(self.tcx.sess, span, E0478, "lifetime bound not satisfied"); + note_and_explain_region( + self.tcx, + &mut err, + "lifetime parameter instantiated with ", + sup, + "", + None, + ); + note_and_explain_region( + self.tcx, + &mut err, + "but lifetime parameter must outlive ", + sub, + "", + None, + ); + err + } + infer::DataBorrowed(ty, span) => { + let mut err = struct_span_err!( + self.tcx.sess, + span, + E0490, + "a value of type `{}` is borrowed for too long", + self.ty_to_string(ty) + ); + note_and_explain_region( + self.tcx, + &mut err, + "the type is valid for ", + sub, + "", + None, + ); + note_and_explain_region( + self.tcx, + &mut err, + "but the borrow lasts for ", + sup, + "", + None, + ); + err + } + infer::ReferenceOutlivesReferent(ty, span) => { + let mut err = struct_span_err!( + self.tcx.sess, + span, + E0491, + "in type `{}`, reference has a longer lifetime than the data it references", + self.ty_to_string(ty) + ); + note_and_explain_region( + self.tcx, + &mut err, + "the pointer is valid for ", + sub, + "", + None, + ); + note_and_explain_region( + self.tcx, + &mut err, + "but the referenced data is only valid for ", + sup, + "", + None, + ); + err + } + infer::CompareImplItemObligation { span, impl_item_def_id, trait_item_def_id } => self + .report_extra_impl_obligation( + span, + impl_item_def_id, + trait_item_def_id, + &format!("`{}: {}`", sup, sub), + ), + infer::CheckAssociatedTypeBounds { impl_item_def_id, trait_item_def_id, parent } => { + let mut err = self.report_concrete_failure(*parent, sub, sup); + + let trait_item_span = self.tcx.def_span(trait_item_def_id); + let item_name = self.tcx.item_name(impl_item_def_id.to_def_id()); + err.span_label( + trait_item_span, + format!("definition of `{}` from trait", item_name), + ); + + let trait_predicates = self.tcx.explicit_predicates_of(trait_item_def_id); + let impl_predicates = self.tcx.explicit_predicates_of(impl_item_def_id); + + let impl_predicates: rustc_data_structures::fx::FxHashSet<_> = + impl_predicates.predicates.into_iter().map(|(pred, _)| pred).collect(); + let clauses: Vec<_> = trait_predicates + .predicates + .into_iter() + .filter(|&(pred, _)| !impl_predicates.contains(pred)) + .map(|(pred, _)| format!("{}", pred)) + .collect(); + + if !clauses.is_empty() { + let generics = self.tcx.hir().get_generics(impl_item_def_id).unwrap(); + let where_clause_span = generics.tail_span_for_predicate_suggestion(); + + let suggestion = format!( + "{} {}", + generics.add_where_or_trailing_comma(), + clauses.join(", "), + ); + err.span_suggestion( + where_clause_span, + &format!( + "try copying {} from the trait", + if clauses.len() > 1 { "these clauses" } else { "this clause" } + ), + suggestion, + rustc_errors::Applicability::MaybeIncorrect, + ); + } + + err + } + infer::AscribeUserTypeProvePredicate(span) => { + let mut err = + struct_span_err!(self.tcx.sess, span, E0478, "lifetime bound not satisfied"); + note_and_explain_region( + self.tcx, + &mut err, + "lifetime instantiated with ", + sup, + "", + None, + ); + note_and_explain_region( + self.tcx, + &mut err, + "but lifetime must outlive ", + sub, + "", + None, + ); + err + } + } + } + + pub(super) fn report_placeholder_failure( + &self, + placeholder_origin: SubregionOrigin<'tcx>, + sub: Region<'tcx>, + sup: Region<'tcx>, + ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> { + // I can't think how to do better than this right now. -nikomatsakis + debug!(?placeholder_origin, ?sub, ?sup, "report_placeholder_failure"); + match placeholder_origin { + infer::Subtype(box ref trace) + if matches!( + &trace.cause.code().peel_derives(), + ObligationCauseCode::BindingObligation(..) + | ObligationCauseCode::ExprBindingObligation(..) + ) => + { + // Hack to get around the borrow checker because trace.cause has an `Rc`. + if let ObligationCauseCode::BindingObligation(_, span) + | ObligationCauseCode::ExprBindingObligation(_, span, ..) = + &trace.cause.code().peel_derives() + { + let span = *span; + let mut err = self.report_concrete_failure(placeholder_origin, sub, sup); + err.span_note(span, "the lifetime requirement is introduced here"); + err + } else { + unreachable!() + } + } + infer::Subtype(box trace) => { + let terr = TypeError::RegionsPlaceholderMismatch; + return self.report_and_explain_type_error(trace, terr); + } + _ => return self.report_concrete_failure(placeholder_origin, sub, sup), + } + } +} diff --git a/compiler/rustc_infer/src/infer/error_reporting/suggest.rs b/compiler/rustc_infer/src/infer/error_reporting/suggest.rs new file mode 100644 index 000000000..73b5a2cc4 --- /dev/null +++ b/compiler/rustc_infer/src/infer/error_reporting/suggest.rs @@ -0,0 +1,672 @@ +use hir::def::CtorKind; +use hir::intravisit::{walk_expr, walk_stmt, Visitor}; +use rustc_data_structures::fx::FxIndexSet; +use rustc_errors::{Applicability, Diagnostic}; +use rustc_hir as hir; +use rustc_middle::traits::{ + IfExpressionCause, MatchExpressionArmCause, ObligationCause, ObligationCauseCode, + StatementAsExpression, +}; +use rustc_middle::ty::print::with_no_trimmed_paths; +use rustc_middle::ty::{self as ty, Ty, TypeVisitable}; +use rustc_span::{sym, BytePos, Span}; + +use crate::errors::SuggAddLetForLetChains; + +use super::TypeErrCtxt; + +impl<'tcx> TypeErrCtxt<'_, 'tcx> { + pub(super) fn suggest_remove_semi_or_return_binding( + &self, + err: &mut Diagnostic, + first_id: Option<hir::HirId>, + first_ty: Ty<'tcx>, + first_span: Span, + second_id: Option<hir::HirId>, + second_ty: Ty<'tcx>, + second_span: Span, + ) { + let remove_semicolon = [ + (first_id, self.resolve_vars_if_possible(second_ty)), + (second_id, self.resolve_vars_if_possible(first_ty)), + ] + .into_iter() + .find_map(|(id, ty)| { + let hir::Node::Block(blk) = self.tcx.hir().get(id?) else { return None }; + self.could_remove_semicolon(blk, ty) + }); + match remove_semicolon { + Some((sp, StatementAsExpression::NeedsBoxing)) => { + err.multipart_suggestion( + "consider removing this semicolon and boxing the expressions", + vec![ + (first_span.shrink_to_lo(), "Box::new(".to_string()), + (first_span.shrink_to_hi(), ")".to_string()), + (second_span.shrink_to_lo(), "Box::new(".to_string()), + (second_span.shrink_to_hi(), ")".to_string()), + (sp, String::new()), + ], + Applicability::MachineApplicable, + ); + } + Some((sp, StatementAsExpression::CorrectType)) => { + err.span_suggestion_short( + sp, + "consider removing this semicolon", + "", + Applicability::MachineApplicable, + ); + } + None => { + for (id, ty) in [(first_id, second_ty), (second_id, first_ty)] { + if let Some(id) = id + && let hir::Node::Block(blk) = self.tcx.hir().get(id) + && self.consider_returning_binding(blk, ty, err) + { + break; + } + } + } + } + } + + pub(super) fn suggest_boxing_for_return_impl_trait( + &self, + err: &mut Diagnostic, + return_sp: Span, + arm_spans: impl Iterator<Item = Span>, + ) { + err.multipart_suggestion( + "you could change the return type to be a boxed trait object", + vec![ + (return_sp.with_hi(return_sp.lo() + BytePos(4)), "Box<dyn".to_string()), + (return_sp.shrink_to_hi(), ">".to_string()), + ], + Applicability::MaybeIncorrect, + ); + let sugg = arm_spans + .flat_map(|sp| { + [(sp.shrink_to_lo(), "Box::new(".to_string()), (sp.shrink_to_hi(), ")".to_string())] + .into_iter() + }) + .collect::<Vec<_>>(); + err.multipart_suggestion( + "if you change the return type to expect trait objects, box the returned expressions", + sugg, + Applicability::MaybeIncorrect, + ); + } + + pub(super) fn suggest_tuple_pattern( + &self, + cause: &ObligationCause<'tcx>, + exp_found: &ty::error::ExpectedFound<Ty<'tcx>>, + diag: &mut Diagnostic, + ) { + // Heavily inspired by `FnCtxt::suggest_compatible_variants`, with + // some modifications due to that being in typeck and this being in infer. + if let ObligationCauseCode::Pattern { .. } = cause.code() { + if let ty::Adt(expected_adt, substs) = exp_found.expected.kind() { + let compatible_variants: Vec<_> = expected_adt + .variants() + .iter() + .filter(|variant| { + variant.fields.len() == 1 && variant.ctor_kind() == Some(CtorKind::Fn) + }) + .filter_map(|variant| { + let sole_field = &variant.fields[0]; + let sole_field_ty = sole_field.ty(self.tcx, substs); + if self.same_type_modulo_infer(sole_field_ty, exp_found.found) { + let variant_path = + with_no_trimmed_paths!(self.tcx.def_path_str(variant.def_id)); + // FIXME #56861: DRYer prelude filtering + if let Some(path) = variant_path.strip_prefix("std::prelude::") { + if let Some((_, path)) = path.split_once("::") { + return Some(path.to_string()); + } + } + Some(variant_path) + } else { + None + } + }) + .collect(); + match &compatible_variants[..] { + [] => {} + [variant] => { + diag.multipart_suggestion_verbose( + &format!("try wrapping the pattern in `{}`", variant), + vec![ + (cause.span.shrink_to_lo(), format!("{}(", variant)), + (cause.span.shrink_to_hi(), ")".to_string()), + ], + Applicability::MaybeIncorrect, + ); + } + _ => { + // More than one matching variant. + diag.multipart_suggestions( + &format!( + "try wrapping the pattern in a variant of `{}`", + self.tcx.def_path_str(expected_adt.did()) + ), + compatible_variants.into_iter().map(|variant| { + vec![ + (cause.span.shrink_to_lo(), format!("{}(", variant)), + (cause.span.shrink_to_hi(), ")".to_string()), + ] + }), + Applicability::MaybeIncorrect, + ); + } + } + } + } + } + + /// A possible error is to forget to add `.await` when using futures: + /// + /// ```compile_fail,E0308 + /// async fn make_u32() -> u32 { + /// 22 + /// } + /// + /// fn take_u32(x: u32) {} + /// + /// async fn foo() { + /// let x = make_u32(); + /// take_u32(x); + /// } + /// ``` + /// + /// This routine checks if the found type `T` implements `Future<Output=U>` where `U` is the + /// expected type. If this is the case, and we are inside of an async body, it suggests adding + /// `.await` to the tail of the expression. + pub(super) fn suggest_await_on_expect_found( + &self, + cause: &ObligationCause<'tcx>, + exp_span: Span, + exp_found: &ty::error::ExpectedFound<Ty<'tcx>>, + diag: &mut Diagnostic, + ) { + debug!( + "suggest_await_on_expect_found: exp_span={:?}, expected_ty={:?}, found_ty={:?}", + exp_span, exp_found.expected, exp_found.found, + ); + + if let ObligationCauseCode::CompareImplItemObligation { .. } = cause.code() { + return; + } + + match ( + self.get_impl_future_output_ty(exp_found.expected), + self.get_impl_future_output_ty(exp_found.found), + ) { + (Some(exp), Some(found)) if self.same_type_modulo_infer(exp, found) => match cause + .code() + { + ObligationCauseCode::IfExpression(box IfExpressionCause { then_id, .. }) => { + let then_span = self.find_block_span_from_hir_id(*then_id); + diag.multipart_suggestion( + "consider `await`ing on both `Future`s", + vec![ + (then_span.shrink_to_hi(), ".await".to_string()), + (exp_span.shrink_to_hi(), ".await".to_string()), + ], + Applicability::MaybeIncorrect, + ); + } + ObligationCauseCode::MatchExpressionArm(box MatchExpressionArmCause { + prior_arms, + .. + }) => { + if let [.., arm_span] = &prior_arms[..] { + diag.multipart_suggestion( + "consider `await`ing on both `Future`s", + vec![ + (arm_span.shrink_to_hi(), ".await".to_string()), + (exp_span.shrink_to_hi(), ".await".to_string()), + ], + Applicability::MaybeIncorrect, + ); + } else { + diag.help("consider `await`ing on both `Future`s"); + } + } + _ => { + diag.help("consider `await`ing on both `Future`s"); + } + }, + (_, Some(ty)) if self.same_type_modulo_infer(exp_found.expected, ty) => { + diag.span_suggestion_verbose( + exp_span.shrink_to_hi(), + "consider `await`ing on the `Future`", + ".await", + Applicability::MaybeIncorrect, + ); + } + (Some(ty), _) if self.same_type_modulo_infer(ty, exp_found.found) => match cause.code() + { + ObligationCauseCode::Pattern { span: Some(then_span), .. } => { + diag.span_suggestion_verbose( + then_span.shrink_to_hi(), + "consider `await`ing on the `Future`", + ".await", + Applicability::MaybeIncorrect, + ); + } + ObligationCauseCode::IfExpression(box IfExpressionCause { then_id, .. }) => { + let then_span = self.find_block_span_from_hir_id(*then_id); + diag.span_suggestion_verbose( + then_span.shrink_to_hi(), + "consider `await`ing on the `Future`", + ".await", + Applicability::MaybeIncorrect, + ); + } + ObligationCauseCode::MatchExpressionArm(box MatchExpressionArmCause { + ref prior_arms, + .. + }) => { + diag.multipart_suggestion_verbose( + "consider `await`ing on the `Future`", + prior_arms + .iter() + .map(|arm| (arm.shrink_to_hi(), ".await".to_string())) + .collect(), + Applicability::MaybeIncorrect, + ); + } + _ => {} + }, + _ => {} + } + } + + pub(super) fn suggest_accessing_field_where_appropriate( + &self, + cause: &ObligationCause<'tcx>, + exp_found: &ty::error::ExpectedFound<Ty<'tcx>>, + diag: &mut Diagnostic, + ) { + debug!( + "suggest_accessing_field_where_appropriate(cause={:?}, exp_found={:?})", + cause, exp_found + ); + if let ty::Adt(expected_def, expected_substs) = exp_found.expected.kind() { + if expected_def.is_enum() { + return; + } + + if let Some((name, ty)) = expected_def + .non_enum_variant() + .fields + .iter() + .filter(|field| field.vis.is_accessible_from(field.did, self.tcx)) + .map(|field| (field.name, field.ty(self.tcx, expected_substs))) + .find(|(_, ty)| self.same_type_modulo_infer(*ty, exp_found.found)) + { + if let ObligationCauseCode::Pattern { span: Some(span), .. } = *cause.code() { + if let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(span) { + let suggestion = if expected_def.is_struct() { + format!("{}.{}", snippet, name) + } else if expected_def.is_union() { + format!("unsafe {{ {}.{} }}", snippet, name) + } else { + return; + }; + diag.span_suggestion( + span, + &format!( + "you might have meant to use field `{}` whose type is `{}`", + name, ty + ), + suggestion, + Applicability::MaybeIncorrect, + ); + } + } + } + } + } + + /// When encountering a case where `.as_ref()` on a `Result` or `Option` would be appropriate, + /// suggests it. + pub(super) fn suggest_as_ref_where_appropriate( + &self, + span: Span, + exp_found: &ty::error::ExpectedFound<Ty<'tcx>>, + diag: &mut Diagnostic, + ) { + if let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(span) + && let Some(msg) = self.should_suggest_as_ref(exp_found.expected, exp_found.found) + { + diag.span_suggestion( + span, + msg, + // HACK: fix issue# 100605, suggesting convert from &Option<T> to Option<&T>, remove the extra `&` + format!("{}.as_ref()", snippet.trim_start_matches('&')), + Applicability::MachineApplicable, + ); + } + } + + pub fn should_suggest_as_ref(&self, expected: Ty<'tcx>, found: Ty<'tcx>) -> Option<&str> { + if let (ty::Adt(exp_def, exp_substs), ty::Ref(_, found_ty, _)) = + (expected.kind(), found.kind()) + { + if let ty::Adt(found_def, found_substs) = *found_ty.kind() { + if exp_def == &found_def { + let have_as_ref = &[ + ( + sym::Option, + "you can convert from `&Option<T>` to `Option<&T>` using \ + `.as_ref()`", + ), + ( + sym::Result, + "you can convert from `&Result<T, E>` to \ + `Result<&T, &E>` using `.as_ref()`", + ), + ]; + if let Some(msg) = have_as_ref.iter().find_map(|(name, msg)| { + self.tcx.is_diagnostic_item(*name, exp_def.did()).then_some(msg) + }) { + let mut show_suggestion = true; + for (exp_ty, found_ty) in + std::iter::zip(exp_substs.types(), found_substs.types()) + { + match *exp_ty.kind() { + ty::Ref(_, exp_ty, _) => { + match (exp_ty.kind(), found_ty.kind()) { + (_, ty::Param(_)) + | (_, ty::Infer(_)) + | (ty::Param(_), _) + | (ty::Infer(_), _) => {} + _ if self.same_type_modulo_infer(exp_ty, found_ty) => {} + _ => show_suggestion = false, + }; + } + ty::Param(_) | ty::Infer(_) => {} + _ => show_suggestion = false, + } + } + if show_suggestion { + return Some(*msg); + } + } + } + } + } + None + } + + /// Try to find code with pattern `if Some(..) = expr` + /// use a `visitor` to mark the `if` which its span contains given error span, + /// and then try to find a assignment in the `cond` part, which span is equal with error span + pub(super) fn suggest_let_for_letchains( + &self, + err: &mut Diagnostic, + cause: &ObligationCause<'_>, + span: Span, + ) { + let hir = self.tcx.hir(); + let fn_hir_id = hir.get_parent_node(cause.body_id); + if let Some(node) = self.tcx.hir().find(fn_hir_id) && + let hir::Node::Item(hir::Item { + kind: hir::ItemKind::Fn(_sig, _, body_id), .. + }) = node { + let body = hir.body(*body_id); + + /// Find the if expression with given span + struct IfVisitor { + pub result: bool, + pub found_if: bool, + pub err_span: Span, + } + + impl<'v> Visitor<'v> for IfVisitor { + fn visit_expr(&mut self, ex: &'v hir::Expr<'v>) { + if self.result { return; } + match ex.kind { + hir::ExprKind::If(cond, _, _) => { + self.found_if = true; + walk_expr(self, cond); + self.found_if = false; + } + _ => walk_expr(self, ex), + } + } + + fn visit_stmt(&mut self, ex: &'v hir::Stmt<'v>) { + if let hir::StmtKind::Local(hir::Local { + span, pat: hir::Pat{..}, ty: None, init: Some(_), .. + }) = &ex.kind + && self.found_if + && span.eq(&self.err_span) { + self.result = true; + } + walk_stmt(self, ex); + } + + fn visit_body(&mut self, body: &'v hir::Body<'v>) { + hir::intravisit::walk_body(self, body); + } + } + + let mut visitor = IfVisitor { err_span: span, found_if: false, result: false }; + visitor.visit_body(&body); + if visitor.result { + err.subdiagnostic(SuggAddLetForLetChains{span: span.shrink_to_lo()}); + } + } + } +} + +impl<'tcx> TypeErrCtxt<'_, 'tcx> { + /// Be helpful when the user wrote `{... expr; }` and taking the `;` off + /// is enough to fix the error. + pub fn could_remove_semicolon( + &self, + blk: &'tcx hir::Block<'tcx>, + expected_ty: Ty<'tcx>, + ) -> Option<(Span, StatementAsExpression)> { + let blk = blk.innermost_block(); + // Do not suggest if we have a tail expr. + if blk.expr.is_some() { + return None; + } + let last_stmt = blk.stmts.last()?; + let hir::StmtKind::Semi(ref last_expr) = last_stmt.kind else { + return None; + }; + let last_expr_ty = self.typeck_results.as_ref()?.expr_ty_opt(*last_expr)?; + let needs_box = match (last_expr_ty.kind(), expected_ty.kind()) { + _ if last_expr_ty.references_error() => return None, + _ if self.same_type_modulo_infer(last_expr_ty, expected_ty) => { + StatementAsExpression::CorrectType + } + (ty::Opaque(last_def_id, _), ty::Opaque(exp_def_id, _)) + if last_def_id == exp_def_id => + { + StatementAsExpression::CorrectType + } + (ty::Opaque(last_def_id, last_bounds), ty::Opaque(exp_def_id, exp_bounds)) => { + debug!( + "both opaque, likely future {:?} {:?} {:?} {:?}", + last_def_id, last_bounds, exp_def_id, exp_bounds + ); + + let last_local_id = last_def_id.as_local()?; + let exp_local_id = exp_def_id.as_local()?; + + match ( + &self.tcx.hir().expect_item(last_local_id).kind, + &self.tcx.hir().expect_item(exp_local_id).kind, + ) { + ( + hir::ItemKind::OpaqueTy(hir::OpaqueTy { bounds: last_bounds, .. }), + hir::ItemKind::OpaqueTy(hir::OpaqueTy { bounds: exp_bounds, .. }), + ) if std::iter::zip(*last_bounds, *exp_bounds).all(|(left, right)| { + match (left, right) { + ( + hir::GenericBound::Trait(tl, ml), + hir::GenericBound::Trait(tr, mr), + ) if tl.trait_ref.trait_def_id() == tr.trait_ref.trait_def_id() + && ml == mr => + { + true + } + ( + hir::GenericBound::LangItemTrait(langl, _, _, argsl), + hir::GenericBound::LangItemTrait(langr, _, _, argsr), + ) if langl == langr => { + // FIXME: consider the bounds! + debug!("{:?} {:?}", argsl, argsr); + true + } + _ => false, + } + }) => + { + StatementAsExpression::NeedsBoxing + } + _ => StatementAsExpression::CorrectType, + } + } + _ => return None, + }; + let span = if last_stmt.span.from_expansion() { + let mac_call = rustc_span::source_map::original_sp(last_stmt.span, blk.span); + self.tcx.sess.source_map().mac_call_stmt_semi_span(mac_call)? + } else { + last_stmt.span.with_lo(last_stmt.span.hi() - BytePos(1)) + }; + Some((span, needs_box)) + } + + /// Suggest returning a local binding with a compatible type if the block + /// has no return expression. + pub fn consider_returning_binding( + &self, + blk: &'tcx hir::Block<'tcx>, + expected_ty: Ty<'tcx>, + err: &mut Diagnostic, + ) -> bool { + let blk = blk.innermost_block(); + // Do not suggest if we have a tail expr. + if blk.expr.is_some() { + return false; + } + let mut shadowed = FxIndexSet::default(); + let mut candidate_idents = vec![]; + let mut find_compatible_candidates = |pat: &hir::Pat<'_>| { + if let hir::PatKind::Binding(_, hir_id, ident, _) = &pat.kind + && let Some(pat_ty) = self + .typeck_results + .as_ref() + .and_then(|typeck_results| typeck_results.node_type_opt(*hir_id)) + { + let pat_ty = self.resolve_vars_if_possible(pat_ty); + if self.same_type_modulo_infer(pat_ty, expected_ty) + && !(pat_ty, expected_ty).references_error() + && shadowed.insert(ident.name) + { + candidate_idents.push((*ident, pat_ty)); + } + } + true + }; + + let hir = self.tcx.hir(); + for stmt in blk.stmts.iter().rev() { + let hir::StmtKind::Local(local) = &stmt.kind else { continue; }; + local.pat.walk(&mut find_compatible_candidates); + } + match hir.find(hir.get_parent_node(blk.hir_id)) { + Some(hir::Node::Expr(hir::Expr { hir_id, .. })) => { + match hir.find(hir.get_parent_node(*hir_id)) { + Some(hir::Node::Arm(hir::Arm { pat, .. })) => { + pat.walk(&mut find_compatible_candidates); + } + Some( + hir::Node::Item(hir::Item { kind: hir::ItemKind::Fn(_, _, body), .. }) + | hir::Node::ImplItem(hir::ImplItem { + kind: hir::ImplItemKind::Fn(_, body), + .. + }) + | hir::Node::TraitItem(hir::TraitItem { + kind: hir::TraitItemKind::Fn(_, hir::TraitFn::Provided(body)), + .. + }) + | hir::Node::Expr(hir::Expr { + kind: hir::ExprKind::Closure(hir::Closure { body, .. }), + .. + }), + ) => { + for param in hir.body(*body).params { + param.pat.walk(&mut find_compatible_candidates); + } + } + Some(hir::Node::Expr(hir::Expr { + kind: + hir::ExprKind::If( + hir::Expr { kind: hir::ExprKind::Let(let_), .. }, + then_block, + _, + ), + .. + })) if then_block.hir_id == *hir_id => { + let_.pat.walk(&mut find_compatible_candidates); + } + _ => {} + } + } + _ => {} + } + + match &candidate_idents[..] { + [(ident, _ty)] => { + let sm = self.tcx.sess.source_map(); + if let Some(stmt) = blk.stmts.last() { + let stmt_span = sm.stmt_span(stmt.span, blk.span); + let sugg = if sm.is_multiline(blk.span) + && let Some(spacing) = sm.indentation_before(stmt_span) + { + format!("\n{spacing}{ident}") + } else { + format!(" {ident}") + }; + err.span_suggestion_verbose( + stmt_span.shrink_to_hi(), + format!("consider returning the local binding `{ident}`"), + sugg, + Applicability::MaybeIncorrect, + ); + } else { + let sugg = if sm.is_multiline(blk.span) + && let Some(spacing) = sm.indentation_before(blk.span.shrink_to_lo()) + { + format!("\n{spacing} {ident}\n{spacing}") + } else { + format!(" {ident} ") + }; + let left_span = sm.span_through_char(blk.span, '{').shrink_to_hi(); + err.span_suggestion_verbose( + sm.span_extend_while(left_span, |c| c.is_whitespace()).unwrap_or(left_span), + format!("consider returning the local binding `{ident}`"), + sugg, + Applicability::MaybeIncorrect, + ); + } + true + } + values if (1..3).contains(&values.len()) => { + let spans = values.iter().map(|(ident, _)| ident.span).collect::<Vec<_>>(); + err.span_note(spans, "consider returning one of these bindings"); + true + } + _ => false, + } + } +} |