diff --git a/compiler/rustc_ast_passes/src/feature_gate.rs b/compiler/rustc_ast_passes/src/feature_gate.rs index 5831636a81b2c..47aa0a0b39f57 100644 --- a/compiler/rustc_ast_passes/src/feature_gate.rs +++ b/compiler/rustc_ast_passes/src/feature_gate.rs @@ -731,10 +731,13 @@ fn check_new_solver_banned_features(sess: &Session, features: &Features) { .map(|feat| feat.attr_sp) { #[allow(rustc::symbol_intern_string_literal)] - sess.dcx().emit_err(errors::IncompatibleFeatures { - spans: vec![gce_span], - f1: Symbol::intern("-Znext-solver=globally"), - f2: sym::generic_const_exprs, - }); + sess.dcx() + .create_fatal(errors::IncompatibleFeatures { + spans: vec![gce_span], + f1: Symbol::intern("-Znext-solver=globally"), + f2: sym::generic_const_exprs, + }) + .with_code(rustc_errors::E0001) + .emit(); } } diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs index e26eb13243762..4e765e5d5d5f1 100644 --- a/compiler/rustc_middle/src/ty/context.rs +++ b/compiler/rustc_middle/src/ty/context.rs @@ -2699,7 +2699,7 @@ impl<'tcx> TyCtxt<'tcx> { } pub fn next_trait_solver_globally(self) -> bool { - self.sess.opts.unstable_opts.next_solver.globally + self.sess.opts.unstable_opts.next_solver.globally && !self.features().generic_const_exprs() } pub fn next_trait_solver_in_coherence(self) -> bool { diff --git a/compiler/rustc_next_trait_solver/src/solve/effect_goals.rs b/compiler/rustc_next_trait_solver/src/solve/effect_goals.rs index aa49997253dc2..06343f8d2b738 100644 --- a/compiler/rustc_next_trait_solver/src/solve/effect_goals.rs +++ b/compiler/rustc_next_trait_solver/src/solve/effect_goals.rs @@ -371,7 +371,7 @@ where _ecx: &mut EvalCtxt<'_, D>, _goal: Goal, ) -> Result, NoSolution> { - todo!("Iterator is not yet const") + Err(NoSolution) } fn consider_builtin_fused_iterator_candidate( diff --git a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs index 0b556f38dd072..6e7286d113427 100644 --- a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs +++ b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs @@ -201,7 +201,7 @@ where span: I::Span, stalled_on: Option>, ) -> Result, NoSolution> { - EvalCtxt::enter_root(self, self.cx().recursion_limit(), span, |ecx| { + EvalCtxt::enter_root(self, self.cx().recursion_limit() * 2, span, |ecx| { ecx.evaluate_goal(GoalSource::Misc, goal, stalled_on) }) } @@ -284,18 +284,10 @@ where // We currently only consider a cycle coinductive if it steps // into a where-clause of a coinductive trait. CurrentGoalKind::CoinductiveTrait => PathKind::Coinductive, - // While normalizing via an impl does step into a where-clause of - // an impl, accessing the associated item immediately steps out of - // it again. This means cycles/recursive calls are not guarded - // by impls used for normalization. - // - // See tests/ui/traits/next-solver/cycles/normalizes-to-is-not-productive.rs - // for how this can go wrong. - CurrentGoalKind::NormalizesTo => PathKind::Inductive, // We probably want to make all traits coinductive in the future, // so we treat cycles involving where-clauses of not-yet coinductive // traits as ambiguous for now. - CurrentGoalKind::Misc => PathKind::Unknown, + CurrentGoalKind::Misc | CurrentGoalKind::NormalizesTo => PathKind::Unknown, }, // Relating types is always unproductive. If we were to map proof trees to // corecursive functions as explained in #136824, relating types never @@ -1491,7 +1483,7 @@ pub fn evaluate_root_goal_for_proof_tree_raw_provider< let mut inspect = inspect::ProofTreeBuilder::new(); let canonical_result = SearchGraph::::evaluate_root_goal_for_proof_tree( cx, - cx.recursion_limit(), + cx.recursion_limit() * 2, canonical_goal, &mut inspect, ); diff --git a/compiler/rustc_next_trait_solver/src/solve/search_graph.rs b/compiler/rustc_next_trait_solver/src/solve/search_graph.rs index 0490b285aedf0..e8603aa876c4a 100644 --- a/compiler/rustc_next_trait_solver/src/solve/search_graph.rs +++ b/compiler/rustc_next_trait_solver/src/solve/search_graph.rs @@ -95,8 +95,9 @@ where response_no_constraints(cx, input, Certainty::overflow(true)) } + const FIXPOINT_OVERFLOW_AMBIGUITY_KIND: Certainty = Certainty::overflow(false); fn fixpoint_overflow_result(cx: I, input: CanonicalInput) -> QueryResult { - response_no_constraints(cx, input, Certainty::overflow(false)) + response_no_constraints(cx, input, Self::FIXPOINT_OVERFLOW_AMBIGUITY_KIND) } fn is_ambiguous_result(result: QueryResult) -> Option { @@ -111,14 +112,6 @@ where }) } - fn propagate_ambiguity( - cx: I, - for_input: CanonicalInput, - certainty: Certainty, - ) -> QueryResult { - response_no_constraints(cx, for_input, certainty) - } - fn compute_goal( search_graph: &mut SearchGraph, cx: I, diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs index 63806cbc701e6..b55116cb6594a 100644 --- a/compiler/rustc_session/src/config.rs +++ b/compiler/rustc_session/src/config.rs @@ -996,7 +996,7 @@ pub struct NextSolverConfig { pub coherence: bool = true, /// Whether the new trait solver should be enabled everywhere. /// This is only `true` if `coherence` is also enabled. - pub globally: bool = false, + pub globally: bool = true, } #[derive(Clone)] diff --git a/compiler/rustc_type_ir/src/interner.rs b/compiler/rustc_type_ir/src/interner.rs index fba43b9cffbe2..be89c9cf186c7 100644 --- a/compiler/rustc_type_ir/src/interner.rs +++ b/compiler/rustc_type_ir/src/interner.rs @@ -558,7 +558,7 @@ impl CollectAndApply for Result { impl search_graph::Cx for I { type Input = CanonicalInput; type Result = QueryResult; - type AmbiguityInfo = Certainty; + type AmbiguityKind = Certainty; type DepNodeIndex = I::DepNodeIndex; type Tracked = I::Tracked; diff --git a/compiler/rustc_type_ir/src/search_graph/mod.rs b/compiler/rustc_type_ir/src/search_graph/mod.rs index 535af2718f3ab..c5d870ebcae97 100644 --- a/compiler/rustc_type_ir/src/search_graph/mod.rs +++ b/compiler/rustc_type_ir/src/search_graph/mod.rs @@ -40,7 +40,7 @@ pub use global_cache::GlobalCache; pub trait Cx: Copy { type Input: Debug + Eq + Hash + Copy; type Result: Debug + Eq + Hash + Copy; - type AmbiguityInfo: Debug + Eq + Hash + Copy; + type AmbiguityKind: Debug + Eq + Hash + Copy; type DepNodeIndex; type Tracked: Debug; @@ -92,6 +92,8 @@ pub trait Delegate: Sized { cx: Self::Cx, input: ::Input, ) -> ::Result; + + const FIXPOINT_OVERFLOW_AMBIGUITY_KIND: ::AmbiguityKind; fn fixpoint_overflow_result( cx: Self::Cx, input: ::Input, @@ -99,12 +101,7 @@ pub trait Delegate: Sized { fn is_ambiguous_result( result: ::Result, - ) -> Option<::AmbiguityInfo>; - fn propagate_ambiguity( - cx: Self::Cx, - for_input: ::Input, - ambiguity_info: ::AmbiguityInfo, - ) -> ::Result; + ) -> Option<::AmbiguityKind>; fn compute_goal( search_graph: &mut SearchGraph, @@ -929,8 +926,7 @@ impl, X: Cx> SearchGraph { #[derive_where(Debug; X: Cx)] enum RebaseReason { NoCycleUsages, - Ambiguity(X::AmbiguityInfo), - Overflow, + Ambiguity(X::AmbiguityKind), /// We've actually reached a fixpoint. /// /// This either happens in the first evaluation step for the cycle head. @@ -961,10 +957,9 @@ impl, X: Cx> SearchGraph { /// cache entries to also be ambiguous. This causes some undesirable ambiguity for nested /// goals whose result doesn't actually depend on this cycle head, but that's acceptable /// to me. - #[instrument(level = "trace", skip(self, cx))] + #[instrument(level = "trace", skip(self))] fn rebase_provisional_cache_entries( &mut self, - cx: X, stack_entry: &StackEntry, rebase_reason: RebaseReason, ) { @@ -1039,18 +1034,22 @@ impl, X: Cx> SearchGraph { } // The provisional cache entry does depend on the provisional result - // of the popped cycle head. We need to mutate the result of our - // provisional cache entry in case we did not reach a fixpoint. + // of the popped cycle head. In case we didn't actually reach a fixpoint, + // we must not keep potentially incorrect provisional cache entries around. match rebase_reason { // If the cycle head does not actually depend on itself, then // the provisional result used by the provisional cache entry // is not actually equal to the final provisional result. We // need to discard the provisional cache entry in this case. RebaseReason::NoCycleUsages => return false, - RebaseReason::Ambiguity(info) => { - *result = D::propagate_ambiguity(cx, input, info); + // If we avoid rerunning a goal due to ambiguity, we only keep provisional + // results which depend on that cycle head if these are already ambiguous + // themselves. + RebaseReason::Ambiguity(kind) => { + if !D::is_ambiguous_result(*result).is_some_and(|k| k == kind) { + return false; + } } - RebaseReason::Overflow => *result = D::fixpoint_overflow_result(cx, input), RebaseReason::ReachedFixpoint(None) => {} RebaseReason::ReachedFixpoint(Some(path_kind)) => { if !popped_head.usages.is_single(path_kind) { @@ -1352,17 +1351,12 @@ impl, X: Cx> SearchGraph { // final result is equal to the initial response for that case. if let Ok(fixpoint) = self.reached_fixpoint(&stack_entry, usages, result) { self.rebase_provisional_cache_entries( - cx, &stack_entry, RebaseReason::ReachedFixpoint(fixpoint), ); return EvaluationResult::finalize(stack_entry, encountered_overflow, result); } else if usages.is_empty() { - self.rebase_provisional_cache_entries( - cx, - &stack_entry, - RebaseReason::NoCycleUsages, - ); + self.rebase_provisional_cache_entries(&stack_entry, RebaseReason::NoCycleUsages); return EvaluationResult::finalize(stack_entry, encountered_overflow, result); } @@ -1371,19 +1365,15 @@ impl, X: Cx> SearchGraph { // response in the next iteration in this case. These changes would // likely either be caused by incompleteness or can change the maybe // cause from ambiguity to overflow. Returning ambiguity always - // preserves soundness and completeness even if the goal is be known - // to succeed or fail. + // preserves soundness and completeness even if the goal could + // otherwise succeed or fail. // // This prevents exponential blowup affecting multiple major crates. // As we only get to this branch if we haven't yet reached a fixpoint, // we also taint all provisional cache entries which depend on the // current goal. - if let Some(info) = D::is_ambiguous_result(result) { - self.rebase_provisional_cache_entries( - cx, - &stack_entry, - RebaseReason::Ambiguity(info), - ); + if let Some(kind) = D::is_ambiguous_result(result) { + self.rebase_provisional_cache_entries(&stack_entry, RebaseReason::Ambiguity(kind)); return EvaluationResult::finalize(stack_entry, encountered_overflow, result); }; @@ -1393,7 +1383,10 @@ impl, X: Cx> SearchGraph { if i >= D::FIXPOINT_STEP_LIMIT { debug!("canonical cycle overflow"); let result = D::fixpoint_overflow_result(cx, input); - self.rebase_provisional_cache_entries(cx, &stack_entry, RebaseReason::Overflow); + self.rebase_provisional_cache_entries( + &stack_entry, + RebaseReason::Ambiguity(D::FIXPOINT_OVERFLOW_AMBIGUITY_KIND), + ); return EvaluationResult::finalize(stack_entry, encountered_overflow, result); } diff --git a/tests/ui/specialization/min_specialization/next-solver-region-resolution.rs b/tests/ui/specialization/min_specialization/next-solver-region-resolution.rs index d4b74802c2df7..9713fc9acdbdc 100644 --- a/tests/ui/specialization/min_specialization/next-solver-region-resolution.rs +++ b/tests/ui/specialization/min_specialization/next-solver-region-resolution.rs @@ -10,9 +10,9 @@ trait Foo { trait Baz {} impl<'a, T> Foo for &'a T //~ ERROR not all trait items implemented, missing: `Item` -//~| ERROR the trait bound `&'a T: Foo` is not satisfied +//~| ERROR type annotations needed: cannot satisfy `&'a T: Foo` where - Self::Item: 'a, //~ ERROR the trait bound `&'a T: Foo` is not satisfied + Self::Item: 'a, { } diff --git a/tests/ui/specialization/min_specialization/next-solver-region-resolution.stderr b/tests/ui/specialization/min_specialization/next-solver-region-resolution.stderr index df0e8fefaa848..8bfd8b773b790 100644 --- a/tests/ui/specialization/min_specialization/next-solver-region-resolution.stderr +++ b/tests/ui/specialization/min_specialization/next-solver-region-resolution.stderr @@ -10,14 +10,13 @@ LL | | where LL | | Self::Item: 'a, | |___________________^ missing `Item` in implementation -error[E0277]: the trait bound `&'a T: Foo` is not satisfied +error[E0283]: type annotations needed: cannot satisfy `&'a T: Foo` --> $DIR/next-solver-region-resolution.rs:12:21 | LL | impl<'a, T> Foo for &'a T - | ^^^^^ the trait `Foo` is not implemented for `&'a T` + | ^^^^^ | -help: the trait `Foo` is not implemented for `&'a _` - but it is implemented for `&_` +note: multiple `impl`s satisfying `&'a T: Foo` found --> $DIR/next-solver-region-resolution.rs:12:1 | LL | / impl<'a, T> Foo for &'a T @@ -25,22 +24,12 @@ LL | | LL | | where LL | | Self::Item: 'a, | |___________________^ - -error[E0277]: the trait bound `&'a T: Foo` is not satisfied - --> $DIR/next-solver-region-resolution.rs:15:17 - | -LL | Self::Item: 'a, - | ^^ the trait `Foo` is not implemented for `&'a T` - | -help: the trait `Foo` is not implemented for `&'a _` - but it is implemented for `&_` - --> $DIR/next-solver-region-resolution.rs:12:1 - | -LL | / impl<'a, T> Foo for &'a T +... +LL | / impl<'a, T> Foo for &T LL | | LL | | where -LL | | Self::Item: 'a, - | |___________________^ +LL | | Self::Item: Baz, + | |____________________^ error[E0046]: not all trait items implemented, missing: `Item` --> $DIR/next-solver-region-resolution.rs:19:1 @@ -63,7 +52,7 @@ LL | | where LL | | Self::Item: Baz, | |____________________^ -error: aborting due to 5 previous errors +error: aborting due to 4 previous errors -Some errors have detailed explanations: E0046, E0277. +Some errors have detailed explanations: E0046, E0283. For more information about an error, try `rustc --explain E0046`. diff --git a/tests/ui/traits/next-solver/cycles/normalizes-to-is-not-productive-2.next.stderr b/tests/ui/traits/next-solver/cycles/normalizes-to-is-not-productive-2.next.stderr new file mode 100644 index 0000000000000..2d70ef51e7f1e --- /dev/null +++ b/tests/ui/traits/next-solver/cycles/normalizes-to-is-not-productive-2.next.stderr @@ -0,0 +1,11 @@ +error[E0283]: type annotations needed: cannot satisfy ` as IntoIterator>::IntoIter: Iterator` + --> $DIR/normalizes-to-is-not-productive-2.rs:21:41 + | +LL | as IntoIterator>::IntoIter: Iterator, + | ^^^^^^^^ + | + = note: cannot satisfy ` as IntoIterator>::IntoIter: Iterator` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0283`. diff --git a/tests/ui/traits/next-solver/cycles/normalizes-to-is-not-productive-2.rs b/tests/ui/traits/next-solver/cycles/normalizes-to-is-not-productive-2.rs index bb3540f9a214f..2032fc7ec73f5 100644 --- a/tests/ui/traits/next-solver/cycles/normalizes-to-is-not-productive-2.rs +++ b/tests/ui/traits/next-solver/cycles/normalizes-to-is-not-productive-2.rs @@ -1,9 +1,9 @@ //@ revisions: current next //@ ignore-compare-mode-next-solver (explicit revisions) //@[next] compile-flags: -Znext-solver -//@ check-pass +//@[current] check-pass -// Regression test for trait-system-refactor-initiative#176. +// Test from trait-system-refactor-initiative#176. // // Normalizing ` as IntoIterator>::IntoIter` has two candidates // inside of the function: @@ -13,11 +13,13 @@ // - where-clause requires ` as IntoIterator>::IntoIter eq Vec` // - normalize ` as IntoIterator>::IntoIter` again, cycle // -// We need to treat this cycle as an error to be able to use the actual impl. +// The blanket impl is unfortunately also a productive cycle, so we have to +// break this code, see trait-system-refactor-initiative#273 fn test() where as IntoIterator>::IntoIter: Iterator, + //[next]~^ ERROR type annotations needed: cannot satisfy ` as IntoIterator>::IntoIter: Iterator` { } diff --git a/tests/ui/traits/next-solver/cycles/normalizes-to-is-not-productive.rs b/tests/ui/traits/next-solver/cycles/normalizes-to-is-not-productive.rs index ffbbecaf89570..fbc663b09b86c 100644 --- a/tests/ui/traits/next-solver/cycles/normalizes-to-is-not-productive.rs +++ b/tests/ui/traits/next-solver/cycles/normalizes-to-is-not-productive.rs @@ -1,9 +1,10 @@ //@ ignore-compare-mode-next-solver (explicit) //@ compile-flags: -Znext-solver -// Make sure that stepping into impl where-clauses of `NormalizesTo` -// goals is unproductive. This must not compile, see the inline -// comments. +// A test for the cycle handling when stepping into where-clauses of `NormalizesTo`. +// Whether stepping into where-clauses is productive depends on how they are used. +// +// In this concrete test, the cycle must not be productive. trait Bound { fn method(); @@ -30,7 +31,7 @@ fn impls_bound() { // The where-clause requires `Foo: Trait` to hold to be wf. // If stepping into where-clauses during normalization is considered -// to be productive, this would be the case: +// to be productive here, this would be the case: // // - `Foo: Trait` // - via blanket impls, requires `Foo: Bound` @@ -40,12 +41,13 @@ fn impls_bound() { fn generic() where >::Assoc: Bound, - //~^ ERROR the trait bound `Foo: Bound` is not satisfied + //~^ ERROR overflow evaluating the requirement `>::Assoc: Bound` + //~| ERROR overflow evaluating whether `>::Assoc` is well-formed { // Requires proving `Foo: Bound` by normalizing // `>::Assoc` to `Foo`. impls_bound::(); - //~^ ERROR the trait bound `Foo: Bound` is not satisfied + //~^ ERROR overflow evaluating the requirement `Foo: Bound` } fn main() { // Requires proving `>::Assoc: Bound`. diff --git a/tests/ui/traits/next-solver/cycles/normalizes-to-is-not-productive.stderr b/tests/ui/traits/next-solver/cycles/normalizes-to-is-not-productive.stderr index f679b94a92377..9d726b482b50b 100644 --- a/tests/ui/traits/next-solver/cycles/normalizes-to-is-not-productive.stderr +++ b/tests/ui/traits/next-solver/cycles/normalizes-to-is-not-productive.stderr @@ -1,56 +1,27 @@ -error[E0277]: the trait bound `Foo: Bound` is not satisfied - --> $DIR/normalizes-to-is-not-productive.rs:42:31 +error[E0275]: overflow evaluating the requirement `>::Assoc: Bound` + --> $DIR/normalizes-to-is-not-productive.rs:43:31 | LL | >::Assoc: Bound, - | ^^^^^ unsatisfied trait bound - | -help: the trait `Bound` is not implemented for `Foo` - --> $DIR/normalizes-to-is-not-productive.rs:18:1 - | -LL | struct Foo; - | ^^^^^^^^^^ -help: the trait `Bound` is implemented for `u32` - --> $DIR/normalizes-to-is-not-productive.rs:11:1 - | -LL | impl Bound for u32 { - | ^^^^^^^^^^^^^^^^^^ -note: required for `Foo` to implement `Trait` - --> $DIR/normalizes-to-is-not-productive.rs:23:19 - | -LL | impl Trait for T { - | ----- ^^^^^^^^ ^ - | | - | unsatisfied trait bound introduced here -note: required by a bound in `Bound` - --> $DIR/normalizes-to-is-not-productive.rs:8:1 + | ^^^^^ + +error[E0275]: overflow evaluating whether `>::Assoc` is well-formed + --> $DIR/normalizes-to-is-not-productive.rs:43:31 | -LL | / trait Bound { -LL | | fn method(); -LL | | } - | |_^ required by this bound in `Bound` +LL | >::Assoc: Bound, + | ^^^^^ -error[E0277]: the trait bound `Foo: Bound` is not satisfied - --> $DIR/normalizes-to-is-not-productive.rs:47:19 +error[E0275]: overflow evaluating the requirement `Foo: Bound` + --> $DIR/normalizes-to-is-not-productive.rs:49:19 | LL | impls_bound::(); - | ^^^ unsatisfied trait bound - | -help: the trait `Bound` is not implemented for `Foo` - --> $DIR/normalizes-to-is-not-productive.rs:18:1 - | -LL | struct Foo; - | ^^^^^^^^^^ -help: the trait `Bound` is implemented for `u32` - --> $DIR/normalizes-to-is-not-productive.rs:11:1 + | ^^^ | -LL | impl Bound for u32 { - | ^^^^^^^^^^^^^^^^^^ note: required by a bound in `impls_bound` - --> $DIR/normalizes-to-is-not-productive.rs:27:19 + --> $DIR/normalizes-to-is-not-productive.rs:28:19 | LL | fn impls_bound() { | ^^^^^ required by this bound in `impls_bound` -error: aborting due to 2 previous errors +error: aborting due to 3 previous errors -For more information about this error, try `rustc --explain E0277`. +For more information about this error, try `rustc --explain E0275`. diff --git a/tests/ui/traits/next-solver/global-where-bound-normalization.rs b/tests/ui/traits/next-solver/global-where-bound-normalization.rs new file mode 100644 index 0000000000000..e57fbf378a0d2 --- /dev/null +++ b/tests/ui/traits/next-solver/global-where-bound-normalization.rs @@ -0,0 +1,45 @@ +//@ check-pass +//@ compile-flags: -Znext-solver + +// Regression test for https://github.com/rust-lang/trait-system-refactor-initiative/issues/257. + +#![feature(rustc_attrs)] +#![expect(internal_features)] +#![rustc_no_implicit_bounds] + +pub trait Bound {} +impl Bound for u8 {} + +pub trait Proj { + type Assoc; +} +impl Proj for U { + type Assoc = U; +} +impl Proj for MyField { + type Assoc = u8; +} + +// While wf-checking the global bounds of `fn foo`, elaborating this outlives predicate triggered a +// cycle in the search graph along a particular probe path, which was not an actual solution. +// That cycle then resulted in a forced false-positive ambiguity due to a performance hack in the +// search graph and then ended up floundering the root goal evaluation. +pub trait Field: Proj {} + +struct MyField; +impl Field for MyField {} + +trait IdReqField { + type This; +} +impl IdReqField for F { + type This = F; +} + +fn foo() +where + ::This: Field, +{ +} + +fn main() {}