mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-27 05:23:24 +00:00
Merge #3263
3263: Implement unsizing coercions using Chalk r=matklad a=flodiebold These are coercions like `&[T; n] -> &[T]`, which are handled by the `Unsize` and `CoerceUnsized` traits. The impls for `Unsize` are all built in to the compiler and require special handling, so we need to provide them to Chalk. This adds the following `Unsize` impls: - `Unsize<[T]> for [T; _]` - `Unsize<dyn Trait> for T where T: Trait` - `Unsize<dyn SuperTrait> for dyn SubTrait` Hence we are still missing the 'unsizing the last field of a generic struct' case. Co-authored-by: Florian Diebold <florian.diebold@freiheit.com> Co-authored-by: Florian Diebold <flodiebold@gmail.com>
This commit is contained in:
commit
2cbe8a4c4b
10 changed files with 480 additions and 261 deletions
|
@ -206,12 +206,6 @@ struct InferenceContext<'a, D: HirDatabase> {
|
|||
/// closures, but currently this is the only field that will change there,
|
||||
/// so it doesn't make sense.
|
||||
return_ty: Ty,
|
||||
|
||||
/// Impls of `CoerceUnsized` used in coercion.
|
||||
/// (from_ty_ctor, to_ty_ctor) => coerce_generic_index
|
||||
// FIXME: Use trait solver for this.
|
||||
// Chalk seems unable to work well with builtin impl of `Unsize` now.
|
||||
coerce_unsized_map: FxHashMap<(TypeCtor, TypeCtor), usize>,
|
||||
}
|
||||
|
||||
impl<'a, D: HirDatabase> InferenceContext<'a, D> {
|
||||
|
@ -222,7 +216,6 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
|
|||
obligations: Vec::default(),
|
||||
return_ty: Ty::Unknown, // set in collect_fn_signature
|
||||
trait_env: TraitEnvironment::lower(db, &resolver),
|
||||
coerce_unsized_map: Self::init_coerce_unsized_map(db, &resolver),
|
||||
db,
|
||||
owner,
|
||||
body: db.body(owner),
|
||||
|
|
|
@ -4,11 +4,12 @@
|
|||
//!
|
||||
//! See: https://doc.rust-lang.org/nomicon/coercions.html
|
||||
|
||||
use hir_def::{lang_item::LangItemTarget, resolver::Resolver, type_ref::Mutability, AdtId};
|
||||
use rustc_hash::FxHashMap;
|
||||
use hir_def::{lang_item::LangItemTarget, type_ref::Mutability};
|
||||
use test_utils::tested_by;
|
||||
|
||||
use crate::{autoderef, db::HirDatabase, Substs, Ty, TypeCtor, TypeWalk};
|
||||
use crate::{
|
||||
autoderef, db::HirDatabase, traits::Solution, Obligation, Substs, TraitRef, Ty, TypeCtor,
|
||||
};
|
||||
|
||||
use super::{unify::TypeVarValue, InEnvironment, InferTy, InferenceContext};
|
||||
|
||||
|
@ -39,44 +40,6 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
|
|||
}
|
||||
}
|
||||
|
||||
pub(super) fn init_coerce_unsized_map(
|
||||
db: &'a D,
|
||||
resolver: &Resolver,
|
||||
) -> FxHashMap<(TypeCtor, TypeCtor), usize> {
|
||||
let krate = resolver.krate().unwrap();
|
||||
let impls = match db.lang_item(krate, "coerce_unsized".into()) {
|
||||
Some(LangItemTarget::TraitId(trait_)) => db.impls_for_trait(krate, trait_),
|
||||
_ => return FxHashMap::default(),
|
||||
};
|
||||
|
||||
impls
|
||||
.iter()
|
||||
.filter_map(|&impl_id| {
|
||||
let trait_ref = db.impl_trait(impl_id)?;
|
||||
|
||||
// `CoerseUnsized` has one generic parameter for the target type.
|
||||
let cur_from_ty = trait_ref.value.substs.0.get(0)?;
|
||||
let cur_to_ty = trait_ref.value.substs.0.get(1)?;
|
||||
|
||||
match (&cur_from_ty, cur_to_ty) {
|
||||
(ty_app!(ctor1, st1), ty_app!(ctor2, st2)) => {
|
||||
// FIXME: We return the first non-equal bound as the type parameter to coerce to unsized type.
|
||||
// This works for smart-pointer-like coercion, which covers all impls from std.
|
||||
st1.iter().zip(st2.iter()).enumerate().find_map(|(i, (ty1, ty2))| {
|
||||
match (ty1, ty2) {
|
||||
(Ty::Bound(idx1), Ty::Bound(idx2)) if idx1 != idx2 => {
|
||||
Some(((*ctor1, *ctor2), i))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn coerce_inner(&mut self, mut from_ty: Ty, to_ty: &Ty) -> bool {
|
||||
match (&from_ty, to_ty) {
|
||||
// Never type will make type variable to fallback to Never Type instead of Unknown.
|
||||
|
@ -157,154 +120,38 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
|
|||
///
|
||||
/// See: https://doc.rust-lang.org/nightly/std/marker/trait.CoerceUnsized.html
|
||||
fn try_coerce_unsized(&mut self, from_ty: &Ty, to_ty: &Ty) -> Option<bool> {
|
||||
let (ctor1, st1, ctor2, st2) = match (from_ty, to_ty) {
|
||||
(ty_app!(ctor1, st1), ty_app!(ctor2, st2)) => (ctor1, st1, ctor2, st2),
|
||||
let krate = self.resolver.krate().unwrap();
|
||||
let coerce_unsized_trait = match self.db.lang_item(krate, "coerce_unsized".into()) {
|
||||
Some(LangItemTarget::TraitId(trait_)) => trait_,
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
let coerce_generic_index = *self.coerce_unsized_map.get(&(*ctor1, *ctor2))?;
|
||||
|
||||
// Check `Unsize` first
|
||||
match self.check_unsize_and_coerce(
|
||||
st1.0.get(coerce_generic_index)?,
|
||||
st2.0.get(coerce_generic_index)?,
|
||||
0,
|
||||
) {
|
||||
Some(true) => {}
|
||||
ret => return ret,
|
||||
let generic_params = crate::utils::generics(self.db, coerce_unsized_trait.into());
|
||||
if generic_params.len() != 2 {
|
||||
// The CoerceUnsized trait should have two generic params: Self and T.
|
||||
return None;
|
||||
}
|
||||
|
||||
let ret = st1
|
||||
.iter()
|
||||
.zip(st2.iter())
|
||||
.enumerate()
|
||||
.filter(|&(idx, _)| idx != coerce_generic_index)
|
||||
.all(|(_, (ty1, ty2))| self.unify(ty1, ty2));
|
||||
let substs = Substs::build_for_generics(&generic_params)
|
||||
.push(from_ty.clone())
|
||||
.push(to_ty.clone())
|
||||
.build();
|
||||
let trait_ref = TraitRef { trait_: coerce_unsized_trait, substs };
|
||||
let goal = InEnvironment::new(self.trait_env.clone(), Obligation::Trait(trait_ref));
|
||||
|
||||
Some(ret)
|
||||
}
|
||||
let canonicalizer = self.canonicalizer();
|
||||
let canonicalized = canonicalizer.canonicalize_obligation(goal);
|
||||
|
||||
/// Check if `from_ty: Unsize<to_ty>`, and coerce to `to_ty` if it holds.
|
||||
///
|
||||
/// It should not be directly called. It is only used by `try_coerce_unsized`.
|
||||
///
|
||||
/// See: https://doc.rust-lang.org/nightly/std/marker/trait.Unsize.html
|
||||
fn check_unsize_and_coerce(&mut self, from_ty: &Ty, to_ty: &Ty, depth: usize) -> Option<bool> {
|
||||
if depth > 1000 {
|
||||
panic!("Infinite recursion in coercion");
|
||||
}
|
||||
let solution = self.db.trait_solve(krate, canonicalized.value.clone())?;
|
||||
|
||||
match (&from_ty, &to_ty) {
|
||||
// `[T; N]` -> `[T]`
|
||||
(ty_app!(TypeCtor::Array, st1), ty_app!(TypeCtor::Slice, st2)) => {
|
||||
Some(self.unify(&st1[0], &st2[0]))
|
||||
match solution {
|
||||
Solution::Unique(v) => {
|
||||
canonicalized.apply_solution(self, v.0);
|
||||
}
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
// `T` -> `dyn Trait` when `T: Trait`
|
||||
(_, Ty::Dyn(_)) => {
|
||||
// FIXME: Check predicates
|
||||
Some(true)
|
||||
}
|
||||
|
||||
// `(..., T)` -> `(..., U)` when `T: Unsize<U>`
|
||||
(
|
||||
ty_app!(TypeCtor::Tuple { cardinality: len1 }, st1),
|
||||
ty_app!(TypeCtor::Tuple { cardinality: len2 }, st2),
|
||||
) => {
|
||||
if len1 != len2 || *len1 == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
match self.check_unsize_and_coerce(
|
||||
st1.last().unwrap(),
|
||||
st2.last().unwrap(),
|
||||
depth + 1,
|
||||
) {
|
||||
Some(true) => {}
|
||||
ret => return ret,
|
||||
}
|
||||
|
||||
let ret = st1[..st1.len() - 1]
|
||||
.iter()
|
||||
.zip(&st2[..st2.len() - 1])
|
||||
.all(|(ty1, ty2)| self.unify(ty1, ty2));
|
||||
|
||||
Some(ret)
|
||||
}
|
||||
|
||||
// Foo<..., T, ...> is Unsize<Foo<..., U, ...>> if:
|
||||
// - T: Unsize<U>
|
||||
// - Foo is a struct
|
||||
// - Only the last field of Foo has a type involving T
|
||||
// - T is not part of the type of any other fields
|
||||
// - Bar<T>: Unsize<Bar<U>>, if the last field of Foo has type Bar<T>
|
||||
(
|
||||
ty_app!(TypeCtor::Adt(AdtId::StructId(struct1)), st1),
|
||||
ty_app!(TypeCtor::Adt(AdtId::StructId(struct2)), st2),
|
||||
) if struct1 == struct2 => {
|
||||
let field_tys = self.db.field_types((*struct1).into());
|
||||
let struct_data = self.db.struct_data(*struct1);
|
||||
|
||||
let mut fields = struct_data.variant_data.fields().iter();
|
||||
let (last_field_id, _data) = fields.next_back()?;
|
||||
|
||||
// Get the generic parameter involved in the last field.
|
||||
let unsize_generic_index = {
|
||||
let mut index = None;
|
||||
let mut multiple_param = false;
|
||||
field_tys[last_field_id].value.walk(&mut |ty| {
|
||||
if let &Ty::Bound(idx) = ty {
|
||||
if index.is_none() {
|
||||
index = Some(idx);
|
||||
} else if Some(idx) != index {
|
||||
multiple_param = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if multiple_param {
|
||||
return None;
|
||||
}
|
||||
index?
|
||||
};
|
||||
|
||||
// Check other fields do not involve it.
|
||||
let mut multiple_used = false;
|
||||
fields.for_each(|(field_id, _data)| {
|
||||
field_tys[field_id].value.walk(&mut |ty| match ty {
|
||||
&Ty::Bound(idx) if idx == unsize_generic_index => multiple_used = true,
|
||||
_ => {}
|
||||
})
|
||||
});
|
||||
if multiple_used {
|
||||
return None;
|
||||
}
|
||||
|
||||
let unsize_generic_index = unsize_generic_index as usize;
|
||||
|
||||
// Check `Unsize` first
|
||||
match self.check_unsize_and_coerce(
|
||||
st1.get(unsize_generic_index)?,
|
||||
st2.get(unsize_generic_index)?,
|
||||
depth + 1,
|
||||
) {
|
||||
Some(true) => {}
|
||||
ret => return ret,
|
||||
}
|
||||
|
||||
// Then unify other parameters
|
||||
let ret = st1
|
||||
.iter()
|
||||
.zip(st2.iter())
|
||||
.enumerate()
|
||||
.filter(|&(idx, _)| idx != unsize_generic_index)
|
||||
.all(|(_, (ty1, ty2))| self.unify(ty1, ty2));
|
||||
|
||||
Some(ret)
|
||||
}
|
||||
|
||||
_ => None,
|
||||
}
|
||||
Some(true)
|
||||
}
|
||||
|
||||
/// Unify `from_ty` to `to_ty` with optional auto Deref
|
||||
|
|
|
@ -7,10 +7,7 @@ use ena::unify::{InPlaceUnificationTable, NoError, UnifyKey, UnifyValue};
|
|||
use test_utils::tested_by;
|
||||
|
||||
use super::{InferenceContext, Obligation};
|
||||
use crate::{
|
||||
db::HirDatabase, utils::make_mut_slice, Canonical, InEnvironment, InferTy, ProjectionPredicate,
|
||||
ProjectionTy, Substs, TraitRef, Ty, TypeCtor, TypeWalk,
|
||||
};
|
||||
use crate::{db::HirDatabase, Canonical, InEnvironment, InferTy, Substs, Ty, TypeCtor, TypeWalk};
|
||||
|
||||
impl<'a, D: HirDatabase> InferenceContext<'a, D> {
|
||||
pub(super) fn canonicalizer<'b>(&'b mut self) -> Canonicalizer<'a, 'b, D>
|
||||
|
@ -50,42 +47,38 @@ where
|
|||
})
|
||||
}
|
||||
|
||||
fn do_canonicalize_ty(&mut self, ty: Ty) -> Ty {
|
||||
ty.fold(&mut |ty| match ty {
|
||||
Ty::Infer(tv) => {
|
||||
let inner = tv.to_inner();
|
||||
if self.var_stack.contains(&inner) {
|
||||
// recursive type
|
||||
return tv.fallback_value();
|
||||
fn do_canonicalize<T: TypeWalk>(&mut self, t: T, binders: usize) -> T {
|
||||
t.fold_binders(
|
||||
&mut |ty, binders| match ty {
|
||||
Ty::Infer(tv) => {
|
||||
let inner = tv.to_inner();
|
||||
if self.var_stack.contains(&inner) {
|
||||
// recursive type
|
||||
return tv.fallback_value();
|
||||
}
|
||||
if let Some(known_ty) =
|
||||
self.ctx.table.var_unification_table.inlined_probe_value(inner).known()
|
||||
{
|
||||
self.var_stack.push(inner);
|
||||
let result = self.do_canonicalize(known_ty.clone(), binders);
|
||||
self.var_stack.pop();
|
||||
result
|
||||
} else {
|
||||
let root = self.ctx.table.var_unification_table.find(inner);
|
||||
let free_var = match tv {
|
||||
InferTy::TypeVar(_) => InferTy::TypeVar(root),
|
||||
InferTy::IntVar(_) => InferTy::IntVar(root),
|
||||
InferTy::FloatVar(_) => InferTy::FloatVar(root),
|
||||
InferTy::MaybeNeverTypeVar(_) => InferTy::MaybeNeverTypeVar(root),
|
||||
};
|
||||
let position = self.add(free_var);
|
||||
Ty::Bound((position + binders) as u32)
|
||||
}
|
||||
}
|
||||
if let Some(known_ty) =
|
||||
self.ctx.table.var_unification_table.inlined_probe_value(inner).known()
|
||||
{
|
||||
self.var_stack.push(inner);
|
||||
let result = self.do_canonicalize_ty(known_ty.clone());
|
||||
self.var_stack.pop();
|
||||
result
|
||||
} else {
|
||||
let root = self.ctx.table.var_unification_table.find(inner);
|
||||
let free_var = match tv {
|
||||
InferTy::TypeVar(_) => InferTy::TypeVar(root),
|
||||
InferTy::IntVar(_) => InferTy::IntVar(root),
|
||||
InferTy::FloatVar(_) => InferTy::FloatVar(root),
|
||||
InferTy::MaybeNeverTypeVar(_) => InferTy::MaybeNeverTypeVar(root),
|
||||
};
|
||||
let position = self.add(free_var);
|
||||
Ty::Bound(position as u32)
|
||||
}
|
||||
}
|
||||
_ => ty,
|
||||
})
|
||||
}
|
||||
|
||||
fn do_canonicalize_trait_ref(&mut self, mut trait_ref: TraitRef) -> TraitRef {
|
||||
for ty in make_mut_slice(&mut trait_ref.substs.0) {
|
||||
*ty = self.do_canonicalize_ty(ty.clone());
|
||||
}
|
||||
trait_ref
|
||||
_ => ty,
|
||||
},
|
||||
binders,
|
||||
)
|
||||
}
|
||||
|
||||
fn into_canonicalized<T>(self, result: T) -> Canonicalized<T> {
|
||||
|
@ -95,28 +88,8 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
fn do_canonicalize_projection_ty(&mut self, mut projection_ty: ProjectionTy) -> ProjectionTy {
|
||||
for ty in make_mut_slice(&mut projection_ty.parameters.0) {
|
||||
*ty = self.do_canonicalize_ty(ty.clone());
|
||||
}
|
||||
projection_ty
|
||||
}
|
||||
|
||||
fn do_canonicalize_projection_predicate(
|
||||
&mut self,
|
||||
projection: ProjectionPredicate,
|
||||
) -> ProjectionPredicate {
|
||||
let ty = self.do_canonicalize_ty(projection.ty);
|
||||
let projection_ty = self.do_canonicalize_projection_ty(projection.projection_ty);
|
||||
|
||||
ProjectionPredicate { ty, projection_ty }
|
||||
}
|
||||
|
||||
// FIXME: add some point, we need to introduce a `Fold` trait that abstracts
|
||||
// over all the things that can be canonicalized (like Chalk and rustc have)
|
||||
|
||||
pub(crate) fn canonicalize_ty(mut self, ty: Ty) -> Canonicalized<Ty> {
|
||||
let result = self.do_canonicalize_ty(ty);
|
||||
let result = self.do_canonicalize(ty, 0);
|
||||
self.into_canonicalized(result)
|
||||
}
|
||||
|
||||
|
@ -125,10 +98,8 @@ where
|
|||
obligation: InEnvironment<Obligation>,
|
||||
) -> Canonicalized<InEnvironment<Obligation>> {
|
||||
let result = match obligation.value {
|
||||
Obligation::Trait(tr) => Obligation::Trait(self.do_canonicalize_trait_ref(tr)),
|
||||
Obligation::Projection(pr) => {
|
||||
Obligation::Projection(self.do_canonicalize_projection_predicate(pr))
|
||||
}
|
||||
Obligation::Trait(tr) => Obligation::Trait(self.do_canonicalize(tr, 0)),
|
||||
Obligation::Projection(pr) => Obligation::Projection(self.do_canonicalize(pr, 0)),
|
||||
};
|
||||
self.into_canonicalized(InEnvironment {
|
||||
value: result,
|
||||
|
|
|
@ -461,6 +461,12 @@ impl<T> Binders<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T: Clone> Binders<&T> {
|
||||
pub fn cloned(&self) -> Binders<T> {
|
||||
Binders { num_binders: self.num_binders, value: self.value.clone() }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: TypeWalk> Binders<T> {
|
||||
/// Substitutes all variables.
|
||||
pub fn subst(self, subst: &Substs) -> T {
|
||||
|
@ -661,6 +667,17 @@ impl Ty {
|
|||
}
|
||||
}
|
||||
|
||||
/// If this is a `dyn Trait` type, this returns the `Trait` part.
|
||||
pub fn dyn_trait_ref(&self) -> Option<&TraitRef> {
|
||||
match self {
|
||||
Ty::Dyn(bounds) => bounds.get(0).and_then(|b| match b {
|
||||
GenericPredicate::Implemented(trait_ref) => Some(trait_ref),
|
||||
_ => None,
|
||||
}),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn builtin_deref(&self) -> Option<Ty> {
|
||||
match self {
|
||||
Ty::Apply(a_ty) => match a_ty.ctor {
|
||||
|
@ -746,6 +763,20 @@ pub trait TypeWalk {
|
|||
/// variable for the self type.
|
||||
fn walk_mut_binders(&mut self, f: &mut impl FnMut(&mut Ty, usize), binders: usize);
|
||||
|
||||
fn fold_binders(mut self, f: &mut impl FnMut(Ty, usize) -> Ty, binders: usize) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.walk_mut_binders(
|
||||
&mut |ty_mut, binders| {
|
||||
let ty = mem::replace(ty_mut, Ty::Unknown);
|
||||
*ty_mut = f(ty, binders);
|
||||
},
|
||||
binders,
|
||||
);
|
||||
self
|
||||
}
|
||||
|
||||
fn fold(mut self, f: &mut impl FnMut(Ty) -> Ty) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
|
@ -783,13 +814,16 @@ pub trait TypeWalk {
|
|||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.fold(&mut |ty| match ty {
|
||||
Ty::Bound(idx) => {
|
||||
assert!(idx as i32 >= -n);
|
||||
Ty::Bound((idx as i32 + n) as u32)
|
||||
}
|
||||
ty => ty,
|
||||
})
|
||||
self.fold_binders(
|
||||
&mut |ty, binders| match ty {
|
||||
Ty::Bound(idx) if idx as usize >= binders => {
|
||||
assert!(idx as i32 >= -n);
|
||||
Ty::Bound((idx as i32 + n) as u32)
|
||||
}
|
||||
ty => ty,
|
||||
},
|
||||
0,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -239,7 +239,10 @@ impl Ty {
|
|||
) -> Ty {
|
||||
let ty = match resolution {
|
||||
TypeNs::TraitId(trait_) => {
|
||||
let trait_ref = TraitRef::from_resolved_path(ctx, trait_, resolved_segment, None);
|
||||
// if this is a bare dyn Trait, we'll directly put the required ^0 for the self type in there
|
||||
let self_ty = if remaining_segments.len() == 0 { Some(Ty::Bound(0)) } else { None };
|
||||
let trait_ref =
|
||||
TraitRef::from_resolved_path(ctx, trait_, resolved_segment, self_ty);
|
||||
return if remaining_segments.len() == 1 {
|
||||
let segment = remaining_segments.first().unwrap();
|
||||
let associated_ty = associated_type_by_name_including_super_traits(
|
||||
|
|
|
@ -548,3 +548,141 @@ impl<TT> S<TT> {
|
|||
"###
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn coerce_unsize_array() {
|
||||
assert_snapshot!(
|
||||
infer_with_mismatches(r#"
|
||||
#[lang = "unsize"]
|
||||
pub trait Unsize<T> {}
|
||||
#[lang = "coerce_unsized"]
|
||||
pub trait CoerceUnsized<T> {}
|
||||
|
||||
impl<T: Unsize<U>, U> CoerceUnsized<&U> for &T {}
|
||||
|
||||
fn test() {
|
||||
let f: &[usize] = &[1, 2, 3];
|
||||
}
|
||||
"#, true),
|
||||
@r###"
|
||||
[162; 199) '{ ... 3]; }': ()
|
||||
[172; 173) 'f': &[usize]
|
||||
[186; 196) '&[1, 2, 3]': &[usize; _]
|
||||
[187; 196) '[1, 2, 3]': [usize; _]
|
||||
[188; 189) '1': usize
|
||||
[191; 192) '2': usize
|
||||
[194; 195) '3': usize
|
||||
"###
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn coerce_unsize_trait_object() {
|
||||
assert_snapshot!(
|
||||
infer_with_mismatches(r#"
|
||||
#[lang = "unsize"]
|
||||
pub trait Unsize<T> {}
|
||||
#[lang = "coerce_unsized"]
|
||||
pub trait CoerceUnsized<T> {}
|
||||
|
||||
impl<T: Unsize<U>, U> CoerceUnsized<&U> for &T {}
|
||||
|
||||
trait Foo<T, U> {}
|
||||
trait Bar<U, T, X>: Foo<T, U> {}
|
||||
trait Baz<T, X>: Bar<usize, T, X> {}
|
||||
|
||||
struct S<T, X>;
|
||||
impl<T, X> Foo<T, usize> for S<T, X> {}
|
||||
impl<T, X> Bar<usize, T, X> for S<T, X> {}
|
||||
impl<T, X> Baz<T, X> for S<T, X> {}
|
||||
|
||||
fn test() {
|
||||
let obj: &dyn Baz<i8, i16> = &S;
|
||||
let obj: &dyn Bar<_, _, _> = obj;
|
||||
let obj: &dyn Foo<_, _> = obj;
|
||||
let obj2: &dyn Baz<i8, i16> = &S;
|
||||
let _: &dyn Foo<_, _> = obj2;
|
||||
}
|
||||
"#, true),
|
||||
@r###"
|
||||
[388; 573) '{ ...bj2; }': ()
|
||||
[398; 401) 'obj': &dyn Baz<i8, i16>
|
||||
[423; 425) '&S': &S<i8, i16>
|
||||
[424; 425) 'S': S<i8, i16>
|
||||
[435; 438) 'obj': &dyn Bar<usize, i8, i16>
|
||||
[460; 463) 'obj': &dyn Baz<i8, i16>
|
||||
[473; 476) 'obj': &dyn Foo<i8, usize>
|
||||
[495; 498) 'obj': &dyn Bar<usize, i8, i16>
|
||||
[508; 512) 'obj2': &dyn Baz<i8, i16>
|
||||
[534; 536) '&S': &S<i8, i16>
|
||||
[535; 536) 'S': S<i8, i16>
|
||||
[546; 547) '_': &dyn Foo<i8, usize>
|
||||
[566; 570) 'obj2': &dyn Baz<i8, i16>
|
||||
"###
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn coerce_unsize_super_trait_cycle() {
|
||||
assert_snapshot!(
|
||||
infer_with_mismatches(r#"
|
||||
#[lang = "unsize"]
|
||||
pub trait Unsize<T> {}
|
||||
#[lang = "coerce_unsized"]
|
||||
pub trait CoerceUnsized<T> {}
|
||||
|
||||
impl<T: Unsize<U>, U> CoerceUnsized<&U> for &T {}
|
||||
|
||||
trait A {}
|
||||
trait B: C + A {}
|
||||
trait C: B {}
|
||||
trait D: C
|
||||
|
||||
struct S;
|
||||
impl A for S {}
|
||||
impl B for S {}
|
||||
impl C for S {}
|
||||
impl D for S {}
|
||||
|
||||
fn test() {
|
||||
let obj: &dyn D = &S;
|
||||
let obj: &dyn A = obj;
|
||||
}
|
||||
"#, true),
|
||||
@r###"
|
||||
[292; 348) '{ ...obj; }': ()
|
||||
[302; 305) 'obj': &dyn D
|
||||
[316; 318) '&S': &S
|
||||
[317; 318) 'S': S
|
||||
[328; 331) 'obj': &dyn A
|
||||
[342; 345) 'obj': &dyn D
|
||||
"###
|
||||
);
|
||||
}
|
||||
|
||||
#[ignore]
|
||||
#[test]
|
||||
fn coerce_unsize_generic() {
|
||||
// FIXME: Implement this
|
||||
// https://doc.rust-lang.org/reference/type-coercions.html#unsized-coercions
|
||||
assert_snapshot!(
|
||||
infer_with_mismatches(r#"
|
||||
#[lang = "unsize"]
|
||||
pub trait Unsize<T> {}
|
||||
#[lang = "coerce_unsized"]
|
||||
pub trait CoerceUnsized<T> {}
|
||||
|
||||
impl<T: Unsize<U>, U> CoerceUnsized<&U> for &T {}
|
||||
|
||||
struct Foo<T> { t: T };
|
||||
struct Bar<T>(Foo<T>);
|
||||
|
||||
fn test() {
|
||||
let _: &Foo<[usize]> = &Foo { t: [1, 2, 3] };
|
||||
let _: &Bar<[usize]> = &Bar(Foo { t: [1, 2, 3] });
|
||||
}
|
||||
"#, true),
|
||||
@r###"
|
||||
"###
|
||||
);
|
||||
}
|
||||
|
|
|
@ -335,6 +335,12 @@ pub struct ClosureFnTraitImplData {
|
|||
fn_trait: FnTrait,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct UnsizeToSuperTraitObjectData {
|
||||
trait_: TraitId,
|
||||
super_trait: TraitId,
|
||||
}
|
||||
|
||||
/// An impl. Usually this comes from an impl block, but some built-in types get
|
||||
/// synthetic impls.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
|
@ -343,6 +349,12 @@ pub enum Impl {
|
|||
ImplBlock(ImplId),
|
||||
/// Closure types implement the Fn traits synthetically.
|
||||
ClosureFnTraitImpl(ClosureFnTraitImplData),
|
||||
/// [T; n]: Unsize<[T]>
|
||||
UnsizeArray,
|
||||
/// T: Unsize<dyn Trait> where T: Trait
|
||||
UnsizeToTraitObject(TraitId),
|
||||
/// dyn Trait: Unsize<dyn SuperTrait> if Trait: SuperTrait
|
||||
UnsizeToSuperTraitObject(UnsizeToSuperTraitObjectData),
|
||||
}
|
||||
/// This exists just for Chalk, because our ImplIds are only unique per module.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
|
|
|
@ -4,8 +4,12 @@ use hir_def::{expr::Expr, lang_item::LangItemTarget, TraitId, TypeAliasId};
|
|||
use hir_expand::name::name;
|
||||
use ra_db::CrateId;
|
||||
|
||||
use super::{AssocTyValue, Impl};
|
||||
use crate::{db::HirDatabase, ApplicationTy, Substs, TraitRef, Ty, TypeCtor};
|
||||
use super::{AssocTyValue, Impl, UnsizeToSuperTraitObjectData};
|
||||
use crate::{
|
||||
db::HirDatabase,
|
||||
utils::{all_super_traits, generics},
|
||||
ApplicationTy, Binders, GenericPredicate, Substs, TraitRef, Ty, TypeCtor,
|
||||
};
|
||||
|
||||
pub(super) struct BuiltinImplData {
|
||||
pub num_vars: usize,
|
||||
|
@ -25,6 +29,8 @@ pub(super) fn get_builtin_impls(
|
|||
db: &impl HirDatabase,
|
||||
krate: CrateId,
|
||||
ty: &Ty,
|
||||
// The first argument for the trait, if present
|
||||
arg: &Option<Ty>,
|
||||
trait_: TraitId,
|
||||
mut callback: impl FnMut(Impl),
|
||||
) {
|
||||
|
@ -43,12 +49,60 @@ pub(super) fn get_builtin_impls(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
let unsize_trait = get_unsize_trait(db, krate);
|
||||
if let Some(actual_trait) = unsize_trait {
|
||||
if trait_ == actual_trait {
|
||||
get_builtin_unsize_impls(db, krate, ty, arg, callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_builtin_unsize_impls(
|
||||
db: &impl HirDatabase,
|
||||
krate: CrateId,
|
||||
ty: &Ty,
|
||||
// The first argument for the trait, if present
|
||||
arg: &Option<Ty>,
|
||||
mut callback: impl FnMut(Impl),
|
||||
) {
|
||||
if !check_unsize_impl_prerequisites(db, krate) {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Ty::Apply(ApplicationTy { ctor: TypeCtor::Array, .. }) = ty {
|
||||
callback(Impl::UnsizeArray);
|
||||
return; // array is unsized, the rest of the impls shouldn't apply
|
||||
}
|
||||
|
||||
if let Some(target_trait) = arg.as_ref().and_then(|t| t.dyn_trait_ref()) {
|
||||
// FIXME what about more complicated dyn tys with marker traits?
|
||||
if let Some(trait_ref) = ty.dyn_trait_ref() {
|
||||
if trait_ref.trait_ != target_trait.trait_ {
|
||||
let super_traits = all_super_traits(db, trait_ref.trait_);
|
||||
if super_traits.contains(&target_trait.trait_) {
|
||||
callback(Impl::UnsizeToSuperTraitObject(UnsizeToSuperTraitObjectData {
|
||||
trait_: trait_ref.trait_,
|
||||
super_trait: target_trait.trait_,
|
||||
}));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// FIXME only for sized types
|
||||
callback(Impl::UnsizeToTraitObject(target_trait.trait_));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn impl_datum(db: &impl HirDatabase, krate: CrateId, impl_: Impl) -> BuiltinImplData {
|
||||
match impl_ {
|
||||
Impl::ImplBlock(_) => unreachable!(),
|
||||
Impl::ClosureFnTraitImpl(data) => closure_fn_trait_impl_datum(db, krate, data),
|
||||
Impl::UnsizeArray => array_unsize_impl_datum(db, krate),
|
||||
Impl::UnsizeToTraitObject(trait_) => trait_object_unsize_impl_datum(db, krate, trait_),
|
||||
Impl::UnsizeToSuperTraitObject(data) => {
|
||||
super_trait_object_unsize_impl_datum(db, krate, data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -65,6 +119,8 @@ pub(super) fn associated_ty_value(
|
|||
}
|
||||
}
|
||||
|
||||
// Closure Fn trait impls
|
||||
|
||||
fn check_closure_fn_trait_impl_prerequisites(
|
||||
db: &impl HirDatabase,
|
||||
krate: CrateId,
|
||||
|
@ -165,6 +221,129 @@ fn closure_fn_trait_output_assoc_ty_value(
|
|||
}
|
||||
}
|
||||
|
||||
// Array unsizing
|
||||
|
||||
fn check_unsize_impl_prerequisites(db: &impl HirDatabase, krate: CrateId) -> bool {
|
||||
// the Unsize trait needs to exist and have two type parameters (Self and T)
|
||||
let unsize_trait = match get_unsize_trait(db, krate) {
|
||||
Some(t) => t,
|
||||
None => return false,
|
||||
};
|
||||
let generic_params = generics(db, unsize_trait.into());
|
||||
generic_params.len() == 2
|
||||
}
|
||||
|
||||
fn array_unsize_impl_datum(db: &impl HirDatabase, krate: CrateId) -> BuiltinImplData {
|
||||
// impl<T> Unsize<[T]> for [T; _]
|
||||
// (this can be a single impl because we don't distinguish array sizes currently)
|
||||
|
||||
let trait_ = get_unsize_trait(db, krate) // get unsize trait
|
||||
// the existence of the Unsize trait has been checked before
|
||||
.expect("Unsize trait missing");
|
||||
|
||||
let var = Ty::Bound(0);
|
||||
let substs = Substs::builder(2)
|
||||
.push(Ty::apply_one(TypeCtor::Array, var.clone()))
|
||||
.push(Ty::apply_one(TypeCtor::Slice, var))
|
||||
.build();
|
||||
|
||||
let trait_ref = TraitRef { trait_, substs };
|
||||
|
||||
BuiltinImplData {
|
||||
num_vars: 1,
|
||||
trait_ref,
|
||||
where_clauses: Vec::new(),
|
||||
assoc_ty_values: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
// Trait object unsizing
|
||||
|
||||
fn trait_object_unsize_impl_datum(
|
||||
db: &impl HirDatabase,
|
||||
krate: CrateId,
|
||||
trait_: TraitId,
|
||||
) -> BuiltinImplData {
|
||||
// impl<T, T1, ...> Unsize<dyn Trait<T1, ...>> for T where T: Trait<T1, ...>
|
||||
|
||||
let unsize_trait = get_unsize_trait(db, krate) // get unsize trait
|
||||
// the existence of the Unsize trait has been checked before
|
||||
.expect("Unsize trait missing");
|
||||
|
||||
let self_ty = Ty::Bound(0);
|
||||
|
||||
let target_substs = Substs::build_for_def(db, trait_)
|
||||
.push(Ty::Bound(0))
|
||||
// starting from ^2 because we want to start with ^1 outside of the
|
||||
// `dyn`, which is ^2 inside
|
||||
.fill_with_bound_vars(2)
|
||||
.build();
|
||||
let num_vars = target_substs.len();
|
||||
let target_trait_ref = TraitRef { trait_, substs: target_substs };
|
||||
let target_bounds = vec![GenericPredicate::Implemented(target_trait_ref)];
|
||||
|
||||
let self_substs = Substs::build_for_def(db, trait_).fill_with_bound_vars(0).build();
|
||||
let self_trait_ref = TraitRef { trait_, substs: self_substs };
|
||||
let where_clauses = vec![GenericPredicate::Implemented(self_trait_ref)];
|
||||
|
||||
let impl_substs =
|
||||
Substs::builder(2).push(self_ty).push(Ty::Dyn(target_bounds.clone().into())).build();
|
||||
|
||||
let trait_ref = TraitRef { trait_: unsize_trait, substs: impl_substs };
|
||||
|
||||
BuiltinImplData { num_vars, trait_ref, where_clauses, assoc_ty_values: Vec::new() }
|
||||
}
|
||||
|
||||
fn super_trait_object_unsize_impl_datum(
|
||||
db: &impl HirDatabase,
|
||||
krate: CrateId,
|
||||
data: UnsizeToSuperTraitObjectData,
|
||||
) -> BuiltinImplData {
|
||||
// impl<T1, ...> Unsize<dyn SuperTrait> for dyn Trait<T1, ...>
|
||||
|
||||
let unsize_trait = get_unsize_trait(db, krate) // get unsize trait
|
||||
// the existence of the Unsize trait has been checked before
|
||||
.expect("Unsize trait missing");
|
||||
|
||||
let self_substs = Substs::build_for_def(db, data.trait_).fill_with_bound_vars(0).build();
|
||||
|
||||
let num_vars = self_substs.len() - 1;
|
||||
|
||||
let self_trait_ref = TraitRef { trait_: data.trait_, substs: self_substs.clone() };
|
||||
let self_bounds = vec![GenericPredicate::Implemented(self_trait_ref.clone())];
|
||||
|
||||
// we need to go from our trait to the super trait, substituting type parameters
|
||||
let path = crate::utils::find_super_trait_path(db, data.trait_, data.super_trait);
|
||||
|
||||
let mut current_trait_ref = self_trait_ref;
|
||||
for t in path.into_iter().skip(1) {
|
||||
let bounds = db.generic_predicates(current_trait_ref.trait_.into());
|
||||
let super_trait_ref = bounds
|
||||
.iter()
|
||||
.find_map(|b| match &b.value {
|
||||
GenericPredicate::Implemented(tr)
|
||||
if tr.trait_ == t && tr.substs[0] == Ty::Bound(0) =>
|
||||
{
|
||||
Some(Binders { value: tr, num_binders: b.num_binders })
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.expect("trait bound for known super trait not found");
|
||||
current_trait_ref = super_trait_ref.cloned().subst(¤t_trait_ref.substs);
|
||||
}
|
||||
|
||||
let super_bounds = vec![GenericPredicate::Implemented(current_trait_ref)];
|
||||
|
||||
let substs = Substs::builder(2)
|
||||
.push(Ty::Dyn(self_bounds.into()))
|
||||
.push(Ty::Dyn(super_bounds.into()))
|
||||
.build();
|
||||
|
||||
let trait_ref = TraitRef { trait_: unsize_trait, substs };
|
||||
|
||||
BuiltinImplData { num_vars, trait_ref, where_clauses: Vec::new(), assoc_ty_values: Vec::new() }
|
||||
}
|
||||
|
||||
fn get_fn_trait(
|
||||
db: &impl HirDatabase,
|
||||
krate: CrateId,
|
||||
|
@ -176,3 +355,11 @@ fn get_fn_trait(
|
|||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_unsize_trait(db: &impl HirDatabase, krate: CrateId) -> Option<TraitId> {
|
||||
let target = db.lang_item(krate, "unsize".into())?;
|
||||
match target {
|
||||
LangItemTarget::TraitId(t) => Some(t),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -572,8 +572,10 @@ where
|
|||
.collect();
|
||||
|
||||
let ty: Ty = from_chalk(self.db, parameters[0].assert_ty_ref().clone());
|
||||
let arg: Option<Ty> =
|
||||
parameters.get(1).map(|p| from_chalk(self.db, p.assert_ty_ref().clone()));
|
||||
|
||||
builtin::get_builtin_impls(self.db, self.krate, &ty, trait_, |i| {
|
||||
builtin::get_builtin_impls(self.db, self.krate, &ty, &arg, trait_, |i| {
|
||||
result.push(i.to_chalk(self.db))
|
||||
});
|
||||
|
||||
|
|
|
@ -62,6 +62,38 @@ pub(super) fn all_super_traits(db: &impl DefDatabase, trait_: TraitId) -> Vec<Tr
|
|||
result
|
||||
}
|
||||
|
||||
/// Finds a path from a trait to one of its super traits. Returns an empty
|
||||
/// vector if there is no path.
|
||||
pub(super) fn find_super_trait_path(
|
||||
db: &impl DefDatabase,
|
||||
trait_: TraitId,
|
||||
super_trait: TraitId,
|
||||
) -> Vec<TraitId> {
|
||||
let mut result = Vec::with_capacity(2);
|
||||
result.push(trait_);
|
||||
return if go(db, super_trait, &mut result) { result } else { Vec::new() };
|
||||
|
||||
fn go(db: &impl DefDatabase, super_trait: TraitId, path: &mut Vec<TraitId>) -> bool {
|
||||
let trait_ = *path.last().unwrap();
|
||||
if trait_ == super_trait {
|
||||
return true;
|
||||
}
|
||||
|
||||
for tt in direct_super_traits(db, trait_) {
|
||||
if path.contains(&tt) {
|
||||
continue;
|
||||
}
|
||||
path.push(tt);
|
||||
if go(db, super_trait, path) {
|
||||
return true;
|
||||
} else {
|
||||
path.pop();
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn associated_type_by_name_including_super_traits(
|
||||
db: &impl DefDatabase,
|
||||
trait_: TraitId,
|
||||
|
|
Loading…
Reference in a new issue