Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_attr_parsing/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,7 @@ attribute_parsers!(
Single<WithoutArgs<RustcLintOptTyParser>>,
Single<WithoutArgs<RustcLintQueryInstabilityParser>>,
Single<WithoutArgs<RustcLintUntrackedQueryInformationParser>>,
Single<WithoutArgs<RustcLowPriorityImplParser>>,
Single<WithoutArgs<RustcMainParser>>,
Single<WithoutArgs<RustcNeverReturnsNullPtrParser>>,
Single<WithoutArgs<RustcNoImplicitAutorefsParser>>,
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_feature/src/builtin_attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_hir/src/attrs/data_structures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1520,6 +1520,8 @@ pub enum AttributeKind {
/// Represents `#[rustc_lint_untracked_query_information]`
RustcLintUntrackedQueryInformation,

RustcLowPriorityImpl,

/// Represents `#[rustc_macro_transparency]`.
RustcMacroTransparency(Transparency),

Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_hir/src/attrs/encode_cross_crate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ impl AttributeKind {
RustcLintOptTy => Yes,
RustcLintQueryInstability => Yes,
RustcLintUntrackedQueryInformation => Yes,
RustcLowPriorityImpl => Yes,
RustcMacroTransparency(..) => Yes,
RustcMain => No,
RustcMir(..) => Yes,
Expand Down
12 changes: 12 additions & 0 deletions compiler/rustc_hir_typeck/src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -1320,3 +1321,14 @@ pub(crate) struct FloatLiteralF32Fallback {
)]
pub span: Option<Span>,
}

#[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<SourceKindSubdiag<'a>>,
}
39 changes: 20 additions & 19 deletions compiler/rustc_hir_typeck/src/fallback.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 = ...)]`.
Expand All @@ -97,30 +100,28 @@ impl<'tcx> FnCtxt<'_, 'tcx> {
diverging_fallback_ty: Ty<'tcx>,
fallback_to_f32: &UnordSet<FloatVid>,
) -> 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,
ty::Infer(ty::FloatVar(_)) => self.tcx.types.f64,
_ if diverging_fallback.contains(&ty) => {

ty::Infer(ty::TyVar(_))
if let Ok(_) = self.try_low_priority_impl_fallback_and_fcw(DUMMY_SP, ty) =>
{
return true;
}

ty::Infer(_) if diverging_fallback.contains(&ty) => {
self.diverging_fallback_has_occurred.set(true);
diverging_fallback_ty
}
Expand Down
177 changes: 163 additions & 14 deletions compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs
Original file line number Diff line number Diff line change
@@ -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::{
Expand All @@ -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,
Expand All @@ -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,
};
Expand Down Expand Up @@ -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<Ty<'tcx>, ()> {
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...
Comment on lines +1578 to +1579

@lcnr lcnr Jul 13, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that feels quite odd 🤔

why do you need the probe/what's the problem if we do leak inference progress here?

View changes since the review

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing this make some tests fail (on the branch that uses #[rustc_low_priority_impl] for impl<T> From<!> for T):

failures:
    [ui] tests/ui/traits/next-solver/cycles/coinduction/fixpoint-exponential-growth.rs
    [ui] tests/ui/traits/next-solver/cycles/inductive-fixpoint-hang.rs
    [ui] tests/ui/traits/next-solver/cycles/coinduction/item-bound-via-impl-where-clause.rs#next
    [ui] tests/ui/traits/next-solver/cycles/cycle-modulo-ambig-aliases.rs
    [ui] tests/ui/traits/next-solver/overflow/exponential-trait-goals.rs

With diffs like these:

diff of stderr:

-	error[E0275]: overflow evaluating the requirement `W<_>: Trait`
+	error[E0275]: overflow evaluating the requirement `W<(W<_>, W<_>)>: Trait`
2	  --> $DIR/exponential-trait-goals.rs:17:13
3	   |
4	LL |     impls::<W<_>>();
-	error[E0275]: overflow evaluating the requirement `W<_>: Trait`
+	error[E0275]: overflow evaluating the requirement `W<W<_>>: Trait`
2	  --> $DIR/inductive-fixpoint-hang.rs:33:19
3	   |
4	LL |     impls_trait::<W<_>>();
10	LL | fn transmute<L: Trait<R>, R>(r: L) -> <L::Proof as Trait<R>>::Proof { r }
11	   |                 ^^^^^^^^ required by this bound in `transmute`
12	
-	error[E0275]: overflow evaluating the requirement `<Vec<u8> as Trait<String>>::Proof == _`
+	error[E0275]: overflow evaluating the requirement `<Vec<u8> as Trait<String>>::Proof == String`
14	  --> $DIR/item-bound-via-impl-where-clause.rs:31:21
15	   |
16	LL |     let s: String = transmute::<_, String>(vec![65_u8, 66, 67]);

17	   |                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

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.

@lcnr lcnr Jul 13, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what? compute_applicable_impls_for_diagnostics should use probes per candidate instead 😅

it's kind of crazy that it currently doesn't

View changes since the review

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is indeed crazy that it doesn't. Do you want me to change it in this PR/as a prerequisite?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you split that out into a separate PR?

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))

@lcnr lcnr Jul 15, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is what recursively uses fallback 🤔

if this necessary anywhere, I can in theory see that there is a necessary fallback chain, but I am very much fine just allowing a single fallback per use and hard error otherwise. We don't have to be perfect, just good enough :>

View changes since the review

}

pub(crate) fn structurally_resolve_const(
Expand Down
Loading
Loading