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
Show file tree
Hide file tree
Changes from 7 commits
Commits
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
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
21 changes: 19 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,19 @@ 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,
})
.filter(|t| t.name(db) == name)
.next()
}

pub(crate) fn trait_data(self, db: &impl DefDatabase) -> Arc<TraitData> {
db.trait_data(self)
}
Expand Down Expand Up @@ -831,8 +844,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)]
fn normalize(
&self,
krate: Crate,
goal: crate::ty::Canonical<crate::ty::traits::ProjectionPredicate>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should make ty a facade module and use ty::Smth everywhere?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. I agree for ProjectionPredicate, but when I started doing this for everything in traits, it feld weird for e.g. Solver and the query functions...

) -> Option<crate::ty::traits::Solution>;
}

#[test]
Expand Down
55 changes: 46 additions & 9 deletions crates/ra_hir/src/lang_item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use rustc_hash::FxHashMap;
use ra_syntax::{SmolStr, 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 +87,60 @@ 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, ...)
// FIXME make this nicer
for def in module.declarations(db) {
match def {
ModuleDef::Trait(trait_) => {
let node = trait_.source(db).ast;
if let Some(lang_item_name) = lang_item_name(&*node) {
self.items.entry(lang_item_name).or_insert(LangItemTarget::Trait(trait_));
}
}
ModuleDef::Enum(e) => {
let node = e.source(db).ast;
if let Some(lang_item_name) = lang_item_name(&*node) {
self.items.entry(lang_item_name).or_insert(LangItemTarget::Enum(e));
}
}
ModuleDef::Struct(s) => {
let node = s.source(db).ast;
if let Some(lang_item_name) = lang_item_name(&*node) {
self.items.entry(lang_item_name).or_insert(LangItemTarget::Struct(s));
}
}
ModuleDef::Function(f) => {
let node = f.source(db).ast;
if let Some(lang_item_name) = lang_item_name(&*node) {
self.items.entry(lang_item_name).or_insert(LangItemTarget::Function(f));
}
}
ModuleDef::Static(s) => {
let node = s.source(db).ast;
if let Some(lang_item_name) = lang_item_name(&*node) {
self.items.entry(lang_item_name).or_insert(LangItemTarget::Static(s));
}
}
_ => {}
}
}

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

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
27 changes: 24 additions & 3 deletions crates/ra_hir/src/ty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,13 @@ 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;

/// 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 +101,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 +226,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 +474,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
87 changes: 78 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,86 @@

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};

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))
}

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;
}

// 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)?;

fn autoderef_step(&self, _db: &impl HirDatabase) -> Option<Ty> {
// FIXME Deref::deref
self.builtin_deref()
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