Skip to content

Commit c401f09

Browse files
committed
Auto merge of rust-lang#119744 - lcnr:assemble-only-rigid, r=compiler-errors
only assemble alias bound candidates for rigid aliases fixes rust-lang/trait-system-refactor-initiative#77 This also causes `<Wrapper<?0> as Trait>::Unwrap: Trait` to always be ambig, as we now normalize the self type before checking whether it is an inference variable. I cannot think of an approach to the underlying issues here which does not require the "may-define means must-define" restriction for opaque types. Going to go ahead with this and added this restriction to the tracking issue for the new solver to make sure we don't stabilize it without getting types + lang signoff here. r? `@compiler-errors`
2 parents 5c9c3c7 + ea4e5b8 commit c401f09

15 files changed

+168
-218
lines changed

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

+26-135
Original file line numberDiff line numberDiff line change
@@ -253,82 +253,39 @@ pub(super) trait GoalKind<'tcx>:
253253
ecx: &mut EvalCtxt<'_, 'tcx>,
254254
goal: Goal<'tcx, Self>,
255255
) -> Vec<(CanonicalResponse<'tcx>, BuiltinImplSource)>;
256-
257-
/// Consider the `Unsize` candidate corresponding to coercing a sized type
258-
/// into a `dyn Trait`.
259-
///
260-
/// This is computed separately from the rest of the `Unsize` candidates
261-
/// since it is only done once per self type, and not once per
262-
/// *normalization step* (in `assemble_candidates_via_self_ty`).
263-
fn consider_unsize_to_dyn_candidate(
264-
ecx: &mut EvalCtxt<'_, 'tcx>,
265-
goal: Goal<'tcx, Self>,
266-
) -> QueryResult<'tcx>;
267256
}
268257

269258
impl<'tcx> EvalCtxt<'_, 'tcx> {
270259
pub(super) fn assemble_and_evaluate_candidates<G: GoalKind<'tcx>>(
271260
&mut self,
272261
goal: Goal<'tcx, G>,
273262
) -> Vec<Candidate<'tcx>> {
274-
debug_assert_eq!(goal, self.resolve_vars_if_possible(goal));
275-
if let Some(ambig) = self.assemble_self_ty_infer_ambiguity_response(goal) {
276-
return vec![ambig];
277-
}
278-
279-
let mut candidates = self.assemble_candidates_via_self_ty(goal, 0);
280-
281-
self.assemble_unsize_to_dyn_candidate(goal, &mut candidates);
282-
283-
self.assemble_blanket_impl_candidates(goal, &mut candidates);
284-
285-
self.assemble_param_env_candidates(goal, &mut candidates);
286-
287-
self.assemble_coherence_unknowable_candidates(goal, &mut candidates);
288-
289-
candidates
290-
}
291-
292-
/// `?0: Trait` is ambiguous, because it may be satisfied via a builtin rule,
293-
/// object bound, alias bound, etc. We are unable to determine this until we can at
294-
/// least structurally resolve the type one layer.
295-
///
296-
/// It would also require us to consider all impls of the trait, which is both pretty
297-
/// bad for perf and would also constrain the self type if there is just a single impl.
298-
fn assemble_self_ty_infer_ambiguity_response<G: GoalKind<'tcx>>(
299-
&mut self,
300-
goal: Goal<'tcx, G>,
301-
) -> Option<Candidate<'tcx>> {
302-
if goal.predicate.self_ty().is_ty_var() {
303-
debug!("adding self_ty_infer_ambiguity_response");
263+
let dummy_candidate = |this: &mut EvalCtxt<'_, 'tcx>, certainty| {
304264
let source = CandidateSource::BuiltinImpl(BuiltinImplSource::Misc);
305-
let result = self
306-
.evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS)
307-
.unwrap();
308-
let mut dummy_probe = self.inspect.new_probe();
265+
let result = this.evaluate_added_goals_and_make_canonical_response(certainty).unwrap();
266+
let mut dummy_probe = this.inspect.new_probe();
309267
dummy_probe.probe_kind(ProbeKind::TraitCandidate { source, result: Ok(result) });
310-
self.inspect.finish_probe(dummy_probe);
311-
Some(Candidate { source, result })
312-
} else {
313-
None
268+
this.inspect.finish_probe(dummy_probe);
269+
vec![Candidate { source, result }]
270+
};
271+
272+
let Some(normalized_self_ty) =
273+
self.try_normalize_ty(goal.param_env, goal.predicate.self_ty())
274+
else {
275+
debug!("overflow while evaluating self type");
276+
return dummy_candidate(self, Certainty::OVERFLOW);
277+
};
278+
279+
if normalized_self_ty.is_ty_var() {
280+
debug!("self type has been normalized to infer");
281+
return dummy_candidate(self, Certainty::AMBIGUOUS);
314282
}
315-
}
316283

317-
/// Assemble candidates which apply to the self type. This only looks at candidate which
318-
/// apply to the specific self type and ignores all others.
319-
///
320-
/// Returns `None` if the self type is still ambiguous.
321-
fn assemble_candidates_via_self_ty<G: GoalKind<'tcx>>(
322-
&mut self,
323-
goal: Goal<'tcx, G>,
324-
num_steps: usize,
325-
) -> Vec<Candidate<'tcx>> {
284+
let goal =
285+
goal.with(self.tcx(), goal.predicate.with_self_ty(self.tcx(), normalized_self_ty));
326286
debug_assert_eq!(goal, self.resolve_vars_if_possible(goal));
327-
if let Some(ambig) = self.assemble_self_ty_infer_ambiguity_response(goal) {
328-
return vec![ambig];
329-
}
330287

331-
let mut candidates = Vec::new();
288+
let mut candidates = vec![];
332289

333290
self.assemble_non_blanket_impl_candidates(goal, &mut candidates);
334291

@@ -338,61 +295,13 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
338295

339296
self.assemble_object_bound_candidates(goal, &mut candidates);
340297

341-
self.assemble_candidates_after_normalizing_self_ty(goal, &mut candidates, num_steps);
342-
candidates
343-
}
298+
self.assemble_blanket_impl_candidates(goal, &mut candidates);
344299

345-
/// If the self type of a goal is an alias we first try to normalize the self type
346-
/// and compute the candidates for the normalized self type in case that succeeds.
347-
///
348-
/// These candidates are used in addition to the ones with the alias as a self type.
349-
/// We do this to simplify both builtin candidates and for better performance.
350-
///
351-
/// We generate the builtin candidates on the fly by looking at the self type, e.g.
352-
/// add `FnPtr` candidates if the self type is a function pointer. Handling builtin
353-
/// candidates while the self type is still an alias seems difficult. This is similar
354-
/// to `try_structurally_resolve_type` during hir typeck (FIXME once implemented).
355-
///
356-
/// Looking at all impls for some trait goal is prohibitively expensive. We therefore
357-
/// only look at implementations with a matching self type. Because of this function,
358-
/// we can avoid looking at all existing impls if the self type is an alias.
359-
#[instrument(level = "debug", skip_all)]
360-
fn assemble_candidates_after_normalizing_self_ty<G: GoalKind<'tcx>>(
361-
&mut self,
362-
goal: Goal<'tcx, G>,
363-
candidates: &mut Vec<Candidate<'tcx>>,
364-
num_steps: usize,
365-
) {
366-
let tcx = self.tcx();
367-
let &ty::Alias(_, alias) = goal.predicate.self_ty().kind() else { return };
368-
369-
candidates.extend(self.probe(|_| ProbeKind::NormalizedSelfTyAssembly).enter(|ecx| {
370-
if tcx.recursion_limit().value_within_limit(num_steps) {
371-
let normalized_ty = ecx.next_ty_infer();
372-
let normalizes_to_goal =
373-
goal.with(tcx, ty::NormalizesTo { alias, term: normalized_ty.into() });
374-
ecx.add_goal(GoalSource::Misc, normalizes_to_goal);
375-
if let Err(NoSolution) = ecx.try_evaluate_added_goals() {
376-
debug!("self type normalization failed");
377-
return vec![];
378-
}
379-
let normalized_ty = ecx.resolve_vars_if_possible(normalized_ty);
380-
debug!(?normalized_ty, "self type normalized");
381-
// NOTE: Alternatively we could call `evaluate_goal` here and only
382-
// have a `Normalized` candidate. This doesn't work as long as we
383-
// use `CandidateSource` in winnowing.
384-
let goal = goal.with(tcx, goal.predicate.with_self_ty(tcx, normalized_ty));
385-
ecx.assemble_candidates_via_self_ty(goal, num_steps + 1)
386-
} else {
387-
match ecx.evaluate_added_goals_and_make_canonical_response(Certainty::OVERFLOW) {
388-
Ok(result) => vec![Candidate {
389-
source: CandidateSource::BuiltinImpl(BuiltinImplSource::Misc),
390-
result,
391-
}],
392-
Err(NoSolution) => vec![],
393-
}
394-
}
395-
}));
300+
self.assemble_param_env_candidates(goal, &mut candidates);
301+
302+
self.assemble_coherence_unknowable_candidates(goal, &mut candidates);
303+
304+
candidates
396305
}
397306

398307
#[instrument(level = "debug", skip_all)]
@@ -500,24 +409,6 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
500409
}
501410
}
502411

503-
#[instrument(level = "debug", skip_all)]
504-
fn assemble_unsize_to_dyn_candidate<G: GoalKind<'tcx>>(
505-
&mut self,
506-
goal: Goal<'tcx, G>,
507-
candidates: &mut Vec<Candidate<'tcx>>,
508-
) {
509-
let tcx = self.tcx();
510-
if tcx.lang_items().unsize_trait() == Some(goal.predicate.trait_def_id(tcx)) {
511-
match G::consider_unsize_to_dyn_candidate(self, goal) {
512-
Ok(result) => candidates.push(Candidate {
513-
source: CandidateSource::BuiltinImpl(BuiltinImplSource::Misc),
514-
result,
515-
}),
516-
Err(NoSolution) => (),
517-
}
518-
}
519-
}
520-
521412
#[instrument(level = "debug", skip_all)]
522413
fn assemble_blanket_impl_candidates<G: GoalKind<'tcx>>(
523414
&mut self,

compiler/rustc_trait_selection/src/solve/mod.rs

+3-5
Original file line numberDiff line numberDiff line change
@@ -288,11 +288,9 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
288288

289289
/// Normalize a type when it is structually matched on.
290290
///
291-
/// For self types this is generally already handled through
292-
/// `assemble_candidates_after_normalizing_self_ty`, so anything happening
293-
/// in [`EvalCtxt::assemble_candidates_via_self_ty`] does not have to normalize
294-
/// the self type. It is required when structurally matching on any other
295-
/// arguments of a trait goal, e.g. when assembling builtin unsize candidates.
291+
/// In nearly all cases this function must be used before matching on a type.
292+
/// Not doing so is likely to be incomplete and therefore unsound during
293+
/// coherence.
296294
#[instrument(level = "debug", skip(self), ret)]
297295
fn try_normalize_ty(
298296
&mut self,

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

-7
Original file line numberDiff line numberDiff line change
@@ -603,13 +603,6 @@ impl<'tcx> assembly::GoalKind<'tcx> for NormalizesTo<'tcx> {
603603
)
604604
}
605605

606-
fn consider_unsize_to_dyn_candidate(
607-
_ecx: &mut EvalCtxt<'_, 'tcx>,
608-
goal: Goal<'tcx, Self>,
609-
) -> QueryResult<'tcx> {
610-
bug!("`Unsize` does not have an associated type: {:?}", goal)
611-
}
612-
613606
fn consider_structural_builtin_unsize_candidates(
614607
_ecx: &mut EvalCtxt<'_, 'tcx>,
615608
goal: Goal<'tcx, Self>,

compiler/rustc_trait_selection/src/solve/trait_goals.rs

+41-49
Original file line numberDiff line numberDiff line change
@@ -490,53 +490,6 @@ impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> {
490490
ecx.evaluate_added_goals_and_make_canonical_response(certainty)
491491
}
492492

493-
fn consider_unsize_to_dyn_candidate(
494-
ecx: &mut EvalCtxt<'_, 'tcx>,
495-
goal: Goal<'tcx, Self>,
496-
) -> QueryResult<'tcx> {
497-
ecx.probe(|_| ProbeKind::UnsizeAssembly).enter(|ecx| {
498-
let a_ty = goal.predicate.self_ty();
499-
// We need to normalize the b_ty since it's destructured as a `dyn Trait`.
500-
let Some(b_ty) =
501-
ecx.try_normalize_ty(goal.param_env, goal.predicate.trait_ref.args.type_at(1))
502-
else {
503-
return ecx.evaluate_added_goals_and_make_canonical_response(Certainty::OVERFLOW);
504-
};
505-
506-
let ty::Dynamic(b_data, b_region, ty::Dyn) = *b_ty.kind() else {
507-
return Err(NoSolution);
508-
};
509-
510-
let tcx = ecx.tcx();
511-
512-
// Can only unsize to an object-safe trait.
513-
if b_data.principal_def_id().is_some_and(|def_id| !tcx.check_is_object_safe(def_id)) {
514-
return Err(NoSolution);
515-
}
516-
517-
// Check that the type implements all of the predicates of the trait object.
518-
// (i.e. the principal, all of the associated types match, and any auto traits)
519-
ecx.add_goals(
520-
GoalSource::ImplWhereBound,
521-
b_data.iter().map(|pred| goal.with(tcx, pred.with_self_ty(tcx, a_ty))),
522-
);
523-
524-
// The type must be `Sized` to be unsized.
525-
if let Some(sized_def_id) = tcx.lang_items().sized_trait() {
526-
ecx.add_goal(
527-
GoalSource::ImplWhereBound,
528-
goal.with(tcx, ty::TraitRef::new(tcx, sized_def_id, [a_ty])),
529-
);
530-
} else {
531-
return Err(NoSolution);
532-
}
533-
534-
// The type must outlive the lifetime of the `dyn` we're unsizing into.
535-
ecx.add_goal(GoalSource::Misc, goal.with(tcx, ty::OutlivesPredicate(a_ty, b_region)));
536-
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
537-
})
538-
}
539-
540493
/// ```ignore (builtin impl example)
541494
/// trait Trait {
542495
/// fn foo(&self);
@@ -588,8 +541,11 @@ impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> {
588541
goal, a_data, a_region, b_data, b_region,
589542
),
590543

591-
// `T` -> `dyn Trait` unsizing is handled separately in `consider_unsize_to_dyn_candidate`
592-
(_, &ty::Dynamic(..)) => vec![],
544+
// `T` -> `dyn Trait` unsizing.
545+
(_, &ty::Dynamic(b_region, b_data, ty::Dyn)) => result_to_single(
546+
ecx.consider_builtin_unsize_to_dyn_candidate(goal, b_region, b_data),
547+
BuiltinImplSource::Misc,
548+
),
593549

594550
// `[T; N]` -> `[T]` unsizing
595551
(&ty::Array(a_elem_ty, ..), &ty::Slice(b_elem_ty)) => result_to_single(
@@ -691,6 +647,42 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
691647
responses
692648
}
693649

650+
fn consider_builtin_unsize_to_dyn_candidate(
651+
&mut self,
652+
goal: Goal<'tcx, (Ty<'tcx>, Ty<'tcx>)>,
653+
b_data: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>,
654+
b_region: ty::Region<'tcx>,
655+
) -> QueryResult<'tcx> {
656+
let tcx = self.tcx();
657+
let Goal { predicate: (a_ty, _), .. } = goal;
658+
659+
// Can only unsize to an object-safe trait.
660+
if b_data.principal_def_id().is_some_and(|def_id| !tcx.check_is_object_safe(def_id)) {
661+
return Err(NoSolution);
662+
}
663+
664+
// Check that the type implements all of the predicates of the trait object.
665+
// (i.e. the principal, all of the associated types match, and any auto traits)
666+
self.add_goals(
667+
GoalSource::ImplWhereBound,
668+
b_data.iter().map(|pred| goal.with(tcx, pred.with_self_ty(tcx, a_ty))),
669+
);
670+
671+
// The type must be `Sized` to be unsized.
672+
if let Some(sized_def_id) = tcx.lang_items().sized_trait() {
673+
self.add_goal(
674+
GoalSource::ImplWhereBound,
675+
goal.with(tcx, ty::TraitRef::new(tcx, sized_def_id, [a_ty])),
676+
);
677+
} else {
678+
return Err(NoSolution);
679+
}
680+
681+
// The type must outlive the lifetime of the `dyn` we're unsizing into.
682+
self.add_goal(GoalSource::Misc, goal.with(tcx, ty::OutlivesPredicate(a_ty, b_region)));
683+
self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
684+
}
685+
694686
fn consider_builtin_upcast_to_principal(
695687
&mut self,
696688
goal: Goal<'tcx, (Ty<'tcx>, Ty<'tcx>)>,

tests/ui/traits/next-solver/alias-bound-unsound.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ trait Foo {
1616

1717
impl Foo for () {
1818
type Item = String where String: Copy;
19-
//~^ ERROR overflow evaluating the requirement `<() as Foo>::Item: Copy`
19+
//~^ ERROR overflow evaluating the requirement `String: Copy`
2020
}
2121

2222
fn main() {

tests/ui/traits/next-solver/alias-bound-unsound.stderr

+8-6
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
1-
error[E0275]: overflow evaluating the requirement `<() as Foo>::Item: Copy`
2-
--> $DIR/alias-bound-unsound.rs:18:17
1+
error[E0275]: overflow evaluating the requirement `String: Copy`
2+
--> $DIR/alias-bound-unsound.rs:18:38
33
|
44
LL | type Item = String where String: Copy;
5-
| ^^^^^^
5+
| ^^^^
66
|
77
= help: consider increasing the recursion limit by adding a `#![recursion_limit = "256"]` attribute to your crate (`alias_bound_unsound`)
8-
note: required by a bound in `Foo::Item`
9-
--> $DIR/alias-bound-unsound.rs:8:16
8+
note: the requirement `String: Copy` appears on the `impl`'s associated type `Item` but not on the corresponding trait's associated type
9+
--> $DIR/alias-bound-unsound.rs:8:10
1010
|
11+
LL | trait Foo {
12+
| --- in this trait
1113
LL | type Item: Copy
12-
| ^^^^ required by this bound in `Foo::Item`
14+
| ^^^^ this trait's associated type doesn't have the requirement `String: Copy`
1315

1416
error[E0275]: overflow evaluating the requirement `String <: <() as Foo>::Item`
1517
--> $DIR/alias-bound-unsound.rs:24:31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// check-pass
2+
// compile-flags: -Znext-solver
3+
4+
trait Reader: Default {
5+
fn read_u8_array<A>(&self) -> Result<A, ()> {
6+
todo!()
7+
}
8+
9+
fn read_u8(&self) -> Result<u8, ()> {
10+
let a: [u8; 1] = self.read_u8_array::<_>()?;
11+
// This results in a nested `<Result<?0, ()> as Try>::Residual: Sized` goal.
12+
// The self type normalizes to `?0`. We previously did not force that to be
13+
// ambiguous but instead incompletely applied the `Self: Sized` candidate
14+
// from the `ParamEnv`, resulting in a type error.
15+
Ok(a[0])
16+
}
17+
}
18+
19+
fn main() {}

0 commit comments

Comments
 (0)