Skip to content

Commit d9ce5b8

Browse files
committed
dont discard overflow from normalizes-to goals
1 parent ca718ff commit d9ce5b8

File tree

6 files changed

+82
-23
lines changed

6 files changed

+82
-23
lines changed

compiler/rustc_middle/src/traits/solve.rs

+3
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,9 @@ pub enum GoalSource {
248248
/// This also impacts whether we erase constraints on overflow.
249249
/// Erasing constraints is generally very useful for perf and also
250250
/// results in better error messages by avoiding spurious errors.
251+
/// We do not erase overflow constraints in `normalizes-to` goals unless
252+
/// they are from an impl where-clause. This is necessary due to
253+
/// backwards compatability, cc trait-system-refactor-initiatitive#70.
251254
ImplWhereBound,
252255
}
253256

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

-14
Original file line numberDiff line numberDiff line change
@@ -94,20 +94,6 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
9494
);
9595

9696
let certainty = certainty.unify_with(goals_certainty);
97-
if let Certainty::OVERFLOW = certainty {
98-
// If we have overflow, it's probable that we're substituting a type
99-
// into itself infinitely and any partial substitutions in the query
100-
// response are probably not useful anyways, so just return an empty
101-
// query response.
102-
//
103-
// This may prevent us from potentially useful inference, e.g.
104-
// 2 candidates, one ambiguous and one overflow, which both
105-
// have the same inference constraints.
106-
//
107-
// Changing this to retain some constraints in the future
108-
// won't be a breaking change, so this is good enough for now.
109-
return Ok(self.make_ambiguous_response_no_constraints(MaybeCause::Overflow));
110-
}
11197

11298
let var_values = self.var_values;
11399
let external_constraints = self.compute_external_query_constraints()?;

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

+35-8
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ impl<'tcx> InferCtxtEvalExt<'tcx> for InferCtxt<'tcx> {
157157
Option<inspect::GoalEvaluation<'tcx>>,
158158
) {
159159
EvalCtxt::enter_root(self, generate_proof_tree, |ecx| {
160-
ecx.evaluate_goal(GoalEvaluationKind::Root, goal)
160+
ecx.evaluate_goal(GoalEvaluationKind::Root, GoalSource::Misc, goal)
161161
})
162162
}
163163
}
@@ -335,6 +335,7 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
335335
fn evaluate_goal(
336336
&mut self,
337337
goal_evaluation_kind: GoalEvaluationKind,
338+
source: GoalSource,
338339
goal: Goal<'tcx, ty::Predicate<'tcx>>,
339340
) -> Result<(bool, Certainty, Vec<Goal<'tcx, ty::Predicate<'tcx>>>), NoSolution> {
340341
let (orig_values, canonical_goal) = self.canonicalize_goal(goal);
@@ -354,13 +355,13 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
354355
Ok(response) => response,
355356
};
356357

357-
let has_changed = !canonical_response.value.var_values.is_identity_modulo_regions()
358-
|| !canonical_response.value.external_constraints.opaque_types.is_empty();
359-
let (certainty, nested_goals) = match self.instantiate_and_apply_query_response(
360-
goal.param_env,
361-
orig_values,
362-
canonical_response,
363-
) {
358+
let (certainty, has_changed, nested_goals) = match self
359+
.instantiate_response_discarding_overflow(
360+
goal.param_env,
361+
source,
362+
orig_values,
363+
canonical_response,
364+
) {
364365
Err(e) => {
365366
self.inspect.goal_evaluation(goal_evaluation);
366367
return Err(e);
@@ -387,6 +388,30 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
387388
Ok((has_changed, certainty, nested_goals))
388389
}
389390

391+
fn instantiate_response_discarding_overflow(
392+
&mut self,
393+
param_env: ty::ParamEnv<'tcx>,
394+
source: GoalSource,
395+
original_values: Vec<ty::GenericArg<'tcx>>,
396+
response: CanonicalResponse<'tcx>,
397+
) -> Result<(Certainty, bool, Vec<Goal<'tcx, ty::Predicate<'tcx>>>), NoSolution> {
398+
let keep_overflow_constraints = || {
399+
self.search_graph.current_goal_is_normalizes_to()
400+
&& source != GoalSource::ImplWhereBound
401+
};
402+
403+
if response.value.certainty == Certainty::OVERFLOW && !keep_overflow_constraints() {
404+
Ok((Certainty::OVERFLOW, false, Vec::new()))
405+
} else {
406+
let has_changed = !response.value.var_values.is_identity_modulo_regions()
407+
|| !response.value.external_constraints.opaque_types.is_empty();
408+
409+
let (certainty, nested_goals) =
410+
self.instantiate_and_apply_query_response(param_env, original_values, response)?;
411+
Ok((certainty, has_changed, nested_goals))
412+
}
413+
}
414+
390415
fn compute_goal(&mut self, goal: Goal<'tcx, ty::Predicate<'tcx>>) -> QueryResult<'tcx> {
391416
let Goal { param_env, predicate } = goal;
392417
let kind = predicate.kind();
@@ -509,6 +534,7 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
509534

510535
let (_, certainty, instantiate_goals) = self.evaluate_goal(
511536
GoalEvaluationKind::Nested { is_normalizes_to_hack: IsNormalizesToHack::Yes },
537+
GoalSource::Misc,
512538
unconstrained_goal,
513539
)?;
514540
self.nested_goals.goals.extend(with_misc_source(instantiate_goals));
@@ -544,6 +570,7 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
544570
for (source, goal) in goals.goals.drain(..) {
545571
let (has_changed, certainty, instantiate_goals) = self.evaluate_goal(
546572
GoalEvaluationKind::Nested { is_normalizes_to_hack: IsNormalizesToHack::No },
573+
source,
547574
goal,
548575
)?;
549576
self.nested_goals.goals.extend(with_misc_source(instantiate_goals));

compiler/rustc_trait_selection/src/solve/search_graph.rs

+10
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use rustc_index::IndexVec;
88
use rustc_middle::dep_graph::dep_kinds;
99
use rustc_middle::traits::solve::CacheData;
1010
use rustc_middle::traits::solve::{CanonicalInput, Certainty, EvaluationCache, QueryResult};
11+
use rustc_middle::ty;
1112
use rustc_middle::ty::TyCtxt;
1213
use rustc_session::Limit;
1314
use std::collections::hash_map::Entry;
@@ -111,6 +112,15 @@ impl<'tcx> SearchGraph<'tcx> {
111112
self.stack.is_empty()
112113
}
113114

115+
pub(super) fn current_goal_is_normalizes_to(&self) -> bool {
116+
self.stack.raw.last().map_or(false, |e| {
117+
matches!(
118+
e.input.value.goal.predicate.kind().skip_binder(),
119+
ty::PredicateKind::NormalizesTo(..)
120+
)
121+
})
122+
}
123+
114124
/// Returns the remaining depth allowed for nested goals.
115125
///
116126
/// This is generally simply one less than the current depth.

tests/ui/traits/issue-90662-projection-caching.rs

+9-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
1+
// revisions: old next
2+
//[next] compile-flags: -Znext-solver=coherence
13
// check-pass
24

35
// Regression test for issue #90662
4-
// Tests that projection caching does not cause a spurious error
6+
// Tests that projection caching does not cause a spurious error.
7+
// Coherence relies on the following overflowing goal to still constrain
8+
// `?0` to `dyn Service`.
9+
//
10+
// Projection(<ServiceImpl as Provider<TestModule>>::Interface. ?0)
11+
//
12+
// cc https://github.com/rust-lang/trait-system-refactor-initiative/issues/70.
513

614
trait HasProvider<T: ?Sized> {}
715
trait Provider<M> {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// compile-flags: -Znext-solver=coherence
2+
// check-pass
3+
4+
// A regression test for trait-system-refactor-initiative#70.
5+
6+
trait Trait {
7+
type Assoc;
8+
}
9+
10+
struct W<T: ?Sized>(*mut T);
11+
impl<T: ?Sized> Trait for W<W<T>>
12+
where
13+
W<T>: Trait,
14+
{
15+
type Assoc = ();
16+
}
17+
18+
trait NoOverlap {}
19+
impl<T: Trait<Assoc = u32>> NoOverlap for T {}
20+
// `Projection(<W<_> as Trait>::Assoc, u32)` should result in error even
21+
// though applying the impl results in overflow. This is necessary to match
22+
// the behavior of the old solver.
23+
impl<T: ?Sized> NoOverlap for W<T> {}
24+
25+
fn main() {}

0 commit comments

Comments
 (0)