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
61 changes: 41 additions & 20 deletions compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ enum CurrentGoalKind {
/// These are currently the only goals whose impl where-clauses are considered to be
/// productive steps.
CoinductiveTrait,
/// `AliasRelate` goals are used for normalization and their inference constraints are
/// necessary to guide inference for other goals. This means that even on overflow, we don't
/// discard inference constraints from `AliasRelate` and return them to the caller.
AliasRelate,
/// Unlike other goals, `NormalizesTo` goals act like functions with the expected term
/// always being fully unconstrained. This would weaken inference however, as the nested
/// goals never get the inference constraints from the actual normalized-to type.
Expand All @@ -67,6 +71,7 @@ impl CurrentGoalKind {
CurrentGoalKind::Misc
}
}
ty::PredicateKind::AliasRelate(..) => CurrentGoalKind::AliasRelate,
ty::PredicateKind::NormalizesTo(_) => CurrentGoalKind::NormalizesTo,
_ => CurrentGoalKind::Misc,
}
Expand Down Expand Up @@ -275,7 +280,8 @@ where
// as inductive even though it should not be, it may be unsound during coherence and
// fixing it may cause inference breakage or introduce ambiguity.
GoalSource::Misc => PathKind::Unknown,
GoalSource::NormalizeGoal(path_kind) => path_kind,
GoalSource::NormalizeGoal(path_kind)
| GoalSource::NestedNormalizationGoal(path_kind) => path_kind,
GoalSource::ImplWhereBound => match self.current_goal_kind {
// We currently only consider a cycle coinductive if it steps
// into a where-clause of a coinductive trait.
Expand All @@ -292,6 +298,7 @@ where
// so we treat cycles involving where-clauses of not-yet coinductive
// traits as ambiguous for now.
CurrentGoalKind::Misc => PathKind::Unknown,
CurrentGoalKind::AliasRelate => unreachable!(),
},
// Relating types is always unproductive. If we were to map proof trees to
// corecursive functions as explained in #136824, relating types never
Expand Down Expand Up @@ -679,7 +686,11 @@ where
) = self.evaluate_goal_raw(source, unconstrained_goal, stalled_on)?;
// Add the nested goals from normalization to our own nested goals.
trace!(?nested_goals);
self.nested_goals.extend(nested_goals.into_iter().map(|(s, g)| (s, g, None)));
self.nested_goals.extend(
nested_goals
.into_iter()
.map(|(s, g)| (GoalSource::NestedNormalizationGoal(s), g, None)),
);

// Finally, equate the goal's RHS with the unconstrained var.
//
Expand Down Expand Up @@ -1258,7 +1269,10 @@ where
(
Certainty::Yes,
NestedNormalizationGoals(
goals.into_iter().map(|(s, g, _)| (s, g)).collect(),
goals
.into_iter()
.map(|(s, g, _)| (self.step_kind_for_source(s), g))
.collect(),
),
)
}
Expand All @@ -1268,23 +1282,30 @@ where
}
};

if let Certainty::Maybe {
cause: cause @ MaybeCause::Overflow { keep_constraints: false, .. },
opaque_types_jank,
} = certainty
{
// If we have overflow, it's probable that we're substituting a type
// into itself infinitely and any partial substitutions in the query
// response are probably not useful anyways, so just return an empty
// query response.
//
// This may prevent us from potentially useful inference, e.g.
// 2 candidates, one ambiguous and one overflow, which both
// have the same inference constraints.
//
// Changing this to retain some constraints in the future
// won't be a breaking change, so this is good enough for now.
return Ok(self.make_ambiguous_response_no_constraints(cause, opaque_types_jank));
match self.current_goal_kind {
CurrentGoalKind::AliasRelate | CurrentGoalKind::NormalizesTo => {}
Copy link
Contributor

Choose a reason for hiding this comment

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

right so here you'd have previously discarded (since AliasRelate got CurrentGoalKind::Misc but now you take this top branch). I'm not 100% sure why you also go here for NormalizesTo. That'd have previously discarded.

Copy link
Contributor Author

@lcnr lcnr Nov 5, 2025

Choose a reason for hiding this comment

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

That'd have previously discarded.

they wouldn't have, because for NormalizesTo we use the shallow_certainty due to the above match (self.current_goal_kind, shallow_certainty). That's subtle though

Copy link
Contributor

@jdonszelmann jdonszelmann Nov 5, 2025

Choose a reason for hiding this comment

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

maybe document it?

Copy link
Contributor

Choose a reason for hiding this comment

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

Or somehow combine the two checks, such that all the ways in which we care about self.current_goal_kind are handled exhaustively. I'm not sure that ends up working though, could also turn out more confusing rather than less

Copy link
Contributor

Choose a reason for hiding this comment

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

I tried: combining doesn't produce a nice check, maybe just document?

CurrentGoalKind::Misc | CurrentGoalKind::CoinductiveTrait => {
if let Certainty::Maybe {
cause: cause @ MaybeCause::Overflow { keep_constraints: false, .. },
opaque_types_jank,
} = certainty
{
// If we have overflow, it's probable that we're substituting a type
// into itself infinitely and any partial substitutions in the query
// response are probably not useful anyways, so just return an empty
// query response.
//
// This may prevent us from potentially useful inference, e.g.
// 2 candidates, one ambiguous and one overflow, which both
// have the same inference constraints.
//
// Changing this to retain some constraints in the future
// won't be a breaking change, so this is good enough for now.
return Ok(
self.make_ambiguous_response_no_constraints(cause, opaque_types_jank)
);
}
}
}

let external_constraints =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -495,7 +495,10 @@ impl<'tcx> ProofTreeVisitor<'tcx> for BestObligation<'tcx> {
match (child_mode, nested_goal.source()) {
(
ChildMode::Trait(_) | ChildMode::Host(_),
GoalSource::Misc | GoalSource::TypeRelating | GoalSource::NormalizeGoal(_),
GoalSource::Misc
| GoalSource::TypeRelating
| GoalSource::NormalizeGoal(_)
| GoalSource::NestedNormalizationGoal(_),
) => {
continue;
}
Expand Down
5 changes: 4 additions & 1 deletion compiler/rustc_type_ir/src/search_graph/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ use std::marker::PhantomData;
use derive_where::derive_where;
#[cfg(feature = "nightly")]
use rustc_macros::{Decodable_NoContext, Encodable_NoContext, HashStable_NoContext};
use rustc_type_ir::Interner;
use rustc_type_ir::data_structures::HashMap;
use rustc_type_ir_macros::{TypeFoldable_Generic, TypeVisitable_Generic};
use tracing::{debug, instrument};

mod stack;
Expand Down Expand Up @@ -122,13 +124,14 @@ pub trait Delegate: Sized {
feature = "nightly",
derive(Decodable_NoContext, Encodable_NoContext, HashStable_NoContext)
)]
#[derive(TypeVisitable_Generic, TypeFoldable_Generic)]
pub enum PathKind {
/// A path consisting of only inductive/unproductive steps. Their initial
/// provisional result is `Err(NoSolution)`. We currently treat them as
/// `PathKind::Unknown` during coherence until we're fully confident in
/// our approach.
Inductive,
/// A path which is not be coinductive right now but we may want
/// A path which is not coinductive right now but we may want
/// to change of them to be so in the future. We return an ambiguous
/// result in this case to prevent people from relying on this.
Unknown,
Expand Down
6 changes: 4 additions & 2 deletions compiler/rustc_type_ir/src/solve/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ pub enum GoalSource {
/// A nested goal required to prove that types are equal/subtypes.
/// This is always an unproductive step.
///
/// This is also used for all `NormalizesTo` goals as we they are used
/// This is also used for all `NormalizesTo` goals as those are used
/// to relate types in `AliasRelate`.
TypeRelating,
/// We're proving a where-bound of an impl.
Expand All @@ -85,6 +85,8 @@ pub enum GoalSource {
/// 2. for rigid projections's trait goal,
/// 3. for GAT where clauses.
AliasWellFormed,
/// A goal from a nested `NormalizesTo` goal which has been propagated to its caller.
NestedNormalizationGoal(PathKind),
/// In case normalizing aliases in nested goals cycles, eagerly normalizing these
/// aliases in the context of the parent may incorrectly change the cycle kind.
/// Normalizing aliases in goals therefore tracks the original path kind for this
Expand Down Expand Up @@ -269,7 +271,7 @@ impl<I: Interner> ExternalConstraintsData<I> {
#[derive_where(Clone, Hash, PartialEq, Debug, Default; I: Interner)]
#[derive(TypeVisitable_Generic, TypeFoldable_Generic)]
#[cfg_attr(feature = "nightly", derive(HashStable_NoContext))]
pub struct NestedNormalizationGoals<I: Interner>(pub Vec<(GoalSource, Goal<I, I::Predicate>)>);
pub struct NestedNormalizationGoals<I: Interner>(pub Vec<(PathKind, Goal<I, I::Predicate>)>);

impl<I: Interner> Eq for NestedNormalizationGoals<I> {}

Expand Down
10 changes: 3 additions & 7 deletions tests/ui/traits/next-solver/alias-bound-unsound.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,8 @@ impl Foo for () {
fn main() {
let x = String::from("hello, world");
let _ = identity(<() as Foo>::copy_me(&x));
//~^ ERROR overflow evaluating the requirement `String <: <() as Foo>::Item`
//~| ERROR overflow evaluating the requirement `<() as Foo>::Item well-formed`
//~| ERROR overflow evaluating the requirement `&<() as Foo>::Item well-formed`
//~| ERROR overflow evaluating the requirement `<() as Foo>::Item == _`
//~| ERROR overflow evaluating the requirement `<() as Foo>::Item == _`
//~| ERROR overflow evaluating the requirement `<() as Foo>::Item == _`
//~| ERROR overflow evaluating the requirement `<() as Foo>::Item == _`
//~^ ERROR type mismatch resolving `<() as Foo>::Item normalizes-to String`
//~| ERROR type mismatch resolving `<() as Foo>::Item normalizes-to String`
//~| ERROR mismatched types
println!("{x}");
}
63 changes: 32 additions & 31 deletions tests/ui/traits/next-solver/alias-bound-unsound.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -12,52 +12,53 @@ LL | trait Foo {
LL | type Item: Copy
| ^^^^ this trait's associated type doesn't have the requirement `String: Copy`

error[E0275]: overflow evaluating the requirement `String <: <() as Foo>::Item`
error[E0271]: type mismatch resolving `<() as Foo>::Item normalizes-to String`
--> $DIR/alias-bound-unsound.rs:28:43
|
LL | let _ = identity(<() as Foo>::copy_me(&x));
| ^^

error[E0275]: overflow evaluating the requirement `<() as Foo>::Item == _`
--> $DIR/alias-bound-unsound.rs:28:22
|
LL | let _ = identity(<() as Foo>::copy_me(&x));
| ^^^^^^^^^^^^^^^^^^^^^^^^

error[E0275]: overflow evaluating the requirement `<() as Foo>::Item == _`
--> $DIR/alias-bound-unsound.rs:28:22
| ^^ types differ
|
LL | let _ = identity(<() as Foo>::copy_me(&x));
| ^^^^^^^^^^^^^^^^^^^^^^^^
note: required by a bound in `Foo::Item`
--> $DIR/alias-bound-unsound.rs:14:30
|
= note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
LL | type Item: Copy
| ---- required by a bound in this associated type
LL | where
LL | <Self as Foo>::Item: Copy;
| ^^^^ required by this bound in `Foo::Item`

error[E0275]: overflow evaluating the requirement `&<() as Foo>::Item well-formed`
error[E0308]: mismatched types
--> $DIR/alias-bound-unsound.rs:28:43
|
LL | let _ = identity(<() as Foo>::copy_me(&x));
| ^^

error[E0275]: overflow evaluating the requirement `<() as Foo>::Item well-formed`
--> $DIR/alias-bound-unsound.rs:28:22
| -------------------- ^^ types differ
| |
| arguments to this function are incorrect
|
LL | let _ = identity(<() as Foo>::copy_me(&x));
| ^^^^^^^^^^^^^^^^^^^^^^^^
= note: expected reference `&<() as Foo>::Item`
found reference `&String`
note: associated function defined here
--> $DIR/alias-bound-unsound.rs:16:8
|
LL | fn copy_me(x: &Self::Item) -> Self::Item {
| ^^^^^^^ --------------

error[E0275]: overflow evaluating the requirement `<() as Foo>::Item == _`
error[E0271]: type mismatch resolving `<() as Foo>::Item normalizes-to String`
--> $DIR/alias-bound-unsound.rs:28:22
|
LL | let _ = identity(<() as Foo>::copy_me(&x));
| ^^^^^^^^^^^^^^^^^^^^^^^^
| ^^^^^^^^^^^^^^^^^^^^^^^^ types differ
|
= note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`

error[E0275]: overflow evaluating the requirement `<() as Foo>::Item == _`
--> $DIR/alias-bound-unsound.rs:28:43
note: required by a bound in `Foo::Item`
--> $DIR/alias-bound-unsound.rs:14:30
|
LL | let _ = identity(<() as Foo>::copy_me(&x));
| ^^
LL | type Item: Copy
| ---- required by a bound in this associated type
LL | where
LL | <Self as Foo>::Item: Copy;
| ^^^^ required by this bound in `Foo::Item`

error: aborting due to 8 previous errors
error: aborting due to 4 previous errors

For more information about this error, try `rustc --explain E0275`.
Some errors have detailed explanations: E0271, E0275, E0308.
For more information about an error, try `rustc --explain E0271`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//@ compile-flags: -Znext-solver
//@ check-pass

// Regression test for trait-system-refactor-initiative#246.

struct MarkedStruct;
pub trait MarkerTrait {}
impl MarkerTrait for MarkedStruct {}

// Necessary indirection to get a cycle with `PathKind::Unknown`
struct ChainStruct;
trait ChainTrait {}
impl ChainTrait for ChainStruct where MarkedStruct: MarkerTrait {}

pub struct FooStruct;
pub trait FooTrait {
type Output;
}
pub struct FooOut;
impl FooTrait for FooStruct
where
ChainStruct: ChainTrait,
{
type Output = FooOut;
}

pub trait Trait<T> {
type Output;
}

// prove <<FooStruct as FooTrait>::Output as Trait<K>>::Output: MarkerTrait
// normalize <<FooStruct as FooTrait>::Output as Trait<K>>::Output
// `<?fresh_infer as Trait<K>>::Output` remains ambiguous, so this alias is never rigid
// alias-relate <FooStruct as FooTrait>::Output ?fresh_infer
// does not constrain `?fresh_infer` as `keep_constraints` is `false`
// normalize <FooStruct as FooTrait>::Output
// result `FooOut` with overflow
// prove ChainStruct: ChainTrait
// prove MarkedStruct: MarkerTrait
// impl trivial (ignored)
// where-clause requires normalize <<FooStruct as FooTrait>::Output as Trait<K>>::Output overflow
pub fn foo<K>()
where
FooOut: Trait<K>,
<<FooStruct as FooTrait>::Output as Trait<K>>::Output: MarkerTrait,
{
}

fn main() {}
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,6 @@ note: required by a bound in `transmute`
LL | fn transmute<L: Trait<R>, R>(r: L) -> <L::Proof as Trait<R>>::Proof { r }
| ^^^^^^^^ required by this bound in `transmute`

error[E0275]: overflow evaluating the requirement `<<Vec<u8> as Trait<String>>::Proof as Trait<String>>::Proof == _`
--> $DIR/item-bound-via-impl-where-clause.rs:31:21
|
LL | let s: String = transmute::<_, String>(vec![65_u8, 66, 67]);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error[E0275]: overflow evaluating the requirement `<<Vec<u8> as Trait<String>>::Proof as Trait<String>>::Proof == String`
--> $DIR/item-bound-via-impl-where-clause.rs:31:21
|
Expand All @@ -36,14 +30,6 @@ error[E0275]: overflow evaluating the requirement `<<Vec<u8> as Trait<String>>::
LL | let s: String = transmute::<_, String>(vec![65_u8, 66, 67]);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error[E0275]: overflow evaluating the requirement `<<Vec<u8> as Trait<String>>::Proof as Trait<String>>::Proof == _`
--> $DIR/item-bound-via-impl-where-clause.rs:31:21
|
LL | let s: String = transmute::<_, String>(vec![65_u8, 66, 67]);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`

error: aborting due to 6 previous errors
error: aborting due to 4 previous errors

For more information about this error, try `rustc --explain E0275`.
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,8 @@ fn transmute<L: Trait<R>, R>(r: L) -> <L::Proof as Trait<R>>::Proof { r }
fn main() {
let s: String = transmute::<_, String>(vec![65_u8, 66, 67]);
//~^ ERROR overflow evaluating the requirement `Vec<u8>: Trait<String>`
//[next]~| ERROR overflow evaluating the requirement `<<Vec<u8> as Trait<String>>::Proof as Trait<String>>::Proof == _`
//[next]~| ERROR overflow evaluating the requirement `<<Vec<u8> as Trait<String>>::Proof as Trait<String>>::Proof == String`
//[next]~| ERROR overflow evaluating the requirement `<<Vec<u8> as Trait<String>>::Proof as Trait<String>>::Proof: Sized`
//[next]~| ERROR overflow evaluating the requirement `<<Vec<u8> as Trait<String>>::Proof as Trait<String>>::Proof well-formed`
//[next]~| ERROR overflow evaluating the requirement `<<Vec<u8> as Trait<String>>::Proof as Trait<String>>::Proof == _`
println!("{}", s); // ABC
}
Loading