From ab950f8c1a5765e2092e190759ddbc803f9190a2 Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Fri, 26 Jun 2026 14:03:07 +0200 Subject: [PATCH 01/11] 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 767fa647612af..f4bfadaaec5e3 100644 --- a/compiler/rustc_lint_defs/src/lib.rs +++ b/compiler/rustc_lint_defs/src/lib.rs @@ -802,6 +802,7 @@ macro_rules! declare_lint_pass { }; } +/// Helper macro to create [`FutureIncompatibilityReason`]. #[macro_export] macro_rules! fcw { (FutureReleaseError # $issue_number: literal) => { From b1d56bffdef5e936fda14836a0cca65637c65186 Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Fri, 26 Jun 2026 14:03:07 +0200 Subject: [PATCH 02/11] 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/engine.rs | 7 +++++++ compiler/rustc_trait_selection/src/traits/fulfill.rs | 4 ++++ 4 files changed, 17 insertions(+) diff --git a/compiler/rustc_infer/src/traits/engine.rs b/compiler/rustc_infer/src/traits/engine.rs index 6adec25be32f0..62b65adf667ce 100644 --- a/compiler/rustc_infer/src/traits/engine.rs +++ b/compiler/rustc_infer/src/traits/engine.rs @@ -138,6 +138,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 342609d9d3bd5..6fe4b9cb37dee 100644 --- a/compiler/rustc_trait_selection/src/solve/fulfill.rs +++ b/compiler/rustc_trait_selection/src/solve/fulfill.rs @@ -371,6 +371,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/engine.rs b/compiler/rustc_trait_selection/src/traits/engine.rs index 71228937a27f4..8445401ca0b53 100644 --- a/compiler/rustc_trait_selection/src/traits/engine.rs +++ b/compiler/rustc_trait_selection/src/traits/engine.rs @@ -142,6 +142,13 @@ where } } } + + fn is_in_probe(&self, infcx: &InferCtxt<'_>) -> bool { + match self { + FulfillmentEngine::Old(engine) => engine.is_in_probe(infcx), + FulfillmentEngine::Next(engine) => engine.is_in_probe(infcx), + } + } } /// Used if you want to have pleasant experience when dealing diff --git a/compiler/rustc_trait_selection/src/traits/fulfill.rs b/compiler/rustc_trait_selection/src/traits/fulfill.rs index 8179b0f6f01a1..8366b9510c437 100644 --- a/compiler/rustc_trait_selection/src/traits/fulfill.rs +++ b/compiler/rustc_trait_selection/src/traits/fulfill.rs @@ -274,6 +274,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 26a462708176b3364edb4aa4540237bf9f97beee Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Fri, 26 Jun 2026 14:03:07 +0200 Subject: [PATCH 03/11] 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 b101d378bab98..fe34922503d2c 100644 --- a/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs +++ b/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs @@ -555,6 +555,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 6254dd73f3263..ea02ebeb5bfd2 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -330,6 +330,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 1f6f97f1310ae..79e4e820ad6d6 100644 --- a/compiler/rustc_feature/src/builtin_attrs.rs +++ b/compiler/rustc_feature/src/builtin_attrs.rs @@ -256,6 +256,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 78a15eeb923a5..58923c4fca045 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -1523,6 +1523,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 a5a1fc2482b4e..9af97a01b36c2 100644 --- a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs +++ b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs @@ -158,6 +158,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 e95b9b2ffdf01..6b577e15aa32d 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -354,6 +354,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 a453cc6b555db..114065b3f2f16 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1823,6 +1823,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 d2dfa09d2fdd343900d7714c95c5f7ad62fc82ee Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Fri, 26 Jun 2026 14:03:07 +0200 Subject: [PATCH 04/11] 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 ae78d68865de3..c066210ea2d19 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, Region, 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 b6441ef28cac0d2ae3464df4760a60560de413c4 Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Fri, 26 Jun 2026 14:03:07 +0200 Subject: [PATCH 05/11] add `CandidateSource::is_low_priority` --- .../src/error_reporting/traits/ambiguity.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) 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 95c42b34499b6..2e522da33717b 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 3574c59fe67561c12f17fe3a527b29a8bf1a8c10 Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Fri, 26 Jun 2026 14:03:07 +0200 Subject: [PATCH 06/11] make things more public --- compiler/rustc_trait_selection/src/diagnostics.rs | 4 ++-- .../src/error_reporting/infer/need_type_info.rs | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/compiler/rustc_trait_selection/src/diagnostics.rs b/compiler/rustc_trait_selection/src/diagnostics.rs index 44b5e669d5251..e0be25d109277 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>, From 2abac7133437e0d44895e67105647a825c5c72fb Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Fri, 26 Jun 2026 14:03:07 +0200 Subject: [PATCH 07/11] declare a lint for low prio impls --- compiler/rustc_lint_defs/src/builtin.rs | 48 +++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs index da2220cf7a5fd..92a017bfed9da 100644 --- a/compiler/rustc_lint_defs/src/builtin.rs +++ b/compiler/rustc_lint_defs/src/builtin.rs @@ -5769,3 +5769,51 @@ declare_lint! { "duplicate tools found in crate-level `#[register_tools]` directives", @feature_gate = register_tool; } + +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, + }; +} From 7c5e1c7101fd2de0182154723b9b791f9a812666 Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Fri, 26 Jun 2026 14:03:07 +0200 Subject: [PATCH 08/11] add a way to search for obligation merely mentioning an infer var --- .../src/fn_ctxt/inspect_obligations.rs | 153 ++++++++++++++++++ 1 file changed, 153 insertions(+) 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 e6de8b55ef2f9..e8be29c331198 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 { @@ -274,6 +371,62 @@ impl<'tcx> ProofTreeVisitor<'tcx> for NestedObligationsForSelfTy<'_, '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 { + InspectConfig { max_depth: MAX_DEPTH_FOR_OBLIGATIONS_VISITORS } + } + + 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 FindFromFloatForF32RootVids<'a, 'tcx> { fcx: &'a FnCtxt<'a, 'tcx>, from_trait: DefId, From 941de120618f25740ef38b2ad3e66c4c2f72a188 Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Fri, 26 Jun 2026 14:03:07 +0200 Subject: [PATCH 09/11] 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 | 176 ++++++++++++++++-- 3 files changed, 181 insertions(+), 14 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/diagnostics.rs b/compiler/rustc_hir_typeck/src/diagnostics.rs index 1a6df92957d00..822aec7b7fb20 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; @@ -1327,3 +1328,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 e753100a5e1a9..39c5a1469210c 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(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 a7651cf365edd..541817784ced1 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, TraitEngine, }; @@ -1530,20 +1539,159 @@ 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(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, + 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(ty) } pub(crate) fn structurally_resolve_const( From ba856a24d5c05f134f0826e1605d2bf0f342012d Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Thu, 9 Jul 2026 12:25:43 +0200 Subject: [PATCH 10/11] 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 | 54 +++++++++++++++++++ .../low_priority_impls/never.current.stderr | 36 +++++++++++++ .../low_priority_impls/never.next.stderr | 36 +++++++++++++ tests/ui/traits/low_priority_impls/never.rs | 37 +++++++++++++ 8 files changed, 283 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..083d02fbf1fbb --- /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:47: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:32: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..083d02fbf1fbb --- /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:47: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:32: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..ea92eac73e157 --- /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:47: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:47: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..ea92eac73e157 --- /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:47: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:47: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..ec64fe52cd590 --- /dev/null +++ b/tests/ui/traits/low_priority_impls/from.rs @@ -0,0 +1,54 @@ +// 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 +//@ ignore-compare-mode-next-solver (explicit revisions) +// +//@[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..d7c79f9e90b2b --- /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:34: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:34: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..d7c79f9e90b2b --- /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:34: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:34: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..d1d61618c6481 --- /dev/null +++ b/tests/ui/traits/low_priority_impls/never.rs @@ -0,0 +1,37 @@ +// This test checks that low priority impls take precedence over never type fallback +// +//@ revisions: current next +//@[next] compile-flags: -Znext-solver +//@ ignore-compare-mode-next-solver (explicit revisions) +// +//@ 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 20b414039d2b3420ba5d33daed39c9d8b0b7c579 Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Thu, 9 Jul 2026 12:25:43 +0200 Subject: [PATCH 11/11] 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 39c5a1469210c..59f371133d905 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 }