Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rollup of 8 pull requests #107498

Merged
merged 18 commits into from
Jan 31, 2023
Merged
Changes from 4 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions compiler/rustc_trait_selection/src/solve/assembly.rs
Original file line number Diff line number Diff line change
@@ -173,6 +173,21 @@ pub(super) trait GoalKind<'tcx>: TypeFoldable<'tcx> + Copy + Eq {
ecx: &mut EvalCtxt<'_, 'tcx>,
goal: Goal<'tcx, Self>,
) -> QueryResult<'tcx>;

// The most common forms of unsizing are array to slice, and concrete (Sized)
// type into a `dyn Trait`. ADTs and Tuples can also have their final field
// unsized if it's generic.
fn consider_builtin_unsize_candidate(
ecx: &mut EvalCtxt<'_, 'tcx>,
goal: Goal<'tcx, Self>,
) -> QueryResult<'tcx>;

// `dyn Trait1` can be unsized to `dyn Trait2` if they are the same trait, or
// if `Trait2` is a (transitive) supertrait of `Trait2`.
fn consider_builtin_dyn_upcast_candidates(
ecx: &mut EvalCtxt<'_, 'tcx>,
goal: Goal<'tcx, Self>,
) -> Vec<CanonicalResponse<'tcx>>;
}

impl<'tcx> EvalCtxt<'_, 'tcx> {
@@ -303,6 +318,8 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
G::consider_builtin_future_candidate(self, goal)
} else if lang_items.gen_trait() == Some(trait_def_id) {
G::consider_builtin_generator_candidate(self, goal)
} else if lang_items.unsize_trait() == Some(trait_def_id) {
G::consider_builtin_unsize_candidate(self, goal)
} else {
Err(NoSolution)
};
@@ -313,6 +330,14 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
}
Err(NoSolution) => (),
}

// There may be multiple unsize candidates for a trait with several supertraits:
// `trait Foo: Bar<A> + Bar<B>` and `dyn Foo: Unsize<dyn Bar<_>>`
if lang_items.unsize_trait() == Some(trait_def_id) {
for result in G::consider_builtin_dyn_upcast_candidates(self, goal) {
candidates.push(Candidate { source: CandidateSource::BuiltinImpl, result });
}
}
}

fn assemble_param_env_candidates<G: GoalKind<'tcx>>(
14 changes: 14 additions & 0 deletions compiler/rustc_trait_selection/src/solve/project_goals.rs
Original file line number Diff line number Diff line change
@@ -554,6 +554,20 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> {
.to_predicate(tcx),
)
}

fn consider_builtin_unsize_candidate(
_ecx: &mut EvalCtxt<'_, 'tcx>,
goal: Goal<'tcx, Self>,
) -> QueryResult<'tcx> {
bug!("`Unsize` does not have an associated type: {:?}", goal);
}

fn consider_builtin_dyn_upcast_candidates(
_ecx: &mut EvalCtxt<'_, 'tcx>,
goal: Goal<'tcx, Self>,
) -> Vec<super::CanonicalResponse<'tcx>> {
bug!("`Unsize` does not have an associated type: {:?}", goal);
}
}

/// This behavior is also implemented in `rustc_ty_utils` and in the old `project` code.
203 changes: 202 additions & 1 deletion compiler/rustc_trait_selection/src/solve/trait_goals.rs
Original file line number Diff line number Diff line change
@@ -4,10 +4,11 @@ use std::iter;

use super::assembly::{self, Candidate, CandidateSource};
use super::infcx_ext::InferCtxtExt;
use super::{Certainty, EvalCtxt, Goal, QueryResult};
use super::{CanonicalResponse, Certainty, EvalCtxt, Goal, QueryResult};
use rustc_hir::def_id::DefId;
use rustc_infer::infer::InferCtxt;
use rustc_infer::traits::query::NoSolution;
use rustc_infer::traits::util::supertraits;
use rustc_middle::ty::fast_reject::{DeepRejectCtxt, TreatParams};
use rustc_middle::ty::{self, ToPredicate, Ty, TyCtxt};
use rustc_middle::ty::{TraitPredicate, TypeVisitable};
@@ -238,6 +239,206 @@ impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> {
.to_predicate(tcx),
)
}

fn consider_builtin_unsize_candidate(
ecx: &mut EvalCtxt<'_, 'tcx>,
goal: Goal<'tcx, Self>,
) -> QueryResult<'tcx> {
let tcx = ecx.tcx();
let a_ty = goal.predicate.self_ty();
let b_ty = goal.predicate.trait_ref.substs.type_at(1);
if b_ty.is_ty_var() {
return ecx.make_canonical_response(Certainty::AMBIGUOUS);
}
ecx.infcx.probe(|_| {
match (a_ty.kind(), b_ty.kind()) {
// Trait upcasting, or `dyn Trait + Auto + 'a` -> `dyn Trait + 'b`
(&ty::Dynamic(_, _, ty::Dyn), &ty::Dynamic(_, _, ty::Dyn)) => {
// Dyn upcasting is handled separately, since due to upcasting,
// when there are two supertraits that differ by substs, we
// may return more than one query response.
return Err(NoSolution);
}
// `T` -> `dyn Trait` unsizing
(_, &ty::Dynamic(data, region, ty::Dyn)) => {
// Can only unsize to an object-safe type
if data
.principal_def_id()
.map_or(false, |def_id| !tcx.check_is_object_safe(def_id))
{
return Err(NoSolution);
}

let Some(sized_def_id) = tcx.lang_items().sized_trait() else {
return Err(NoSolution);
};
let nested_goals: Vec<_> = data
.iter()
// Check that the type implements all of the predicates of the def-id.
// (i.e. the principal, all of the associated types match, and any auto traits)
.map(|pred| goal.with(tcx, pred.with_self_ty(tcx, a_ty)))
.chain([
// The type must be Sized to be unsized.
goal.with(
tcx,
ty::Binder::dummy(tcx.mk_trait_ref(sized_def_id, [a_ty])),
),
// The type must outlive the lifetime of the `dyn` we're unsizing into.
goal.with(tcx, ty::Binder::dummy(ty::OutlivesPredicate(a_ty, region))),
])
.collect();

ecx.evaluate_all_and_make_canonical_response(nested_goals)
}
// `[T; n]` -> `[T]` unsizing
(&ty::Array(a_elem_ty, ..), &ty::Slice(b_elem_ty)) => {
// We just require that the element type stays the same
let nested_goals = ecx.infcx.eq(goal.param_env, a_elem_ty, b_elem_ty)?;
ecx.evaluate_all_and_make_canonical_response(nested_goals)
}
// Struct unsizing `Struct<T>` -> `Struct<U>` where `T: Unsize<U>`
(&ty::Adt(a_def, a_substs), &ty::Adt(b_def, b_substs))
if a_def.is_struct() && a_def.did() == b_def.did() =>
{
let unsizing_params = tcx.unsizing_params_for_adt(a_def.did());
// We must be unsizing some type parameters. This also implies
// that the struct has a tail field.
if unsizing_params.is_empty() {
return Err(NoSolution);
}

let tail_field = a_def
.non_enum_variant()
.fields
.last()
.expect("expected unsized ADT to have a tail field");
let tail_field_ty = tcx.bound_type_of(tail_field.did);

let a_tail_ty = tail_field_ty.subst(tcx, a_substs);
let b_tail_ty = tail_field_ty.subst(tcx, b_substs);

// Substitute just the unsizing params from B into A. The type after
// this substitution must be equal to B. This is so we don't unsize
// unrelated type parameters.
let new_a_substs = tcx.mk_substs(a_substs.iter().enumerate().map(|(i, a)| {
if unsizing_params.contains(i as u32) { b_substs[i] } else { a }
}));
let unsized_a_ty = tcx.mk_adt(a_def, new_a_substs);

// Finally, we require that `TailA: Unsize<TailB>` for the tail field
// types.
let mut nested_goals = ecx.infcx.eq(goal.param_env, unsized_a_ty, b_ty)?;
nested_goals.push(goal.with(
tcx,
ty::Binder::dummy(
tcx.mk_trait_ref(goal.predicate.def_id(), [a_tail_ty, b_tail_ty]),
),
));

ecx.evaluate_all_and_make_canonical_response(nested_goals)
}
// Tuple unsizing `(.., T)` -> `(.., U)` where `T: Unsize<U>`
(&ty::Tuple(a_tys), &ty::Tuple(b_tys))
if a_tys.len() == b_tys.len() && !a_tys.is_empty() =>
{
let (a_last_ty, a_rest_tys) = a_tys.split_last().unwrap();
let b_last_ty = b_tys.last().unwrap();

// Substitute just the tail field of B., and require that they're equal.
let unsized_a_ty = tcx.mk_tup(a_rest_tys.iter().chain([b_last_ty]));
let mut nested_goals = ecx.infcx.eq(goal.param_env, unsized_a_ty, b_ty)?;

// Similar to ADTs, require that the rest of the fields are equal.
nested_goals.push(goal.with(
tcx,
ty::Binder::dummy(
tcx.mk_trait_ref(goal.predicate.def_id(), [*a_last_ty, *b_last_ty]),
),
));

ecx.evaluate_all_and_make_canonical_response(nested_goals)
}
_ => Err(NoSolution),
}
})
}

fn consider_builtin_dyn_upcast_candidates(
ecx: &mut EvalCtxt<'_, 'tcx>,
goal: Goal<'tcx, Self>,
) -> Vec<CanonicalResponse<'tcx>> {
let tcx = ecx.tcx();

let a_ty = goal.predicate.self_ty();
let b_ty = goal.predicate.trait_ref.substs.type_at(1);
let ty::Dynamic(a_data, a_region, ty::Dyn) = *a_ty.kind() else {
return vec![];
};
let ty::Dynamic(b_data, b_region, ty::Dyn) = *b_ty.kind() else {
return vec![];
};

// All of a's auto traits need to be in b's auto traits.
let auto_traits_compatible =
b_data.auto_traits().all(|b| a_data.auto_traits().any(|a| a == b));
if !auto_traits_compatible {
return vec![];
}

let mut unsize_dyn_to_principal = |principal: Option<ty::PolyExistentialTraitRef<'tcx>>| {
ecx.infcx.probe(|_| -> Result<_, NoSolution> {
// Require that all of the trait predicates from A match B, except for
// the auto traits. We do this by constructing a new A type with B's
// auto traits, and equating these types.
let new_a_data = principal
.into_iter()
.map(|trait_ref| trait_ref.map_bound(ty::ExistentialPredicate::Trait))
.chain(a_data.iter().filter(|a| {
matches!(a.skip_binder(), ty::ExistentialPredicate::Projection(_))
}))
.chain(
b_data
.auto_traits()
.map(ty::ExistentialPredicate::AutoTrait)
.map(ty::Binder::dummy),
);
let new_a_data = tcx.mk_poly_existential_predicates(new_a_data);
let new_a_ty = tcx.mk_dynamic(new_a_data, b_region, ty::Dyn);

// We also require that A's lifetime outlives B's lifetime.
let mut nested_obligations = ecx.infcx.eq(goal.param_env, new_a_ty, b_ty)?;
nested_obligations.push(
goal.with(tcx, ty::Binder::dummy(ty::OutlivesPredicate(a_region, b_region))),
);

ecx.evaluate_all_and_make_canonical_response(nested_obligations)
})
};

let mut responses = vec![];
// If the principal def ids match (or are both none), then we're not doing
// trait upcasting. We're just removing auto traits (or shortening the lifetime).
if a_data.principal_def_id() == b_data.principal_def_id() {
if let Ok(response) = unsize_dyn_to_principal(a_data.principal()) {
responses.push(response);
}
} else if let Some(a_principal) = a_data.principal()
&& let Some(b_principal) = b_data.principal()
{
for super_trait_ref in supertraits(tcx, a_principal.with_self_ty(tcx, a_ty)) {
if super_trait_ref.def_id() != b_principal.def_id() {
continue;
}
let erased_trait_ref = super_trait_ref
.map_bound(|trait_ref| ty::ExistentialTraitRef::erase_self_ty(tcx, trait_ref));
if let Ok(response) = unsize_dyn_to_principal(Some(erased_trait_ref)) {
responses.push(response);
}
}
}

responses
}
}

impl<'tcx> EvalCtxt<'_, 'tcx> {
4 changes: 0 additions & 4 deletions compiler/rustc_ty_utils/src/ty.rs
Original file line number Diff line number Diff line change
@@ -426,10 +426,6 @@ fn unsizing_params_for_adt<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId) -> BitSet<u32
},
};

// FIXME(eddyb) cache this (including computing `unsizing_params`)
// by putting it in a query; it would only need the `DefId` as it
// looks at declared field types, not anything substituted.

// The last field of the structure has to exist and contain type/const parameters.
let Some((tail_field, prefix_fields)) =
def.non_enum_variant().fields.split_last() else
25 changes: 25 additions & 0 deletions tests/ui/traits/new-solver/unsize-good.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// compile-flags: -Ztrait-solver=next
// check-pass

#![feature(unsized_tuple_coercion)]

trait Foo {}

impl Foo for i32 {}

fn main() {
// Unsizing via struct
let _: Box<dyn Foo> = Box::new(1i32);

// Slice unsizing
let y = [1, 2, 3];
let _: &[i32] = &y;

// Tuple unsizing
let hi = (1i32,);
let _: &(dyn Foo,) = &hi;

// Dropping auto traits
let a: &(dyn Foo + Send) = &1;
let _: &dyn Foo = a;
}
14 changes: 14 additions & 0 deletions tests/ui/traits/new-solver/upcast-right-substs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// compile-flags: -Ztrait-solver=next
// check-pass

#![feature(trait_upcasting)]

trait Foo: Bar<i32> + Bar<u32> {}

trait Bar<T> {}

fn main() {
let x: &dyn Foo = todo!();
let y: &dyn Bar<i32> = x;
let z: &dyn Bar<u32> = x;
}
13 changes: 13 additions & 0 deletions tests/ui/traits/new-solver/upcast-wrong-substs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// compile-flags: -Ztrait-solver=next

#![feature(trait_upcasting)]

trait Foo: Bar<i32> + Bar<u32> {}

trait Bar<T> {}

fn main() {
let x: &dyn Foo = todo!();
let y: &dyn Bar<usize> = x;
//~^ ERROR mismatched types
}
14 changes: 14 additions & 0 deletions tests/ui/traits/new-solver/upcast-wrong-substs.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
error[E0308]: mismatched types
--> $DIR/upcast-wrong-substs.rs:11:30
|
LL | let y: &dyn Bar<usize> = x;
| --------------- ^ expected trait `Bar`, found trait `Foo`
| |
| expected due to this
|
= note: expected reference `&dyn Bar<usize>`
found reference `&dyn Foo`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0308`.