// FIXME: this badly needs rename/rewrite (matklad, 2020-02-06).
use hir::{
- db::HirDatabase, Crate, Field, GenericParam, 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},
}
}
+/// 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::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::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::Definition(it) | NameClass::ConstReference(it) => it,
+ 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 }
}
// 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 => {
}
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(