Skip to content

Commit 1ae8d1d

Browse files
committed
NormalizesTo return nested goals
1 parent 33c274f commit 1ae8d1d

File tree

13 files changed

+176
-94
lines changed

13 files changed

+176
-94
lines changed

compiler/rustc_middle/src/traits/solve.rs

+21-2
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,19 @@ pub struct ExternalConstraintsData<'tcx> {
164164
// FIXME: implement this.
165165
pub region_constraints: QueryRegionConstraints<'tcx>,
166166
pub opaque_types: Vec<(ty::OpaqueTypeKey<'tcx>, Ty<'tcx>)>,
167+
pub normalization_nested_goals: NestedNormalizationGoals<'tcx>,
168+
}
169+
170+
#[derive(Debug, PartialEq, Eq, Clone, Hash, HashStable, Default, TypeVisitable, TypeFoldable)]
171+
pub struct NestedNormalizationGoals<'tcx>(pub Vec<(GoalSource, Goal<'tcx, ty::Predicate<'tcx>>)>);
172+
impl<'tcx> NestedNormalizationGoals<'tcx> {
173+
pub fn empty() -> Self {
174+
NestedNormalizationGoals(vec![])
175+
}
176+
177+
pub fn is_empty(&self) -> bool {
178+
self.0.is_empty()
179+
}
167180
}
168181

169182
// FIXME: Having to clone `region_constraints` for folding feels bad and
@@ -183,21 +196,27 @@ impl<'tcx> TypeFoldable<TyCtxt<'tcx>> for ExternalConstraints<'tcx> {
183196
.iter()
184197
.map(|opaque| opaque.try_fold_with(folder))
185198
.collect::<Result<_, F::Error>>()?,
199+
normalization_nested_goals: self
200+
.normalization_nested_goals
201+
.clone()
202+
.try_fold_with(folder)?,
186203
}))
187204
}
188205

189206
fn fold_with<F: TypeFolder<TyCtxt<'tcx>>>(self, folder: &mut F) -> Self {
190207
TypeFolder::interner(folder).mk_external_constraints(ExternalConstraintsData {
191208
region_constraints: self.region_constraints.clone().fold_with(folder),
192209
opaque_types: self.opaque_types.iter().map(|opaque| opaque.fold_with(folder)).collect(),
210+
normalization_nested_goals: self.normalization_nested_goals.clone().fold_with(folder),
193211
})
194212
}
195213
}
196214

197215
impl<'tcx> TypeVisitable<TyCtxt<'tcx>> for ExternalConstraints<'tcx> {
198216
fn visit_with<V: TypeVisitor<TyCtxt<'tcx>>>(&self, visitor: &mut V) -> V::Result {
199217
try_visit!(self.region_constraints.visit_with(visitor));
200-
self.opaque_types.visit_with(visitor)
218+
try_visit!(self.opaque_types.visit_with(visitor));
219+
self.normalization_nested_goals.visit_with(visitor)
201220
}
202221
}
203222

@@ -239,7 +258,7 @@ impl<'tcx> TypeVisitable<TyCtxt<'tcx>> for PredefinedOpaques<'tcx> {
239258
///
240259
/// This is necessary as we treat nested goals different depending on
241260
/// their source.
242-
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
261+
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, HashStable, TypeVisitable, TypeFoldable)]
243262
pub enum GoalSource {
244263
Misc,
245264
/// We're proving a where-bound of an impl.

compiler/rustc_trait_selection/src/solve/eval_ctxt/canonical.rs

+32-12
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
//!
1010
//! [c]: https://rustc-dev-guide.rust-lang.org/solve/canonicalization.html
1111
use super::{CanonicalInput, Certainty, EvalCtxt, Goal};
12+
use crate::solve::eval_ctxt::NestedGoals;
1213
use crate::solve::{
1314
inspect, response_no_constraints_raw, CanonicalResponse, QueryResult, Response,
1415
};
@@ -19,6 +20,7 @@ use rustc_infer::infer::canonical::CanonicalVarValues;
1920
use rustc_infer::infer::canonical::{CanonicalExt, QueryRegionConstraints};
2021
use rustc_infer::infer::resolve::EagerResolver;
2122
use rustc_infer::infer::{InferCtxt, InferOk};
23+
use rustc_infer::traits::solve::NestedNormalizationGoals;
2224
use rustc_middle::infer::canonical::Canonical;
2325
use rustc_middle::traits::query::NoSolution;
2426
use rustc_middle::traits::solve::{
@@ -93,13 +95,26 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
9395
previous call to `try_evaluate_added_goals!`"
9496
);
9597

96-
let certainty = certainty.unify_with(goals_certainty);
97-
98-
let var_values = self.var_values;
99-
let external_constraints = self.compute_external_query_constraints()?;
100-
98+
// When normalizing, we've replaced the expected term with an unconstrained
99+
// inference variable. This means that we dropped information which could
100+
// have been important. We handle this by instead returning the nested goals
101+
// to the caller, where they are then handled.
102+
//
103+
// As we return all ambiguous nested goals, we can ignore the certainty returned
104+
// by `try_evaluate_added_goals()`.
105+
let (certainty, normalization_nested_goals) = if self.is_normalizes_to_goal {
106+
let NestedGoals { normalizes_to_goals, goals } = std::mem::take(&mut self.nested_goals);
107+
assert!(normalizes_to_goals.is_empty());
108+
(certainty, NestedNormalizationGoals(goals))
109+
} else {
110+
let certainty = certainty.unify_with(goals_certainty);
111+
(certainty, NestedNormalizationGoals::empty())
112+
};
113+
114+
let external_constraints =
115+
self.compute_external_query_constraints(normalization_nested_goals)?;
101116
let (var_values, mut external_constraints) =
102-
(var_values, external_constraints).fold_with(&mut EagerResolver::new(self.infcx));
117+
(self.var_values, external_constraints).fold_with(&mut EagerResolver::new(self.infcx));
103118
// Remove any trivial region constraints once we've resolved regions
104119
external_constraints
105120
.region_constraints
@@ -146,6 +161,7 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
146161
#[instrument(level = "debug", skip(self), ret)]
147162
fn compute_external_query_constraints(
148163
&self,
164+
normalization_nested_goals: NestedNormalizationGoals<'tcx>,
149165
) -> Result<ExternalConstraintsData<'tcx>, NoSolution> {
150166
// We only check for leaks from universes which were entered inside
151167
// of the query.
@@ -176,7 +192,7 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
176192
self.predefined_opaques_in_body.opaque_types.iter().all(|(pa, _)| pa != a)
177193
});
178194

179-
Ok(ExternalConstraintsData { region_constraints, opaque_types })
195+
Ok(ExternalConstraintsData { region_constraints, opaque_types, normalization_nested_goals })
180196
}
181197

182198
/// After calling a canonical query, we apply the constraints returned
@@ -185,13 +201,14 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
185201
/// This happens in three steps:
186202
/// - we instantiate the bound variables of the query response
187203
/// - we unify the `var_values` of the response with the `original_values`
188-
/// - we apply the `external_constraints` returned by the query
204+
/// - we apply the `external_constraints` returned by the query, returning
205+
/// the `normalization_nested_goals`
189206
pub(super) fn instantiate_and_apply_query_response(
190207
&mut self,
191208
param_env: ty::ParamEnv<'tcx>,
192209
original_values: Vec<ty::GenericArg<'tcx>>,
193210
response: CanonicalResponse<'tcx>,
194-
) -> Certainty {
211+
) -> (NestedNormalizationGoals<'tcx>, Certainty) {
195212
let instantiation = Self::compute_query_response_instantiation_values(
196213
self.infcx,
197214
&original_values,
@@ -203,11 +220,14 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
203220

204221
Self::unify_query_var_values(self.infcx, param_env, &original_values, var_values);
205222

206-
let ExternalConstraintsData { region_constraints, opaque_types } =
207-
external_constraints.deref();
223+
let ExternalConstraintsData {
224+
region_constraints,
225+
opaque_types,
226+
normalization_nested_goals,
227+
} = external_constraints.deref();
208228
self.register_region_constraints(region_constraints);
209229
self.register_new_opaque_types(param_env, opaque_types);
210-
certainty
230+
(normalization_nested_goals.clone(), certainty)
211231
}
212232

213233
/// This returns the canoncial variable values to instantiate the bound variables of

compiler/rustc_trait_selection/src/solve/eval_ctxt/commit_if_ok.rs

+2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
1111
infcx: self.infcx,
1212
variables: self.variables,
1313
var_values: self.var_values,
14+
is_normalizes_to_goal: self.is_normalizes_to_goal,
1415
predefined_opaques_in_body: self.predefined_opaques_in_body,
1516
max_input_universe: self.max_input_universe,
1617
search_graph: self.search_graph,
@@ -25,6 +26,7 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
2526
infcx: _,
2627
variables: _,
2728
var_values: _,
29+
is_normalizes_to_goal: _,
2830
predefined_opaques_in_body: _,
2931
max_input_universe: _,
3032
search_graph: _,

compiler/rustc_trait_selection/src/solve/eval_ctxt/mod.rs

+47-39
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use rustc_infer::infer::{
77
BoundRegionConversionTime, DefineOpaqueTypes, InferCtxt, InferOk, TyCtxtInferExt,
88
};
99
use rustc_infer::traits::query::NoSolution;
10-
use rustc_infer::traits::solve::MaybeCause;
10+
use rustc_infer::traits::solve::{MaybeCause, NestedNormalizationGoals};
1111
use rustc_infer::traits::ObligationCause;
1212
use rustc_middle::infer::canonical::CanonicalVarInfos;
1313
use rustc_middle::infer::unify_key::{ConstVariableOrigin, ConstVariableOriginKind};
@@ -61,6 +61,14 @@ pub struct EvalCtxt<'a, 'tcx> {
6161
/// The variable info for the `var_values`, only used to make an ambiguous response
6262
/// with no constraints.
6363
variables: CanonicalVarInfos<'tcx>,
64+
/// Whether we're currently computing a `NormalizesTo` goal. Unlike other goals,
65+
/// `NormalizesTo` goals act like functions with the expected term always being
66+
/// fully unconstrained. This would weaken inference however, as the nested goals
67+
/// never get the inference constraints from the actual normalized-to type. Because
68+
/// of this we return any ambiguous nested goals from `NormalizesTo` to the caller
69+
/// when then adds these to its own context. The caller is always an `AliasRelate`
70+
/// goal so this never leaks out of the solver.
71+
is_normalizes_to_goal: bool,
6472
pub(super) var_values: CanonicalVarValues<'tcx>,
6573

6674
predefined_opaques_in_body: PredefinedOpaques<'tcx>,
@@ -91,7 +99,7 @@ pub struct EvalCtxt<'a, 'tcx> {
9199
pub(super) inspect: ProofTreeBuilder<'tcx>,
92100
}
93101

94-
#[derive(Debug, Clone)]
102+
#[derive(Default, Debug, Clone)]
95103
pub(super) struct NestedGoals<'tcx> {
96104
/// These normalizes-to goals are treated specially during the evaluation
97105
/// loop. In each iteration we take the RHS of the projection, replace it with
@@ -153,6 +161,10 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
153161
self.search_graph.solver_mode()
154162
}
155163

164+
pub(super) fn set_is_normalizes_to_goal(&mut self) {
165+
self.is_normalizes_to_goal = true;
166+
}
167+
156168
/// Creates a root evaluation context and search graph. This should only be
157169
/// used from outside of any evaluation, and other methods should be preferred
158170
/// over using this manually (such as [`InferCtxtEvalExt::evaluate_root_goal`]).
@@ -165,8 +177,8 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
165177
let mut search_graph = search_graph::SearchGraph::new(mode);
166178

167179
let mut ecx = EvalCtxt {
168-
search_graph: &mut search_graph,
169180
infcx,
181+
search_graph: &mut search_graph,
170182
nested_goals: NestedGoals::new(),
171183
inspect: ProofTreeBuilder::new_maybe_root(infcx.tcx, generate_proof_tree),
172184

@@ -178,6 +190,7 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
178190
max_input_universe: ty::UniverseIndex::ROOT,
179191
variables: ty::List::empty(),
180192
var_values: CanonicalVarValues::dummy(),
193+
is_normalizes_to_goal: false,
181194
tainted: Ok(()),
182195
};
183196
let result = f(&mut ecx);
@@ -231,6 +244,7 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
231244
infcx,
232245
variables: canonical_input.variables,
233246
var_values,
247+
is_normalizes_to_goal: false,
234248
predefined_opaques_in_body: input.predefined_opaques_in_body,
235249
max_input_universe: canonical_input.max_universe,
236250
search_graph,
@@ -317,6 +331,20 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
317331
source: GoalSource,
318332
goal: Goal<'tcx, ty::Predicate<'tcx>>,
319333
) -> Result<(bool, Certainty), NoSolution> {
334+
let (normalization_nested_goals, has_changed, certainty) =
335+
self.evaluate_goal_raw(goal_evaluation_kind, source, goal)?;
336+
assert!(normalization_nested_goals.is_empty());
337+
Ok((has_changed, certainty))
338+
}
339+
340+
/// FIXME(-Znext-solver=coinduction): `_source` is currently unused but will
341+
/// be necessary once we implement the new coinduction approach.
342+
fn evaluate_goal_raw(
343+
&mut self,
344+
goal_evaluation_kind: GoalEvaluationKind,
345+
_source: GoalSource,
346+
goal: Goal<'tcx, ty::Predicate<'tcx>>,
347+
) -> Result<(NestedNormalizationGoals<'tcx>, bool, Certainty), NoSolution> {
320348
let (orig_values, canonical_goal) = self.canonicalize_goal(goal);
321349
let mut goal_evaluation =
322350
self.inspect.new_goal_evaluation(goal, &orig_values, goal_evaluation_kind);
@@ -334,12 +362,12 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
334362
Ok(response) => response,
335363
};
336364

337-
let (certainty, has_changed) = self.instantiate_response_discarding_overflow(
338-
goal.param_env,
339-
source,
340-
orig_values,
341-
canonical_response,
342-
);
365+
let (normalization_nested_goals, certainty, has_changed) = self
366+
.instantiate_response_discarding_overflow(
367+
goal.param_env,
368+
orig_values,
369+
canonical_response,
370+
);
343371
self.inspect.goal_evaluation(goal_evaluation);
344372
// FIXME: We previously had an assert here that checked that recomputing
345373
// a goal after applying its constraints did not change its response.
@@ -351,47 +379,25 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
351379
// Once we have decided on how to handle trait-system-refactor-initiative#75,
352380
// we should re-add an assert here.
353381

354-
Ok((has_changed, certainty))
382+
Ok((normalization_nested_goals, has_changed, certainty))
355383
}
356384

357385
fn instantiate_response_discarding_overflow(
358386
&mut self,
359387
param_env: ty::ParamEnv<'tcx>,
360-
source: GoalSource,
361388
original_values: Vec<ty::GenericArg<'tcx>>,
362389
response: CanonicalResponse<'tcx>,
363-
) -> (Certainty, bool) {
364-
// The old solver did not evaluate nested goals when normalizing.
365-
// It returned the selection constraints allowing a `Projection`
366-
// obligation to not hold in coherence while avoiding the fatal error
367-
// from overflow.
368-
//
369-
// We match this behavior here by considering all constraints
370-
// from nested goals which are not from where-bounds. We will already
371-
// need to track which nested goals are required by impl where-bounds
372-
// for coinductive cycles, so we simply reuse that here.
373-
//
374-
// While we could consider overflow constraints in more cases, this should
375-
// not be necessary for backcompat and results in better perf. It also
376-
// avoids a potential inconsistency which would otherwise require some
377-
// tracking for root goals as well. See #119071 for an example.
378-
let keep_overflow_constraints = || {
379-
self.search_graph.current_goal_is_normalizes_to()
380-
&& source != GoalSource::ImplWhereBound
381-
};
382-
383-
if let Certainty::Maybe(MaybeCause::Overflow { .. }) = response.value.certainty
384-
&& !keep_overflow_constraints()
385-
{
386-
return (response.value.certainty, false);
390+
) -> (NestedNormalizationGoals<'tcx>, Certainty, bool) {
391+
if let Certainty::Maybe(MaybeCause::Overflow { .. }) = response.value.certainty {
392+
return (NestedNormalizationGoals::empty(), response.value.certainty, false);
387393
}
388394

389395
let has_changed = !response.value.var_values.is_identity_modulo_regions()
390396
|| !response.value.external_constraints.opaque_types.is_empty();
391397

392-
let certainty =
398+
let (normalization_nested_goals, certainty) =
393399
self.instantiate_and_apply_query_response(param_env, original_values, response);
394-
(certainty, has_changed)
400+
(normalization_nested_goals, certainty, has_changed)
395401
}
396402

397403
fn compute_goal(&mut self, goal: Goal<'tcx, ty::Predicate<'tcx>>) -> QueryResult<'tcx> {
@@ -494,7 +500,7 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
494500
/// Goals for the next step get directly added to the nested goals of the `EvalCtxt`.
495501
fn evaluate_added_goals_step(&mut self) -> Result<Option<Certainty>, NoSolution> {
496502
let tcx = self.tcx();
497-
let mut goals = core::mem::replace(&mut self.nested_goals, NestedGoals::new());
503+
let mut goals = core::mem::take(&mut self.nested_goals);
498504

499505
self.inspect.evaluate_added_goals_loop_start();
500506

@@ -515,11 +521,13 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
515521
ty::NormalizesTo { alias: goal.predicate.alias, term: unconstrained_rhs },
516522
);
517523

518-
let (_, certainty) = self.evaluate_goal(
524+
let (NestedNormalizationGoals(nested_goals), _, certainty) = self.evaluate_goal_raw(
519525
GoalEvaluationKind::Nested { is_normalizes_to_hack: IsNormalizesToHack::Yes },
520526
GoalSource::Misc,
521527
unconstrained_goal,
522528
)?;
529+
// Add the nested goals from normalization to our own nested goals.
530+
goals.goals.extend(nested_goals);
523531

524532
// Finally, equate the goal's RHS with the unconstrained var.
525533
// We put the nested goals from this into goals instead of

compiler/rustc_trait_selection/src/solve/eval_ctxt/probe.rs

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ where
2424
infcx: outer_ecx.infcx,
2525
variables: outer_ecx.variables,
2626
var_values: outer_ecx.var_values,
27+
is_normalizes_to_goal: outer_ecx.is_normalizes_to_goal,
2728
predefined_opaques_in_body: outer_ecx.predefined_opaques_in_body,
2829
max_input_universe: outer_ecx.max_input_universe,
2930
search_graph: outer_ecx.search_graph,

compiler/rustc_trait_selection/src/solve/eval_ctxt/select.rs

+7-6
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,13 @@ impl<'tcx> InferCtxt<'tcx> {
5858
}
5959

6060
let candidate = candidates.pop().unwrap();
61-
let certainty = ecx.instantiate_and_apply_query_response(
62-
trait_goal.param_env,
63-
orig_values,
64-
candidate.result,
65-
);
66-
61+
let (normalization_nested_goals, certainty) = ecx
62+
.instantiate_and_apply_query_response(
63+
trait_goal.param_env,
64+
orig_values,
65+
candidate.result,
66+
);
67+
assert!(normalization_nested_goals.is_empty());
6768
Ok(Some((candidate, certainty)))
6869
});
6970

0 commit comments

Comments
 (0)