Skip to content

Commit 085a48e

Browse files
Implement unsizing in the new trait solver
1 parent 006ca9b commit 085a48e

File tree

4 files changed

+217
-0
lines changed

4 files changed

+217
-0
lines changed

compiler/rustc_trait_selection/src/solve/assembly.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,14 @@ pub(super) trait GoalKind<'tcx>: TypeFoldable<'tcx> + Copy + Eq {
173173
ecx: &mut EvalCtxt<'_, 'tcx>,
174174
goal: Goal<'tcx, Self>,
175175
) -> QueryResult<'tcx>;
176+
177+
// Implement unsizing. The most common forms of unsizing are array to slice,
178+
// and concrete (Sized) type into a `dyn Trait`. ADTs and Tuples can also
179+
// have their final field unsized if it's generic.
180+
fn consider_builtin_unsize_candidate(
181+
ecx: &mut EvalCtxt<'_, 'tcx>,
182+
goal: Goal<'tcx, Self>,
183+
) -> QueryResult<'tcx>;
176184
}
177185

178186
impl<'tcx> EvalCtxt<'_, 'tcx> {
@@ -303,6 +311,8 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
303311
G::consider_builtin_future_candidate(self, goal)
304312
} else if lang_items.gen_trait() == Some(trait_def_id) {
305313
G::consider_builtin_generator_candidate(self, goal)
314+
} else if lang_items.unsize_trait() == Some(trait_def_id) {
315+
G::consider_builtin_unsize_candidate(self, goal)
306316
} else {
307317
Err(NoSolution)
308318
};

compiler/rustc_trait_selection/src/solve/project_goals.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -554,6 +554,13 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> {
554554
.to_predicate(tcx),
555555
)
556556
}
557+
558+
fn consider_builtin_unsize_candidate(
559+
_ecx: &mut EvalCtxt<'_, 'tcx>,
560+
goal: Goal<'tcx, Self>,
561+
) -> QueryResult<'tcx> {
562+
bug!("`Unsize` does not have an associated type: {:?}", goal);
563+
}
557564
}
558565

559566
/// This behavior is also implemented in `rustc_ty_utils` and in the old `project` code.

compiler/rustc_trait_selection/src/solve/trait_goals.rs

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use super::{Certainty, EvalCtxt, Goal, QueryResult};
88
use rustc_hir::def_id::DefId;
99
use rustc_infer::infer::InferCtxt;
1010
use rustc_infer::traits::query::NoSolution;
11+
use rustc_infer::traits::util::supertraits;
1112
use rustc_middle::ty::fast_reject::{DeepRejectCtxt, TreatParams};
1213
use rustc_middle::ty::{self, ToPredicate, Ty, TyCtxt};
1314
use rustc_middle::ty::{TraitPredicate, TypeVisitable};
@@ -238,6 +239,180 @@ impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> {
238239
.to_predicate(tcx),
239240
)
240241
}
242+
243+
fn consider_builtin_unsize_candidate(
244+
ecx: &mut EvalCtxt<'_, 'tcx>,
245+
goal: Goal<'tcx, Self>,
246+
) -> QueryResult<'tcx> {
247+
let tcx = ecx.tcx();
248+
let a_ty = goal.predicate.self_ty();
249+
let b_ty = goal.predicate.trait_ref.substs.type_at(1);
250+
if b_ty.is_ty_var() {
251+
return ecx.make_canonical_response(Certainty::AMBIGUOUS);
252+
}
253+
ecx.infcx.probe(|_| {
254+
match (a_ty.kind(), b_ty.kind()) {
255+
// Trait upcasting, or `dyn Trait + Auto + 'a` -> `dyn Trait + 'b`
256+
(
257+
&ty::Dynamic(a_data, a_region, ty::Dyn),
258+
&ty::Dynamic(b_data, b_region, ty::Dyn),
259+
) => {
260+
// All of a's auto traits need to be in b's auto traits.
261+
let auto_traits_compatible = b_data
262+
.auto_traits()
263+
.all(|b| a_data.auto_traits().any(|a| a == b));
264+
if !auto_traits_compatible {
265+
return Err(NoSolution);
266+
}
267+
268+
// If the principal def ids match (or are both none), then we're not doing
269+
// trait upcasting. We're just removing auto traits (or shortening the lifetime).
270+
if a_data.principal_def_id() == b_data.principal_def_id() {
271+
// Require that all of the trait predicates from A match B, except for
272+
// the auto traits. We do this by constructing a new A type with B's
273+
// auto traits, and equating these types.
274+
let new_a_data = a_data
275+
.iter()
276+
.filter(|a| {
277+
matches!(
278+
a.skip_binder(),
279+
ty::ExistentialPredicate::Trait(_) | ty::ExistentialPredicate::Projection(_)
280+
)
281+
})
282+
.chain(
283+
b_data
284+
.auto_traits()
285+
.map(ty::ExistentialPredicate::AutoTrait)
286+
.map(ty::Binder::dummy),
287+
);
288+
let new_a_data = tcx.mk_poly_existential_predicates(new_a_data);
289+
let new_a_ty = tcx.mk_dynamic(new_a_data, b_region, ty::Dyn);
290+
291+
// We also require that A's lifetime outlives B's lifetime.
292+
let mut nested_obligations = ecx.infcx.eq(goal.param_env, new_a_ty, b_ty)?;
293+
nested_obligations.push(goal.with(tcx, ty::Binder::dummy(ty::OutlivesPredicate(a_region, b_region))));
294+
295+
ecx.evaluate_all_and_make_canonical_response(nested_obligations)
296+
} else if let Some(a_principal) = a_data.principal()
297+
&& let Some(b_principal) = b_data.principal()
298+
&& supertraits(tcx, a_principal.with_self_ty(tcx, a_ty))
299+
.any(|trait_ref| trait_ref.def_id() == b_principal.def_id())
300+
{
301+
// FIXME: Intentionally ignoring `need_migrate_deref_output_trait_object` here for now.
302+
// Confirm upcasting candidate
303+
todo!()
304+
} else {
305+
Err(NoSolution)
306+
}
307+
}
308+
// `T` -> `dyn Trait` unsizing
309+
(_, &ty::Dynamic(data, region, ty::Dyn)) => {
310+
// Can only unsize to an object-safe type
311+
// FIXME: Can auto traits be *not* object safe?
312+
if data
313+
.auto_traits()
314+
.chain(data.principal_def_id())
315+
.any(|def_id| !tcx.is_object_safe(def_id))
316+
{
317+
return Err(NoSolution);
318+
}
319+
320+
let Some(sized_def_id) = tcx.lang_items().sized_trait() else {
321+
return Err(NoSolution);
322+
};
323+
let nested_goals: Vec<_> = data
324+
.iter()
325+
// Check that the type implements all of the predicates of the def-id.
326+
// (i.e. the principal, all of the associated types match, and any auto traits)
327+
.map(|pred| goal.with(tcx, pred.with_self_ty(tcx, a_ty)))
328+
.chain([
329+
// The type must be Sized to be unsized.
330+
goal.with(
331+
tcx,
332+
ty::Binder::dummy(tcx.mk_trait_ref(sized_def_id, [a_ty])),
333+
),
334+
// The type must outlive the lifetime of the `dyn` we're unsizing into.
335+
goal.with(
336+
tcx,
337+
ty::Binder::dummy(ty::OutlivesPredicate(a_ty, region)),
338+
),
339+
])
340+
.collect();
341+
342+
ecx.evaluate_all_and_make_canonical_response(nested_goals)
343+
}
344+
// `[T; n]` -> `[T]` unsizing
345+
(&ty::Array(a_elem_ty, ..), &ty::Slice(b_elem_ty)) => {
346+
// We just require that the element type stays the same
347+
let nested_goals = ecx.infcx.eq(goal.param_env, a_elem_ty, b_elem_ty)?;
348+
ecx.evaluate_all_and_make_canonical_response(nested_goals)
349+
}
350+
// Struct unsizing `Struct<T>` -> `Struct<U>` where `T: Unsize<U>`
351+
(&ty::Adt(a_def, a_substs), &ty::Adt(b_def, b_substs))
352+
if a_def.is_struct() && a_def.did() == b_def.did() =>
353+
{
354+
let unsizing_params = tcx.unsizing_params_for_adt(a_def.did());
355+
// We must be unsizing some type parameters. This also implies
356+
// that the struct has a tail field.
357+
if unsizing_params.is_empty() {
358+
return Err(NoSolution);
359+
}
360+
361+
let tail_field = a_def
362+
.non_enum_variant()
363+
.fields
364+
.last()
365+
.expect("expected unsized ADT to have a tail field");
366+
let tail_field_ty = tcx.bound_type_of(tail_field.did);
367+
368+
let a_tail_ty = tail_field_ty.subst(tcx, a_substs);
369+
let b_tail_ty = tail_field_ty.subst(tcx, b_substs);
370+
371+
// Substitute just the unsizing params from B into A. The type after
372+
// this substitution must be equal to B. This is so we don't unsize
373+
// unrelated type parameters.
374+
let new_a_substs = tcx.mk_substs(a_substs.iter().enumerate().map(|(i, a)| {
375+
if unsizing_params.contains(i as u32) { b_substs[i] } else { a }
376+
}));
377+
let unsized_a_ty = tcx.mk_adt(a_def, new_a_substs);
378+
379+
// Finally, we require that `TailA: Unsize<TailB>` for the tail field
380+
// types.
381+
let mut nested_goals = ecx.infcx.eq(goal.param_env, unsized_a_ty, b_ty)?;
382+
nested_goals.push(goal.with(
383+
tcx,
384+
ty::Binder::dummy(
385+
tcx.mk_trait_ref(goal.predicate.def_id(), [a_tail_ty, b_tail_ty]),
386+
),
387+
));
388+
389+
ecx.evaluate_all_and_make_canonical_response(nested_goals)
390+
}
391+
// Tuple unsizing `(.., T)` -> `(.., U)` where `T: Unsize<U>`
392+
(&ty::Tuple(a_tys), &ty::Tuple(b_tys))
393+
if a_tys.len() == b_tys.len() && !a_tys.is_empty() =>
394+
{
395+
let (a_last_ty, a_rest_tys) = a_tys.split_last().unwrap();
396+
let b_last_ty = b_tys.last().unwrap();
397+
398+
// Substitute just the tail field of B., and require that they're equal.
399+
let unsized_a_ty = tcx.mk_tup(a_rest_tys.iter().chain([b_last_ty]));
400+
let mut nested_goals = ecx.infcx.eq(goal.param_env, unsized_a_ty, b_ty)?;
401+
402+
// Similar to ADTs, require that the rest of the fields are equal.
403+
nested_goals.push(goal.with(
404+
tcx,
405+
ty::Binder::dummy(
406+
tcx.mk_trait_ref(goal.predicate.def_id(), [*a_last_ty, *b_last_ty]),
407+
),
408+
));
409+
410+
ecx.evaluate_all_and_make_canonical_response(nested_goals)
411+
}
412+
_ => Err(NoSolution),
413+
}
414+
})
415+
}
241416
}
242417

243418
impl<'tcx> EvalCtxt<'_, 'tcx> {
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// compile-flags: -Ztrait-solver=next
2+
// check-pass
3+
4+
#![feature(unsized_tuple_coercion)]
5+
6+
trait Foo {}
7+
8+
impl Foo for i32 {}
9+
10+
fn main() {
11+
// Unsizing via struct
12+
let _: Box<dyn Foo> = Box::new(1i32);
13+
14+
// Slice unsizing
15+
let y = [1, 2, 3];
16+
let _: &[i32] = &y;
17+
18+
// Tuple unsizing
19+
let hi = (1i32,);
20+
let _: &(dyn Foo,) = &hi;
21+
22+
// Dropping auto traits
23+
let a: &(dyn Foo + Send) = &1;
24+
let _: &dyn Foo = a;
25+
}

0 commit comments

Comments
 (0)