rust-analyzer/crates/ide_db/src/defs.rs

515 lines
22 KiB
Rust
Raw Normal View History

2020-02-06 15:23:28 +00:00
//! `NameDefinition` keeps information about the element we want to search references for.
//! The element is represented by `NameKind`. It's located inside some `container` and
//! has a `visibility`, which defines a search scope.
//! Note that the reference search is possible for not all of the classified items.
2020-02-06 15:24:14 +00:00
// FIXME: this badly needs rename/rewrite (matklad, 2020-02-06).
2021-09-23 13:37:52 +00:00
use arrayvec::ArrayVec;
2020-02-06 15:23:28 +00:00
use hir::{
Field, GenericParam, HasVisibility, Impl, Label, Local, MacroDef, Module, ModuleDef, Name,
PathResolution, Semantics, Visibility,
2020-02-06 15:23:28 +00:00
};
2020-08-12 16:26:51 +00:00
use syntax::{
2021-07-23 00:14:59 +00:00
ast::{self, AstNode},
match_ast, AstToken, SyntaxKind, SyntaxNode, SyntaxToken,
2020-02-06 15:23:28 +00:00
};
use crate::{helpers::try_resolve_derive_input, RootDatabase};
2020-02-06 15:23:28 +00:00
2020-03-03 17:36:39 +00:00
// FIXME: a more precise name would probably be `Symbol`?
#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)]
2020-03-03 17:36:39 +00:00
pub enum Definition {
2020-02-06 15:23:28 +00:00
Macro(MacroDef),
2020-04-25 12:23:34 +00:00
Field(Field),
2020-02-07 13:26:59 +00:00
ModuleDef(ModuleDef),
SelfType(Impl),
2020-02-06 15:23:28 +00:00
Local(Local),
GenericParam(GenericParam),
2020-12-23 16:15:01 +00:00
Label(Label),
2020-02-06 15:23:28 +00:00
}
2020-03-03 17:36:39 +00:00
impl Definition {
2021-09-23 13:37:52 +00:00
pub fn from_token(
sema: &Semantics<RootDatabase>,
token: &SyntaxToken,
) -> ArrayVec<Definition, 2> {
let parent = match token.parent() {
Some(parent) => parent,
None => return Default::default(),
2021-09-22 15:05:54 +00:00
};
if let Some(ident) = ast::Ident::cast(token.clone()) {
let attr = parent
.ancestors()
.find_map(ast::TokenTree::cast)
.and_then(|tt| tt.parent_meta())
.and_then(|meta| meta.parent_attr());
if let Some(attr) = attr {
return try_resolve_derive_input(&sema, &attr, &ident)
.map(Into::into)
.into_iter()
.collect();
}
2021-09-22 15:05:54 +00:00
}
Self::from_node(sema, &parent)
2021-09-23 13:37:52 +00:00
}
pub fn from_node(sema: &Semantics<RootDatabase>, node: &SyntaxNode) -> ArrayVec<Definition, 2> {
let mut res = ArrayVec::new();
(|| {
match_ast! {
match node {
ast::Name(name) => {
match NameClass::classify(&sema, &name)? {
NameClass::Definition(it) | NameClass::ConstReference(it) => res.push(it),
NameClass::PatFieldShorthand { local_def, field_ref } => {
res.push(Definition::Local(local_def));
res.push(Definition::Field(field_ref));
}
}
},
ast::NameRef(name_ref) => {
match NameRefClass::classify(sema, &name_ref)? {
NameRefClass::Definition(it) => res.push(it),
NameRefClass::FieldShorthand { local_ref, field_ref } => {
res.push(Definition::Local(local_ref));
res.push(Definition::Field(field_ref));
}
}
},
ast::Lifetime(lifetime) => {
let def = if let Some(x) = NameClass::classify_lifetime(&sema, &lifetime) {
NameClass::defined(x)
} else {
NameRefClass::classify_lifetime(&sema, &lifetime).and_then(|class| match class {
NameRefClass::Definition(it) => Some(it),
_ => None,
})
};
if let Some(def) = def {
res.push(def);
2021-09-22 15:05:54 +00:00
}
2021-09-23 13:37:52 +00:00
},
_ => (),
}
2021-09-22 15:05:54 +00:00
}
2021-09-23 13:37:52 +00:00
Some(())
})();
res
2021-09-22 15:05:54 +00:00
}
pub fn module(&self, db: &RootDatabase) -> Option<Module> {
2020-02-19 13:56:22 +00:00
match self {
2020-03-03 17:36:39 +00:00
Definition::Macro(it) => it.module(db),
2020-04-25 12:23:34 +00:00
Definition::Field(it) => Some(it.parent_def(db).module(db)),
2020-03-03 17:36:39 +00:00
Definition::ModuleDef(it) => it.module(db),
Definition::SelfType(it) => Some(it.module(db)),
Definition::Local(it) => Some(it.module(db)),
Definition::GenericParam(it) => Some(it.module(db)),
2020-12-23 16:15:01 +00:00
Definition::Label(it) => Some(it.module(db)),
}
}
2020-02-19 13:45:49 +00:00
2020-03-24 20:45:42 +00:00
pub fn visibility(&self, db: &RootDatabase) -> Option<Visibility> {
2020-02-19 13:56:22 +00:00
match self {
2020-04-25 12:23:34 +00:00
Definition::Field(sf) => Some(sf.visibility(db)),
2021-07-20 14:49:02 +00:00
Definition::ModuleDef(def) => Some(def.visibility(db)),
Definition::Macro(_)
| Definition::SelfType(_)
| Definition::Local(_)
| Definition::GenericParam(_)
| Definition::Label(_) => None,
2020-02-19 13:45:49 +00:00
}
}
2020-03-03 17:22:52 +00:00
pub fn name(&self, db: &RootDatabase) -> Option<Name> {
let name = match self {
2020-03-03 17:36:39 +00:00
Definition::Macro(it) => it.name(db)?,
2020-04-25 12:23:34 +00:00
Definition::Field(it) => it.name(db),
2020-03-03 17:36:39 +00:00
Definition::ModuleDef(def) => match def {
2020-03-03 17:22:52 +00:00
hir::ModuleDef::Module(it) => it.name(db)?,
hir::ModuleDef::Function(it) => it.name(db),
hir::ModuleDef::Adt(def) => match def {
hir::Adt::Struct(it) => it.name(db),
hir::Adt::Union(it) => it.name(db),
hir::Adt::Enum(it) => it.name(db),
},
2020-12-20 07:05:24 +00:00
hir::ModuleDef::Variant(it) => it.name(db),
2020-03-03 17:22:52 +00:00
hir::ModuleDef::Const(it) => it.name(db)?,
hir::ModuleDef::Static(it) => it.name(db)?,
hir::ModuleDef::Trait(it) => it.name(db),
hir::ModuleDef::TypeAlias(it) => it.name(db),
2021-03-15 08:32:06 +00:00
hir::ModuleDef::BuiltinType(it) => it.name(),
2020-03-03 17:22:52 +00:00
},
2020-03-03 17:36:39 +00:00
Definition::SelfType(_) => return None,
Definition::Local(it) => it.name(db)?,
Definition::GenericParam(it) => it.name(db),
2020-12-23 16:15:01 +00:00
Definition::Label(it) => it.name(db),
2020-03-03 17:22:52 +00:00
};
Some(name)
}
}
/// On a first blush, a single `ast::Name` defines a single definition at some
/// scope. That is, that, by just looking at the syntactical category, we can
/// unambiguously define the semantic category.
///
2021-07-11 12:13:42 +00:00
/// Sadly, that's not 100% true, there are special cases. To make sure that
/// callers handle all the special cases correctly via exhaustive matching, we
/// add a [`NameClass`] enum which lists all of them!
///
/// A model special case is `None` constant in pattern.
#[derive(Debug)]
pub enum NameClass {
2020-03-03 17:36:39 +00:00
Definition(Definition),
2020-10-15 15:18:07 +00:00
/// `None` in `if let None = Some(82) {}`.
/// Syntactically, it is a name, but semantically it is a reference.
2020-03-03 17:36:39 +00:00
ConstReference(Definition),
/// `field` in `if let Foo { field } = foo`. Here, `ast::Name` both introduces
/// a definition into a local scope, and refers to an existing definition.
PatFieldShorthand {
2020-10-15 15:18:07 +00:00
local_def: Local,
field_ref: Field,
},
}
impl NameClass {
2020-10-15 15:33:32 +00:00
/// `Definition` defined by this name.
pub fn defined(self) -> Option<Definition> {
let res = match self {
NameClass::Definition(it) => it,
NameClass::ConstReference(_) => return None,
2020-10-15 15:18:07 +00:00
NameClass::PatFieldShorthand { local_def, field_ref: _ } => {
Definition::Local(local_def)
}
};
Some(res)
}
2020-10-15 15:27:50 +00:00
pub fn classify(sema: &Semantics<RootDatabase>, name: &ast::Name) -> Option<NameClass> {
let _p = profile::span("classify_name");
2020-10-15 15:27:50 +00:00
let parent = name.syntax().parent()?;
2020-10-15 15:27:50 +00:00
if let Some(bind_pat) = ast::IdentPat::cast(parent.clone()) {
if let Some(def) = sema.resolve_bind_pat_to_const(&bind_pat) {
return Some(NameClass::ConstReference(Definition::ModuleDef(def)));
}
2020-02-28 15:36:14 +00:00
}
2020-10-15 15:27:50 +00:00
match_ast! {
match parent {
ast::Rename(it) => {
if let Some(use_tree) = it.syntax().parent().and_then(ast::UseTree::cast) {
let path = use_tree.path()?;
let path_segment = path.segment()?;
2021-07-23 00:14:59 +00:00
let name_ref = path_segment.name_ref()?;
let name_ref = if name_ref.self_token().is_some() {
use_tree
.syntax()
.parent()
.as_ref()
// Skip over UseTreeList
.and_then(|it| {
let use_tree = it.parent().and_then(ast::UseTree::cast)?;
let path = use_tree.path()?;
let path_segment = path.segment()?;
path_segment.name_ref()
}).unwrap_or(name_ref)
} else {
name_ref
};
let name_ref_class = NameRefClass::classify(sema, &name_ref)?;
Some(NameClass::Definition(match name_ref_class {
NameRefClass::Definition(def) => def,
NameRefClass::FieldShorthand { local_ref: _, field_ref } => {
Definition::Field(field_ref)
}
}))
2020-10-15 15:27:50 +00:00
} else {
let extern_crate = it.syntax().parent().and_then(ast::ExternCrate::cast)?;
let krate = sema.resolve_extern_crate(&extern_crate)?;
let root_module = krate.root_module(sema.db);
Some(NameClass::Definition(Definition::ModuleDef(root_module.into())))
2020-10-15 15:27:50 +00:00
}
},
ast::IdentPat(it) => {
let local = sema.to_def(&it)?;
2020-10-15 15:27:50 +00:00
if let Some(record_pat_field) = it.syntax().parent().and_then(ast::RecordPatField::cast) {
if record_pat_field.name_ref().is_none() {
if let Some(field) = sema.resolve_record_pat_field(&record_pat_field) {
return Some(NameClass::PatFieldShorthand { local_def: local, field_ref: field });
}
}
}
2020-06-06 19:16:59 +00:00
2020-10-15 15:27:50 +00:00
Some(NameClass::Definition(Definition::Local(local)))
},
ast::SelfParam(it) => {
let def = sema.to_def(&it)?;
Some(NameClass::Definition(Definition::Local(def)))
},
2020-10-15 15:27:50 +00:00
ast::RecordField(it) => {
let field: hir::Field = sema.to_def(&it)?;
Some(NameClass::Definition(Definition::Field(field)))
},
ast::Module(it) => {
let def = sema.to_def(&it)?;
Some(NameClass::Definition(Definition::ModuleDef(def.into())))
},
ast::Struct(it) => {
let def: hir::Struct = sema.to_def(&it)?;
Some(NameClass::Definition(Definition::ModuleDef(def.into())))
},
ast::Union(it) => {
let def: hir::Union = sema.to_def(&it)?;
Some(NameClass::Definition(Definition::ModuleDef(def.into())))
},
ast::Enum(it) => {
let def: hir::Enum = sema.to_def(&it)?;
Some(NameClass::Definition(Definition::ModuleDef(def.into())))
},
ast::Trait(it) => {
let def: hir::Trait = sema.to_def(&it)?;
Some(NameClass::Definition(Definition::ModuleDef(def.into())))
},
ast::Static(it) => {
let def: hir::Static = sema.to_def(&it)?;
Some(NameClass::Definition(Definition::ModuleDef(def.into())))
},
ast::Variant(it) => {
2020-12-20 07:05:24 +00:00
let def: hir::Variant = sema.to_def(&it)?;
2020-10-15 15:27:50 +00:00
Some(NameClass::Definition(Definition::ModuleDef(def.into())))
},
ast::Fn(it) => {
let def: hir::Function = sema.to_def(&it)?;
Some(NameClass::Definition(Definition::ModuleDef(def.into())))
},
ast::Const(it) => {
let def: hir::Const = sema.to_def(&it)?;
Some(NameClass::Definition(Definition::ModuleDef(def.into())))
},
ast::TypeAlias(it) => {
let def: hir::TypeAlias = sema.to_def(&it)?;
Some(NameClass::Definition(Definition::ModuleDef(def.into())))
},
ast::Macro(it) => {
2020-10-15 15:27:50 +00:00
let def = sema.to_def(&it)?;
Some(NameClass::Definition(Definition::Macro(def)))
},
ast::TypeParam(it) => {
let def = sema.to_def(&it)?;
Some(NameClass::Definition(Definition::GenericParam(def.into())))
2020-10-15 15:27:50 +00:00
},
2021-01-01 09:07:01 +00:00
ast::ConstParam(it) => {
let def = sema.to_def(&it)?;
Some(NameClass::Definition(Definition::GenericParam(def.into())))
2021-01-01 09:07:01 +00:00
},
2020-10-15 15:27:50 +00:00
_ => None,
}
2020-02-06 15:23:28 +00:00
}
}
2020-12-16 20:35:15 +00:00
pub fn classify_lifetime(
sema: &Semantics<RootDatabase>,
lifetime: &ast::Lifetime,
) -> Option<NameClass> {
let _p = profile::span("classify_lifetime").detail(|| lifetime.to_string());
let parent = lifetime.syntax().parent()?;
match_ast! {
match parent {
ast::LifetimeParam(it) => {
let def = sema.to_def(&it)?;
Some(NameClass::Definition(Definition::GenericParam(def.into())))
2020-12-16 20:35:15 +00:00
},
2020-12-23 16:15:01 +00:00
ast::Label(it) => {
let def = sema.to_def(&it)?;
Some(NameClass::Definition(Definition::Label(def)))
},
2020-12-16 20:35:15 +00:00
_ => None,
}
}
}
2020-02-06 15:23:28 +00:00
}
/// This is similar to [`NameClass`], but works for [`ast::NameRef`] rather than
/// for [`ast::Name`]. Similarly, what looks like a reference in syntax is a
/// reference most of the time, but there are a couple of annoying exceptions.
///
/// A model special case is field shorthand syntax, which uses a single
/// reference to point to two different defs.
#[derive(Debug)]
pub enum NameRefClass {
Definition(Definition),
FieldShorthand { local_ref: Local, field_ref: Field },
}
impl NameRefClass {
2020-10-15 15:27:50 +00:00
// Note: we don't have unit-tests for this rather important function.
// It is primarily exercised via goto definition tests in `ide`.
pub fn classify(
sema: &Semantics<RootDatabase>,
name_ref: &ast::NameRef,
) -> Option<NameRefClass> {
2020-12-04 19:26:28 +00:00
let _p = profile::span("classify_name_ref").detail(|| name_ref.to_string());
2020-10-15 15:27:50 +00:00
let parent = name_ref.syntax().parent()?;
2020-10-15 15:27:50 +00:00
if let Some(method_call) = ast::MethodCallExpr::cast(parent.clone()) {
if let Some(func) = sema.resolve_method_call(&method_call) {
return Some(NameRefClass::Definition(Definition::ModuleDef(func.into())));
}
}
2020-10-15 15:27:50 +00:00
if let Some(field_expr) = ast::FieldExpr::cast(parent.clone()) {
if let Some(field) = sema.resolve_field(&field_expr) {
return Some(NameRefClass::Definition(Definition::Field(field)));
}
}
2020-10-15 15:27:50 +00:00
if let Some(record_field) = ast::RecordExprField::for_field_name(name_ref) {
2021-05-23 21:54:35 +00:00
if let Some((field, local, _)) = sema.resolve_record_field(&record_field) {
2020-10-15 15:27:50 +00:00
let res = match local {
None => NameRefClass::Definition(Definition::Field(field)),
2020-10-15 15:33:32 +00:00
Some(local) => {
NameRefClass::FieldShorthand { field_ref: field, local_ref: local }
}
2020-10-15 15:27:50 +00:00
};
return Some(res);
}
}
2020-10-15 15:27:50 +00:00
if let Some(record_pat_field) = ast::RecordPatField::cast(parent.clone()) {
if let Some(field) = sema.resolve_record_pat_field(&record_pat_field) {
let field = Definition::Field(field);
return Some(NameRefClass::Definition(field));
}
}
if let Some(assoc_type_arg) = ast::AssocTypeArg::cast(parent.clone()) {
if assoc_type_arg.name_ref().as_ref() == Some(name_ref) {
// `Trait<Assoc = Ty>`
// ^^^^^
let path = name_ref.syntax().ancestors().find_map(ast::Path::cast)?;
let resolved = sema.resolve_path(&path)?;
if let PathResolution::Def(ModuleDef::Trait(tr)) = resolved {
// FIXME: resolve in supertraits
if let Some(ty) = tr
.items(sema.db)
.iter()
.filter_map(|assoc| match assoc {
hir::AssocItem::TypeAlias(it) => Some(*it),
_ => None,
})
.find(|alias| alias.name(sema.db).to_smol_str() == name_ref.text().as_str())
{
return Some(NameRefClass::Definition(Definition::ModuleDef(
ModuleDef::TypeAlias(ty),
)));
}
2020-10-15 15:27:50 +00:00
}
return None;
}
}
if let Some(path) = name_ref.syntax().ancestors().find_map(ast::Path::cast) {
if path.qualifier().is_none() {
if let Some(macro_call) = path.syntax().parent().and_then(ast::MacroCall::cast) {
2020-10-15 15:27:50 +00:00
// Only use this to resolve single-segment macro calls like `foo!()`. Multi-segment
2021-01-06 20:15:48 +00:00
// paths are handled below (allowing `log$0::info!` to resolve to the log crate).
2020-10-15 15:27:50 +00:00
if let Some(macro_def) = sema.resolve_macro_call(&macro_call) {
return Some(NameRefClass::Definition(Definition::Macro(macro_def)));
}
}
}
let top_path = path.top_path();
let is_attribute_path = top_path
.syntax()
.ancestors()
.find_map(ast::Attr::cast)
.map(|attr| attr.path().as_ref() == Some(&top_path));
return match is_attribute_path {
Some(true) if path == top_path => sema
.resolve_path_as_macro(&path)
.filter(|mac| mac.kind() == hir::MacroKind::Attr)
.map(Definition::Macro)
.map(NameRefClass::Definition),
// in case of the path being a qualifier, don't resolve to anything but a module
Some(true) => match sema.resolve_path(&path)? {
PathResolution::Def(module @ ModuleDef::Module(_)) => {
cov_mark::hit!(name_ref_classify_attr_path_qualifier);
Some(NameRefClass::Definition(Definition::ModuleDef(module)))
}
_ => None,
},
// inside attribute, but our path isn't part of the attribute's path(might be in its expression only)
Some(false) => None,
None => sema.resolve_path(&path).map(Into::into).map(NameRefClass::Definition),
};
}
2020-10-15 15:27:50 +00:00
let extern_crate = ast::ExternCrate::cast(parent)?;
let krate = sema.resolve_extern_crate(&extern_crate)?;
let root_module = krate.root_module(sema.db);
Some(NameRefClass::Definition(Definition::ModuleDef(root_module.into())))
2020-10-15 15:27:50 +00:00
}
2020-12-16 20:35:15 +00:00
pub fn classify_lifetime(
sema: &Semantics<RootDatabase>,
lifetime: &ast::Lifetime,
) -> Option<NameRefClass> {
let _p = profile::span("classify_lifetime_ref").detail(|| lifetime.to_string());
let parent = lifetime.syntax().parent()?;
match parent.kind() {
2020-12-23 16:15:01 +00:00
SyntaxKind::BREAK_EXPR | SyntaxKind::CONTINUE_EXPR => {
sema.resolve_label(lifetime).map(Definition::Label).map(NameRefClass::Definition)
}
2020-12-16 20:35:15 +00:00
SyntaxKind::LIFETIME_ARG
| SyntaxKind::SELF_PARAM
| SyntaxKind::TYPE_BOUND
| SyntaxKind::WHERE_PRED
| SyntaxKind::REF_TYPE => sema
.resolve_lifetime_param(lifetime)
.map(GenericParam::LifetimeParam)
.map(Definition::GenericParam)
2020-12-16 20:35:15 +00:00
.map(NameRefClass::Definition),
// lifetime bounds, as in the 'b in 'a: 'b aren't wrapped in TypeBound nodes so we gotta check
// if our lifetime is in a LifetimeParam without being the constrained lifetime
_ if ast::LifetimeParam::cast(parent).and_then(|param| param.lifetime()).as_ref()
!= Some(lifetime) =>
{
sema.resolve_lifetime_param(lifetime)
.map(GenericParam::LifetimeParam)
.map(Definition::GenericParam)
2020-12-16 20:35:15 +00:00
.map(NameRefClass::Definition)
}
_ => None,
}
}
}
impl From<PathResolution> for Definition {
fn from(path_resolution: PathResolution) -> Self {
match path_resolution {
PathResolution::Def(def) => Definition::ModuleDef(def),
PathResolution::AssocItem(item) => {
let def = match item {
hir::AssocItem::Function(it) => it.into(),
hir::AssocItem::Const(it) => it.into(),
hir::AssocItem::TypeAlias(it) => it.into(),
};
Definition::ModuleDef(def)
}
PathResolution::Local(local) => Definition::Local(local),
PathResolution::TypeParam(par) => Definition::GenericParam(par.into()),
PathResolution::Macro(def) => Definition::Macro(def),
PathResolution::SelfType(impl_def) => Definition::SelfType(impl_def),
PathResolution::ConstParam(par) => Definition::GenericParam(par.into()),
}
}
}