Skip to content

Commit 04964d7

Browse files
committed
Use a fresh InferCtxt when we 'speculatively' evaluate predicates
The existing `InferCtxt` may already have in-progress projections for some of the predicates we may (recursively evaluate). This can cause us to incorrect produce (and cache!) `EvaluatedToRecur`, leading us to incorrectly mark a predicate as unimplemented. We now create a fresh `InferCtxt` for each sub-obligation that we 'speculatively' evaluate. This causes us to miss out on some legitimate caching opportunities, but ensures that our speculative evaluation cannot pollute any of the caches from the 'real' `InferCtxt`. The evaluation can still update *global* caches, but our global caches don't have any notion of 'in progress', so this is fine. Fixes #90662
1 parent 1e79d79 commit 04964d7

File tree

2 files changed

+50
-4
lines changed

2 files changed

+50
-4
lines changed

compiler/rustc_trait_selection/src/traits/project.rs

+16-4
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use super::{
1616
ImplSourceGeneratorData, ImplSourcePointeeData, ImplSourceUserDefinedData,
1717
};
1818
use super::{Normalized, NormalizedTy, ProjectionCacheEntry, ProjectionCacheKey};
19+
use rustc_infer::infer::TyCtxtInferExt;
1920

2021
use crate::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
2122
use crate::infer::{InferCtxt, InferOk, LateBoundRegionConversionTime};
@@ -944,8 +945,8 @@ fn opt_normalize_projection_type<'a, 'b, 'tcx>(
944945
Normalized { value: projected_ty, obligations: projected_obligations }
945946
};
946947

947-
let mut canonical =
948-
SelectionContext::with_query_mode(selcx.infcx(), TraitQueryMode::Canonical);
948+
let tcx = selcx.infcx().tcx;
949+
949950
result.obligations.drain_filter(|projected_obligation| {
950951
// If any global obligations always apply, considering regions, then we don't
951952
// need to include them. The `is_global` check rules out inference variables,
@@ -957,10 +958,21 @@ fn opt_normalize_projection_type<'a, 'b, 'tcx>(
957958
// the result to `EvaluatedToOkModuloRegions`, while an
958959
// `EvaluatedToOk` obligation will never change the result.
959960
// See #85360 for more details
960-
projected_obligation.is_global(canonical.tcx())
961-
&& canonical
961+
if !projected_obligation.is_global(tcx) {
962+
return false;
963+
}
964+
965+
// We create a fresh `InferCtxt` for each predicate we speculatively evaluate,
966+
// so that we won't create (and cache) any spurious projection cycles in the main
967+
// `InferCtxt`
968+
tcx.infer_ctxt().enter(|infcx| {
969+
let mut canonical =
970+
SelectionContext::with_query_mode(&infcx, TraitQueryMode::Canonical);
971+
972+
canonical
962973
.evaluate_root_obligation(projected_obligation)
963974
.map_or(false, |res| res.must_apply_considering_regions())
975+
})
964976
});
965977

966978
if use_cache {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// check-pass
2+
3+
// Regression test for issue #90662
4+
// Tests that projection caching does not cause a spurious error
5+
6+
trait HasProvider<T: ?Sized> {}
7+
trait Provider<M> {
8+
type Interface: ?Sized;
9+
}
10+
11+
trait Repository {}
12+
trait Service {}
13+
14+
struct DbConnection;
15+
impl<M> Provider<M> for DbConnection {
16+
type Interface = DbConnection;
17+
}
18+
19+
struct RepositoryImpl;
20+
impl<M: HasProvider<DbConnection>> Provider<M> for RepositoryImpl {
21+
type Interface = dyn Repository;
22+
}
23+
24+
struct ServiceImpl;
25+
impl<M: HasProvider<dyn Repository>> Provider<M> for ServiceImpl {
26+
type Interface = dyn Service;
27+
}
28+
29+
struct TestModule;
30+
impl HasProvider<<DbConnection as Provider<Self>>::Interface> for TestModule {}
31+
impl HasProvider<<RepositoryImpl as Provider<Self>>::Interface> for TestModule {}
32+
impl HasProvider<<ServiceImpl as Provider<Self>>::Interface> for TestModule {}
33+
34+
fn main() {}

0 commit comments

Comments
 (0)