rust-analyzer/crates/hir_ty/src/autoderef.rs

189 lines
6.7 KiB
Rust
Raw Normal View History

//! In certain situations, rust automatically inserts derefs as necessary: for
2019-01-06 18:51:42 +00:00
//! example, field accesses `foo.bar` still work when `foo` is actually a
//! reference to a type with the field `bar`. This is an approximation of the
//! logic in rustc (which lives in librustc_typeck/check/autoderef.rs).
use std::iter::successors;
2019-01-06 18:51:42 +00:00
2020-08-13 14:25:38 +00:00
use base_db::CrateId;
use chalk_ir::{cast::Cast, fold::Fold, interner::HasInterner, VariableKind};
use hir_def::lang_item::LangItemTarget;
2019-12-13 21:01:06 +00:00
use hir_expand::name::name;
use log::{info, warn};
2019-01-06 18:51:42 +00:00
use crate::{
db::HirDatabase, static_lifetime, AliasEq, AliasTy, BoundVar, Canonical, CanonicalVarKinds,
DebruijnIndex, InEnvironment, Interner, ProjectionTyExt, Solution, Substitution, Ty, TyBuilder,
TyKind,
2019-11-25 09:45:45 +00:00
};
2019-06-16 10:04:08 +00:00
const AUTODEREF_RECURSION_LIMIT: usize = 10;
2019-11-27 14:46:02 +00:00
pub fn autoderef<'a>(
db: &'a dyn HirDatabase,
krate: Option<CrateId>,
ty: InEnvironment<Canonical<Ty>>,
) -> impl Iterator<Item = Canonical<Ty>> + 'a {
2021-04-14 12:52:56 +00:00
let _p = profile::span("autoderef");
let InEnvironment { goal: ty, environment } = ty;
successors(Some(ty), move |ty| {
2021-04-14 12:52:56 +00:00
let _p = profile::span("autoderef.step");
deref(db, krate?, InEnvironment { goal: ty, environment: environment.clone() })
})
.take(AUTODEREF_RECURSION_LIMIT)
}
pub(crate) fn deref(
db: &dyn HirDatabase,
krate: CrateId,
ty: InEnvironment<&Canonical<Ty>>,
) -> Option<Canonical<Ty>> {
2021-04-07 11:06:48 +00:00
if let Some(derefed) = builtin_deref(&ty.goal.value) {
Some(Canonical { value: derefed, binders: ty.goal.binders.clone() })
} else {
deref_by_trait(db, krate, ty)
2019-01-06 18:51:42 +00:00
}
}
2021-04-07 11:06:48 +00:00
fn builtin_deref(ty: &Ty) -> Option<Ty> {
match ty.kind(&Interner) {
TyKind::Ref(.., ty) => Some(ty.clone()),
TyKind::Raw(.., ty) => Some(ty.clone()),
_ => None,
}
}
fn deref_by_trait(
db: &dyn HirDatabase,
2019-11-25 09:45:45 +00:00
krate: CrateId,
ty: InEnvironment<&Canonical<Ty>>,
) -> Option<Canonical<Ty>> {
2019-12-20 20:14:30 +00:00
let deref_trait = match db.lang_item(krate, "deref".into())? {
2019-11-26 14:21:29 +00:00
LangItemTarget::TraitId(it) => it,
_ => return None,
};
2019-12-13 21:01:06 +00:00
let target = db.trait_data(deref_trait).associated_type_by_name(&name![Target])?;
2021-04-03 19:56:18 +00:00
let projection = {
let b = TyBuilder::assoc_type_projection(db, target);
if b.remaining() != 1 {
// the Target type + Deref trait should only have one generic parameter,
// namely Deref's Self type
return None;
}
b.push(ty.goal.value.clone()).build()
};
// FIXME make the Canonical / bound var handling nicer
// Check that the type implements Deref at all
2021-04-03 19:56:18 +00:00
let trait_ref = projection.trait_ref(db);
let implements_goal = Canonical {
binders: ty.goal.binders.clone(),
value: InEnvironment {
goal: trait_ref.cast(&Interner),
environment: ty.environment.clone(),
},
};
if db.trait_solve(krate, implements_goal).is_none() {
return None;
}
// Now do the assoc type projection
2021-04-03 19:56:18 +00:00
let alias_eq = AliasEq {
alias: AliasTy::Projection(projection),
ty: TyKind::BoundVar(BoundVar::new(
DebruijnIndex::INNERMOST,
ty.goal.binders.len(&Interner),
))
.intern(&Interner),
};
2021-04-03 19:56:18 +00:00
let in_env = InEnvironment { goal: alias_eq.cast(&Interner), environment: ty.environment };
let canonical = Canonical {
value: in_env,
binders: CanonicalVarKinds::from_iter(
&Interner,
ty.goal.binders.iter(&Interner).cloned().chain(Some(chalk_ir::WithKind::new(
VariableKind::Ty(chalk_ir::TyVariableKind::General),
chalk_ir::UniverseIndex::ROOT,
))),
),
};
2019-12-20 20:14:30 +00:00
let solution = db.trait_solve(krate, canonical)?;
2019-01-06 18:51:42 +00:00
match &solution {
Solution::Unique(vars) => {
// FIXME: vars may contain solutions for any inference variables
// that happened to be inside ty. To correctly handle these, we
// would have to pass the solution up to the inference context, but
// that requires a larger refactoring (especially if the deref
// happens during method resolution). So for the moment, we just
// check that we're not in the situation we're we would actually
// need to handle the values of the additional variables, i.e.
// they're just being 'passed through'. In the 'standard' case where
// we have `impl<T> Deref for Foo<T> { Target = T }`, that should be
// the case.
// FIXME: if the trait solver decides to truncate the type, these
// assumptions will be broken. We would need to properly introduce
// new variables in that case
for i in 1..vars.binders.len(&Interner) {
if vars.value.subst.at(&Interner, i - 1).assert_ty_ref(&Interner).kind(&Interner)
!= &TyKind::BoundVar(BoundVar::new(DebruijnIndex::INNERMOST, i - 1))
{
warn!("complex solution for derefing {:?}: {:?}, ignoring", ty.goal, solution);
return None;
}
}
// FIXME: we remove lifetime variables here since they can confuse
// the method resolution code later
Some(fixup_lifetime_variables(Canonical {
value: vars
.value
.subst
.at(&Interner, vars.value.subst.len(&Interner) - 1)
.assert_ty_ref(&Interner)
.clone(),
binders: vars.binders.clone(),
}))
}
Solution::Ambig(_) => {
info!("Ambiguous solution for derefing {:?}: {:?}", ty.goal, solution);
None
}
2019-01-06 18:51:42 +00:00
}
}
fn fixup_lifetime_variables<T: Fold<Interner, Result = T> + HasInterner<Interner = Interner>>(
c: Canonical<T>,
) -> Canonical<T> {
// Removes lifetime variables from the Canonical, replacing them by static lifetimes.
let mut i = 0;
let subst = Substitution::from_iter(
&Interner,
c.binders.iter(&Interner).map(|vk| match vk.kind {
VariableKind::Ty(_) => {
let index = i;
i += 1;
BoundVar::new(DebruijnIndex::INNERMOST, index).to_ty(&Interner).cast(&Interner)
}
VariableKind::Lifetime => static_lifetime().cast(&Interner),
VariableKind::Const(_) => unimplemented!(),
}),
);
let binders = CanonicalVarKinds::from_iter(
&Interner,
c.binders.iter(&Interner).filter(|vk| match vk.kind {
VariableKind::Ty(_) => true,
VariableKind::Lifetime => false,
VariableKind::Const(_) => true,
}),
);
let value = subst.apply(c.value, &Interner);
Canonical { binders, value }
}