1154: Initial support for lang items (and str completion) r=flodiebold a=marcogroppo

This PR adds partial support for lang items.
For now, the only supported lang items are the ones that target an impl block.

Lang items are now resolved during type inference - this means that `str` completion now works.

Fixes #1139.

(thanks Florian Diebold for the help!)


Co-authored-by: Marco Groppo <marco.groppo@gmail.com>
This commit is contained in:
bors[bot] 2019-04-20 16:13:50 +00:00
commit 4ad2e4ce4e
11 changed files with 174 additions and 19 deletions

View file

@ -1,6 +1,6 @@
use std::sync::Arc;
use ra_syntax::{SyntaxNode, TreeArc, SourceFile, ast};
use ra_syntax::{SyntaxNode, TreeArc, SourceFile, SmolStr, ast};
use ra_db::{SourceDatabase, salsa};
use crate::{
@ -16,6 +16,7 @@ use crate::{
generics::{GenericParams, GenericDef},
type_ref::TypeRef,
traits::TraitData, Trait, ty::TraitRef,
lang_item::{LangItems, LangItemTarget},
ids
};
@ -100,6 +101,12 @@ pub trait DefDatabase: SourceDatabase {
#[salsa::invoke(crate::ConstSignature::static_signature_query)]
fn static_signature(&self, konst: Static) -> Arc<ConstSignature>;
#[salsa::invoke(crate::lang_item::LangItems::lang_items_query)]
fn lang_items(&self, krate: Crate) -> Arc<LangItems>;
#[salsa::invoke(crate::lang_item::LangItems::lang_item_query)]
fn lang_item(&self, start_crate: Crate, item: SmolStr) -> Option<LangItemTarget>;
}
#[salsa::query_group(HirDatabaseStorage)]

View file

@ -0,0 +1,102 @@
use std::sync::Arc;
use rustc_hash::FxHashMap;
use ra_syntax::{SmolStr, ast::AttrsOwner};
use crate::{
Crate, DefDatabase, Enum, Function, HirDatabase, ImplBlock, Module, Static, Struct, Trait
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum LangItemTarget {
Enum(Enum),
Function(Function),
ImplBlock(ImplBlock),
Static(Static),
Struct(Struct),
Trait(Trait),
}
impl LangItemTarget {
pub(crate) fn krate(&self, db: &impl HirDatabase) -> Option<Crate> {
match self {
LangItemTarget::Enum(e) => e.module(db).krate(db),
LangItemTarget::Function(f) => f.module(db).krate(db),
LangItemTarget::ImplBlock(i) => i.module().krate(db),
LangItemTarget::Static(s) => s.module(db).krate(db),
LangItemTarget::Struct(s) => s.module(db).krate(db),
LangItemTarget::Trait(t) => t.module(db).krate(db),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LangItems {
items: FxHashMap<SmolStr, LangItemTarget>,
}
impl LangItems {
pub fn target<'a>(&'a self, item: &str) -> Option<&'a LangItemTarget> {
self.items.get(item)
}
/// Salsa query. This will look for lang items in a specific crate.
pub(crate) fn lang_items_query(db: &impl DefDatabase, krate: Crate) -> Arc<LangItems> {
let mut lang_items = LangItems { items: FxHashMap::default() };
if let Some(module) = krate.root_module(db) {
lang_items.collect_lang_items_recursive(db, &module);
}
Arc::new(lang_items)
}
/// Salsa query. Look for a lang item, starting from the specified crate and recursively
/// traversing its dependencies.
pub(crate) fn lang_item_query(
db: &impl DefDatabase,
start_crate: Crate,
item: SmolStr,
) -> Option<LangItemTarget> {
let lang_items = db.lang_items(start_crate);
let start_crate_target = lang_items.items.get(&item);
if let Some(target) = start_crate_target {
Some(*target)
} else {
for dep in start_crate.dependencies(db) {
let dep_crate = dep.krate;
let dep_target = db.lang_item(dep_crate, item.clone());
if dep_target.is_some() {
return dep_target;
}
}
None
}
}
fn collect_lang_items_recursive(&mut self, db: &impl DefDatabase, module: &Module) {
// Look for impl targets
let (impl_blocks, source_map) = db.impls_in_module_with_source_map(module.clone());
let source = module.definition_source(db).1;
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 {
let imp = ImplBlock::from_id(*module, impl_id);
self.items.entry(lang_item_name).or_insert(LangItemTarget::ImplBlock(imp));
}
}
// FIXME we should look for the other lang item targets (traits, structs, ...)
// Look for lang items in the children
for child in module.children(db) {
self.collect_lang_items_recursive(db, &child);
}
}
}

View file

@ -36,6 +36,7 @@ mod type_ref;
mod ty;
mod impl_block;
mod expr;
mod lang_item;
mod generics;
mod docs;
mod resolve;

View file

@ -202,6 +202,10 @@ impl CrateDefMap {
Arc::new(def_map)
}
pub(crate) fn krate(&self) -> Crate {
self.krate
}
pub(crate) fn root(&self) -> CrateModuleId {
self.root
}

View file

@ -5,13 +5,15 @@ use rustc_hash::FxHashMap;
use crate::{
ModuleDef,
code_model_api::Crate,
db::HirDatabase,
name::{Name, KnownName},
nameres::{PerNs, CrateDefMap, CrateModuleId},
generics::GenericParams,
expr::{scope::{ExprScopes, ScopeId}, PatId},
impl_block::ImplBlock,
path::Path, Trait
path::Path,
Trait
};
#[derive(Debug, Clone, Default)]
@ -197,6 +199,10 @@ impl Resolver {
_ => None,
})
}
pub(crate) fn krate(&self) -> Option<Crate> {
self.module().map(|t| t.0.krate())
}
}
impl Resolver {

View file

@ -462,6 +462,7 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
let remaining_index = remaining_index.unwrap_or(path.segments.len());
let mut actual_def_ty: Option<Ty> = None;
let krate = resolver.krate()?;
// resolve intermediate segments
for (i, segment) in path.segments[remaining_index..].iter().enumerate() {
let ty = match resolved {
@ -500,9 +501,10 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
// Attempt to find an impl_item for the type which has a name matching
// the current segment
log::debug!("looking for path segment: {:?}", segment);
actual_def_ty = Some(ty.clone());
let item: crate::ModuleDef = ty.iterate_impl_items(self.db, |item| {
let item: crate::ModuleDef = ty.iterate_impl_items(self.db, krate, |item| {
let matching_def: Option<crate::ModuleDef> = match item {
crate::ImplItem::Method(func) => {
let sig = func.signature(self.db);

View file

@ -14,6 +14,7 @@ use crate::{
resolve::Resolver,
traits::TraitItem,
generics::HasGenericParams,
ty::primitive::{UncertainIntTy, UncertainFloatTy}
};
use super::{TraitRef, Substs};
@ -110,10 +111,19 @@ impl CrateImplBlocks {
}
}
fn def_crate(db: &impl HirDatabase, ty: &Ty) -> Option<Crate> {
fn def_crate(db: &impl HirDatabase, cur_crate: Crate, ty: &Ty) -> Option<Crate> {
match ty {
Ty::Apply(a_ty) => match a_ty.ctor {
TypeCtor::Adt(def_id) => def_id.krate(db),
TypeCtor::Bool => db.lang_item(cur_crate, "bool".into())?.krate(db),
TypeCtor::Char => db.lang_item(cur_crate, "char".into())?.krate(db),
TypeCtor::Float(UncertainFloatTy::Known(f)) => {
db.lang_item(cur_crate, f.ty_to_string().into())?.krate(db)
}
TypeCtor::Int(UncertainIntTy::Known(i)) => {
db.lang_item(cur_crate, i.ty_to_string().into())?.krate(db)
}
TypeCtor::Str => db.lang_item(cur_crate, "str".into())?.krate(db),
_ => None,
},
_ => None,
@ -150,8 +160,11 @@ impl Ty {
// find in the end takes &self, we still do the autoderef step (just as
// rustc does an autoderef and then autoref again).
let krate = resolver.krate()?;
for derefed_ty in self.autoderef(db) {
if let Some(result) = derefed_ty.iterate_inherent_methods(db, name, &mut callback) {
if let Some(result) =
derefed_ty.iterate_inherent_methods(db, name, krate, &mut callback)
{
return Some(result);
}
if let Some(result) =
@ -208,9 +221,10 @@ impl Ty {
&self,
db: &impl HirDatabase,
name: Option<&Name>,
krate: Crate,
mut callback: impl FnMut(&Ty, Function) -> Option<T>,
) -> Option<T> {
let krate = match def_crate(db, self) {
let krate = match def_crate(db, krate, self) {
Some(krate) => krate,
None => return None,
};
@ -239,9 +253,10 @@ impl Ty {
pub fn iterate_impl_items<T>(
self,
db: &impl HirDatabase,
krate: Crate,
mut callback: impl FnMut(ImplItem) -> Option<T>,
) -> Option<T> {
let krate = def_crate(db, &self)?;
let krate = def_crate(db, krate, &self)?;
let impls = db.impls_in_crate(krate);
for impl_block in impls.lookup_impl_blocks(&self) {

View file

@ -38,7 +38,9 @@ pub(super) fn complete_path(acc: &mut Completions, ctx: &CompletionContext) {
}
hir::ModuleDef::Struct(s) => {
let ty = s.ty(ctx.db);
ty.iterate_impl_items(ctx.db, |item| {
let krate = ctx.module.and_then(|m| m.krate(ctx.db));
if let Some(krate) = krate {
ty.iterate_impl_items(ctx.db, krate, |item| {
match item {
hir::ImplItem::Method(func) => {
let sig = func.signature(ctx.db);
@ -52,6 +54,7 @@ pub(super) fn complete_path(acc: &mut Completions, ctx: &CompletionContext) {
None::<()>
});
}
}
_ => return,
};
}

View file

@ -65,6 +65,20 @@ impl ast::Attr {
None
}
}
pub fn as_key_value(&self) -> Option<(SmolStr, SmolStr)> {
let tt = self.value()?;
let tt_node = tt.syntax();
let attr = tt_node.children_with_tokens().nth(1)?;
if attr.kind() == IDENT {
let key = attr.as_token()?.text().clone();
let val_node = tt_node.children_with_tokens().find(|t| t.kind() == STRING)?;
let val = val_node.as_token()?.text().trim_start_matches("\"").trim_end_matches("\"");
Some((key, SmolStr::new(val)))
} else {
None
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]

View file

@ -1325,6 +1325,7 @@ impl ToOwned for ImplBlock {
impl ast::TypeParamsOwner for ImplBlock {}
impl ast::AttrsOwner for ImplBlock {}
impl ImplBlock {
pub fn item_list(&self) -> Option<&ItemList> {
super::child_opt(self)

View file

@ -345,7 +345,7 @@ Grammar(
],
options: ["TypeRef"]
),
"ImplBlock": (options: ["ItemList"], traits: ["TypeParamsOwner"]),
"ImplBlock": (options: ["ItemList"], traits: ["TypeParamsOwner", "AttrsOwner"]),
"ParenType": (options: ["TypeRef"]),
"TupleType": ( collections: [["fields", "TypeRef"]] ),