Repository: rust-lang/rust
Pull request #138846: Tweaks to writeback and `Obligation -> Goal` conversion
Author: login: compiler-errors
Labels: name: T-compiler
description: Relevant to the compiler team, which will review and decide on the PR/issue.
name: S-waiting-on-bors
description: Status: Waiting on bors to run and complete tests. Bors will change the label on completion.
name: WG-trait-system-refactor
description: The Rustc Trait System Refactor Initiative (-Znext-solver)
Created: 2025-03-22T23:59:41Z | Merged: 2025-03-24T04:12:35Z

Each of these commits are self-contained, but are prerequisites that I'd like to land before #138845, which still needs some cleaning.

The ""most controversial"" one is probably [Explicitly don't fold coroutine obligations in writeback](https://github.com/rust-lang/rust/commit/e7d27bae27b3b213a44e12b780e5df74a9929e58), which I prefer because I think using `fold_predicate` to control against not normalizing predicates seems... easy to mess up 🤔, and we could have *other things* that we don't want to normalize. 

Explicitly noting whether we want `resolve` to normalize is a lot clearer (and currently in writeback is limited to resolving stalled coroutine obligations), since we can attach it to a comment that explains *why*.


Diff:
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 95b9cb3be627c..e068e60790277 100644
--- a/compiler/rustc_hir_typeck/src/fn_ctxt/inspect_obligations.rs
+++ b/compiler/rustc_hir_typeck/src/fn_ctxt/inspect_obligations.rs
@@ -1,7 +1,7 @@
 //! A utility module to inspect currently ambiguous obligations in the current context.
 
 use rustc_infer::traits::{self, ObligationCause, PredicateObligations};
-use rustc_middle::traits::solve::{Goal, GoalSource};
+use rustc_middle::traits::solve::GoalSource;
 use rustc_middle::ty::{self, Ty, TypeVisitableExt};
 use rustc_span::Span;
 use rustc_trait_selection::solve::inspect::{
@@ -85,7 +85,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                 root_cause: &obligation.cause,
             };
 
-            let goal = Goal::new(self.tcx, obligation.param_env, obligation.predicate);
+            let goal = obligation.as_goal();
             self.visit_proof_tree(goal, &mut visitor);
         }
 
diff --git a/compiler/rustc_hir_typeck/src/writeback.rs b/compiler/rustc_hir_typeck/src/writeback.rs
index 6a3417ae5d6fb..b63c0b6ab7e09 100644
--- a/compiler/rustc_hir_typeck/src/writeback.rs
+++ b/compiler/rustc_hir_typeck/src/writeback.rs
@@ -548,7 +548,8 @@ impl<'cx, 'tcx> WritebackCx<'cx, 'tcx> {
         let fcx_typeck_results = self.fcx.typeck_results.borrow();
         assert_eq!(fcx_typeck_results.hir_owner, self.typeck_results.hir_owner);
         for (predicate, cause) in &fcx_typeck_results.coroutine_stalled_predicates {
-            let (predicate, cause) = self.resolve((*predicate, cause.clone()), &cause.span);
+            let (predicate, cause) =
+                self.resolve_coroutine_predicate((*predicate, cause.clone()), &cause.span);
             self.typeck_results.coroutine_stalled_predicates.insert((predicate, cause));
         }
     }
@@ -730,7 +731,25 @@ impl<'cx, 'tcx> WritebackCx<'cx, 'tcx> {
         T: TypeFoldable<TyCtxt<'tcx>>,
     {
         let value = self.fcx.resolve_vars_if_possible(value);
-        let value = value.fold_with(&mut Resolver::new(self.fcx, span, self.body));
+        let value = value.fold_with(&mut Resolver::new(self.fcx, span, self.body, true));
+        assert!(!value.has_infer());
+
+        // We may have introduced e.g. `ty::Error`, if inference failed, make sure
+        // to mark the `TypeckResults` as tainted in that case, so that downstream
+        // users of the typeck results don't produce extra errors, or worse, ICEs.
+        if let Err(guar) = value.error_reported() {
+            self.typeck_results.tainted_by_errors = Some(guar);
+        }
+
+        value
+    }
+
+    fn resolve_coroutine_predicate<T>(&mut self, value: T, span: &dyn Locatable) -> T
+    where
+        T: TypeFoldable<TyCtxt<'tcx>>,
+    {
+        let value = self.fcx.resolve_vars_if_possible(value);
+        let value = value.fold_with(&mut Resolver::new(self.fcx, span, self.body, false));
         assert!(!value.has_infer());
 
         // We may have introduced e.g. `ty::Error`, if inference failed, make sure
@@ -774,8 +793,9 @@ impl<'cx, 'tcx> Resolver<'cx, 'tcx> {
         fcx: &'cx FnCtxt<'cx, 'tcx>,
         span: &'cx dyn Locatable,
         body: &'tcx hir::Body<'tcx>,
+        should_normalize: bool,
     ) -> Resolver<'cx, 'tcx> {
-        Resolver { fcx, span, body, should_normalize: fcx.next_trait_solver() }
+        Resolver { fcx, span, body, should_normalize }
     }
 
     fn report_error(&self, p: impl Into<ty::GenericArg<'tcx>>) -> ErrorGuaranteed {
@@ -805,10 +825,9 @@ impl<'cx, 'tcx> Resolver<'cx, 'tcx> {
         T: Into<ty::GenericArg<'tcx>> + TypeSuperFoldable<TyCtxt<'tcx>> + Copy,
     {
         let tcx = self.fcx.tcx;
-        // We must deeply normalize in the new solver, since later lints
-        // expect that types that show up in the typeck are fully
-        // normalized.
-        let mut value = if self.should_normalize {
+        // We must deeply normalize in the new solver, since later lints expect
+        // that types that show up in the typeck are fully normalized.
+        let mut value = if self.should_normalize && self.fcx.next_trait_solver() {
             let body_id = tcx.hir_body_owner_def_id(self.body.id());
             let cause = ObligationCause::misc(self.span.to_span(tcx), body_id);
             let at = self.fcx.at(&cause, self.fcx.param_env);
@@ -864,20 +883,15 @@ impl<'cx, 'tcx> TypeFolder<TyCtxt<'tcx>> for Resolver<'cx, 'tcx> {
     }
 
     fn fold_const(&mut self, ct: ty::Const<'tcx>) -> ty::Const<'tcx> {
-        self.handle_term(ct, ty::Const::outer_exclusive_binder, |tcx, guar| {
-            ty::Const::new_error(tcx, guar)
-        })
-        .super_fold_with(self)
+        self.handle_term(ct, ty::Const::outer_exclusive_binder, ty::Const::new_error)
     }
 
     fn fold_predicate(&mut self, predicate: ty::Predicate<'tcx>) -> ty::Predicate<'tcx> {
-        // Do not normalize predicates in the new solver. The new solver is
-        // supposed to handle unnormalized predicates and incorrectly normalizing
-        // them can be unsound, e.g. for `WellFormed` predicates.
-        let prev = mem::replace(&mut self.should_normalize, false);
-        let predicate = predicate.super_fold_with(self);
-        self.should_normalize = prev;
-        predicate
+        assert!(
+            !self.should_normalize,
+            "normalizing predicates in writeback is not generally sound"
+        );
+        predicate.super_fold_with(self)
     }
 }
 
diff --git a/compiler/rustc_infer/src/infer/opaque_types/mod.rs b/compiler/rustc_infer/src/infer/opaque_types/mod.rs
index 3fa1923121a23..215b133372664 100644
--- a/compiler/rustc_infer/src/infer/opaque_types/mod.rs
+++ b/compiler/rustc_infer/src/infer/opaque_types/mod.rs
@@ -246,8 +246,7 @@ impl<'tcx> InferCtxt<'tcx> {
                             .eq(DefineOpaqueTypes::Yes, prev, hidden_ty)?
                             .obligations
                             .into_iter()
-                            // FIXME: Shuttling between obligations and goals is awkward.
-                            .map(Goal::from),
+                            .map(|obligation| obligation.as_goal()),
                     );
                 }
             }
diff --git a/compiler/rustc_infer/src/traits/mod.rs b/compiler/rustc_infer/src/traits/mod.rs
index ac641ef565228..b537750f1b51b 100644
--- a/compiler/rustc_infer/src/traits/mod.rs
+++ b/compiler/rustc_infer/src/traits/mod.rs
@@ -54,6 +54,12 @@ pub struct Obligation<'tcx, T> {
     pub recursion_depth: usize,
 }
 
+impl<'tcx, T: Copy> Obligation<'tcx, T> {
+    pub fn as_goal(&self) -> solve::Goal<'tcx, T> {
+        solve::Goal { param_env: self.param_env, predicate: self.predicate }
+    }
+}
+
 impl<'tcx, T: PartialEq> PartialEq<Obligation<'tcx, T>> for Obligation<'tcx, T> {
     #[inline]
     fn eq(&self, other: &Obligation<'tcx, T>) -> bool {
@@ -75,12 +81,6 @@ impl<T: Hash> Hash for Obligation<'_, T> {
     }
 }
 
-impl<'tcx, P> From<Obligation<'tcx, P>> for solve::Goal<'tcx, P> {
-    fn from(value: Obligation<'tcx, P>) -> Self {
-        solve::Goal { param_env: value.param_env, predicate: value.predicate }
-    }
-}
-
 pub type PredicateObligation<'tcx> = Obligation<'tcx, ty::Predicate<'tcx>>;
 pub type TraitObligation<'tcx> = Obligation<'tcx, ty::TraitPredicate<'tcx>>;
 pub type PolyTraitObligation<'tcx> = Obligation<'tcx, ty::PolyTraitPredicate<'tcx>>;
diff --git a/compiler/rustc_trait_selection/src/solve/delegate.rs b/compiler/rustc_trait_selection/src/solve/delegate.rs
index af5a60027ba4a..3d9a90eb74e7a 100644
--- a/compiler/rustc_trait_selection/src/solve/delegate.rs
+++ b/compiler/rustc_trait_selection/src/solve/delegate.rs
@@ -96,7 +96,7 @@ impl<'tcx> rustc_next_trait_solver::delegate::SolverDelegate for SolverDelegate<
     ) -> Option<Vec<Goal<'tcx, ty::Predicate<'tcx>>>> {
         crate::traits::wf::unnormalized_obligations(&self.0, param_env, arg, DUMMY_SP, CRATE_DEF_ID)
             .map(|obligations| {
-                obligations.into_iter().map(|obligation| obligation.into()).collect()
+                obligations.into_iter().map(|obligation| obligation.as_goal()).collect()
             })
     }
 
diff --git a/compiler/rustc_trait_selection/src/solve/fulfill.rs b/compiler/rustc_trait_selection/src/solve/fulfill.rs
index 704ba6e501d8c..192e632a2d5b9 100644
--- a/compiler/rustc_trait_selection/src/solve/fulfill.rs
+++ b/compiler/rustc_trait_selection/src/solve/fulfill.rs
@@ -80,7 +80,7 @@ impl<'tcx> ObligationStorage<'tcx> {
             // change.
             // FIXME: <https://github.com/Gankra/thin-vec/pull/66> is merged, this can be removed.
             self.overflowed.extend(ExtractIf::new(&mut self.pending, |o| {
-                let goal = o.clone().into();
+                let goal = o.as_goal();
                 let result = <&SolverDelegate<'tcx>>::from(infcx)
                     .evaluate_root_goal(goal, GenerateProofTree::No, o.cause.span)
                     .0;
@@ -161,7 +161,7 @@ where
 
             let mut has_changed = false;
             for obligation in self.obligations.unstalled_for_select() {
-                let goal = obligation.clone().into();
+                let goal = obligation.as_goal();
                 let result = <&SolverDelegate<'tcx>>::from(infcx)
                     .evaluate_root_goal(goal, GenerateProofTree::No, obligation.cause.span)
                     .0;
diff --git a/compiler/rustc_trait_selection/src/solve/fulfill/derive_errors.rs b/compiler/rustc_trait_selection/src/solve/fulfill/derive_errors.rs
index 352ac7c1a4e6f..3a939df25e07b 100644
--- a/compiler/rustc_trait_selection/src/solve/fulfill/derive_errors.rs
+++ b/compiler/rustc_trait_selection/src/solve/fulfill/derive_errors.rs
@@ -10,7 +10,7 @@ use rustc_middle::ty::error::{ExpectedFound, TypeError};
 use rustc_middle::ty::{self, Ty, TyCtxt};
 use rustc_middle::{bug, span_bug};
 use rustc_next_trait_solver::solve::{GenerateProofTree, SolverDelegateEvalExt as _};
-use rustc_type_ir::solve::{Goal, NoSolution};
+use rustc_type_ir::solve::NoSolution;
 use tracing::{instrument, trace};
 
 use crate::solve::Certainty;
@@ -89,7 +89,7 @@ pub(super) fn fulfillment_error_for_stalled<'tcx>(
     let (code, refine_obligation) = infcx.probe(|_| {
         match <&SolverDelegate<'tcx>>::from(infcx)
             .evaluate_root_goal(
-                root_obligation.clone().into(),
+                root_obligation.as_goal(),
                 GenerateProofTree::No,
                 root_obligation.cause.span,
             )
@@ -155,7 +155,7 @@ fn find_best_leaf_obligation<'tcx>(
         .fudge_inference_if_ok(|| {
             infcx
                 .visit_proof_tree(
-                    obligation.clone().into(),
+                    obligation.as_goal(),
                     &mut BestObligation { obligation: obligation.clone(), consider_ambiguities },
                 )
                 .break_value()
@@ -245,7 +245,7 @@ impl<'tcx> BestObligation<'tcx> {
         {
             let nested_goal = candidate.instantiate_proof_tree_for_nested_goal(
                 GoalSource::Misc,
-                Goal::new(infcx.tcx, obligation.param_env, obligation.predicate),
+                obligation.as_goal(),
                 self.span(),
             );
             // Skip nested goals that aren't the *reason* for our goal's failure.
diff --git a/compiler/rustc_trait_selection/src/traits/coherence.rs b/compiler/rustc_trait_selection/src/traits/coherence.rs
index 4c7172c32781a..bcc247ba53c2b 100644
--- a/compiler/rustc_trait_selection/src/traits/coherence.rs
+++ b/compiler/rustc_trait_selection/src/traits/coherence.rs
@@ -625,7 +625,7 @@ fn compute_intercrate_ambiguity_causes<'tcx>(
     let mut causes: FxIndexSet<IntercrateAmbiguityCause<'tcx>> = Default::default();
 
     for obligation in obligations {
-        search_ambiguity_causes(infcx, obligation.clone().into(), &mut causes);
+        search_ambiguity_causes(infcx, obligation.as_goal(), &mut causes);
     }
 
     causes


Reviews:
user: login: compiler-errors
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-23T00:00:13Z
user: login: lcnr
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-23T17:56:11Z
user: login: lcnr
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-23T17:56:36Z
user: login: compiler-errors
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-23T17:57:36Z
user: login: compiler-errors
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-23T17:57:41Z

Review comments:
diff_hunk: @@ -864,10 +864,7 @@ impl<'cx, 'tcx> TypeFolder<TyCtxt<'tcx>> for Resolver<'cx, 'tcx> {
     }
 
     fn fold_const(&mut self, ct: ty::Const<'tcx>) -> ty::Const<'tcx> {
-        self.handle_term(ct, ty::Const::outer_exclusive_binder, |tcx, guar| {
-            ty::Const::new_error(tcx, guar)
-        })
-        .super_fold_with(self)
path: compiler/rustc_hir_typeck/src/writeback.rs
user: login: compiler-errors
body: No need to super fold b/c it should be fully resolved :)
created_at: 2025-03-23T00:00:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -720,17 +720,18 @@ impl<'cx, 'tcx> WritebackCx<'cx, 'tcx> {
             fcx_typeck_results.offset_of_data().items_in_stable_order()
         {
             let hir_id = HirId { owner: common_hir_owner, local_id };
-            let container = self.resolve(container, &hir_id);
+            let container = self.resolve(container, &hir_id, true);
             self.typeck_results.offset_of_data_mut().insert(hir_id, (container, indices.clone()));
         }
     }
 
-    fn resolve<T>(&mut self, value: T, span: &dyn Locatable) -> T
+    fn resolve<T>(&mut self, value: T, span: &dyn Locatable, should_normalize: bool) -> T
path: compiler/rustc_hir_typeck/src/writeback.rs
user: login: lcnr
body: having a bool argument for this seems confusing.

Please either change this to a custom enum or split this into `(normalize_and_)resolve` and `resolve_delayed_obligations`
created_at: 2025-03-23T17:56:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -864,20 +865,15 @@ impl<'cx, 'tcx> TypeFolder<TyCtxt<'tcx>> for Resolver<'cx, 'tcx> {
     }
 
     fn fold_const(&mut self, ct: ty::Const<'tcx>) -> ty::Const<'tcx> {
-        self.handle_term(ct, ty::Const::outer_exclusive_binder, |tcx, guar| {
-            ty::Const::new_error(tcx, guar)
-        })
-        .super_fold_with(self)
+        self.handle_term(ct, ty::Const::outer_exclusive_binder, ty::Const::new_error)
     }
 
     fn fold_predicate(&mut self, predicate: ty::Predicate<'tcx>) -> ty::Predicate<'tcx> {
-        // Do not normalize predicates in the new solver. The new solver is
-        // supposed to handle unnormalized predicates and incorrectly normalizing
-        // them can be unsound, e.g. for `WellFormed` predicates.
-        let prev = mem::replace(&mut self.should_normalize, false);
-        let predicate = predicate.super_fold_with(self);
-        self.should_normalize = prev;
-        predicate
+        assert!(
+            !self.should_normalize,
+            "normalizing predicats in writeback is not generally sound"
path: compiler/rustc_hir_typeck/src/writeback.rs
user: login: lcnr
body: ```suggestion
            "normalizing predicates in writeback is not generally sound"
```
predi-🐱 
created_at: 2025-03-23T17:56:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -720,17 +720,18 @@ impl<'cx, 'tcx> WritebackCx<'cx, 'tcx> {
             fcx_typeck_results.offset_of_data().items_in_stable_order()
         {
             let hir_id = HirId { owner: common_hir_owner, local_id };
-            let container = self.resolve(container, &hir_id);
+            let container = self.resolve(container, &hir_id, true);
             self.typeck_results.offset_of_data_mut().insert(hir_id, (container, indices.clone()));
         }
     }
 
-    fn resolve<T>(&mut self, value: T, span: &dyn Locatable) -> T
+    fn resolve<T>(&mut self, value: T, span: &dyn Locatable, should_normalize: bool) -> T
path: compiler/rustc_hir_typeck/src/writeback.rs
user: login: compiler-errors
body: i think `normalize_and_resolve` + `resolve_delayed_coroutine_obligations` is fine. a bit of code duplication, but I also dislike all the `, true` additions.
created_at: 2025-03-23T17:57:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -864,20 +865,15 @@ impl<'cx, 'tcx> TypeFolder<TyCtxt<'tcx>> for Resolver<'cx, 'tcx> {
     }
 
     fn fold_const(&mut self, ct: ty::Const<'tcx>) -> ty::Const<'tcx> {
-        self.handle_term(ct, ty::Const::outer_exclusive_binder, |tcx, guar| {
-            ty::Const::new_error(tcx, guar)
-        })
-        .super_fold_with(self)
+        self.handle_term(ct, ty::Const::outer_exclusive_binder, ty::Const::new_error)
     }
 
     fn fold_predicate(&mut self, predicate: ty::Predicate<'tcx>) -> ty::Predicate<'tcx> {
-        // Do not normalize predicates in the new solver. The new solver is
-        // supposed to handle unnormalized predicates and incorrectly normalizing
-        // them can be unsound, e.g. for `WellFormed` predicates.
-        let prev = mem::replace(&mut self.should_normalize, false);
-        let predicate = predicate.super_fold_with(self);
-        self.should_normalize = prev;
-        predicate
+        assert!(
+            !self.should_normalize,
+            "normalizing predicats in writeback is not generally sound"
path: compiler/rustc_hir_typeck/src/writeback.rs
user: login: compiler-errors
body: lol
created_at: 2025-03-23T17:57:41Z
author_association: CONTRIBUTOR

PR comments:
user: login: compiler-errors
created_at: 2025-03-22T23:59:45Z
body: r? lcnr
author_association: CONTRIBUTOR
user: login: rustbot
created_at: 2025-03-22T23:59:45Z
body: r? @Nadrieril

rustbot has assigned @Nadrieril.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use `r?` to explicitly pick a reviewer
author_association: COLLABORATOR
user: login: rustbot
created_at: 2025-03-22T23:59:47Z
body: Some changes occurred to the core trait solver

cc @rust-lang/initiative-trait-system-refactor

changes to `inspect_obligations.rs`

cc @compiler-errors, @lcnr
author_association: COLLABORATOR
user: login: lcnr
created_at: 2025-03-23T20:24:15Z
body: @bors r+ rollup
author_association: CONTRIBUTOR
user: login: bors
created_at: 2025-03-23T20:24:17Z
body: :pushpin: Commit fad34c603cd6c7e432f297e682bb68a2cf55df0b has been approved by `lcnr`

It is now in the [queue](https://bors.rust-lang.org/homu/queue/rust) for this repository.

<!-- @bors r=lcnr fad34c603cd6c7e432f297e682bb68a2cf55df0b -->
<!-- homu: {"type":"Approved","sha":"fad34c603cd6c7e432f297e682bb68a2cf55df0b","approver":"lcnr","queue":"https://bors.rust-lang.org/homu/queue/rust"} -->
author_association: COLLABORATOR