Skip to content
Draft
Show file tree
Hide file tree
Changes from 12 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>>,
}
7 changes: 7 additions & 0 deletions compiler/rustc_hir_typeck/src/fallback.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
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