// FIXME: this badly needs rename/rewrite (matklad, 2020-02-06).
use hir::{
- db::HirDatabase, Crate, Field, GenericParam, HasAttrs, HasVisibility, Impl, Label, Local,
- MacroDef, Module, ModuleDef, Name, PathResolution, Semantics, Visibility,
+ Field, GenericParam, HasVisibility, Impl, Label, Local, MacroDef, Module, ModuleDef, Name,
+ PathResolution, Semantics, Visibility,
};
use syntax::{
ast::{self, AstNode, PathSegmentKind},
pub fn visibility(&self, db: &RootDatabase) -> Option<Visibility> {
match self {
- Definition::Macro(_) => None,
Definition::Field(sf) => Some(sf.visibility(db)),
- Definition::ModuleDef(def) => def.definition_visibility(db),
- Definition::SelfType(_) => None,
- Definition::Local(_) => None,
- Definition::GenericParam(_) => None,
- Definition::Label(_) => None,
+ Definition::ModuleDef(def) => match def {
+ ModuleDef::Module(it) => {
+ // FIXME: should work like other cases here.
+ let parent = it.parent(db)?;
+ parent.visibility_of(db, def)
+ }
+ ModuleDef::Function(it) => Some(it.visibility(db)),
+ ModuleDef::Adt(it) => Some(it.visibility(db)),
+ ModuleDef::Const(it) => Some(it.visibility(db)),
+ ModuleDef::Static(it) => Some(it.visibility(db)),
+ ModuleDef::Trait(it) => Some(it.visibility(db)),
+ ModuleDef::TypeAlias(it) => Some(it.visibility(db)),
+ // NB: Variants don't have their own visibility, and just inherit
+ // one from the parent. Not sure if that's the right thing to do.
+ ModuleDef::Variant(it) => Some(it.parent_enum(db).visibility(db)),
+ ModuleDef::BuiltinType(_) => None,
+ },
+ Definition::Macro(_)
+ | Definition::SelfType(_)
+ | Definition::Local(_)
+ | Definition::GenericParam(_)
+ | Definition::Label(_) => None,
}
}
}
}
+/// 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.
+///
+/// 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 {
- ExternCrate(Crate),
Definition(Definition),
/// `None` in `if let None = Some(82) {}`.
+ /// Syntactically, it is a name, but semantically it is a reference.
ConstReference(Definition),
- /// `field` in `if let Foo { field } = foo`.
+ /// `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 {
local_def: Local,
- field_ref: Definition,
+ field_ref: Field,
},
}
impl NameClass {
/// `Definition` defined by this name.
- pub fn defined(self, db: &dyn HirDatabase) -> Option<Definition> {
+ pub fn defined(self) -> Option<Definition> {
let res = match self {
- NameClass::ExternCrate(krate) => Definition::ModuleDef(krate.root_module(db).into()),
NameClass::Definition(it) => it,
NameClass::ConstReference(_) => return None,
NameClass::PatFieldShorthand { local_def, field_ref: _ } => {
Some(res)
}
- /// `Definition` referenced or defined by this name.
- pub fn referenced_or_defined(self, db: &dyn HirDatabase) -> Definition {
+ /// `Definition` referenced or defined by this name, in case of a shorthand this will yield the field reference.
+ pub fn defined_or_referenced_field(self) -> Definition {
+ match self {
+ NameClass::Definition(it) | NameClass::ConstReference(it) => it,
+ NameClass::PatFieldShorthand { local_def: _, field_ref } => {
+ Definition::Field(field_ref)
+ }
+ }
+ }
+
+ /// `Definition` referenced or defined by this name, in case of a shorthand this will yield the local definition.
+ pub fn defined_or_referenced_local(self) -> Definition {
match self {
- NameClass::ExternCrate(krate) => Definition::ModuleDef(krate.root_module(db).into()),
NameClass::Definition(it) | NameClass::ConstReference(it) => it,
- NameClass::PatFieldShorthand { local_def: _, field_ref } => field_ref,
+ NameClass::PatFieldShorthand { local_def, field_ref: _ } => {
+ Definition::Local(local_def)
+ }
}
}
})
.and_then(|name_ref| NameRefClass::classify(sema, &name_ref))?;
- Some(NameClass::Definition(name_ref_class.referenced(sema.db)))
+ Some(NameClass::Definition(name_ref_class.referenced_field()))
} else {
let extern_crate = it.syntax().parent().and_then(ast::ExternCrate::cast)?;
- let resolved = sema.resolve_extern_crate(&extern_crate)?;
- Some(NameClass::ExternCrate(resolved))
+ let krate = sema.resolve_extern_crate(&extern_crate)?;
+ let root_module = krate.root_module(sema.db);
+ Some(NameClass::Definition(Definition::ModuleDef(root_module.into())))
}
},
ast::IdentPat(it) => {
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) {
- let field = Definition::Field(field);
return Some(NameClass::PatFieldShorthand { local_def: local, field_ref: field });
}
}
}
}
+/// 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 {
- ExternCrate(Crate),
Definition(Definition),
- FieldShorthand { local_ref: Local, field_ref: Definition },
+ FieldShorthand { local_ref: Local, field_ref: Field },
}
impl NameRefClass {
- /// `Definition`, which this name refers to.
- pub fn referenced(self, db: &dyn HirDatabase) -> Definition {
+ /// `Definition`, which this name refers to with a preference for the field reference in case of a field shorthand.
+ pub fn referenced_field(self) -> Definition {
+ match self {
+ NameRefClass::Definition(def) => def,
+ NameRefClass::FieldShorthand { local_ref: _, field_ref } => {
+ Definition::Field(field_ref)
+ }
+ }
+ }
+
+ /// `Definition`, which this name refers to with a preference for the local reference in case of a field shorthand.
+ pub fn referenced_local(self) -> Definition {
match self {
- NameRefClass::ExternCrate(krate) => Definition::ModuleDef(krate.root_module(db).into()),
NameRefClass::Definition(def) => def,
NameRefClass::FieldShorthand { local_ref, field_ref: _ } => {
- // FIXME: this is inherently ambiguous -- this name refers to
- // two different defs....
Definition::Local(local_ref)
}
}
if let Some(record_field) = ast::RecordExprField::for_field_name(name_ref) {
if let Some((field, local, _)) = sema.resolve_record_field(&record_field) {
- let field = Definition::Field(field);
let res = match local {
- None => NameRefClass::Definition(field),
+ None => NameRefClass::Definition(Definition::Field(field)),
Some(local) => {
NameRefClass::FieldShorthand { field_ref: field, local_ref: local }
}
}
}
}
-
- if let Some(resolved) = sema.resolve_path(&path) {
- if path.syntax().ancestors().find_map(ast::Attr::cast).is_some() {
- if let PathResolution::Def(ModuleDef::Function(func)) = resolved {
- if func.attrs(sema.db).by_key("proc_macro_attribute").exists() {
- return Some(NameRefClass::Definition(resolved.into()));
+ 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) => sema.resolve_path(&path).and_then(|resolved| {
+ match resolved {
+ // Don't wanna collide with builtin attributes here like `test` hence guard
+ // so only resolve to modules that aren't the last segment
+ PathResolution::Def(module @ ModuleDef::Module(_)) if path != top_path => {
+ cov_mark::hit!(name_ref_classify_attr_path_qualifier);
+ Some(NameRefClass::Definition(Definition::ModuleDef(module)))
+ }
+ PathResolution::Macro(mac) if mac.kind() == hir::MacroKind::Attr => {
+ Some(NameRefClass::Definition(Definition::Macro(mac)))
}
+ _ => None,
}
- } else {
- return Some(NameRefClass::Definition(resolved.into()));
- }
- }
+ }),
+ Some(false) => None,
+ None => sema.resolve_path(&path).map(Into::into).map(NameRefClass::Definition),
+ };
}
let extern_crate = ast::ExternCrate::cast(parent)?;
- let resolved = sema.resolve_extern_crate(&extern_crate)?;
- Some(NameRefClass::ExternCrate(resolved))
+ let krate = sema.resolve_extern_crate(&extern_crate)?;
+ let root_module = krate.root_module(sema.db);
+ Some(NameRefClass::Definition(Definition::ModuleDef(root_module.into())))
}
pub fn classify_lifetime(