Skip to content

Associated type basics & Deref support #1408

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

Merged
merged 9 commits into from
Jun 16, 2019
Merged
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion crates/ra_assists/src/fill_match_arms.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ pub(crate) fn fill_match_arms(mut ctx: AssistCtx<impl HirDatabase>) -> Option<As
let expr = match_expr.expr()?;
let analyzer = hir::SourceAnalyzer::new(ctx.db, ctx.frange.file_id, expr.syntax(), None);
let match_expr_ty = analyzer.type_of(ctx.db, expr)?;
let enum_def = match_expr_ty.autoderef(ctx.db).find_map(|ty| match ty.as_adt() {
let enum_def = analyzer.autoderef(ctx.db, match_expr_ty).find_map(|ty| match ty.as_adt() {
Some((AdtDef::Enum(e), _)) => Some(e),
_ => None,
})?;
Expand Down
1 change: 1 addition & 0 deletions crates/ra_hir/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ ra_prof = { path = "../ra_prof" }
chalk-solve = { git = "https://github.com/flodiebold/chalk.git", branch = "fuel" }
chalk-rust-ir = { git = "https://github.com/flodiebold/chalk.git", branch = "fuel" }
chalk-ir = { git = "https://github.com/flodiebold/chalk.git", branch = "fuel" }
lalrpop-intern = "0.15.1"

[dev-dependencies]
flexi_logger = "0.11.0"
Expand Down
20 changes: 18 additions & 2 deletions crates/ra_hir/src/code_model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -779,6 +779,18 @@ impl Trait {
self.trait_data(db).items().to_vec()
}

pub fn associated_type_by_name(self, db: &impl DefDatabase, name: Name) -> Option<TypeAlias> {
let trait_data = self.trait_data(db);
trait_data
.items()
.iter()
.filter_map(|item| match item {
TraitItem::TypeAlias(t) => Some(*t),
_ => None,
})
.find(|t| t.name(db) == name)
}

pub(crate) fn trait_data(self, db: &impl DefDatabase) -> Arc<TraitData> {
db.trait_data(self)
}
Expand Down Expand Up @@ -831,8 +843,12 @@ impl TypeAlias {
}
}

pub fn type_ref(self, db: &impl DefDatabase) -> Arc<TypeRef> {
db.type_alias_ref(self)
pub fn type_ref(self, db: &impl DefDatabase) -> Option<TypeRef> {
db.type_alias_data(self).type_ref.clone()
}

pub fn name(self, db: &impl DefDatabase) -> Name {
db.type_alias_data(self).name.clone()
}

/// Builds a resolver for the type references in this type alias.
Expand Down
14 changes: 10 additions & 4 deletions crates/ra_hir/src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,8 @@ use crate::{
adt::{StructData, EnumData},
impl_block::{ModuleImplBlocks, ImplSourceMap, ImplBlock},
generics::{GenericParams, GenericDef},
type_ref::TypeRef,
traits::TraitData,
lang_item::{LangItems, LangItemTarget},
lang_item::{LangItems, LangItemTarget}, type_alias::TypeAliasData,
};

// This database has access to source code, so queries here are not really
Expand Down Expand Up @@ -113,8 +112,8 @@ pub trait DefDatabase: SourceDatabase {
#[salsa::invoke(crate::FnSignature::fn_signature_query)]
fn fn_signature(&self, func: Function) -> Arc<FnSignature>;

#[salsa::invoke(crate::type_alias::type_alias_ref_query)]
fn type_alias_ref(&self, typ: TypeAlias) -> Arc<TypeRef>;
#[salsa::invoke(crate::type_alias::type_alias_data_query)]
fn type_alias_data(&self, typ: TypeAlias) -> Arc<TypeAliasData>;

#[salsa::invoke(crate::ConstSignature::const_signature_query)]
fn const_signature(&self, konst: Const) -> Arc<ConstSignature>;
Expand Down Expand Up @@ -185,6 +184,13 @@ pub trait HirDatabase: DefDatabase + AstDatabase {
krate: Crate,
goal: crate::ty::Canonical<crate::ty::TraitRef>,
) -> Option<crate::ty::traits::Solution>;

#[salsa::invoke(crate::ty::traits::normalize_query)]
fn normalize(
&self,
krate: Crate,
goal: crate::ty::Canonical<crate::ty::ProjectionPredicate>,
) -> Option<crate::ty::traits::Solution>;
}

#[test]
Expand Down
49 changes: 39 additions & 10 deletions crates/ra_hir/src/lang_item.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use std::sync::Arc;
use rustc_hash::FxHashMap;

use ra_syntax::{SmolStr, ast::AttrsOwner};
use ra_syntax::{SmolStr, TreeArc, ast::AttrsOwner};

use crate::{
Crate, DefDatabase, Enum, Function, HirDatabase, ImplBlock, Module, Static, Struct, Trait, AstDatabase,
Crate, DefDatabase, Enum, Function, HirDatabase, ImplBlock, Module,
Static, Struct, Trait, ModuleDef, AstDatabase, HasSource
};

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
Expand Down Expand Up @@ -87,23 +88,51 @@ impl LangItems {
let source = module.definition_source(db).ast;
for (impl_id, _) in impl_blocks.impls.iter() {
let impl_block = source_map.get(&source, impl_id);
let lang_item_name = impl_block
.attrs()
.filter_map(|a| a.as_key_value())
.filter(|(key, _)| key == "lang")
.map(|(_, val)| val)
.nth(0);
if let Some(lang_item_name) = lang_item_name {
if let Some(lang_item_name) = lang_item_name(&*impl_block) {
let imp = ImplBlock::from_id(*module, impl_id);
self.items.entry(lang_item_name).or_insert_with(|| LangItemTarget::ImplBlock(imp));
}
}

// FIXME we should look for the other lang item targets (traits, structs, ...)
for def in module.declarations(db) {
match def {
ModuleDef::Trait(trait_) => {
self.collect_lang_item(db, trait_, LangItemTarget::Trait)
}
ModuleDef::Enum(e) => self.collect_lang_item(db, e, LangItemTarget::Enum),
ModuleDef::Struct(s) => self.collect_lang_item(db, s, LangItemTarget::Struct),
ModuleDef::Function(f) => self.collect_lang_item(db, f, LangItemTarget::Function),
ModuleDef::Static(s) => self.collect_lang_item(db, s, LangItemTarget::Static),
_ => {}
}
}

// Look for lang items in the children
for child in module.children(db) {
self.collect_lang_items_recursive(db, &child);
}
}

fn collect_lang_item<T, N>(
&mut self,
db: &(impl DefDatabase + AstDatabase),
item: T,
constructor: fn(T) -> LangItemTarget,
) where
T: Copy + HasSource<Ast = TreeArc<N>>,
N: AttrsOwner,
{
let node = item.source(db).ast;
if let Some(lang_item_name) = lang_item_name(&*node) {
self.items.entry(lang_item_name).or_insert(constructor(item));
}
}
}

fn lang_item_name<T: AttrsOwner>(node: &T) -> Option<SmolStr> {
node.attrs()
.filter_map(|a| a.as_key_value())
.filter(|(key, _)| key == "lang")
.map(|(_, val)| val)
.nth(0)
}
5 changes: 5 additions & 0 deletions crates/ra_hir/src/name.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ impl Name {
Name::new(idx.to_string().into())
}

// Needed for Deref
pub(crate) fn target() -> Name {
Name::new("Target".into())
}

// There's should be no way to extract a string out of `Name`: `Name` in the
// future, `Name` will include hygiene information, and you can't encode
// hygiene into a String.
Expand Down
11 changes: 11 additions & 0 deletions crates/ra_hir/src/source_binder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,17 @@ impl SourceAnalyzer {
)
}

pub fn autoderef<'a>(
&'a self,
db: &'a impl HirDatabase,
ty: Ty,
) -> impl Iterator<Item = Ty> + 'a {
// There should be no inference vars in types passed here
// FIXME check that?
let canonical = crate::ty::Canonical { value: ty, num_vars: 0 };
crate::ty::autoderef(db, &self.resolver, canonical).map(|canonical| canonical.value)
}

#[cfg(test)]
pub(crate) fn body_source_map(&self) -> Arc<BodySourceMap> {
self.body_source_map.clone().unwrap()
Expand Down
28 changes: 25 additions & 3 deletions crates/ra_hir/src/ty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,14 @@ use std::sync::Arc;
use std::ops::Deref;
use std::{fmt, mem};

use crate::{Name, AdtDef, type_ref::Mutability, db::HirDatabase, Trait, GenericParams};
use crate::{Name, AdtDef, type_ref::Mutability, db::HirDatabase, Trait, GenericParams, TypeAlias};
use display::{HirDisplay, HirFormatter};

pub(crate) use lower::{TypableDef, type_for_def, type_for_field, callable_item_sig, generic_predicates, generic_defaults};
pub(crate) use infer::{infer_query, InferenceResult, InferTy};
pub use lower::CallableDef;
pub(crate) use autoderef::autoderef;
pub(crate) use traits::ProjectionPredicate;

/// A type constructor or type name: this might be something like the primitive
/// type `bool`, a struct like `Vec`, or things like function pointers or
Expand Down Expand Up @@ -100,6 +102,15 @@ pub struct ApplicationTy {
pub parameters: Substs,
}

/// A "projection" type corresponds to an (unnormalized)
/// projection like `<P0 as Trait<P1..Pn>>::Foo`. Note that the
/// trait and all its parameters are fully known.
#[derive(Clone, PartialEq, Eq, Debug, Hash)]
pub struct ProjectionTy {
pub associated_ty: TypeAlias,
pub parameters: Substs,
}

/// A type.
///
/// See also the `TyKind` enum in rustc (librustc/ty/sty.rs), which represents
Expand Down Expand Up @@ -216,8 +227,8 @@ impl Deref for Substs {
#[derive(Clone, PartialEq, Eq, Debug, Hash)]
pub struct TraitRef {
/// FIXME name?
trait_: Trait,
substs: Substs,
pub trait_: Trait,
pub substs: Substs,
}

impl TraitRef {
Expand Down Expand Up @@ -464,6 +475,17 @@ impl Ty {
_ => None,
}
}

/// Shifts up `Ty::Bound` vars by `n`.
pub fn shift_bound_vars(self, n: i32) -> Ty {
self.fold(&mut |ty| match ty {
Ty::Bound(idx) => {
assert!(idx as i32 >= -n);
Ty::Bound((idx as i32 + n) as u32)
}
ty => ty,
})
}
}

impl HirDisplay for &Ty {
Expand Down
89 changes: 80 additions & 9 deletions crates/ra_hir/src/ty/autoderef.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,88 @@

use std::iter::successors;

use crate::HirDatabase;
use super::Ty;
use log::{info, warn};

impl Ty {
/// Iterates over the possible derefs of `ty`.
pub fn autoderef<'a>(self, db: &'a impl HirDatabase) -> impl Iterator<Item = Ty> + 'a {
successors(Some(self), move |ty| ty.autoderef_step(db))
use crate::{HirDatabase, Name, Resolver, HasGenericParams};
use super::{traits::Solution, Ty, Canonical};

const AUTODEREF_RECURSION_LIMIT: usize = 10;

pub(crate) fn autoderef<'a>(
db: &'a impl HirDatabase,
resolver: &'a Resolver,
ty: Canonical<Ty>,
) -> impl Iterator<Item = Canonical<Ty>> + 'a {
successors(Some(ty), move |ty| deref(db, resolver, ty)).take(AUTODEREF_RECURSION_LIMIT)
}

pub(crate) fn deref(
db: &impl HirDatabase,
resolver: &Resolver,
ty: &Canonical<Ty>,
) -> Option<Canonical<Ty>> {
if let Some(derefed) = ty.value.builtin_deref() {
Some(Canonical { value: derefed, num_vars: ty.num_vars })
} else {
deref_by_trait(db, resolver, ty)
}
}

fn deref_by_trait(
db: &impl HirDatabase,
resolver: &Resolver,
ty: &Canonical<Ty>,
) -> Option<Canonical<Ty>> {
let krate = resolver.krate()?;
let deref_trait = match db.lang_item(krate, "deref".into())? {
crate::lang_item::LangItemTarget::Trait(t) => t,
_ => return None,
};
let target = deref_trait.associated_type_by_name(db, Name::target())?;

if target.generic_params(db).count_params_including_parent() != 1 {
// the Target type + Deref trait should only have one generic parameter,
// namely Deref's Self type
return None;
}

fn autoderef_step(&self, _db: &impl HirDatabase) -> Option<Ty> {
// FIXME Deref::deref
self.builtin_deref()
// FIXME make the Canonical handling nicer

let projection = super::traits::ProjectionPredicate {
ty: Ty::Bound(0),
projection_ty: super::ProjectionTy {
associated_ty: target,
parameters: vec![ty.value.clone().shift_bound_vars(1)].into(),
},
};

let canonical = super::Canonical { num_vars: 1 + ty.num_vars, value: projection };

let solution = db.normalize(krate, canonical)?;

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.
for i in 1..vars.0.num_vars {
if vars.0.value[i] != Ty::Bound((i - 1) as u32) {
warn!("complex solution for derefing {:?}: {:?}, ignoring", ty, solution);
return None;
}
}
Some(Canonical { value: vars.0.value[0].clone(), num_vars: vars.0.num_vars })
}
Solution::Ambig(_) => {
info!("Ambiguous solution for derefing {:?}: {:?}", ty, solution);
None
}
}
}
Loading