Skip to content

Commit 47d53cd

Browse files
committed
Add trait obligation tracking to FulfillCtxt and expose FnCtxt in rustc_infer using callback.
Pass each obligation to an fn callback with its respective inference context. This avoids needing to keep around copies of obligations or inference contexts.
1 parent 2457c02 commit 47d53cd

File tree

7 files changed

+157
-66
lines changed

7 files changed

+157
-66
lines changed

compiler/rustc_hir_typeck/src/lib.rs

+17-3
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ use rustc_hir::{HirIdMap, Node};
6060
use rustc_hir_analysis::astconv::AstConv;
6161
use rustc_hir_analysis::check::check_abi;
6262
use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
63+
use rustc_infer::traits::ObligationInspector;
6364
use rustc_middle::query::Providers;
6465
use rustc_middle::traits;
6566
use rustc_middle::ty::{self, Ty, TyCtxt};
@@ -139,7 +140,7 @@ fn used_trait_imports(tcx: TyCtxt<'_>, def_id: LocalDefId) -> &UnordSet<LocalDef
139140

140141
fn typeck<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> &ty::TypeckResults<'tcx> {
141142
let fallback = move || tcx.type_of(def_id.to_def_id()).instantiate_identity();
142-
typeck_with_fallback(tcx, def_id, fallback)
143+
typeck_with_fallback(tcx, def_id, fallback, None)
143144
}
144145

145146
/// Used only to get `TypeckResults` for type inference during error recovery.
@@ -149,14 +150,24 @@ fn diagnostic_only_typeck<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> &ty::T
149150
let span = tcx.hir().span(tcx.local_def_id_to_hir_id(def_id));
150151
Ty::new_error_with_message(tcx, span, "diagnostic only typeck table used")
151152
};
152-
typeck_with_fallback(tcx, def_id, fallback)
153+
typeck_with_fallback(tcx, def_id, fallback, None)
153154
}
154155

155-
#[instrument(level = "debug", skip(tcx, fallback), ret)]
156+
pub fn inspect_typeck<'tcx>(
157+
tcx: TyCtxt<'tcx>,
158+
def_id: LocalDefId,
159+
inspect: ObligationInspector<'tcx>,
160+
) -> &'tcx ty::TypeckResults<'tcx> {
161+
let fallback = move || tcx.type_of(def_id.to_def_id()).instantiate_identity();
162+
typeck_with_fallback(tcx, def_id, fallback, Some(inspect))
163+
}
164+
165+
#[instrument(level = "debug", skip(tcx, fallback, inspector), ret)]
156166
fn typeck_with_fallback<'tcx>(
157167
tcx: TyCtxt<'tcx>,
158168
def_id: LocalDefId,
159169
fallback: impl Fn() -> Ty<'tcx> + 'tcx,
170+
inspector: Option<ObligationInspector<'tcx>>,
160171
) -> &'tcx ty::TypeckResults<'tcx> {
161172
// Closures' typeck results come from their outermost function,
162173
// as they are part of the same "inference environment".
@@ -178,6 +189,9 @@ fn typeck_with_fallback<'tcx>(
178189
let param_env = tcx.param_env(def_id);
179190

180191
let inh = Inherited::new(tcx, def_id);
192+
if let Some(inspector) = inspector {
193+
inh.infcx.attach_obligation_inspector(inspector);
194+
}
181195
let mut fcx = FnCtxt::new(&inh, param_env, def_id);
182196

183197
if let Some(hir::FnSig { header, decl, .. }) = fn_sig {

compiler/rustc_infer/src/infer/at.rs

+1
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ impl<'tcx> InferCtxt<'tcx> {
9090
universe: self.universe.clone(),
9191
intercrate,
9292
next_trait_solver: self.next_trait_solver,
93+
obligation_inspector: self.obligation_inspector.clone(),
9394
}
9495
}
9596
}

compiler/rustc_infer/src/infer/mod.rs

+14-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ use rustc_middle::infer::unify_key::{ConstVidKey, EffectVidKey};
1313
use self::opaque_types::OpaqueTypeStorage;
1414
pub(crate) use self::undo_log::{InferCtxtUndoLogs, Snapshot, UndoLog};
1515

16-
use crate::traits::{self, ObligationCause, PredicateObligations, TraitEngine, TraitEngineExt};
16+
use crate::traits::{
17+
self, ObligationCause, ObligationInspector, PredicateObligations, TraitEngine, TraitEngineExt,
18+
};
1719

1820
use rustc_data_structures::fx::FxIndexMap;
1921
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
@@ -334,6 +336,8 @@ pub struct InferCtxt<'tcx> {
334336
pub intercrate: bool,
335337

336338
next_trait_solver: bool,
339+
340+
pub obligation_inspector: Cell<Option<ObligationInspector<'tcx>>>,
337341
}
338342

339343
impl<'tcx> ty::InferCtxtLike for InferCtxt<'tcx> {
@@ -708,6 +712,7 @@ impl<'tcx> InferCtxtBuilder<'tcx> {
708712
universe: Cell::new(ty::UniverseIndex::ROOT),
709713
intercrate,
710714
next_trait_solver,
715+
obligation_inspector: Cell::new(None),
711716
}
712717
}
713718
}
@@ -1724,6 +1729,14 @@ impl<'tcx> InferCtxt<'tcx> {
17241729
}
17251730
}
17261731
}
1732+
1733+
pub fn attach_obligation_inspector(&self, inspector: ObligationInspector<'tcx>) {
1734+
debug_assert!(
1735+
self.obligation_inspector.get().is_none(),
1736+
"shouldn't override a set obligation inspector"
1737+
);
1738+
self.obligation_inspector.set(Some(inspector));
1739+
}
17271740
}
17281741

17291742
impl<'tcx> TypeErrCtxt<'_, 'tcx> {

compiler/rustc_infer/src/traits/mod.rs

+9
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,15 @@ use std::hash::{Hash, Hasher};
1313

1414
use hir::def_id::LocalDefId;
1515
use rustc_hir as hir;
16+
use rustc_middle::traits::query::NoSolution;
17+
use rustc_middle::traits::solve::Certainty;
1618
use rustc_middle::ty::error::{ExpectedFound, TypeError};
1719
use rustc_middle::ty::{self, Const, ToPredicate, Ty, TyCtxt};
1820
use rustc_span::Span;
1921

2022
pub use self::ImplSource::*;
2123
pub use self::SelectionError::*;
24+
use crate::infer::InferCtxt;
2225

2326
pub use self::engine::{TraitEngine, TraitEngineExt};
2427
pub use self::project::MismatchedProjectionTypes;
@@ -116,6 +119,12 @@ pub type PredicateObligations<'tcx> = Vec<PredicateObligation<'tcx>>;
116119

117120
pub type Selection<'tcx> = ImplSource<'tcx, PredicateObligation<'tcx>>;
118121

122+
/// A callback that can be provided to `inspect_typeck`. Invoked on evaluation
123+
/// of root obligations.
124+
pub type ObligationInspector<'tcx> =
125+
fn(&InferCtxt<'tcx>, &PredicateObligation<'tcx>, Result<Certainty, NoSolution>);
126+
127+
#[derive(Clone)]
119128
pub struct FulfillmentError<'tcx> {
120129
pub obligation: PredicateObligation<'tcx>,
121130
pub code: FulfillmentErrorCode<'tcx>,

compiler/rustc_session/src/options.rs

+3
Original file line numberDiff line numberDiff line change
@@ -1914,6 +1914,9 @@ written to standard error output)"),
19141914
"for every macro invocation, print its name and arguments (default: no)"),
19151915
track_diagnostics: bool = (false, parse_bool, [UNTRACKED],
19161916
"tracks where in rustc a diagnostic was emitted"),
1917+
track_trait_obligations: bool = (false, parse_bool, [TRACKED],
1918+
"tracks evaluated obligations while trait solving, option is only \
1919+
valid when -Z next-solver=globally (default: no)"),
19171920
// Diagnostics are considered side-effects of a query (see `QuerySideEffects`) and are saved
19181921
// alongside query results and changes to translation options can affect diagnostics - so
19191922
// translation options should be tracked.

compiler/rustc_trait_selection/src/solve/fulfill.rs

+81-62
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use rustc_middle::ty;
1111
use rustc_middle::ty::error::{ExpectedFound, TypeError};
1212

1313
use super::eval_ctxt::GenerateProofTree;
14-
use super::{Certainty, InferCtxtEvalExt};
14+
use super::{Certainty, Goal, InferCtxtEvalExt};
1515

1616
/// A trait engine using the new trait solver.
1717
///
@@ -43,6 +43,21 @@ impl<'tcx> FulfillmentCtxt<'tcx> {
4343
);
4444
FulfillmentCtxt { obligations: Vec::new(), usable_in_snapshot: infcx.num_open_snapshots() }
4545
}
46+
47+
fn track_evaluated_obligation(
48+
&self,
49+
infcx: &InferCtxt<'tcx>,
50+
obligation: &PredicateObligation<'tcx>,
51+
result: &Result<(bool, Certainty, Vec<Goal<'tcx, ty::Predicate<'tcx>>>), NoSolution>,
52+
) {
53+
if let Some(inspector) = infcx.obligation_inspector.get() {
54+
let result = match result {
55+
Ok((_, c, _)) => Ok(*c),
56+
Err(NoSolution) => Err(NoSolution),
57+
};
58+
(inspector)(infcx, &obligation, result);
59+
}
60+
}
4661
}
4762

4863
impl<'tcx> TraitEngine<'tcx> for FulfillmentCtxt<'tcx> {
@@ -57,7 +72,8 @@ impl<'tcx> TraitEngine<'tcx> for FulfillmentCtxt<'tcx> {
5772
}
5873

5974
fn collect_remaining_errors(&mut self, infcx: &InferCtxt<'tcx>) -> Vec<FulfillmentError<'tcx>> {
60-
self.obligations
75+
let errors = self
76+
.obligations
6177
.drain(..)
6278
.map(|obligation| {
6379
let code = infcx.probe(|_| {
@@ -86,7 +102,9 @@ impl<'tcx> TraitEngine<'tcx> for FulfillmentCtxt<'tcx> {
86102
root_obligation: obligation,
87103
}
88104
})
89-
.collect()
105+
.collect();
106+
107+
errors
90108
}
91109

92110
fn select_where_possible(&mut self, infcx: &InferCtxt<'tcx>) -> Vec<FulfillmentError<'tcx>> {
@@ -100,65 +118,66 @@ impl<'tcx> TraitEngine<'tcx> for FulfillmentCtxt<'tcx> {
100118
let mut has_changed = false;
101119
for obligation in mem::take(&mut self.obligations) {
102120
let goal = obligation.clone().into();
103-
let (changed, certainty, nested_goals) =
104-
match infcx.evaluate_root_goal(goal, GenerateProofTree::IfEnabled).0 {
105-
Ok(result) => result,
106-
Err(NoSolution) => {
107-
errors.push(FulfillmentError {
108-
obligation: obligation.clone(),
109-
code: match goal.predicate.kind().skip_binder() {
110-
ty::PredicateKind::Clause(ty::ClauseKind::Projection(_)) => {
111-
FulfillmentErrorCode::ProjectionError(
112-
// FIXME: This could be a `Sorts` if the term is a type
113-
MismatchedProjectionTypes { err: TypeError::Mismatch },
114-
)
115-
}
116-
ty::PredicateKind::NormalizesTo(..) => {
117-
FulfillmentErrorCode::ProjectionError(
118-
MismatchedProjectionTypes { err: TypeError::Mismatch },
119-
)
120-
}
121-
ty::PredicateKind::AliasRelate(_, _, _) => {
122-
FulfillmentErrorCode::ProjectionError(
123-
MismatchedProjectionTypes { err: TypeError::Mismatch },
124-
)
125-
}
126-
ty::PredicateKind::Subtype(pred) => {
127-
let (a, b) = infcx.instantiate_binder_with_placeholders(
128-
goal.predicate.kind().rebind((pred.a, pred.b)),
129-
);
130-
let expected_found = ExpectedFound::new(true, a, b);
131-
FulfillmentErrorCode::SubtypeError(
132-
expected_found,
133-
TypeError::Sorts(expected_found),
134-
)
135-
}
136-
ty::PredicateKind::Coerce(pred) => {
137-
let (a, b) = infcx.instantiate_binder_with_placeholders(
138-
goal.predicate.kind().rebind((pred.a, pred.b)),
139-
);
140-
let expected_found = ExpectedFound::new(false, a, b);
141-
FulfillmentErrorCode::SubtypeError(
142-
expected_found,
143-
TypeError::Sorts(expected_found),
144-
)
145-
}
146-
ty::PredicateKind::Clause(_)
147-
| ty::PredicateKind::ObjectSafe(_)
148-
| ty::PredicateKind::Ambiguous => {
149-
FulfillmentErrorCode::SelectionError(
150-
SelectionError::Unimplemented,
151-
)
152-
}
153-
ty::PredicateKind::ConstEquate(..) => {
154-
bug!("unexpected goal: {goal:?}")
155-
}
156-
},
157-
root_obligation: obligation,
158-
});
159-
continue;
160-
}
161-
};
121+
let result = infcx.evaluate_root_goal(goal, GenerateProofTree::IfEnabled).0;
122+
self.track_evaluated_obligation(infcx, &obligation, &result);
123+
let (changed, certainty, nested_goals) = match result {
124+
Ok(result) => result,
125+
Err(NoSolution) => {
126+
errors.push(FulfillmentError {
127+
obligation: obligation.clone(),
128+
code: match goal.predicate.kind().skip_binder() {
129+
ty::PredicateKind::Clause(ty::ClauseKind::Projection(_)) => {
130+
FulfillmentErrorCode::ProjectionError(
131+
// FIXME: This could be a `Sorts` if the term is a type
132+
MismatchedProjectionTypes { err: TypeError::Mismatch },
133+
)
134+
}
135+
ty::PredicateKind::NormalizesTo(..) => {
136+
FulfillmentErrorCode::ProjectionError(
137+
MismatchedProjectionTypes { err: TypeError::Mismatch },
138+
)
139+
}
140+
ty::PredicateKind::AliasRelate(_, _, _) => {
141+
FulfillmentErrorCode::ProjectionError(
142+
MismatchedProjectionTypes { err: TypeError::Mismatch },
143+
)
144+
}
145+
ty::PredicateKind::Subtype(pred) => {
146+
let (a, b) = infcx.instantiate_binder_with_placeholders(
147+
goal.predicate.kind().rebind((pred.a, pred.b)),
148+
);
149+
let expected_found = ExpectedFound::new(true, a, b);
150+
FulfillmentErrorCode::SubtypeError(
151+
expected_found,
152+
TypeError::Sorts(expected_found),
153+
)
154+
}
155+
ty::PredicateKind::Coerce(pred) => {
156+
let (a, b) = infcx.instantiate_binder_with_placeholders(
157+
goal.predicate.kind().rebind((pred.a, pred.b)),
158+
);
159+
let expected_found = ExpectedFound::new(false, a, b);
160+
FulfillmentErrorCode::SubtypeError(
161+
expected_found,
162+
TypeError::Sorts(expected_found),
163+
)
164+
}
165+
ty::PredicateKind::Clause(_)
166+
| ty::PredicateKind::ObjectSafe(_)
167+
| ty::PredicateKind::Ambiguous => {
168+
FulfillmentErrorCode::SelectionError(
169+
SelectionError::Unimplemented,
170+
)
171+
}
172+
ty::PredicateKind::ConstEquate(..) => {
173+
bug!("unexpected goal: {goal:?}")
174+
}
175+
},
176+
root_obligation: obligation,
177+
});
178+
continue;
179+
}
180+
};
162181
// Push any nested goals that we get from unifying our canonical response
163182
// with our obligation onto the fulfillment context.
164183
self.obligations.extend(nested_goals.into_iter().map(|goal| {
+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// compile-flags: -Ztrack-trait-obligations
2+
// run-pass
3+
4+
// Just making sure this flag is accepted and doesn't crash the compiler
5+
use traits::IntoString;
6+
7+
fn does_impl_into_string<T: IntoString>(_: T) {}
8+
9+
fn main() {
10+
let v = vec![(0, 1), (2, 3)];
11+
12+
does_impl_into_string(v);
13+
}
14+
15+
mod traits {
16+
pub trait IntoString {
17+
fn to_string(&self) -> String;
18+
}
19+
20+
impl IntoString for (i32, i32) {
21+
fn to_string(&self) -> String {
22+
format!("({}, {})", self.0, self.1)
23+
}
24+
}
25+
26+
impl<T: IntoString> IntoString for Vec<T> {
27+
fn to_string(&self) -> String {
28+
let s = self.iter().map(|v| v.to_string()).collect::<Vec<_>>().join(", ");
29+
format!("[{s}]")
30+
}
31+
}
32+
}

0 commit comments

Comments
 (0)