Skip to content

Commit a171994

Browse files
committed
Auto merge of #146329 - lcnr:opaque-type-infer-alias-candidates, r=BoxyUwU
consider item bounds for non-yet-defined opaque types Based on #140405. fixes rust-lang/trait-system-refactor-initiative#182 fixes rust-lang/trait-system-refactor-initiative#196 fixes rust-lang/trait-system-refactor-initiative#205 there's some jank here, see rust-lang/trait-system-refactor-initiative#229 ## Design If the self type is an inference variable which has been sub-unified with am opaque type, we need to incompletely guide inference to avoid breakage. In this case, we - look at the item bounds of all sub-unified opaque types, and - blanket impls which do not constrain the self type Even if there are applicable candidates, we always force their certainty to be `Maybe`, so they will always have to be reproven once we've constrained the inference variable. This is a bit iffy, see the added tests. r? `@BoxyUwU`
2 parents 408eacf + cf224ea commit a171994

19 files changed

+490
-38
lines changed

compiler/rustc_middle/src/ty/context.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -651,7 +651,11 @@ impl<'tcx> Interner for TyCtxt<'tcx> {
651651
| ty::Bound(_, _) => bug!("unexpected self type: {self_ty}"),
652652
}
653653

654-
let trait_impls = tcx.trait_impls_of(trait_def_id);
654+
#[allow(rustc::usage_of_type_ir_traits)]
655+
self.for_each_blanket_impl(trait_def_id, f)
656+
}
657+
fn for_each_blanket_impl(self, trait_def_id: DefId, mut f: impl FnMut(DefId)) {
658+
let trait_impls = self.trait_impls_of(trait_def_id);
655659
for &impl_def_id in trait_impls.blanket_impls() {
656660
f(impl_def_id);
657661
}

compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs

Lines changed: 130 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@ use rustc_type_ir::lang_items::SolverTraitLangItem;
1111
use rustc_type_ir::search_graph::CandidateHeadUsages;
1212
use rustc_type_ir::solve::SizedTraitKind;
1313
use rustc_type_ir::{
14-
self as ty, Interner, TypeFlags, TypeFoldable, TypeSuperVisitable, TypeVisitable,
15-
TypeVisitableExt as _, TypeVisitor, TypingMode, Upcast as _, elaborate,
14+
self as ty, Interner, TypeFlags, TypeFoldable, TypeFolder, TypeSuperFoldable,
15+
TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor, TypingMode, Upcast,
16+
elaborate,
1617
};
1718
use tracing::{debug, instrument};
1819

@@ -187,6 +188,7 @@ where
187188
ecx: &mut EvalCtxt<'_, D>,
188189
goal: Goal<I, Self>,
189190
impl_def_id: I::ImplId,
191+
then: impl FnOnce(&mut EvalCtxt<'_, D>, Certainty) -> QueryResult<I>,
190192
) -> Result<Candidate<I>, NoSolution>;
191193

192194
/// If the predicate contained an error, we want to avoid emitting unnecessary trait
@@ -365,6 +367,15 @@ pub(super) enum AssembleCandidatesFrom {
365367
EnvAndBounds,
366368
}
367369

370+
impl AssembleCandidatesFrom {
371+
fn should_assemble_impl_candidates(&self) -> bool {
372+
match self {
373+
AssembleCandidatesFrom::All => true,
374+
AssembleCandidatesFrom::EnvAndBounds => false,
375+
}
376+
}
377+
}
378+
368379
/// This is currently used to track the [CandidateHeadUsages] of all failed `ParamEnv`
369380
/// candidates. This is then used to ignore their head usages in case there's another
370381
/// always applicable `ParamEnv` candidate. Look at how `param_env_head_usages` is
@@ -397,14 +408,15 @@ where
397408
return (candidates, failed_candidate_info);
398409
};
399410

411+
let goal: Goal<I, G> = goal
412+
.with(self.cx(), goal.predicate.with_replaced_self_ty(self.cx(), normalized_self_ty));
413+
400414
if normalized_self_ty.is_ty_var() {
401415
debug!("self type has been normalized to infer");
402-
candidates.extend(self.forced_ambiguity(MaybeCause::Ambiguity));
416+
self.try_assemble_bounds_via_registered_opaques(goal, assemble_from, &mut candidates);
403417
return (candidates, failed_candidate_info);
404418
}
405419

406-
let goal: Goal<I, G> = goal
407-
.with(self.cx(), goal.predicate.with_replaced_self_ty(self.cx(), normalized_self_ty));
408420
// Vars that show up in the rest of the goal substs may have been constrained by
409421
// normalizing the self type as well, since type variables are not uniquified.
410422
let goal = self.resolve_vars_if_possible(goal);
@@ -484,8 +496,9 @@ where
484496
if cx.impl_is_default(impl_def_id) {
485497
return;
486498
}
487-
488-
match G::consider_impl_candidate(self, goal, impl_def_id) {
499+
match G::consider_impl_candidate(self, goal, impl_def_id, |ecx, certainty| {
500+
ecx.evaluate_added_goals_and_make_canonical_response(certainty)
501+
}) {
489502
Ok(candidate) => candidates.push(candidate),
490503
Err(NoSolution) => (),
491504
}
@@ -943,6 +956,116 @@ where
943956
}
944957
}
945958

959+
/// If the self type is the hidden type of an opaque, try to assemble
960+
/// candidates for it by consider its item bounds and by using blanket
961+
/// impls. This is used to incompletely guide type inference when handling
962+
/// non-defining uses in the defining scope.
963+
///
964+
/// We otherwise just fail fail with ambiguity. Even if we're using an
965+
/// opaque type item bound or a blank impls, we still force its certainty
966+
/// to be `Maybe` so that we properly prove this goal later.
967+
///
968+
/// See <https://github.com/rust-lang/trait-system-refactor-initiative/issues/182>
969+
/// for why this is necessary.
970+
fn try_assemble_bounds_via_registered_opaques<G: GoalKind<D>>(
971+
&mut self,
972+
goal: Goal<I, G>,
973+
assemble_from: AssembleCandidatesFrom,
974+
candidates: &mut Vec<Candidate<I>>,
975+
) {
976+
let self_ty = goal.predicate.self_ty();
977+
// If the self type is sub unified with any opaque type, we
978+
// also look at blanket impls for it.
979+
let mut assemble_blanket_impls = false;
980+
for alias_ty in self.opaques_with_sub_unified_hidden_type(self_ty) {
981+
assemble_blanket_impls = true;
982+
debug!("self ty is sub unified with {alias_ty:?}");
983+
984+
struct ReplaceOpaque<I: Interner> {
985+
cx: I,
986+
alias_ty: ty::AliasTy<I>,
987+
self_ty: I::Ty,
988+
}
989+
impl<I: Interner> TypeFolder<I> for ReplaceOpaque<I> {
990+
fn cx(&self) -> I {
991+
self.cx
992+
}
993+
fn fold_ty(&mut self, ty: I::Ty) -> I::Ty {
994+
if let ty::Alias(ty::Opaque, alias_ty) = ty.kind() {
995+
if alias_ty == self.alias_ty {
996+
return self.self_ty;
997+
}
998+
}
999+
ty.super_fold_with(self)
1000+
}
1001+
}
1002+
1003+
// We look at all item-bounds of the opaque, replacing the
1004+
// opaque with the current self type before considering
1005+
// them as a candidate. Imagine e've got `?x: Trait<?y>`
1006+
// and `?x` has been sub-unified with the hidden type of
1007+
// `impl Trait<u32>`, We take the item bound `opaque: Trait<u32>`
1008+
// and replace all occurrences of `opaque` with `?x`. This results
1009+
// in a `?x: Trait<u32>` alias-bound candidate.
1010+
for item_bound in self
1011+
.cx()
1012+
.item_self_bounds(alias_ty.def_id)
1013+
.iter_instantiated(self.cx(), alias_ty.args)
1014+
{
1015+
let assumption =
1016+
item_bound.fold_with(&mut ReplaceOpaque { cx: self.cx(), alias_ty, self_ty });
1017+
candidates.extend(G::probe_and_match_goal_against_assumption(
1018+
self,
1019+
CandidateSource::AliasBound,
1020+
goal,
1021+
assumption,
1022+
|ecx| {
1023+
// We want to reprove this goal once we've inferred the
1024+
// hidden type, so we force the certainty to `Maybe`.
1025+
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS)
1026+
},
1027+
));
1028+
}
1029+
}
1030+
1031+
// We also need to consider blanket impls for not-yet-defined opaque types.
1032+
//
1033+
// See tests/ui/impl-trait/non-defining-uses/use-blanket-impl.rs for an example.
1034+
if assemble_blanket_impls && assemble_from.should_assemble_impl_candidates() {
1035+
let cx = self.cx();
1036+
cx.for_each_blanket_impl(goal.predicate.trait_def_id(cx), |impl_def_id| {
1037+
// For every `default impl`, there's always a non-default `impl`
1038+
// that will *also* apply. There's no reason to register a candidate
1039+
// for this impl, since it is *not* proof that the trait goal holds.
1040+
if cx.impl_is_default(impl_def_id) {
1041+
return;
1042+
}
1043+
1044+
match G::consider_impl_candidate(self, goal, impl_def_id, |ecx, certainty| {
1045+
if ecx.shallow_resolve(self_ty).is_ty_var() {
1046+
// We force the certainty of impl candidates to be `Maybe`.
1047+
let certainty = certainty.and(Certainty::AMBIGUOUS);
1048+
ecx.evaluate_added_goals_and_make_canonical_response(certainty)
1049+
} else {
1050+
// We don't want to use impls if they constrain the opaque.
1051+
//
1052+
// FIXME(trait-system-refactor-initiative#229): This isn't
1053+
// perfect yet as it still allows us to incorrectly constrain
1054+
// other inference variables.
1055+
Err(NoSolution)
1056+
}
1057+
}) {
1058+
Ok(candidate) => candidates.push(candidate),
1059+
Err(NoSolution) => (),
1060+
}
1061+
});
1062+
}
1063+
1064+
if candidates.is_empty() {
1065+
candidates.extend(self.forced_ambiguity(MaybeCause::Ambiguity));
1066+
}
1067+
}
1068+
9461069
/// Assemble and merge candidates for goals which are related to an underlying trait
9471070
/// goal. Right now, this is normalizes-to and host effect goals.
9481071
///

compiler/rustc_next_trait_solver/src/solve/effect_goals.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ where
124124
ecx: &mut EvalCtxt<'_, D>,
125125
goal: Goal<I, Self>,
126126
impl_def_id: I::ImplId,
127+
then: impl FnOnce(&mut EvalCtxt<'_, D>, Certainty) -> QueryResult<I>,
127128
) -> Result<Candidate<I>, NoSolution> {
128129
let cx = ecx.cx();
129130

@@ -175,7 +176,7 @@ where
175176
});
176177
ecx.add_goals(GoalSource::ImplWhereBound, const_conditions);
177178

178-
ecx.evaluate_added_goals_and_make_canonical_response(certainty)
179+
then(ecx, certainty)
179180
})
180181
}
181182

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

Lines changed: 62 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -407,20 +407,21 @@ where
407407
// If we have run this goal before, and it was stalled, check that any of the goal's
408408
// args have changed. Otherwise, we don't need to re-run the goal because it'll remain
409409
// stalled, since it'll canonicalize the same way and evaluation is pure.
410-
if let Some(stalled_on) = stalled_on
411-
&& !stalled_on.stalled_vars.iter().any(|value| self.delegate.is_changed_arg(*value))
412-
&& !self
413-
.delegate
414-
.opaque_types_storage_num_entries()
415-
.needs_reevaluation(stalled_on.num_opaques)
410+
if let Some(GoalStalledOn { num_opaques, ref stalled_vars, ref sub_roots, stalled_cause }) =
411+
stalled_on
412+
&& !stalled_vars.iter().any(|value| self.delegate.is_changed_arg(*value))
413+
&& !sub_roots
414+
.iter()
415+
.any(|&vid| self.delegate.sub_unification_table_root_var(vid) != vid)
416+
&& !self.delegate.opaque_types_storage_num_entries().needs_reevaluation(num_opaques)
416417
{
417418
return Ok((
418419
NestedNormalizationGoals::empty(),
419420
GoalEvaluation {
420421
goal,
421-
certainty: Certainty::Maybe(stalled_on.stalled_cause),
422+
certainty: Certainty::Maybe(stalled_cause),
422423
has_changed: HasChanged::No,
423-
stalled_on: Some(stalled_on),
424+
stalled_on,
424425
},
425426
));
426427
}
@@ -476,16 +477,6 @@ where
476477
HasChanged::No => {
477478
let mut stalled_vars = orig_values;
478479

479-
// Remove the canonicalized universal vars, since we only care about stalled existentials.
480-
stalled_vars.retain(|arg| match arg.kind() {
481-
ty::GenericArgKind::Type(ty) => matches!(ty.kind(), ty::Infer(_)),
482-
ty::GenericArgKind::Const(ct) => {
483-
matches!(ct.kind(), ty::ConstKind::Infer(_))
484-
}
485-
// Lifetimes can never stall goals.
486-
ty::GenericArgKind::Lifetime(_) => false,
487-
});
488-
489480
// Remove the unconstrained RHS arg, which is expected to have changed.
490481
if let Some(normalizes_to) = goal.predicate.as_normalizes_to() {
491482
let normalizes_to = normalizes_to.skip_binder();
@@ -497,6 +488,27 @@ where
497488
stalled_vars.swap_remove(idx);
498489
}
499490

491+
// Remove the canonicalized universal vars, since we only care about stalled existentials.
492+
let mut sub_roots = Vec::new();
493+
stalled_vars.retain(|arg| match arg.kind() {
494+
// Lifetimes can never stall goals.
495+
ty::GenericArgKind::Lifetime(_) => false,
496+
ty::GenericArgKind::Type(ty) => match ty.kind() {
497+
ty::Infer(ty::TyVar(vid)) => {
498+
sub_roots.push(self.delegate.sub_unification_table_root_var(vid));
499+
true
500+
}
501+
ty::Infer(_) => true,
502+
ty::Param(_) | ty::Placeholder(_) => false,
503+
_ => unreachable!("unexpected orig_value: {ty:?}"),
504+
},
505+
ty::GenericArgKind::Const(ct) => match ct.kind() {
506+
ty::ConstKind::Infer(_) => true,
507+
ty::ConstKind::Param(_) | ty::ConstKind::Placeholder(_) => false,
508+
_ => unreachable!("unexpected orig_value: {ct:?}"),
509+
},
510+
});
511+
500512
Some(GoalStalledOn {
501513
num_opaques: canonical_goal
502514
.canonical
@@ -505,6 +517,7 @@ where
505517
.opaque_types
506518
.len(),
507519
stalled_vars,
520+
sub_roots,
508521
stalled_cause,
509522
})
510523
}
@@ -1047,6 +1060,10 @@ where
10471060
self.delegate.resolve_vars_if_possible(value)
10481061
}
10491062

1063+
pub(super) fn shallow_resolve(&self, ty: I::Ty) -> I::Ty {
1064+
self.delegate.shallow_resolve(ty)
1065+
}
1066+
10501067
pub(super) fn eager_resolve_region(&self, r: I::Region) -> I::Region {
10511068
if let ty::ReVar(vid) = r.kind() {
10521069
self.delegate.opportunistic_resolve_lt_var(vid)
@@ -1163,6 +1180,33 @@ where
11631180
) -> bool {
11641181
may_use_unstable_feature(&**self.delegate, param_env, symbol)
11651182
}
1183+
1184+
pub(crate) fn opaques_with_sub_unified_hidden_type(
1185+
&self,
1186+
self_ty: I::Ty,
1187+
) -> impl Iterator<Item = ty::AliasTy<I>> + use<'a, D, I> {
1188+
let delegate = self.delegate;
1189+
delegate
1190+
.clone_opaque_types_lookup_table()
1191+
.into_iter()
1192+
.chain(delegate.clone_duplicate_opaque_types())
1193+
.filter_map(move |(key, hidden_ty)| {
1194+
if let ty::Infer(ty::TyVar(self_vid)) = self_ty.kind() {
1195+
if let ty::Infer(ty::TyVar(hidden_vid)) = hidden_ty.kind() {
1196+
if delegate.sub_unification_table_root_var(self_vid)
1197+
== delegate.sub_unification_table_root_var(hidden_vid)
1198+
{
1199+
return Some(ty::AliasTy::new_from_args(
1200+
delegate.cx(),
1201+
key.def_id.into(),
1202+
key.args,
1203+
));
1204+
}
1205+
}
1206+
}
1207+
None
1208+
})
1209+
}
11661210
}
11671211

11681212
/// Eagerly replace aliases with inference variables, emitting `AliasRelate`

compiler/rustc_next_trait_solver/src/solve/mod.rs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ mod trait_goals;
2424
use derive_where::derive_where;
2525
use rustc_type_ir::inherent::*;
2626
pub use rustc_type_ir::solve::*;
27-
use rustc_type_ir::{self as ty, Interner, TypingMode};
27+
use rustc_type_ir::{self as ty, Interner, TyVid, TypingMode};
2828
use tracing::instrument;
2929

3030
pub use self::eval_ctxt::{
@@ -418,11 +418,6 @@ pub struct GoalEvaluation<I: Interner> {
418418
pub has_changed: HasChanged,
419419
/// If the [`Certainty`] was `Maybe`, then keep track of whether the goal has changed
420420
/// before rerunning it.
421-
///
422-
/// We knowingly ignore the `sub_root` of our inference variables here. This means we
423-
/// may not reevaluate a goal even though a change to the `sub_root` could cause a goal
424-
/// to make progress. Tracking them adds additional complexity for an incredibly minor
425-
/// type inference improvement. We could look into properly handling this in the future.
426421
pub stalled_on: Option<GoalStalledOn<I>>,
427422
}
428423

@@ -431,6 +426,7 @@ pub struct GoalEvaluation<I: Interner> {
431426
pub struct GoalStalledOn<I: Interner> {
432427
pub num_opaques: usize,
433428
pub stalled_vars: Vec<I::GenericArg>,
429+
pub sub_roots: Vec<TyVid>,
434430
/// The cause that will be returned on subsequent evaluations if this goal remains stalled.
435431
pub stalled_cause: MaybeCause,
436432
}

compiler/rustc_next_trait_solver/src/solve/normalizes_to/mod.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@ where
194194
ecx: &mut EvalCtxt<'_, D>,
195195
goal: Goal<I, NormalizesTo<I>>,
196196
impl_def_id: I::ImplId,
197+
then: impl FnOnce(&mut EvalCtxt<'_, D>, Certainty) -> QueryResult<I>,
197198
) -> Result<Candidate<I>, NoSolution> {
198199
let cx = ecx.cx();
199200

@@ -314,8 +315,7 @@ where
314315
// nested goal for consistency.
315316
ty::TypingMode::Coherence => {
316317
ecx.add_goal(GoalSource::Misc, goal.with(cx, PredicateKind::Ambiguous));
317-
return ecx
318-
.evaluate_added_goals_and_make_canonical_response(Certainty::Yes);
318+
return then(ecx, Certainty::Yes);
319319
}
320320
ty::TypingMode::Analysis { .. }
321321
| ty::TypingMode::Borrowck { .. }
@@ -325,8 +325,7 @@ where
325325
goal,
326326
goal.predicate.alias,
327327
);
328-
return ecx
329-
.evaluate_added_goals_and_make_canonical_response(Certainty::Yes);
328+
return then(ecx, Certainty::Yes);
330329
}
331330
}
332331
} else {

0 commit comments

Comments
 (0)