Skip to content
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
Contributor

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