From 826bceaf67de1fd35e35bda03e0ed58b26fba325 Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Fri, 26 Jun 2026 14:03:07 +0200 Subject: [PATCH 01/13] Add `TraitEngine::is_in_probe` --- compiler/rustc_infer/src/traits/engine.rs | 2 ++ compiler/rustc_trait_selection/src/solve/fulfill.rs | 4 ++++ compiler/rustc_trait_selection/src/traits/fulfill.rs | 4 ++++ 3 files changed, 10 insertions(+) diff --git a/compiler/rustc_infer/src/traits/engine.rs b/compiler/rustc_infer/src/traits/engine.rs index 38fc991fcfeb6..59e55e46a739e 100644 --- a/compiler/rustc_infer/src/traits/engine.rs +++ b/compiler/rustc_infer/src/traits/engine.rs @@ -127,6 +127,8 @@ pub trait TraitEngine<'tcx, E: 'tcx>: 'tcx { &mut self, infcx: &InferCtxt<'tcx>, ) -> PredicateObligations<'tcx>; + + fn is_in_probe(&self, infcx: &InferCtxt<'_>) -> bool; } pub trait FromSolverError<'tcx, E>: Debug + 'tcx { diff --git a/compiler/rustc_trait_selection/src/solve/fulfill.rs b/compiler/rustc_trait_selection/src/solve/fulfill.rs index 4b80d22d6bb49..68a09655ee520 100644 --- a/compiler/rustc_trait_selection/src/solve/fulfill.rs +++ b/compiler/rustc_trait_selection/src/solve/fulfill.rs @@ -349,6 +349,10 @@ where .map(|(o, _)| o) .collect() } + + fn is_in_probe(&self, infcx: &InferCtxt<'_>) -> bool { + self.usable_in_snapshot != infcx.num_open_snapshots() + } } pub enum NextSolverError<'tcx> { diff --git a/compiler/rustc_trait_selection/src/traits/fulfill.rs b/compiler/rustc_trait_selection/src/traits/fulfill.rs index aa963b1bca749..456c7d5d1e687 100644 --- a/compiler/rustc_trait_selection/src/traits/fulfill.rs +++ b/compiler/rustc_trait_selection/src/traits/fulfill.rs @@ -273,6 +273,10 @@ where fn pending_obligations(&self) -> PredicateObligations<'tcx> { self.predicates.map_pending_obligations(|o| o.obligation.clone()) } + + fn is_in_probe(&self, infcx: &InferCtxt<'_>) -> bool { + self.usable_in_snapshot != infcx.num_open_snapshots() + } } struct FulfillProcessor<'a, 'tcx> { From fcb4680590530a5a66b8504124a316da8eeb5598 Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Fri, 26 Jun 2026 14:03:07 +0200 Subject: [PATCH 02/13] split out suggestion generation for type hints into a separate function --- .../rustc_trait_selection/src/diagnostics.rs | 2 +- .../error_reporting/infer/need_type_info.rs | 101 +++++++++++------- 2 files changed, 64 insertions(+), 39 deletions(-) diff --git a/compiler/rustc_trait_selection/src/diagnostics.rs b/compiler/rustc_trait_selection/src/diagnostics.rs index ba89b0987659e..5cab305164d7b 100644 --- a/compiler/rustc_trait_selection/src/diagnostics.rs +++ b/compiler/rustc_trait_selection/src/diagnostics.rs @@ -294,7 +294,7 @@ pub(crate) enum SourceKindSubdiag<'a> { x_kind: &'static str, prefix_kind: UnderspecifiedArgKind, prefix: &'a str, - arg_name: String, + arg_name: &'a str, }, #[label( "cannot infer {$is_type -> diff --git a/compiler/rustc_trait_selection/src/error_reporting/infer/need_type_info.rs b/compiler/rustc_trait_selection/src/error_reporting/infer/need_type_info.rs index 624aef89911f7..cb93777f63b9a 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/infer/need_type_info.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/infer/need_type_info.rs @@ -564,7 +564,67 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { let mut infer_subdiags = Vec::new(); let mut multi_suggestions = Vec::new(); - match kind { + self.suggestion( + body_def_id, + term, + &arg_data, + typeck_results, + span, + &kind, + &mut infer_subdiags, + &mut multi_suggestions, + ); + let mut err = match error_code { + TypeAnnotationNeeded::E0282 => self.dcx().create_err(AnnotationRequired { + span, + source_kind, + source_name: &name, + failure_span, + infer_subdiags, + multi_suggestions, + bad_label: None, + }), + TypeAnnotationNeeded::E0283 => self.dcx().create_err(AmbiguousImpl { + span, + source_kind, + source_name: &name, + failure_span, + infer_subdiags, + multi_suggestions, + bad_label: None, + }), + TypeAnnotationNeeded::E0284 => self.dcx().create_err(AmbiguousReturn { + span, + source_kind, + source_name: &name, + failure_span, + infer_subdiags, + multi_suggestions, + bad_label: None, + }), + }; + *err.long_ty_path() = long_ty_path; + if let InferSourceKind::ClosureArg { kind: PatKind::Err(_), .. } = kind { + // We will have already emitted an error about this pattern. + err.downgrade_to_delayed_bug(); + } + err + } + + fn suggestion<'local>( + &self, + body_def_id: LocalDefId, + term: Term<'tcx>, + arg_data: &'local InferenceDiagnosticsData, + typeck_results: &TypeckResults<'tcx>, + span: Span, + kind: &InferSourceKind<'tcx>, + infer_subdiags: &mut Vec>, + multi_suggestions: &mut Vec>, + ) where + 'tcx: 'local, + { + match *kind { InferSourceKind::LetBinding { insert_span, pattern_name, ty, def_id } => { infer_subdiags.push(SourceKindSubdiag::LetLike { span: insert_span, @@ -572,7 +632,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { x_kind: arg_data.where_x_is_kind(self.infcx, ty), prefix_kind: arg_data.kind.clone(), prefix: arg_data.kind.try_get_prefix().unwrap_or_default(), - arg_name: arg_data.name, + arg_name: &arg_data.name, kind: if pattern_name.is_some() { "with_pattern" } else { "other" }, type_name: ty_to_string(self, ty, def_id), }); @@ -584,7 +644,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { x_kind: arg_data.where_x_is_kind(self.infcx, ty), prefix_kind: arg_data.kind.clone(), prefix: arg_data.kind.try_get_prefix().unwrap_or_default(), - arg_name: arg_data.name, + arg_name: &arg_data.name, kind: "closure", type_name: ty_to_string(self, ty, None), }); @@ -737,41 +797,6 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { } } } - let mut err = match error_code { - TypeAnnotationNeeded::E0282 => self.dcx().create_err(AnnotationRequired { - span, - source_kind, - source_name: &name, - failure_span, - infer_subdiags, - multi_suggestions, - bad_label: None, - }), - TypeAnnotationNeeded::E0283 => self.dcx().create_err(AmbiguousImpl { - span, - source_kind, - source_name: &name, - failure_span, - infer_subdiags, - multi_suggestions, - bad_label: None, - }), - TypeAnnotationNeeded::E0284 => self.dcx().create_err(AmbiguousReturn { - span, - source_kind, - source_name: &name, - failure_span, - infer_subdiags, - multi_suggestions, - bad_label: None, - }), - }; - *err.long_ty_path() = long_ty_path; - if let InferSourceKind::ClosureArg { kind: PatKind::Err(_), .. } = kind { - // We will have already emitted an error about this pattern. - err.downgrade_to_delayed_bug(); - } - err } } From 7517333c3c2efd34bdadd339c81e63879a6bab67 Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Fri, 3 Jul 2026 20:55:53 +0200 Subject: [PATCH 03/13] merge `SourceKindSubdiag` and `SourceKindMultiSuggestion` There is only one place where `SourceKindSubdiag` is used without `SourceKindMultiSuggestion`; it doesn't seem useful to distinguish between them (I have no idea what either of the names means tbh) --- .../rustc_trait_selection/src/diagnostics.rs | 12 +----------- .../src/error_reporting/infer/need_type_info.rs | 17 +++-------------- 2 files changed, 4 insertions(+), 25 deletions(-) diff --git a/compiler/rustc_trait_selection/src/diagnostics.rs b/compiler/rustc_trait_selection/src/diagnostics.rs index 5cab305164d7b..d0e448d2e852c 100644 --- a/compiler/rustc_trait_selection/src/diagnostics.rs +++ b/compiler/rustc_trait_selection/src/diagnostics.rs @@ -189,8 +189,6 @@ pub(crate) struct AnnotationRequired<'a> { pub bad_label: Option>, #[subdiagnostic] pub infer_subdiags: Vec>, - #[subdiagnostic] - pub multi_suggestions: Vec>, } // Copy of `AnnotationRequired` for E0283 @@ -211,8 +209,6 @@ pub(crate) struct AmbiguousImpl<'a> { pub bad_label: Option>, #[subdiagnostic] pub infer_subdiags: Vec>, - #[subdiagnostic] - pub multi_suggestions: Vec>, } // Copy of `AnnotationRequired` for E0284 @@ -233,8 +229,6 @@ pub(crate) struct AmbiguousReturn<'a> { pub bad_label: Option>, #[subdiagnostic] pub infer_subdiags: Vec>, - #[subdiagnostic] - pub multi_suggestions: Vec>, } // Used when a better one isn't available @@ -354,10 +348,6 @@ pub(crate) enum SourceKindSubdiag<'a> { span: Span, param: String, }, -} - -#[derive(Subdiagnostic)] -pub(crate) enum SourceKindMultiSuggestion<'a> { #[multipart_suggestion( "try using a fully qualified path to specify the expected types", style = "verbose", @@ -386,7 +376,7 @@ pub(crate) enum SourceKindMultiSuggestion<'a> { }, } -impl<'a> SourceKindMultiSuggestion<'a> { +impl<'a> SourceKindSubdiag<'a> { pub(crate) fn new_fully_qualified( span: Span, def_path: String, diff --git a/compiler/rustc_trait_selection/src/error_reporting/infer/need_type_info.rs b/compiler/rustc_trait_selection/src/error_reporting/infer/need_type_info.rs index cb93777f63b9a..f73f62de06fbb 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/infer/need_type_info.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/infer/need_type_info.rs @@ -24,8 +24,7 @@ use tracing::{debug, instrument, warn}; use super::nice_region_error::placeholder_error::Highlighted; use crate::diagnostics::{ - AmbiguousImpl, AmbiguousReturn, AnnotationRequired, InferenceBadError, - SourceKindMultiSuggestion, SourceKindSubdiag, + AmbiguousImpl, AmbiguousReturn, AnnotationRequired, InferenceBadError, SourceKindSubdiag, }; use crate::error_reporting::TypeErrCtxt; use crate::infer::{InferCtxt, TyOrConstInferVar}; @@ -449,7 +448,6 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { let source_name = ""; let failure_span = None; let infer_subdiags = Vec::new(); - let multi_suggestions = Vec::new(); let bad_label = Some(arg_data.make_bad_error(span)); match error_code { TypeAnnotationNeeded::E0282 => self.dcx().create_err(AnnotationRequired { @@ -458,7 +456,6 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { source_name, failure_span, infer_subdiags, - multi_suggestions, bad_label, }), TypeAnnotationNeeded::E0283 => self.dcx().create_err(AmbiguousImpl { @@ -467,7 +464,6 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { source_name, failure_span, infer_subdiags, - multi_suggestions, bad_label, }), TypeAnnotationNeeded::E0284 => self.dcx().create_err(AmbiguousReturn { @@ -476,7 +472,6 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { source_name, failure_span, infer_subdiags, - multi_suggestions, bad_label, }), } @@ -563,7 +558,6 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { }; let mut infer_subdiags = Vec::new(); - let mut multi_suggestions = Vec::new(); self.suggestion( body_def_id, term, @@ -572,7 +566,6 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { span, &kind, &mut infer_subdiags, - &mut multi_suggestions, ); let mut err = match error_code { TypeAnnotationNeeded::E0282 => self.dcx().create_err(AnnotationRequired { @@ -581,7 +574,6 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { source_name: &name, failure_span, infer_subdiags, - multi_suggestions, bad_label: None, }), TypeAnnotationNeeded::E0283 => self.dcx().create_err(AmbiguousImpl { @@ -590,7 +582,6 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { source_name: &name, failure_span, infer_subdiags, - multi_suggestions, bad_label: None, }), TypeAnnotationNeeded::E0284 => self.dcx().create_err(AmbiguousReturn { @@ -599,7 +590,6 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { source_name: &name, failure_span, infer_subdiags, - multi_suggestions, bad_label: None, }), }; @@ -620,7 +610,6 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { span: Span, kind: &InferSourceKind<'tcx>, infer_subdiags: &mut Vec>, - multi_suggestions: &mut Vec>, ) where 'tcx: 'local, { @@ -777,7 +766,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { _ => "", }; - multi_suggestions.push(SourceKindMultiSuggestion::new_fully_qualified( + infer_subdiags.push(SourceKindSubdiag::new_fully_qualified( receiver.span, def_path, adjustment, @@ -789,7 +778,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { let placeholder = Some(self.next_ty_var(DUMMY_SP)); if let Some(ty) = ty.make_suggestable(self.infcx.tcx, true, placeholder) { let ty_info = ty_to_string(self, ty, None); - multi_suggestions.push(SourceKindMultiSuggestion::new_closure_return( + infer_subdiags.push(SourceKindSubdiag::new_closure_return( ty_info, data, should_wrap_expr, From f7abfc872fc66cb1488157df1c1383c9dc9e87c5 Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Fri, 26 Jun 2026 14:03:07 +0200 Subject: [PATCH 04/13] add a doc comment for `fcw!` --- compiler/rustc_lint_defs/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/rustc_lint_defs/src/lib.rs b/compiler/rustc_lint_defs/src/lib.rs index 6949090781cf6..6f297c120d79a 100644 --- a/compiler/rustc_lint_defs/src/lib.rs +++ b/compiler/rustc_lint_defs/src/lib.rs @@ -803,6 +803,7 @@ macro_rules! declare_lint_pass { }; } +/// Helper macro to create [`FutureIncompatibilityReason`]. #[macro_export] macro_rules! fcw { (FutureReleaseError # $issue_number: literal) => { From 74208dae2a3a0e18ef49d49821fedd5b665d49dd Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Sat, 4 Jul 2026 03:07:18 +0200 Subject: [PATCH 05/13] Combine `sourceKindSubdiag::Generic*` into one variant This makes it clear that (what used to be called) `GenericLabel` and other generic variants are related -- the latter are suggestions, which can sometimes be present in *addition* to the label. Although this mainly helps future cleanups. --- .../rustc_trait_selection/src/diagnostics.rs | 85 ++++++++++--------- .../error_reporting/infer/need_type_info.rs | 40 ++++----- 2 files changed, 68 insertions(+), 57 deletions(-) diff --git a/compiler/rustc_trait_selection/src/diagnostics.rs b/compiler/rustc_trait_selection/src/diagnostics.rs index d0e448d2e852c..8563a7f3522b0 100644 --- a/compiler/rustc_trait_selection/src/diagnostics.rs +++ b/compiler/rustc_trait_selection/src/diagnostics.rs @@ -302,7 +302,7 @@ pub(crate) enum SourceKindSubdiag<'a> { *[false] parameter {$param_name} }" )] - GenericLabel { + Generic { #[primary_span] span: Span, is_type: bool, @@ -310,43 +310,8 @@ pub(crate) enum SourceKindSubdiag<'a> { parent_exists: bool, parent_prefix: String, parent_name: String, - }, - #[suggestion( - "consider specifying the generic {$arg_count -> - [one] argument - *[other] arguments - }", - style = "verbose", - code = "::<{args}>", - applicability = "has-placeholders" - )] - GenericSuggestion { - #[primary_span] - span: Span, - arg_count: usize, - args: String, - }, - #[suggestion( - "consider specifying a concrete type for the type parameter `{$param}`", - style = "verbose", - code = "::", - applicability = "has-placeholders" - )] - GenericTypeSuggestion { - #[primary_span] - span: Span, - param: String, - }, - #[suggestion( - "consider specifying a const for the const parameter `{$param}`", - style = "verbose", - code = "::", - applicability = "has-placeholders" - )] - ConstGenericSuggestion { - #[primary_span] - span: Span, - param: String, + #[subdiagnostic] + suggestion: Option, }, #[multipart_suggestion( "try using a fully qualified path to specify the expected types", @@ -409,6 +374,50 @@ impl<'a> SourceKindSubdiag<'a> { } } +/// Suggestion to specify generic parameter(s) via `::<>`. +#[derive(Subdiagnostic)] +pub(crate) enum SpecifyGenericParamsSuggestion { + #[suggestion( + "consider specifying the generic {$arg_count -> + [one] argument + *[other] arguments + }", + style = "verbose", + code = "::<{args}>", + applicability = "has-placeholders" + )] + GenericSuggestion { + #[primary_span] + span: Span, + arg_count: usize, + args: String, + }, + // FIXME: there is really no reason for `Generic{Type,Const}Suggestion` to be separate + // variants, all of these should be unified. + #[suggestion( + "consider specifying a concrete type for the type parameter `{$param}`", + style = "verbose", + code = "::", + applicability = "has-placeholders" + )] + GenericTypeSuggestion { + #[primary_span] + span: Span, + param: String, + }, + #[suggestion( + "consider specifying a const for the const parameter `{$param}`", + style = "verbose", + code = "::", + applicability = "has-placeholders" + )] + ConstGenericSuggestion { + #[primary_span] + span: Span, + param: String, + }, +} + pub(crate) enum RegionOriginNote<'a> { Plain { span: Span, diff --git a/compiler/rustc_trait_selection/src/error_reporting/infer/need_type_info.rs b/compiler/rustc_trait_selection/src/error_reporting/infer/need_type_info.rs index f73f62de06fbb..5862b1b7da9dd 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/infer/need_type_info.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/infer/need_type_info.rs @@ -25,6 +25,7 @@ use tracing::{debug, instrument, warn}; use super::nice_region_error::placeholder_error::Highlighted; use crate::diagnostics::{ AmbiguousImpl, AmbiguousReturn, AnnotationRequired, InferenceBadError, SourceKindSubdiag, + SpecifyGenericParamsSuggestion, }; use crate::error_reporting::TypeErrCtxt; use crate::infer::{InferCtxt, TyOrConstInferVar}; @@ -659,15 +660,6 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { let param = &generics.own_params[argument_index]; let param_name = param.name.to_string(); - infer_subdiags.push(SourceKindSubdiag::GenericLabel { - span, - is_type, - param_name: param_name.clone(), - parent_exists, - parent_prefix, - parent_name, - }); - let mut used_fallback = false; let args = if self.tcx.get_diagnostic_item(sym::iterator_collect_fn) == Some(generics_def_id) @@ -715,33 +707,43 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { p.into_buffer() }; - if !have_turbofish { + let suggestion = have_turbofish.not().then(|| { if generic_args.len() == 1 && used_fallback { match param.kind { GenericParamDefKind::Type { .. } => { - infer_subdiags.push(SourceKindSubdiag::GenericTypeSuggestion { + SpecifyGenericParamsSuggestion::GenericTypeSuggestion { span: insert_span, - param: param_name, - }); + param: param_name.clone(), + } } GenericParamDefKind::Const { .. } => { - infer_subdiags.push(SourceKindSubdiag::ConstGenericSuggestion { + SpecifyGenericParamsSuggestion::ConstGenericSuggestion { span: insert_span, - param: param_name, - }); + param: param_name.clone(), + } } GenericParamDefKind::Lifetime => { bug!("unexpected lifetime") } } } else { - infer_subdiags.push(SourceKindSubdiag::GenericSuggestion { + SpecifyGenericParamsSuggestion::GenericSuggestion { span: insert_span, arg_count: generic_args.len(), args, - }); + } } - } + }); + + infer_subdiags.push(SourceKindSubdiag::Generic { + span, + is_type, + param_name, + parent_exists, + parent_prefix, + parent_name, + suggestion, + }) } InferSourceKind::FullyQualifiedMethodCall { receiver, successor, args, def_id } => { let placeholder = Some(self.next_ty_var(DUMMY_SP)); From 50d233dc31b29241a35bac0af411de9bafadecd4 Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Sat, 4 Jul 2026 02:18:22 +0200 Subject: [PATCH 06/13] need type info: only allow 0-1 subdiagnostic This simplifies things a bunch, as we don't need any mutable state to track subdiagnostics now. Also, this makes it clearer which diagnostics are possible. --- .../rustc_trait_selection/src/diagnostics.rs | 6 +- .../error_reporting/infer/need_type_info.rs | 132 ++++++++---------- 2 files changed, 63 insertions(+), 75 deletions(-) diff --git a/compiler/rustc_trait_selection/src/diagnostics.rs b/compiler/rustc_trait_selection/src/diagnostics.rs index 8563a7f3522b0..bb2614992adf2 100644 --- a/compiler/rustc_trait_selection/src/diagnostics.rs +++ b/compiler/rustc_trait_selection/src/diagnostics.rs @@ -188,7 +188,7 @@ pub(crate) struct AnnotationRequired<'a> { #[subdiagnostic] pub bad_label: Option>, #[subdiagnostic] - pub infer_subdiags: Vec>, + pub subdiagnostic: Option>, } // Copy of `AnnotationRequired` for E0283 @@ -208,7 +208,7 @@ pub(crate) struct AmbiguousImpl<'a> { #[subdiagnostic] pub bad_label: Option>, #[subdiagnostic] - pub infer_subdiags: Vec>, + pub subdiagnostic: Option>, } // Copy of `AnnotationRequired` for E0284 @@ -228,7 +228,7 @@ pub(crate) struct AmbiguousReturn<'a> { #[subdiagnostic] pub bad_label: Option>, #[subdiagnostic] - pub infer_subdiags: Vec>, + pub subdiagnostic: Option>, } // Used when a better one isn't available diff --git a/compiler/rustc_trait_selection/src/error_reporting/infer/need_type_info.rs b/compiler/rustc_trait_selection/src/error_reporting/infer/need_type_info.rs index 5862b1b7da9dd..07f7f65364a27 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/infer/need_type_info.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/infer/need_type_info.rs @@ -448,7 +448,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { let source_kind = "other"; let source_name = ""; let failure_span = None; - let infer_subdiags = Vec::new(); + let subdiagnostic = None; let bad_label = Some(arg_data.make_bad_error(span)); match error_code { TypeAnnotationNeeded::E0282 => self.dcx().create_err(AnnotationRequired { @@ -456,7 +456,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { source_kind, source_name, failure_span, - infer_subdiags, + subdiagnostic, bad_label, }), TypeAnnotationNeeded::E0283 => self.dcx().create_err(AmbiguousImpl { @@ -464,7 +464,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { source_kind, source_name, failure_span, - infer_subdiags, + subdiagnostic, bad_label, }), TypeAnnotationNeeded::E0284 => self.dcx().create_err(AmbiguousReturn { @@ -472,7 +472,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { source_kind, source_name, failure_span, - infer_subdiags, + subdiagnostic, bad_label, }), } @@ -558,23 +558,16 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { None }; - let mut infer_subdiags = Vec::new(); - self.suggestion( - body_def_id, - term, - &arg_data, - typeck_results, - span, - &kind, - &mut infer_subdiags, - ); + let subdiagnostic = + self.suggestion(body_def_id, term, &arg_data, typeck_results, span, &kind); + let mut err = match error_code { TypeAnnotationNeeded::E0282 => self.dcx().create_err(AnnotationRequired { span, source_kind, source_name: &name, failure_span, - infer_subdiags, + subdiagnostic, bad_label: None, }), TypeAnnotationNeeded::E0283 => self.dcx().create_err(AmbiguousImpl { @@ -582,7 +575,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { source_kind, source_name: &name, failure_span, - infer_subdiags, + subdiagnostic, bad_label: None, }), TypeAnnotationNeeded::E0284 => self.dcx().create_err(AmbiguousReturn { @@ -590,7 +583,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { source_kind, source_name: &name, failure_span, - infer_subdiags, + subdiagnostic, bad_label: None, }), }; @@ -610,13 +603,13 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { typeck_results: &TypeckResults<'tcx>, span: Span, kind: &InferSourceKind<'tcx>, - infer_subdiags: &mut Vec>, - ) where + ) -> Option> + where 'tcx: 'local, { - match *kind { + let subdiag = match *kind { InferSourceKind::LetBinding { insert_span, pattern_name, ty, def_id } => { - infer_subdiags.push(SourceKindSubdiag::LetLike { + SourceKindSubdiag::LetLike { span: insert_span, name: pattern_name.map(|name| name.to_string()).unwrap_or_else(String::new), x_kind: arg_data.where_x_is_kind(self.infcx, ty), @@ -625,20 +618,18 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { arg_name: &arg_data.name, kind: if pattern_name.is_some() { "with_pattern" } else { "other" }, type_name: ty_to_string(self, ty, def_id), - }); - } - InferSourceKind::ClosureArg { insert_span, ty, .. } => { - infer_subdiags.push(SourceKindSubdiag::LetLike { - span: insert_span, - name: String::new(), - x_kind: arg_data.where_x_is_kind(self.infcx, ty), - prefix_kind: arg_data.kind.clone(), - prefix: arg_data.kind.try_get_prefix().unwrap_or_default(), - arg_name: &arg_data.name, - kind: "closure", - type_name: ty_to_string(self, ty, None), - }); + } } + InferSourceKind::ClosureArg { insert_span, ty, .. } => SourceKindSubdiag::LetLike { + span: insert_span, + name: String::new(), + x_kind: arg_data.where_x_is_kind(self.infcx, ty), + prefix_kind: arg_data.kind.clone(), + prefix: arg_data.kind.try_get_prefix().unwrap_or_default(), + arg_name: &arg_data.name, + kind: "closure", + type_name: ty_to_string(self, ty, None), + }, InferSourceKind::GenericArg { insert_span, argument_index, @@ -735,7 +726,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { } }); - infer_subdiags.push(SourceKindSubdiag::Generic { + SourceKindSubdiag::Generic { span, is_type, param_name, @@ -743,51 +734,48 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { parent_prefix, parent_name, suggestion, - }) + } } InferSourceKind::FullyQualifiedMethodCall { receiver, successor, args, def_id } => { let placeholder = Some(self.next_ty_var(DUMMY_SP)); - if let Some(args) = args.make_suggestable(self.infcx.tcx, true, placeholder) { - let mut p = fmt_printer(self, Namespace::ValueNS); - p.print_def_path(def_id, args).unwrap(); - let def_path = p.into_buffer(); - - // We only care about whether we have to add `&` or `&mut ` for now. - // This is the case if the last adjustment is a borrow and the - // first adjustment was not a builtin deref. - let adjustment = match typeck_results.expr_adjustments(receiver) { - [ - Adjustment { kind: Adjust::Deref(DerefAdjustKind::Builtin), target: _ }, - .., - Adjustment { kind: Adjust::Borrow(AutoBorrow::Ref(..)), target: _ }, - ] => "", - [ - .., - Adjustment { kind: Adjust::Borrow(AutoBorrow::Ref(mut_)), target: _ }, - ] => hir::Mutability::from(*mut_).ref_prefix_str(), - _ => "", - }; + let args = args.make_suggestable(self.infcx.tcx, true, placeholder)?; + + let mut p = fmt_printer(self, Namespace::ValueNS); + p.print_def_path(def_id, args).unwrap(); + let def_path = p.into_buffer(); + + // We only care about whether we have to add `&` or `&mut ` for now. + // This is the case if the last adjustment is a borrow and the + // first adjustment was not a builtin deref. + let adjustment = match typeck_results.expr_adjustments(receiver) { + [ + Adjustment { kind: Adjust::Deref(DerefAdjustKind::Builtin), target: _ }, + .., + Adjustment { kind: Adjust::Borrow(AutoBorrow::Ref(..)), target: _ }, + ] => "", + [.., Adjustment { kind: Adjust::Borrow(AutoBorrow::Ref(mut_)), target: _ }] => { + hir::Mutability::from(*mut_).ref_prefix_str() + } + _ => "", + }; - infer_subdiags.push(SourceKindSubdiag::new_fully_qualified( - receiver.span, - def_path, - adjustment, - successor, - )); - } + SourceKindSubdiag::new_fully_qualified( + receiver.span, + def_path, + adjustment, + successor, + ) } InferSourceKind::ClosureReturn { ty, data, should_wrap_expr } => { let placeholder = Some(self.next_ty_var(DUMMY_SP)); - if let Some(ty) = ty.make_suggestable(self.infcx.tcx, true, placeholder) { - let ty_info = ty_to_string(self, ty, None); - infer_subdiags.push(SourceKindSubdiag::new_closure_return( - ty_info, - data, - should_wrap_expr, - )); - } + + let ty = ty.make_suggestable(self.infcx.tcx, true, placeholder)?; + let ty_info = ty_to_string(self, ty, None); + SourceKindSubdiag::new_closure_return(ty_info, data, should_wrap_expr) } - } + }; + + Some(subdiag) } } From 979a7e4b034609bba61f307018024a4bda141a8e Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Sat, 4 Jul 2026 03:34:19 +0200 Subject: [PATCH 07/13] move `TypeErrCtxt::suggesion` to `InferSourceKind` --- .../error_reporting/infer/need_type_info.rs | 290 +++++++++--------- 1 file changed, 145 insertions(+), 145 deletions(-) diff --git a/compiler/rustc_trait_selection/src/error_reporting/infer/need_type_info.rs b/compiler/rustc_trait_selection/src/error_reporting/infer/need_type_info.rs index 07f7f65364a27..991464048b2e6 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/infer/need_type_info.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/infer/need_type_info.rs @@ -559,7 +559,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { }; let subdiagnostic = - self.suggestion(body_def_id, term, &arg_data, typeck_results, span, &kind); + kind.suggestion(self, body_def_id, term, &arg_data, typeck_results, span); let mut err = match error_code { TypeAnnotationNeeded::E0282 => self.dcx().create_err(AnnotationRequired { @@ -594,41 +594,135 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { } err } +} + +#[derive(Debug)] +struct InferSource<'tcx> { + span: Span, + kind: InferSourceKind<'tcx>, +} + +#[derive(Debug)] +enum InferSourceKind<'tcx> { + LetBinding { + insert_span: Span, + pattern_name: Option, + ty: Ty<'tcx>, + def_id: Option, + }, + ClosureArg { + insert_span: Span, + ty: Ty<'tcx>, + kind: PatKind<'tcx>, + }, + GenericArg { + insert_span: Span, + argument_index: usize, + generics_def_id: DefId, + def_id: DefId, + generic_args: &'tcx [GenericArg<'tcx>], + have_turbofish: bool, + hir_id: HirId, + }, + FullyQualifiedMethodCall { + receiver: &'tcx Expr<'tcx>, + /// If the method has other arguments, this is ", " and the start of the first argument, + /// while for methods without arguments this is ")" and the end of the method call. + successor: (&'static str, BytePos), + args: GenericArgsRef<'tcx>, + def_id: DefId, + }, + ClosureReturn { + ty: Ty<'tcx>, + data: &'tcx FnRetTy<'tcx>, + should_wrap_expr: Option, + }, +} + +impl<'tcx> InferSource<'tcx> { + fn from_expansion(&self) -> bool { + let source_from_expansion = match self.kind { + InferSourceKind::LetBinding { insert_span, .. } + | InferSourceKind::ClosureArg { insert_span, .. } + | InferSourceKind::GenericArg { insert_span, .. } => insert_span.from_expansion(), + InferSourceKind::FullyQualifiedMethodCall { receiver, .. } => { + receiver.span.from_expansion() + } + InferSourceKind::ClosureReturn { data, should_wrap_expr, .. } => { + data.span().from_expansion() || should_wrap_expr.is_some_and(Span::from_expansion) + } + }; + source_from_expansion || self.span.from_expansion() + } +} + +impl<'tcx> InferSourceKind<'tcx> { + fn ty_localized_msg(&self, infcx: &InferCtxt<'tcx>) -> (&'static str, String, Option) { + let mut long_ty_path = None; + match *self { + InferSourceKind::LetBinding { ty, .. } + | InferSourceKind::ClosureArg { ty, .. } + | InferSourceKind::ClosureReturn { ty, .. } => { + if ty.is_closure() { + ("closure", closure_as_fn_str(infcx, ty), long_ty_path) + } else if ty.is_ty_or_numeric_infer() + || ty.is_primitive() + || matches!( + ty.kind(), + ty::Adt(_, args) + if args.types().count() == 0 && args.consts().count() == 0 + ) + { + // `ty` is either `_`, a primitive type like `u32` or a type with no type or + // const parameters. We will not mention the type in the main inference error + // message. + ("other", String::new(), long_ty_path) + } else { + ("normal", infcx.tcx.short_string(ty, &mut long_ty_path), long_ty_path) + } + } + // FIXME: We should be able to add some additional info here. + InferSourceKind::GenericArg { .. } + | InferSourceKind::FullyQualifiedMethodCall { .. } => { + ("other", String::new(), long_ty_path) + } + } + } fn suggestion<'local>( &self, + this: &TypeErrCtxt<'_, 'tcx>, body_def_id: LocalDefId, term: Term<'tcx>, arg_data: &'local InferenceDiagnosticsData, typeck_results: &TypeckResults<'tcx>, span: Span, - kind: &InferSourceKind<'tcx>, ) -> Option> where 'tcx: 'local, { - let subdiag = match *kind { + let subdiag = match *self { InferSourceKind::LetBinding { insert_span, pattern_name, ty, def_id } => { SourceKindSubdiag::LetLike { span: insert_span, name: pattern_name.map(|name| name.to_string()).unwrap_or_else(String::new), - x_kind: arg_data.where_x_is_kind(self.infcx, ty), + x_kind: arg_data.where_x_is_kind(this.infcx, ty), prefix_kind: arg_data.kind.clone(), prefix: arg_data.kind.try_get_prefix().unwrap_or_default(), arg_name: &arg_data.name, kind: if pattern_name.is_some() { "with_pattern" } else { "other" }, - type_name: ty_to_string(self, ty, def_id), + type_name: ty_to_string(this, ty, def_id), } } InferSourceKind::ClosureArg { insert_span, ty, .. } => SourceKindSubdiag::LetLike { span: insert_span, name: String::new(), - x_kind: arg_data.where_x_is_kind(self.infcx, ty), + x_kind: arg_data.where_x_is_kind(this.infcx, ty), prefix_kind: arg_data.kind.clone(), prefix: arg_data.kind.try_get_prefix().unwrap_or_default(), arg_name: &arg_data.name, kind: "closure", - type_name: ty_to_string(self, ty, None), + type_name: ty_to_string(this, ty, None), }, InferSourceKind::GenericArg { insert_span, @@ -639,11 +733,11 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { have_turbofish, hir_id, } => { - let generics = self.tcx.generics_of(generics_def_id); + let generics = this.tcx.generics_of(generics_def_id); let is_type = term.as_type().is_some(); let (parent_exists, parent_prefix, parent_name) = - InferenceDiagnosticsParentData::for_parent_def_id(self.tcx, generics_def_id) + InferenceDiagnosticsParentData::for_parent_def_id(this.tcx, generics_def_id) .map_or((false, String::new(), String::new()), |parent| { (true, parent.prefix.to_string(), parent.name) }); @@ -652,18 +746,18 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { let param_name = param.name.to_string(); let mut used_fallback = false; - let args = if self.tcx.get_diagnostic_item(sym::iterator_collect_fn) + let args = if this.tcx.get_diagnostic_item(sym::iterator_collect_fn) == Some(generics_def_id) { - if let hir::Node::Expr(expr) = self.tcx.parent_hir_node(hir_id) + if let hir::Node::Expr(expr) = this.tcx.parent_hir_node(hir_id) && let hir::ExprKind::Call(expr, _args) = expr.kind && let hir::ExprKind::Path(hir::QPath::Resolved(_, path)) = expr.kind && let Res::Def(DefKind::AssocFn, def_id) = path.res - && let Some(try_trait) = self.tcx.lang_items().try_trait() - && try_trait == self.tcx.parent(def_id) + && let Some(try_trait) = this.tcx.lang_items().try_trait() + && try_trait == this.tcx.parent(def_id) && let DefKind::Fn | DefKind::AssocFn = - self.tcx.def_kind(body_def_id.to_def_id()) - && let ret = self + this.tcx.def_kind(body_def_id.to_def_id()) + && let ret = this .tcx .fn_sig(body_def_id.to_def_id()) .instantiate_identity() @@ -671,9 +765,9 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { .output() && let ty::Adt(adt, _args) = ret.kind() && let Some(sym::Option | sym::Result) = - self.tcx.get_diagnostic_name(adt.did()) + this.tcx.get_diagnostic_name(adt.did()) { - if let Some(sym::Option) = self.tcx.get_diagnostic_name(adt.did()) { + if let Some(sym::Option) = this.tcx.get_diagnostic_name(adt.did()) { "Option<_>".to_string() } else { "Result<_, _>".to_string() @@ -682,49 +776,49 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { "Vec<_>".to_string() } } else { - let mut p = fmt_printer(self, Namespace::TypeNS); + let mut p = fmt_printer(this, Namespace::TypeNS); p.comma_sep(generic_args.iter().copied().map(|arg| { - if arg.is_suggestable(self.tcx, true) { + if arg.is_suggestable(this.tcx, true) { used_fallback = true; return arg; } match arg.kind() { GenericArgKind::Lifetime(_) => bug!("unexpected lifetime"), - GenericArgKind::Type(_) => self.next_ty_var(DUMMY_SP).into(), - GenericArgKind::Const(_) => self.next_const_var(DUMMY_SP).into(), + GenericArgKind::Type(_) => this.next_ty_var(DUMMY_SP).into(), + GenericArgKind::Const(_) => this.next_const_var(DUMMY_SP).into(), } })) .unwrap(); p.into_buffer() }; - let suggestion = have_turbofish.not().then(|| { - if generic_args.len() == 1 && used_fallback { - match param.kind { - GenericParamDefKind::Type { .. } => { - SpecifyGenericParamsSuggestion::GenericTypeSuggestion { - span: insert_span, - param: param_name.clone(), - } - } - GenericParamDefKind::Const { .. } => { - SpecifyGenericParamsSuggestion::ConstGenericSuggestion { - span: insert_span, - param: param_name.clone(), - } - } - GenericParamDefKind::Lifetime => { - bug!("unexpected lifetime") - } + let suggestion = if have_turbofish { + None + } else if generic_args.len() == 1 && used_fallback { + match param.kind { + GenericParamDefKind::Type { .. } => { + Some(SpecifyGenericParamsSuggestion::GenericTypeSuggestion { + span: insert_span, + param: param_name.clone(), + }) } - } else { - SpecifyGenericParamsSuggestion::GenericSuggestion { - span: insert_span, - arg_count: generic_args.len(), - args, + GenericParamDefKind::Const { .. } => { + Some(SpecifyGenericParamsSuggestion::ConstGenericSuggestion { + span: insert_span, + param: param_name.clone(), + }) + } + GenericParamDefKind::Lifetime => { + bug!("unexpected lifetime") } } - }); + } else { + Some(SpecifyGenericParamsSuggestion::GenericSuggestion { + span: insert_span, + arg_count: generic_args.len(), + args, + }) + }; SourceKindSubdiag::Generic { span, @@ -737,10 +831,10 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { } } InferSourceKind::FullyQualifiedMethodCall { receiver, successor, args, def_id } => { - let placeholder = Some(self.next_ty_var(DUMMY_SP)); - let args = args.make_suggestable(self.infcx.tcx, true, placeholder)?; + let placeholder = Some(this.next_ty_var(DUMMY_SP)); + let args = args.make_suggestable(this.infcx.tcx, true, placeholder)?; - let mut p = fmt_printer(self, Namespace::ValueNS); + let mut p = fmt_printer(this, Namespace::ValueNS); p.print_def_path(def_id, args).unwrap(); let def_path = p.into_buffer(); @@ -767,10 +861,10 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { ) } InferSourceKind::ClosureReturn { ty, data, should_wrap_expr } => { - let placeholder = Some(self.next_ty_var(DUMMY_SP)); + let placeholder = Some(this.next_ty_var(DUMMY_SP)); - let ty = ty.make_suggestable(self.infcx.tcx, true, placeholder)?; - let ty_info = ty_to_string(self, ty, None); + let ty = ty.make_suggestable(this.infcx.tcx, true, placeholder)?; + let ty_info = ty_to_string(this, ty, None); SourceKindSubdiag::new_closure_return(ty_info, data, should_wrap_expr) } }; @@ -779,100 +873,6 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { } } -#[derive(Debug)] -struct InferSource<'tcx> { - span: Span, - kind: InferSourceKind<'tcx>, -} - -#[derive(Debug)] -enum InferSourceKind<'tcx> { - LetBinding { - insert_span: Span, - pattern_name: Option, - ty: Ty<'tcx>, - def_id: Option, - }, - ClosureArg { - insert_span: Span, - ty: Ty<'tcx>, - kind: PatKind<'tcx>, - }, - GenericArg { - insert_span: Span, - argument_index: usize, - generics_def_id: DefId, - def_id: DefId, - generic_args: &'tcx [GenericArg<'tcx>], - have_turbofish: bool, - hir_id: HirId, - }, - FullyQualifiedMethodCall { - receiver: &'tcx Expr<'tcx>, - /// If the method has other arguments, this is ", " and the start of the first argument, - /// while for methods without arguments this is ")" and the end of the method call. - successor: (&'static str, BytePos), - args: GenericArgsRef<'tcx>, - def_id: DefId, - }, - ClosureReturn { - ty: Ty<'tcx>, - data: &'tcx FnRetTy<'tcx>, - should_wrap_expr: Option, - }, -} - -impl<'tcx> InferSource<'tcx> { - fn from_expansion(&self) -> bool { - let source_from_expansion = match self.kind { - InferSourceKind::LetBinding { insert_span, .. } - | InferSourceKind::ClosureArg { insert_span, .. } - | InferSourceKind::GenericArg { insert_span, .. } => insert_span.from_expansion(), - InferSourceKind::FullyQualifiedMethodCall { receiver, .. } => { - receiver.span.from_expansion() - } - InferSourceKind::ClosureReturn { data, should_wrap_expr, .. } => { - data.span().from_expansion() || should_wrap_expr.is_some_and(Span::from_expansion) - } - }; - source_from_expansion || self.span.from_expansion() - } -} - -impl<'tcx> InferSourceKind<'tcx> { - fn ty_localized_msg(&self, infcx: &InferCtxt<'tcx>) -> (&'static str, String, Option) { - let mut long_ty_path = None; - match *self { - InferSourceKind::LetBinding { ty, .. } - | InferSourceKind::ClosureArg { ty, .. } - | InferSourceKind::ClosureReturn { ty, .. } => { - if ty.is_closure() { - ("closure", closure_as_fn_str(infcx, ty), long_ty_path) - } else if ty.is_ty_or_numeric_infer() - || ty.is_primitive() - || matches!( - ty.kind(), - ty::Adt(_, args) - if args.types().count() == 0 && args.consts().count() == 0 - ) - { - // `ty` is either `_`, a primitive type like `u32` or a type with no type or - // const parameters. We will not mention the type in the main inference error - // message. - ("other", String::new(), long_ty_path) - } else { - ("normal", infcx.tcx.short_string(ty, &mut long_ty_path), long_ty_path) - } - } - // FIXME: We should be able to add some additional info here. - InferSourceKind::GenericArg { .. } - | InferSourceKind::FullyQualifiedMethodCall { .. } => { - ("other", String::new(), long_ty_path) - } - } - } -} - #[derive(Debug)] struct InsertableGenericArgs<'tcx> { insert_span: Span, From 4706067ad6f250473fc6510757c029eec0941aa7 Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Sat, 4 Jul 2026 03:39:32 +0200 Subject: [PATCH 08/13] decouple `InferSourceKind::suggesion` from `TypeErrCtxt` --- .../error_reporting/infer/need_type_info.rs | 64 ++++++++++--------- 1 file changed, 35 insertions(+), 29 deletions(-) diff --git a/compiler/rustc_trait_selection/src/error_reporting/infer/need_type_info.rs b/compiler/rustc_trait_selection/src/error_reporting/infer/need_type_info.rs index 991464048b2e6..f9b484cc6cadd 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/infer/need_type_info.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/infer/need_type_info.rs @@ -558,8 +558,15 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { None }; - let subdiagnostic = - kind.suggestion(self, body_def_id, term, &arg_data, typeck_results, span); + let subdiagnostic = kind.suggestion( + self.tcx, + self.infcx, + body_def_id, + term, + &arg_data, + typeck_results, + span, + ); let mut err = match error_code { TypeAnnotationNeeded::E0282 => self.dcx().create_err(AnnotationRequired { @@ -691,7 +698,8 @@ impl<'tcx> InferSourceKind<'tcx> { fn suggestion<'local>( &self, - this: &TypeErrCtxt<'_, 'tcx>, + tcx: TyCtxt<'tcx>, + infcx: &InferCtxt<'tcx>, body_def_id: LocalDefId, term: Term<'tcx>, arg_data: &'local InferenceDiagnosticsData, @@ -706,23 +714,23 @@ impl<'tcx> InferSourceKind<'tcx> { SourceKindSubdiag::LetLike { span: insert_span, name: pattern_name.map(|name| name.to_string()).unwrap_or_else(String::new), - x_kind: arg_data.where_x_is_kind(this.infcx, ty), + x_kind: arg_data.where_x_is_kind(infcx, ty), prefix_kind: arg_data.kind.clone(), prefix: arg_data.kind.try_get_prefix().unwrap_or_default(), arg_name: &arg_data.name, kind: if pattern_name.is_some() { "with_pattern" } else { "other" }, - type_name: ty_to_string(this, ty, def_id), + type_name: ty_to_string(infcx, ty, def_id), } } InferSourceKind::ClosureArg { insert_span, ty, .. } => SourceKindSubdiag::LetLike { span: insert_span, name: String::new(), - x_kind: arg_data.where_x_is_kind(this.infcx, ty), + x_kind: arg_data.where_x_is_kind(infcx, ty), prefix_kind: arg_data.kind.clone(), prefix: arg_data.kind.try_get_prefix().unwrap_or_default(), arg_name: &arg_data.name, kind: "closure", - type_name: ty_to_string(this, ty, None), + type_name: ty_to_string(infcx, ty, None), }, InferSourceKind::GenericArg { insert_span, @@ -733,11 +741,11 @@ impl<'tcx> InferSourceKind<'tcx> { have_turbofish, hir_id, } => { - let generics = this.tcx.generics_of(generics_def_id); + let generics = tcx.generics_of(generics_def_id); let is_type = term.as_type().is_some(); let (parent_exists, parent_prefix, parent_name) = - InferenceDiagnosticsParentData::for_parent_def_id(this.tcx, generics_def_id) + InferenceDiagnosticsParentData::for_parent_def_id(tcx, generics_def_id) .map_or((false, String::new(), String::new()), |parent| { (true, parent.prefix.to_string(), parent.name) }); @@ -746,28 +754,26 @@ impl<'tcx> InferSourceKind<'tcx> { let param_name = param.name.to_string(); let mut used_fallback = false; - let args = if this.tcx.get_diagnostic_item(sym::iterator_collect_fn) + let args = if tcx.get_diagnostic_item(sym::iterator_collect_fn) == Some(generics_def_id) { - if let hir::Node::Expr(expr) = this.tcx.parent_hir_node(hir_id) + if let hir::Node::Expr(expr) = tcx.parent_hir_node(hir_id) && let hir::ExprKind::Call(expr, _args) = expr.kind && let hir::ExprKind::Path(hir::QPath::Resolved(_, path)) = expr.kind && let Res::Def(DefKind::AssocFn, def_id) = path.res - && let Some(try_trait) = this.tcx.lang_items().try_trait() - && try_trait == this.tcx.parent(def_id) + && let Some(try_trait) = tcx.lang_items().try_trait() + && try_trait == tcx.parent(def_id) && let DefKind::Fn | DefKind::AssocFn = - this.tcx.def_kind(body_def_id.to_def_id()) - && let ret = this - .tcx + tcx.def_kind(body_def_id.to_def_id()) + && let ret = tcx .fn_sig(body_def_id.to_def_id()) .instantiate_identity() .skip_binder() .output() && let ty::Adt(adt, _args) = ret.kind() - && let Some(sym::Option | sym::Result) = - this.tcx.get_diagnostic_name(adt.did()) + && let Some(sym::Option | sym::Result) = tcx.get_diagnostic_name(adt.did()) { - if let Some(sym::Option) = this.tcx.get_diagnostic_name(adt.did()) { + if let Some(sym::Option) = tcx.get_diagnostic_name(adt.did()) { "Option<_>".to_string() } else { "Result<_, _>".to_string() @@ -776,16 +782,16 @@ impl<'tcx> InferSourceKind<'tcx> { "Vec<_>".to_string() } } else { - let mut p = fmt_printer(this, Namespace::TypeNS); + let mut p = fmt_printer(infcx, Namespace::TypeNS); p.comma_sep(generic_args.iter().copied().map(|arg| { - if arg.is_suggestable(this.tcx, true) { + if arg.is_suggestable(tcx, true) { used_fallback = true; return arg; } match arg.kind() { GenericArgKind::Lifetime(_) => bug!("unexpected lifetime"), - GenericArgKind::Type(_) => this.next_ty_var(DUMMY_SP).into(), - GenericArgKind::Const(_) => this.next_const_var(DUMMY_SP).into(), + GenericArgKind::Type(_) => infcx.next_ty_var(DUMMY_SP).into(), + GenericArgKind::Const(_) => infcx.next_const_var(DUMMY_SP).into(), } })) .unwrap(); @@ -831,10 +837,10 @@ impl<'tcx> InferSourceKind<'tcx> { } } InferSourceKind::FullyQualifiedMethodCall { receiver, successor, args, def_id } => { - let placeholder = Some(this.next_ty_var(DUMMY_SP)); - let args = args.make_suggestable(this.infcx.tcx, true, placeholder)?; + let placeholder = Some(infcx.next_ty_var(DUMMY_SP)); + let args = args.make_suggestable(tcx, true, placeholder)?; - let mut p = fmt_printer(this, Namespace::ValueNS); + let mut p = fmt_printer(infcx, Namespace::ValueNS); p.print_def_path(def_id, args).unwrap(); let def_path = p.into_buffer(); @@ -861,10 +867,10 @@ impl<'tcx> InferSourceKind<'tcx> { ) } InferSourceKind::ClosureReturn { ty, data, should_wrap_expr } => { - let placeholder = Some(this.next_ty_var(DUMMY_SP)); + let placeholder = Some(infcx.next_ty_var(DUMMY_SP)); - let ty = ty.make_suggestable(this.infcx.tcx, true, placeholder)?; - let ty_info = ty_to_string(this, ty, None); + let ty = ty.make_suggestable(tcx, true, placeholder)?; + let ty_info = ty_to_string(infcx, ty, None); SourceKindSubdiag::new_closure_return(ty_info, data, should_wrap_expr) } }; From f3a2305a830d97476a30c81d6851ce792bf5ec5c Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Fri, 26 Jun 2026 14:03:07 +0200 Subject: [PATCH 09/13] Add `#[rustc_low_priority]` attribute --- .../src/attributes/rustc_internal.rs | 10 ++++++++++ compiler/rustc_attr_parsing/src/context.rs | 1 + compiler/rustc_feature/src/builtin_attrs.rs | 1 + compiler/rustc_hir/src/attrs/data_structures.rs | 2 ++ compiler/rustc_hir/src/attrs/encode_cross_crate.rs | 1 + compiler/rustc_passes/src/check_attr.rs | 1 + compiler/rustc_span/src/symbol.rs | 1 + 7 files changed, 17 insertions(+) diff --git a/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs b/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs index 24df9b100ce95..806825405a7ea 100644 --- a/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs +++ b/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs @@ -527,6 +527,16 @@ impl NoArgsAttributeParser for RustcLintUntrackedQueryInformationParser { const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcLintUntrackedQueryInformation; } +pub(crate) struct RustcLowPriorityImplParser; + +impl NoArgsAttributeParser for RustcLowPriorityImplParser { + const PATH: &[Symbol] = &[sym::rustc_low_priority_impl]; + const ALLOWED_TARGETS: AllowedTargets<'_> = + AllowedTargets::AllowList(&[Allow(Target::Impl { of_trait: true })]); + const STABILITY: AttributeStability = unstable!(rustc_attrs); + const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcLowPriorityImpl; +} + pub(crate) struct RustcSimdMonomorphizeLaneLimitParser; impl SingleAttributeParser for RustcSimdMonomorphizeLaneLimitParser { diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs index 1595d2f80ddc7..013aabe5f1dc3 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -306,6 +306,7 @@ attribute_parsers!( Single>, Single>, Single>, + Single>, Single>, Single>, Single>, diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs index 6a9cfeff49339..223ced7601822 100644 --- a/compiler/rustc_feature/src/builtin_attrs.rs +++ b/compiler/rustc_feature/src/builtin_attrs.rs @@ -255,6 +255,7 @@ pub static BUILTIN_ATTRIBUTES: &[Symbol] = &[ sym::may_dangle, sym::rustc_never_type_options, + sym::rustc_low_priority_impl, // ========================================================================== // Internal attributes: Runtime related: diff --git a/compiler/rustc_hir/src/attrs/data_structures.rs b/compiler/rustc_hir/src/attrs/data_structures.rs index 17d00863d99d5..abc3b0436b0f4 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -1520,6 +1520,8 @@ pub enum AttributeKind { /// Represents `#[rustc_lint_untracked_query_information]` RustcLintUntrackedQueryInformation, + RustcLowPriorityImpl, + /// Represents `#[rustc_macro_transparency]`. RustcMacroTransparency(Transparency), diff --git a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs index f5a6eeec07406..6a97d29f63db7 100644 --- a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs +++ b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs @@ -156,6 +156,7 @@ impl AttributeKind { RustcLintOptTy => Yes, RustcLintQueryInstability => Yes, RustcLintUntrackedQueryInformation => Yes, + RustcLowPriorityImpl => Yes, RustcMacroTransparency(..) => Yes, RustcMain => No, RustcMir(..) => Yes, diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index 7ae6005e9bc98..26786f718b0a4 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -364,6 +364,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { AttributeKind::RustcLintOptTy => (), AttributeKind::RustcLintQueryInstability => (), AttributeKind::RustcLintUntrackedQueryInformation => (), + AttributeKind::RustcLowPriorityImpl => (), AttributeKind::RustcMacroTransparency(_) => (), AttributeKind::RustcMain => (), AttributeKind::RustcMir(_) => (), diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index db2804a3f83bf..8c1f509c57ed5 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1812,6 +1812,7 @@ symbols! { rustc_lint_opt_ty, rustc_lint_query_instability, rustc_lint_untracked_query_information, + rustc_low_priority_impl, rustc_macro_transparency, rustc_main, rustc_mir, From ae74397686ffcc7fedae32bd241942d573af610f Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Fri, 26 Jun 2026 14:03:07 +0200 Subject: [PATCH 10/13] extract_tupled_inputs_and_output_from_callable: don't crash on self_ty referencing error Without this change `tests/ui/async-await/async-closures/is-not-fn.rs` ICEing when constructing proof tree, supposedly to check if an infer var is referenced in a goal... --- .../src/solve/assembly/structural_traits.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_next_trait_solver/src/solve/assembly/structural_traits.rs b/compiler/rustc_next_trait_solver/src/solve/assembly/structural_traits.rs index 5bc56eae7755d..4b4bc4d45d7d6 100644 --- a/compiler/rustc_next_trait_solver/src/solve/assembly/structural_traits.rs +++ b/compiler/rustc_next_trait_solver/src/solve/assembly/structural_traits.rs @@ -9,7 +9,7 @@ use rustc_type_ir::solve::SizedTraitKind; use rustc_type_ir::solve::inspect::ProbeKind; use rustc_type_ir::{ self as ty, Binder, FallibleTypeFolder, Interner, Movability, Mutability, TypeFoldable, - TypeSuperFoldable, Unnormalized, Upcast as _, elaborate, + TypeSuperFoldable, TypeVisitableExt, Unnormalized, Upcast as _, elaborate, }; use rustc_type_ir_macros::{TypeFoldable_Generic, TypeVisitable_Generic}; use tracing::instrument; @@ -279,13 +279,14 @@ where } } -// Returns a binder of the tupled inputs types and output type from a builtin callable type. +/// Returns a binder of the tupled inputs types and output type from a builtin callable type. pub(in crate::solve) fn extract_tupled_inputs_and_output_from_callable( cx: I, self_ty: I::Ty, goal_kind: ty::ClosureKind, ) -> Result>, NoSolution> { match self_ty.kind() { + _ if self_ty.references_error() => Err(NoSolution), // keep this in sync with assemble_fn_pointer_candidates until the old solver is removed. ty::FnDef(def_id, args) => { let sig = cx.fn_sig(def_id); From e2bb1c1eb3ca4dcdfe16bbb3c330f0ce04ead748 Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Fri, 26 Jun 2026 14:03:07 +0200 Subject: [PATCH 11/13] implement low priority impls? --- compiler/rustc_hir_typeck/src/diagnostics.rs | 12 ++ compiler/rustc_hir_typeck/src/fallback.rs | 7 + .../rustc_hir_typeck/src/fn_ctxt/_impl.rs | 177 ++++++++++++++++-- .../src/fn_ctxt/inspect_obligations.rs | 156 +++++++++++++++ compiler/rustc_lint_defs/src/builtin.rs | 50 ++++- .../rustc_trait_selection/src/diagnostics.rs | 4 +- .../error_reporting/infer/need_type_info.rs | 9 +- .../src/error_reporting/traits/ambiguity.rs | 12 +- 8 files changed, 403 insertions(+), 24 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/diagnostics.rs b/compiler/rustc_hir_typeck/src/diagnostics.rs index a5c39bd1584a1..662a5c8c62ac8 100644 --- a/compiler/rustc_hir_typeck/src/diagnostics.rs +++ b/compiler/rustc_hir_typeck/src/diagnostics.rs @@ -15,6 +15,7 @@ use rustc_macros::{Diagnostic, Subdiagnostic}; use rustc_middle::ty::{self, Ty}; use rustc_span::edition::{Edition, LATEST_STABLE_EDITION}; use rustc_span::{Ident, Span, Spanned, Symbol}; +use rustc_trait_selection::diagnostics::SourceKindSubdiag; use crate::FnCtxt; @@ -1320,3 +1321,14 @@ pub(crate) struct FloatLiteralF32Fallback { )] pub span: Option, } + +#[derive(Diagnostic)] +#[help("specify the types explicitly")] +#[note("in the future, the requirement `{$obligation}` will fail")] +#[diag("dependency on trait impl fallback")] +pub(crate) struct DependencyOnTraitImplFallback<'tcx, 'a> { + pub obligation_span: Span, + pub obligation: ty::Predicate<'tcx>, + #[subdiagnostic] + pub subdiagnostic: Option>, +} diff --git a/compiler/rustc_hir_typeck/src/fallback.rs b/compiler/rustc_hir_typeck/src/fallback.rs index 0e34a6120b609..aaa93b98e310e 100644 --- a/compiler/rustc_hir_typeck/src/fallback.rs +++ b/compiler/rustc_hir_typeck/src/fallback.rs @@ -120,6 +120,13 @@ impl<'tcx> FnCtxt<'_, 'tcx> { ty::Infer(ty::IntVar(_)) => self.tcx.types.i32, ty::Infer(ty::FloatVar(vid)) if fallback_to_f32.contains(vid) => self.tcx.types.f32, ty::Infer(ty::FloatVar(_)) => self.tcx.types.f64, + + ty::Infer(ty::TyVar(_)) + if let Ok(_) = self.try_low_priority_impl_fallback_and_fcw(DUMMY_SP, ty) => + { + return true; + } + _ if diverging_fallback.contains(&ty) => { self.diverging_fallback_has_occurred.set(true); diverging_fallback_ty diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs index 9a1b1f8300957..cf9926b6fe785 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs @@ -1,6 +1,7 @@ use std::collections::hash_map::Entry; use std::slice; +use itertools::Itertools; use rustc_abi::FieldIdx; use rustc_data_structures::fx::FxHashSet; use rustc_errors::{ @@ -21,6 +22,8 @@ use rustc_hir_analysis::hir_ty_lowering::{ }; use rustc_infer::infer::canonical::{Canonical, OriginalQueryValues, QueryResponse}; use rustc_infer::infer::{DefineOpaqueTypes, InferResult}; +use rustc_infer::traits::ObligationCause; +use rustc_infer::traits::util::Elaboratable; use rustc_lint::builtin::SELF_CONSTRUCTOR_FROM_OUTER_ITEM; use rustc_middle::ty::adjustment::{ Adjust, Adjustment, AutoBorrow, AutoBorrowMutability, DerefAdjustKind, @@ -32,10 +35,16 @@ use rustc_middle::ty::{ }; use rustc_middle::{bug, span_bug}; use rustc_session::lint; -use rustc_span::Span; use rustc_span::def_id::LocalDefId; use rustc_span::hygiene::DesugaringKind; -use rustc_trait_selection::error_reporting::infer::need_type_info::TypeAnnotationNeeded; +use rustc_span::{DUMMY_SP, Span}; +use rustc_trait_selection::error_reporting::InferCtxtErrorExt; +use rustc_trait_selection::error_reporting::infer::need_type_info::{ + TypeAnnotationNeeded, find_infer_source, +}; +use rustc_trait_selection::error_reporting::traits::ambiguity::{ + CandidateSource, compute_applicable_impls_for_diagnostics, +}; use rustc_trait_selection::traits::{ self, NormalizeExt, ObligationCauseCode, StructurallyNormalizeExt, }; @@ -1531,20 +1540,160 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { #[cold] pub(crate) fn type_must_be_known_at_this_point(&self, sp: Span, ty: Ty<'tcx>) -> Ty<'tcx> { - let guar = self.tainted_by_errors().unwrap_or_else(|| { - self.err_ctxt() - .emit_inference_failure_err( - self.body_def_id, - sp, + self.try_fallback_and_fcw_or_error(sp, ty) + } + + #[cold] + pub(crate) fn try_fallback_and_fcw_or_error(&self, sp: Span, ty: Ty<'tcx>) -> Ty<'tcx> { + self.try_low_priority_impl_fallback_and_fcw(sp, ty).unwrap_or_else(|()| { + let guar = self.tainted_by_errors().unwrap_or_else(|| { + self.err_ctxt() + .emit_inference_failure_err( + self.body_def_id, + sp, + ty.into(), + TypeAnnotationNeeded::E0282, + true, + ) + .emit() + }); + let err = Ty::new_error(self.tcx, guar); + self.demand_suptype(sp, err, ty); + err + }) + } + + /// Tries to apply low priority impl fallback and emit a FCW if fallback has been applied. + /// + /// Returns `Ok(new_ty)` if fallback happened and `ty` was unified with `new_ty`. + /// + /// Panics if called while in a probe. + pub(crate) fn try_low_priority_impl_fallback_and_fcw( + &self, + sp: Span, + ty: Ty<'tcx>, + ) -> Result, ()> { + assert!(!self.fulfillment_cx.borrow().is_in_probe(&self.infcx)); + + // On new solver `obligations_referencing_infer_var` calls `resolve_vars_if_possible`, + // which can lead to inference progress. Use a probe so that this doesn't leak... + let obligations = + self.infcx.probe(|_| self.obligations_referencing_infer_var(ty.ty_vid().unwrap())); + + let fallback_opportunity = { + obligations + .into_iter() + .filter_map(|obligation| { + let clause = obligation.predicate().as_trait_clause()?; + let trait_obligation = obligation.with(self.tcx, clause); + + // `compute_applicable_impls_for_diagnostics` has a lot of side effects, put it in a probe. + let impls = self.infcx.probe(|_| { + compute_applicable_impls_for_diagnostics( + &self.infcx, + &trait_obligation, + false, + ) + }); + + // Below, we check that there is exactly one low priority impl. In combination + // with that, this checks that there is also at least one low priority impl. + if impls.len() <= 1 { + return None; + } + + // Exactly one candidate is not low priority + impls + .into_iter() + .filter(|candidate| !candidate.is_low_priority(self.tcx)) + .exactly_one() + .ok() + // ... and it's an impl rather than a param env candidate + .and_then(|candidate| match candidate { + CandidateSource::DefId(impl_def_id) => Some(impl_def_id), + CandidateSource::ParamEnv(_) => None, + }) + .map(|imp| (imp, obligation, trait_obligation)) + }) + .next() + }; + + let Some((impl_def_id, obligation, trait_obligation)) = fallback_opportunity else { + return Err(()); + }; + + self.infcx.enter_forall(trait_obligation.predicate, |placeholder_obligation| { + let obligation_trait_ref = + self.normalize(DUMMY_SP, Unnormalized::new_wip(placeholder_obligation.trait_ref)); + + let impl_args = self.infcx.fresh_args_for_item(DUMMY_SP, impl_def_id); + let impl_trait_ref = self + .tcx + .impl_trait_ref(impl_def_id) + .instantiate(self.tcx, impl_args) + .skip_norm_wip(); + let impl_trait_ref = self.normalize(DUMMY_SP, Unnormalized::new_wip(impl_trait_ref)); + + let cause = ObligationCause::dummy(); + + let extract_inference_diagnostics_data = + self.infcx.err_ctxt().extract_inference_diagnostics_data( ty.into(), - TypeAnnotationNeeded::E0282, - true, - ) - .emit() + ty::print::RegionHighlightMode::default(), + ); + + let source = + self.tcx.hir_node_by_def_id(self.body_def_id).body_id().and_then(|body_id| { + find_infer_source( + self.tcx, + &self.infcx, + &self.typeck_results.borrow(), + ty.into(), + body_id, + ) + }); + + _ = self + .at(&cause, self.param_env) + .eq(DefineOpaqueTypes::Yes, obligation_trait_ref, impl_trait_ref) + .map(|infer_ok| self.register_infer_ok_obligations(infer_ok)) + .map(|()| { + let (hir_id, span) = source + .as_ref() + .map(|source| (source.hir_id, source.span)) + .unwrap_or_else(|| { + ( + self.tcx.local_def_id_to_hir_id(self.body_def_id), + trait_obligation.cause.span, + ) + }); + + let subdiagnostic = source.and_then(|source| { + source.kind.suggestion( + self.tcx, + &self.infcx, + self.body_def_id, + ty.into(), + &extract_inference_diagnostics_data, + &self.typeck_results.borrow(), + span, + ) + }); + + self.tcx.emit_node_span_lint( + lint::builtin::TRAIT_IMPL_FALLBACK, + hir_id, + span, + diagnostics::DependencyOnTraitImplFallback { + obligation_span: trait_obligation.cause.span, + obligation: obligation.predicate, + subdiagnostic, + }, + ) + }); }); - let err = Ty::new_error(self.tcx, guar); - self.demand_suptype(sp, err, ty); - err + + Ok(self.structurally_resolve_type(sp, ty)) } pub(crate) fn structurally_resolve_const( diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/inspect_obligations.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/inspect_obligations.rs index cfaa60231379e..f38098dae385d 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/inspect_obligations.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/inspect_obligations.rs @@ -49,6 +49,26 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } + /// Returns a list of all obligations whose self type has been unified + /// with the unconstrained type `self_ty`. + #[instrument(skip(self), level = "debug")] + pub(crate) fn obligations_referencing_infer_var( + &self, + infer: ty::TyVid, + ) -> PredicateObligations<'tcx> { + if self.next_trait_solver() { + self.obligations_referencing_infer_var_next(infer) + } else { + let ty_var_root = self.root_var(infer); + let mut obligations = self.fulfillment_cx.borrow().pending_obligations(); + trace!("pending_obligations = {:#?}", obligations); + obligations.retain(|obligation| { + self.predicate_references_infer_var(obligation.predicate, ty_var_root) + }); + obligations + } + } + #[instrument(level = "debug", skip(self), ret)] fn predicate_has_self_ty( &self, @@ -83,6 +103,46 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } + #[instrument(level = "debug", skip(self), ret)] + fn predicate_references_infer_var( + &self, + predicate: ty::Predicate<'tcx>, + expected_vid: ty::TyVid, + ) -> bool { + match predicate.kind().skip_binder() { + ty::PredicateKind::Clause(ty::ClauseKind::Trait(data)) => data + .trait_ref + .args + .iter() + .filter_map(|arg| arg.as_type()) + .any(|t| self.type_matches_expected_vid(t, expected_vid, UseSubtyping::Yes)), + ty::PredicateKind::Clause(ty::ClauseKind::Projection(data)) => { + if data.projection_term.kind.is_trait_projection() { + data.projection_term + .args + .iter() + .filter_map(|arg| arg.as_type()) + .any(|t| self.type_matches_expected_vid(t, expected_vid, UseSubtyping::Yes)) + } else { + false + } + } + ty::PredicateKind::Clause(ty::ClauseKind::ConstArgHasType(..)) + | ty::PredicateKind::Subtype(..) + | ty::PredicateKind::Coerce(..) + | ty::PredicateKind::Clause(ty::ClauseKind::RegionOutlives(..)) + | ty::PredicateKind::Clause(ty::ClauseKind::TypeOutlives(..)) + | ty::PredicateKind::Clause(ty::ClauseKind::WellFormed(..)) + | ty::PredicateKind::DynCompatible(..) + | ty::PredicateKind::NormalizesTo(..) + | ty::PredicateKind::Clause(ty::ClauseKind::ConstEvaluatable(..)) + | ty::PredicateKind::ConstEquate(..) + | ty::PredicateKind::Clause(ty::ClauseKind::HostEffect(..)) + | ty::PredicateKind::Clause(ty::ClauseKind::UnstableFeature(_)) + | ty::PredicateKind::Ambiguous => false, + } + } + #[instrument(level = "debug", skip(self), ret)] fn type_matches_expected_vid( &self, @@ -145,6 +205,43 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { obligations_for_self_ty } + pub(crate) fn obligations_referencing_infer_var_next( + &self, + infer: ty::TyVid, + ) -> PredicateObligations<'tcx> { + // We only look at obligations which may reference the self type. + // This lookup uses the `sub_root` instead of the inference variable + // itself as that's slightly nicer to implement. It shouldn't really + // matter. + // + // This is really impactful when typechecking functions with a lot of + // stalled obligations, e.g. in the `wg-grammar` benchmark. + let sub_root_var = self.sub_unification_table_root_var(infer); + let obligations = self + .fulfillment_cx + .borrow() + .pending_obligations_potentially_referencing_sub_root(&self.infcx, sub_root_var); + debug!(?obligations); + let mut obligations_referencing_infer_var = PredicateObligations::new(); + for obligation in obligations { + let mut visitor = NestedObligationsReferencingInferVar { + fcx: self, + infer, + obligations_referencing_infer_var: &mut obligations_referencing_infer_var, + root_cause: &obligation.cause, + }; + + let goal = obligation.as_goal(); + self.visit_proof_tree(goal, &mut visitor); + } + + obligations_referencing_infer_var.retain_mut(|obligation| { + obligation.predicate = self.resolve_vars_if_possible(obligation.predicate); + !obligation.predicate.has_placeholders() + }); + obligations_referencing_infer_var + } + /// Only needed for the `From<{float}>` for `f32` type fallback. #[instrument(skip(self), level = "debug")] pub(crate) fn from_float_for_f32_root_vids(&self) -> UnordSet { @@ -209,6 +306,65 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } +struct NestedObligationsReferencingInferVar<'a, 'tcx> { + fcx: &'a FnCtxt<'a, 'tcx>, + infer: ty::TyVid, + root_cause: &'a ObligationCause<'tcx>, + obligations_referencing_infer_var: &'a mut PredicateObligations<'tcx>, +} + +impl<'tcx> ProofTreeVisitor<'tcx> for NestedObligationsReferencingInferVar<'_, 'tcx> { + fn span(&self) -> Span { + self.root_cause.span + } + + fn config(&self) -> InspectConfig { + // Using an intentionally low depth to minimize the chance of future + // breaking changes in case we adapt the approach later on. This also + // avoids any hangs for exponentially growing proof trees. + InspectConfig { max_depth: 5 } + } + + fn visit_goal(&mut self, inspect_goal: &InspectGoal<'_, 'tcx>) { + // No need to walk into goal subtrees that certainly hold, since they + // wouldn't then be stalled on an infer var. + if inspect_goal.result() == Ok(Certainty::Yes) { + return; + } + + // We don't care about any pending goals which don't actually + // use the self type. + if !inspect_goal + .orig_values() + .iter() + .filter_map(|arg| arg.as_type()) + .any(|ty| self.fcx.type_matches_expected_vid(ty, self.infer, UseSubtyping::Yes)) + { + debug!(goal = ?inspect_goal.goal(), "goal does not mention self type"); + return; + } + + let tcx = self.fcx.tcx; + let goal = inspect_goal.goal(); + if self.fcx.predicate_references_infer_var(goal.predicate, self.infer) { + self.obligations_referencing_infer_var.push(traits::Obligation::new( + tcx, + self.root_cause.clone(), + goal.param_env, + goal.predicate, + )); + } + + // If there's a unique way to prove a given goal, recurse into + // that candidate. This means that for `impl Trait for () {}` + // and a `(): Trait` goal we recurse into the impl and look at + // the nested `?0: FnOnce(u32)` goal. + if let Some(candidate) = inspect_goal.unique_applicable_candidate() { + candidate.visit_nested_no_probe(self) + } + } +} + struct NestedObligationsForSelfTy<'a, 'tcx> { fcx: &'a FnCtxt<'a, 'tcx>, self_ty: ty::TyVid, diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs index d38b1cf47bd6f..2510a5e6e876e 100644 --- a/compiler/rustc_lint_defs/src/builtin.rs +++ b/compiler/rustc_lint_defs/src/builtin.rs @@ -4270,7 +4270,7 @@ declare_lint! { /// --> src/lib.rs:3:10 /// | /// 3 | #[derive(Trait)] - /// | ^^^^^ + /// | ^^^^^ /// ``` pub AMBIGUOUS_DERIVE_HELPERS, Warn, @@ -5578,3 +5578,51 @@ declare_lint! { "usage of `unsafe` code and other potentially unsound constructs", @eval_always = true } + +declare_lint! { + /// The `trait_impl_fallback` lint detects code depending on the compiler + /// to choose a particular trait implementation when multiple apply. + /// + /// ### Example + /// + /// ``` + /// #![feature(rustc_attrs)] + /// + /// struct X; + /// struct Y; + /// + /// #[rustc_low_priority_impl] + /// impl From for X { + /// fn from(Y: Y) -> X { + /// X + /// } + /// } + /// + /// fn main() { + /// let _: X = From::from(loop {} as _); + /// } + /// ``` + /// + /// ### Explanation + /// + /// When there is only one applicable implementation of a trait, `rustc` + /// uses it. While convenient, this leads to adding trait implementations + /// being a breaking change (as it can lead to the number of applicable + /// implementations to go from 1 to 2). + /// + /// To allow evolution of the standard library, `rustc` provides an + /// attribute to mark the newly added implementation as "low priority". + /// The compiler then chooses the old implementation over the "low + /// priority" ones, if there is exactly 1 normal priority applicabble + /// implementation. + /// + /// This is a [future-incompatible] lint, in the future we will remove the + /// low priority annotations, breaking code which depends on them. + pub TRAIT_IMPL_FALLBACK, + Warn, + "code depending on fallback to an older implementation of a trait", + @future_incompatible = FutureIncompatibleInfo { + reason: fcw!(FutureReleaseError #0), + report_in_deps: true, + }; +} diff --git a/compiler/rustc_trait_selection/src/diagnostics.rs b/compiler/rustc_trait_selection/src/diagnostics.rs index bb2614992adf2..37fbcf8d3d0f3 100644 --- a/compiler/rustc_trait_selection/src/diagnostics.rs +++ b/compiler/rustc_trait_selection/src/diagnostics.rs @@ -259,7 +259,7 @@ pub(crate) struct InferenceBadError<'a> { } #[derive(Subdiagnostic)] -pub(crate) enum SourceKindSubdiag<'a> { +pub enum SourceKindSubdiag<'a> { #[suggestion( "{$kind -> [with_pattern] consider giving `{$name}` an explicit type @@ -376,7 +376,7 @@ impl<'a> SourceKindSubdiag<'a> { /// Suggestion to specify generic parameter(s) via `::<>`. #[derive(Subdiagnostic)] -pub(crate) enum SpecifyGenericParamsSuggestion { +pub enum SpecifyGenericParamsSuggestion { #[suggestion( "consider specifying the generic {$arg_count -> [one] argument diff --git a/compiler/rustc_trait_selection/src/error_reporting/infer/need_type_info.rs b/compiler/rustc_trait_selection/src/error_reporting/infer/need_type_info.rs index d6dcdd42bee0b..13b565f71d29e 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/infer/need_type_info.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/infer/need_type_info.rs @@ -535,6 +535,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { return self.bad_inference_failure_err(failure_span, arg_data, error_code); }; + // FIXME: use find_infer_source(tcx, infcx, typeck_results, target, body) let mut local_visitor = FindInferSourceVisitor::new(self.tcx, self.infcx, typeck_results, term, ty); if let Some(body) = @@ -624,13 +625,13 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { #[derive(Debug)] pub struct InferSource<'tcx> { - span: Span, + pub span: Span, pub hir_id: HirId, - kind: InferSourceKind<'tcx>, + pub kind: InferSourceKind<'tcx>, } #[derive(Debug)] -enum InferSourceKind<'tcx> { +pub enum InferSourceKind<'tcx> { LetBinding { insert_span: Span, pattern_name: Option, @@ -716,7 +717,7 @@ impl<'tcx> InferSourceKind<'tcx> { } } - fn suggestion<'local>( + pub fn suggestion<'local>( &self, tcx: TyCtxt<'tcx>, infcx: &InferCtxt<'tcx>, diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/ambiguity.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/ambiguity.rs index 40bae03a649db..b63f1b28055cc 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/ambiguity.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/ambiguity.rs @@ -1,11 +1,10 @@ use std::ops::ControlFlow; use rustc_errors::{Applicability, Diag, E0283, E0284, E0790, MultiSpan, struct_span_code_err}; -use rustc_hir as hir; -use rustc_hir::LangItem; use rustc_hir::def::{DefKind, Res}; use rustc_hir::def_id::{CRATE_DEF_ID, DefId}; use rustc_hir::intravisit::Visitor as _; +use rustc_hir::{self as hir, LangItem, find_attr}; use rustc_infer::infer::{BoundRegionConversionTime, InferCtxt}; use rustc_infer::traits::util::elaborate; use rustc_infer::traits::{ @@ -23,12 +22,19 @@ use crate::error_reporting::traits::{FindExprBySpan, to_pretty_impl_header}; use crate::traits::ObligationCtxt; use crate::traits::query::evaluate_obligation::InferCtxtExt; -#[derive(Debug)] +#[derive(Debug, Copy, Clone)] pub enum CandidateSource { DefId(DefId), ParamEnv(Span), } +impl CandidateSource { + #[instrument(target = "meow", level = "debug", skip(tcx), ret)] + pub fn is_low_priority(self, tcx: TyCtxt<'_>) -> bool { + matches!(self, Self::DefId(def_id) if find_attr!(tcx, def_id, RustcLowPriorityImpl)) + } +} + pub fn compute_applicable_impls_for_diagnostics<'tcx>( infcx: &InferCtxt<'tcx>, obligation: &PolyTraitObligation<'tcx>, From 0a70e1cab5497595f2690792e9c3cbd12739bea7 Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Thu, 9 Jul 2026 12:25:43 +0200 Subject: [PATCH 12/13] add tests for low priority impls --- .../from.implbreaking-current.stderr | 24 +++++++++ .../from.implbreaking-next.stderr | 24 +++++++++ .../from.lowprio-current.stderr | 36 +++++++++++++ .../from.lowprio-next.stderr | 36 +++++++++++++ tests/ui/traits/low_priority_impls/from.rs | 53 +++++++++++++++++++ .../low_priority_impls/never.current.stderr | 36 +++++++++++++ .../low_priority_impls/never.next.stderr | 36 +++++++++++++ tests/ui/traits/low_priority_impls/never.rs | 36 +++++++++++++ 8 files changed, 281 insertions(+) create mode 100644 tests/ui/traits/low_priority_impls/from.implbreaking-current.stderr create mode 100644 tests/ui/traits/low_priority_impls/from.implbreaking-next.stderr create mode 100644 tests/ui/traits/low_priority_impls/from.lowprio-current.stderr create mode 100644 tests/ui/traits/low_priority_impls/from.lowprio-next.stderr create mode 100644 tests/ui/traits/low_priority_impls/from.rs create mode 100644 tests/ui/traits/low_priority_impls/never.current.stderr create mode 100644 tests/ui/traits/low_priority_impls/never.next.stderr create mode 100644 tests/ui/traits/low_priority_impls/never.rs diff --git a/tests/ui/traits/low_priority_impls/from.implbreaking-current.stderr b/tests/ui/traits/low_priority_impls/from.implbreaking-current.stderr new file mode 100644 index 0000000000000..df4ed1164d453 --- /dev/null +++ b/tests/ui/traits/low_priority_impls/from.implbreaking-current.stderr @@ -0,0 +1,24 @@ +error[E0283]: type annotations needed + --> $DIR/from.rs:46:16 + | +LL | let _: X = Meow::f(<_>::default()); + | ^^^^^^^ -------------- type must be known at this point + | | + | cannot infer type of the type parameter `T` declared on the trait `Meow` + | +note: multiple `impl`s satisfying `X: Meow<_>` found + --> $DIR/from.rs:31:1 + | +LL | impl Meow for T { + | ^^^^^^^^^^^^^^^^^^^^^ +... +LL | impl Meow for X { + | ^^^^^^^^^^^^^^^^^^ +help: consider specifying a concrete type for the type parameter `T` + | +LL | let _: X = Meow::::f(<_>::default()); + | ++++++++++++++ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0283`. diff --git a/tests/ui/traits/low_priority_impls/from.implbreaking-next.stderr b/tests/ui/traits/low_priority_impls/from.implbreaking-next.stderr new file mode 100644 index 0000000000000..df4ed1164d453 --- /dev/null +++ b/tests/ui/traits/low_priority_impls/from.implbreaking-next.stderr @@ -0,0 +1,24 @@ +error[E0283]: type annotations needed + --> $DIR/from.rs:46:16 + | +LL | let _: X = Meow::f(<_>::default()); + | ^^^^^^^ -------------- type must be known at this point + | | + | cannot infer type of the type parameter `T` declared on the trait `Meow` + | +note: multiple `impl`s satisfying `X: Meow<_>` found + --> $DIR/from.rs:31:1 + | +LL | impl Meow for T { + | ^^^^^^^^^^^^^^^^^^^^^ +... +LL | impl Meow for X { + | ^^^^^^^^^^^^^^^^^^ +help: consider specifying a concrete type for the type parameter `T` + | +LL | let _: X = Meow::::f(<_>::default()); + | ++++++++++++++ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0283`. diff --git a/tests/ui/traits/low_priority_impls/from.lowprio-current.stderr b/tests/ui/traits/low_priority_impls/from.lowprio-current.stderr new file mode 100644 index 0000000000000..400a2b4f6e23e --- /dev/null +++ b/tests/ui/traits/low_priority_impls/from.lowprio-current.stderr @@ -0,0 +1,36 @@ + WARN rustc_trait_selection::error_reporting::infer::need_type_info resolved ty var in error message +warning: dependency on trait impl fallback + --> $DIR/from.rs:46:16 + | +LL | let _: X = Meow::f(<_>::default()); + | ^^^^^^^ cannot infer type of the type parameter `T` declared on the trait `Meow` + | + = help: specify the types explicitly + = note: in the future, the requirement `X: Meow<_>` will fail + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #0 + = note: `#[warn(trait_impl_fallback)]` on by default +help: consider specifying a concrete type for the type parameter `T` + | +LL | let _: X = Meow::::f(<_>::default()); + | ++++++++++++++ + +warning: 1 warning emitted + +Future incompatibility report: Future breakage diagnostic: +warning: dependency on trait impl fallback + --> $DIR/from.rs:46:16 + | +LL | let _: X = Meow::f(<_>::default()); + | ^^^^^^^ cannot infer type of the type parameter `T` declared on the trait `Meow` + | + = help: specify the types explicitly + = note: in the future, the requirement `X: Meow<_>` will fail + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #0 + = note: `#[warn(trait_impl_fallback)]` on by default +help: consider specifying a concrete type for the type parameter `T` + | +LL | let _: X = Meow::::f(<_>::default()); + | ++++++++++++++ + diff --git a/tests/ui/traits/low_priority_impls/from.lowprio-next.stderr b/tests/ui/traits/low_priority_impls/from.lowprio-next.stderr new file mode 100644 index 0000000000000..400a2b4f6e23e --- /dev/null +++ b/tests/ui/traits/low_priority_impls/from.lowprio-next.stderr @@ -0,0 +1,36 @@ + WARN rustc_trait_selection::error_reporting::infer::need_type_info resolved ty var in error message +warning: dependency on trait impl fallback + --> $DIR/from.rs:46:16 + | +LL | let _: X = Meow::f(<_>::default()); + | ^^^^^^^ cannot infer type of the type parameter `T` declared on the trait `Meow` + | + = help: specify the types explicitly + = note: in the future, the requirement `X: Meow<_>` will fail + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #0 + = note: `#[warn(trait_impl_fallback)]` on by default +help: consider specifying a concrete type for the type parameter `T` + | +LL | let _: X = Meow::::f(<_>::default()); + | ++++++++++++++ + +warning: 1 warning emitted + +Future incompatibility report: Future breakage diagnostic: +warning: dependency on trait impl fallback + --> $DIR/from.rs:46:16 + | +LL | let _: X = Meow::f(<_>::default()); + | ^^^^^^^ cannot infer type of the type parameter `T` declared on the trait `Meow` + | + = help: specify the types explicitly + = note: in the future, the requirement `X: Meow<_>` will fail + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #0 + = note: `#[warn(trait_impl_fallback)]` on by default +help: consider specifying a concrete type for the type parameter `T` + | +LL | let _: X = Meow::::f(<_>::default()); + | ++++++++++++++ + diff --git a/tests/ui/traits/low_priority_impls/from.rs b/tests/ui/traits/low_priority_impls/from.rs new file mode 100644 index 0000000000000..cc5de589d520b --- /dev/null +++ b/tests/ui/traits/low_priority_impls/from.rs @@ -0,0 +1,53 @@ +// This test tests the basic usage of `#[rustc_low_priority_impl]`: +// - There was only a single applicable impl (identity `Meow` impl) +// - Adding a second impl (`Meow for X`) breaks uses of `Meow` depending on "1 impl "rule"" +// - Marking the second impl as low priority fixes the issue, but introduces a FCW +// +// This test has revisions of [noimpl, implbreaking, lowprio] x [current, next]. +// +// ignore-tidy-linelength +//@ revisions: noimpl-current implbreaking-current lowprio-current noimpl-next implbreaking-next lowprio-next +// +//@[noimpl-next] compile-flags: -Znext-solver +//@[implbreaking-next] compile-flags: -Znext-solver +//@[lowprio-next] compile-flags: -Znext-solver +// +//@[noimpl-current] check-pass +//@[lowprio-current] check-pass +//@[noimpl-next] check-pass +//@[lowprio-next] check-pass + +#![feature(rustc_attrs)] + +#[derive(Default)] +struct X; +#[derive(Default)] +struct Y; + +trait Meow { + fn f(x: T) -> Self; +} + +impl Meow for T { + fn f(x: T) -> T { + x + } +} + +#[cfg(not(any(noimpl_current, noimpl_next)))] +#[cfg_attr(any(lowprio_current, lowprio_next), rustc_low_priority_impl)] +impl Meow for X { + fn f(Y: Y) -> X { + X + } +} + +fn main() { + let _: X = Meow::f(<_>::default()); + //[implbreaking-current]~^ error: type annotations needed [E0283] + //[implbreaking-next]~^^ error: type annotations needed [E0283] + //[lowprio-current]~^^^ warn: dependency on trait impl fallback [trait_impl_fallback] + //[lowprio-current]~^^^^ warn: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + //[lowprio-next]~^^^^^ warn: dependency on trait impl fallback [trait_impl_fallback] + //[lowprio-next]~^^^^^^ warn: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! +} diff --git a/tests/ui/traits/low_priority_impls/never.current.stderr b/tests/ui/traits/low_priority_impls/never.current.stderr new file mode 100644 index 0000000000000..f64a3bd8937ae --- /dev/null +++ b/tests/ui/traits/low_priority_impls/never.current.stderr @@ -0,0 +1,36 @@ + WARN rustc_trait_selection::error_reporting::infer::need_type_info resolved ty var in error message +warning: dependency on trait impl fallback + --> $DIR/never.rs:33:16 + | +LL | let _: X = Meow::f(loop {}); + | ^^^^^^^ cannot infer type of the type parameter `T` declared on the trait `Meow` + | + = help: specify the types explicitly + = note: in the future, the requirement `X: Meow<_>` will fail + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #0 + = note: `#[warn(trait_impl_fallback)]` on by default +help: consider specifying a concrete type for the type parameter `T` + | +LL | let _: X = Meow::::f(loop {}); + | ++++++++++++++ + +warning: 1 warning emitted + +Future incompatibility report: Future breakage diagnostic: +warning: dependency on trait impl fallback + --> $DIR/never.rs:33:16 + | +LL | let _: X = Meow::f(loop {}); + | ^^^^^^^ cannot infer type of the type parameter `T` declared on the trait `Meow` + | + = help: specify the types explicitly + = note: in the future, the requirement `X: Meow<_>` will fail + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #0 + = note: `#[warn(trait_impl_fallback)]` on by default +help: consider specifying a concrete type for the type parameter `T` + | +LL | let _: X = Meow::::f(loop {}); + | ++++++++++++++ + diff --git a/tests/ui/traits/low_priority_impls/never.next.stderr b/tests/ui/traits/low_priority_impls/never.next.stderr new file mode 100644 index 0000000000000..f64a3bd8937ae --- /dev/null +++ b/tests/ui/traits/low_priority_impls/never.next.stderr @@ -0,0 +1,36 @@ + WARN rustc_trait_selection::error_reporting::infer::need_type_info resolved ty var in error message +warning: dependency on trait impl fallback + --> $DIR/never.rs:33:16 + | +LL | let _: X = Meow::f(loop {}); + | ^^^^^^^ cannot infer type of the type parameter `T` declared on the trait `Meow` + | + = help: specify the types explicitly + = note: in the future, the requirement `X: Meow<_>` will fail + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #0 + = note: `#[warn(trait_impl_fallback)]` on by default +help: consider specifying a concrete type for the type parameter `T` + | +LL | let _: X = Meow::::f(loop {}); + | ++++++++++++++ + +warning: 1 warning emitted + +Future incompatibility report: Future breakage diagnostic: +warning: dependency on trait impl fallback + --> $DIR/never.rs:33:16 + | +LL | let _: X = Meow::f(loop {}); + | ^^^^^^^ cannot infer type of the type parameter `T` declared on the trait `Meow` + | + = help: specify the types explicitly + = note: in the future, the requirement `X: Meow<_>` will fail + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #0 + = note: `#[warn(trait_impl_fallback)]` on by default +help: consider specifying a concrete type for the type parameter `T` + | +LL | let _: X = Meow::::f(loop {}); + | ++++++++++++++ + diff --git a/tests/ui/traits/low_priority_impls/never.rs b/tests/ui/traits/low_priority_impls/never.rs new file mode 100644 index 0000000000000..a870f89057144 --- /dev/null +++ b/tests/ui/traits/low_priority_impls/never.rs @@ -0,0 +1,36 @@ +// This test checks that low priority impls take precedence over never type fallback +// +//@ revisions: current next +//@[next] compile-flags: -Znext-solver +// +//@ check-pass + +#![feature(rustc_attrs)] + +#[derive(Default)] +struct X; +#[derive(Default)] +struct Y; + +trait Meow { + fn f(x: T) -> Self; +} + +impl Meow for T { + fn f(x: T) -> T { + x + } +} + +#[rustc_low_priority_impl] +impl Meow for X { + fn f(Y: Y) -> X { + X + } +} + +fn main() { + let _: X = Meow::f(loop {}); + //~^ warn: dependency on trait impl fallback [trait_impl_fallback] + //~| warn: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! +} From eda475a31823c9285bb37cb9675695c7bc67e2e6 Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Thu, 9 Jul 2026 12:25:43 +0200 Subject: [PATCH 13/13] fix `low_priority_impls/never.rs#next` test --- compiler/rustc_hir_typeck/src/fallback.rs | 32 +++++++++-------------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/fallback.rs b/compiler/rustc_hir_typeck/src/fallback.rs index aaa93b98e310e..c5f4802f16cf9 100644 --- a/compiler/rustc_hir_typeck/src/fallback.rs +++ b/compiler/rustc_hir_typeck/src/fallback.rs @@ -81,6 +81,9 @@ impl<'tcx> FnCtxt<'_, 'tcx> { /// - Unconstrained floats are replaced with `f64`, except when there is a trait predicate /// `f32: From<{float}>`, in which case `f32` is used as the fallback instead. /// + /// - Non-numberics may get constrained if there are obligations which have multiple applicable + /// impls, all bot one of which are low priority. + /// /// - Non-numerics may get replaced with `()` or `!`, depending on how they /// were categorized by [`Self::calculate_diverging_fallback`], crate's /// edition, and the setting of `#![rustc_never_type_options(fallback = ...)]`. @@ -97,25 +100,16 @@ impl<'tcx> FnCtxt<'_, 'tcx> { diverging_fallback_ty: Ty<'tcx>, fallback_to_f32: &UnordSet, ) -> bool { - // Careful: we do NOT shallow-resolve `ty`. We know that `ty` - // is an unsolved variable, and we determine its fallback - // based solely on how it was created, not what other type - // variables it may have been unified with since then. - // - // The reason this matters is that other attempts at fallback - // may (in principle) conflict with this fallback, and we wish - // to generate a type error in that case. (However, this - // actually isn't true right now, because we're only using the - // builtin fallback rules. This would be true if we were using - // user-supplied fallbacks. But it's still useful to write the - // code to detect bugs.) + // Resolve is needed because both low priority impl fallback and diverging fallback might + // apply to the same variable. We want low priority impl fallback to take precedence, so it + // happens first. // - // (Note though that if we have a general type variable `?T` - // that is then unified with an integer type variable `?I` - // that ultimately never gets resolved to a special integral - // type, `?T` is not considered unsolved, but `?I` is. The - // same is true for float variables.) - let fallback = match ty.kind() { + // Yet, there might be a case where multiple related type variables require fallback, such + // that low priority impl fallback applies to the first, resolving both of them. In such + // cases low priority impl fallback wouldn't apply to the second, allowing diverging + // fallback to trigger. To prevent such cases, resolve the variables in `ty` first, to make + // sure it is still unresolved. + let fallback = match self.resolve_vars_if_possible(ty).kind() { _ if let Some(e) = self.tainted_by_errors() => Ty::new_error(self.tcx, e), ty::Infer(ty::IntVar(_)) => self.tcx.types.i32, ty::Infer(ty::FloatVar(vid)) if fallback_to_f32.contains(vid) => self.tcx.types.f32, @@ -127,7 +121,7 @@ impl<'tcx> FnCtxt<'_, 'tcx> { return true; } - _ if diverging_fallback.contains(&ty) => { + ty::Infer(_) if diverging_fallback.contains(&ty) => { self.diverging_fallback_has_occurred.set(true); diverging_fallback_ty }