]> git.lizzy.rs Git - rust.git/blob - crates/ide_db/src/defs.rs
internal: document NameClass and NameRefClass
[rust.git] / crates / ide_db / src / defs.rs
1 //! `NameDefinition` keeps information about the element we want to search references for.
2 //! The element is represented by `NameKind`. It's located inside some `container` and
3 //! has a `visibility`, which defines a search scope.
4 //! Note that the reference search is possible for not all of the classified items.
5
6 // FIXME: this badly needs rename/rewrite (matklad, 2020-02-06).
7
8 use hir::{
9     db::HirDatabase, Crate, Field, GenericParam, HasVisibility, Impl, Label, Local, MacroDef,
10     Module, ModuleDef, Name, PathResolution, Semantics, Visibility,
11 };
12 use syntax::{
13     ast::{self, AstNode, PathSegmentKind},
14     match_ast, SyntaxKind, SyntaxNode,
15 };
16
17 use crate::RootDatabase;
18
19 // FIXME: a more precise name would probably be `Symbol`?
20 #[derive(Debug, PartialEq, Eq, Copy, Clone)]
21 pub enum Definition {
22     Macro(MacroDef),
23     Field(Field),
24     ModuleDef(ModuleDef),
25     SelfType(Impl),
26     Local(Local),
27     GenericParam(GenericParam),
28     Label(Label),
29 }
30
31 impl Definition {
32     pub fn module(&self, db: &RootDatabase) -> Option<Module> {
33         match self {
34             Definition::Macro(it) => it.module(db),
35             Definition::Field(it) => Some(it.parent_def(db).module(db)),
36             Definition::ModuleDef(it) => it.module(db),
37             Definition::SelfType(it) => Some(it.module(db)),
38             Definition::Local(it) => Some(it.module(db)),
39             Definition::GenericParam(it) => Some(it.module(db)),
40             Definition::Label(it) => Some(it.module(db)),
41         }
42     }
43
44     pub fn visibility(&self, db: &RootDatabase) -> Option<Visibility> {
45         match self {
46             Definition::Field(sf) => Some(sf.visibility(db)),
47             Definition::ModuleDef(def) => match def {
48                 ModuleDef::Module(it) => {
49                     // FIXME: should work like other cases here.
50                     let parent = it.parent(db)?;
51                     parent.visibility_of(db, def)
52                 }
53                 ModuleDef::Function(it) => Some(it.visibility(db)),
54                 ModuleDef::Adt(it) => Some(it.visibility(db)),
55                 ModuleDef::Const(it) => Some(it.visibility(db)),
56                 ModuleDef::Static(it) => Some(it.visibility(db)),
57                 ModuleDef::Trait(it) => Some(it.visibility(db)),
58                 ModuleDef::TypeAlias(it) => Some(it.visibility(db)),
59                 // NB: Variants don't have their own visibility, and just inherit
60                 // one from the parent. Not sure if that's the right thing to do.
61                 ModuleDef::Variant(it) => Some(it.parent_enum(db).visibility(db)),
62                 ModuleDef::BuiltinType(_) => None,
63             },
64             Definition::Macro(_)
65             | Definition::SelfType(_)
66             | Definition::Local(_)
67             | Definition::GenericParam(_)
68             | Definition::Label(_) => None,
69         }
70     }
71
72     pub fn name(&self, db: &RootDatabase) -> Option<Name> {
73         let name = match self {
74             Definition::Macro(it) => it.name(db)?,
75             Definition::Field(it) => it.name(db),
76             Definition::ModuleDef(def) => match def {
77                 hir::ModuleDef::Module(it) => it.name(db)?,
78                 hir::ModuleDef::Function(it) => it.name(db),
79                 hir::ModuleDef::Adt(def) => match def {
80                     hir::Adt::Struct(it) => it.name(db),
81                     hir::Adt::Union(it) => it.name(db),
82                     hir::Adt::Enum(it) => it.name(db),
83                 },
84                 hir::ModuleDef::Variant(it) => it.name(db),
85                 hir::ModuleDef::Const(it) => it.name(db)?,
86                 hir::ModuleDef::Static(it) => it.name(db)?,
87                 hir::ModuleDef::Trait(it) => it.name(db),
88                 hir::ModuleDef::TypeAlias(it) => it.name(db),
89                 hir::ModuleDef::BuiltinType(it) => it.name(),
90             },
91             Definition::SelfType(_) => return None,
92             Definition::Local(it) => it.name(db)?,
93             Definition::GenericParam(it) => it.name(db),
94             Definition::Label(it) => it.name(db),
95         };
96         Some(name)
97     }
98 }
99
100 /// On a first blush, a single `ast::Name` defines a single definition at some
101 /// scope. That is, that, by just looking at the syntactical category, we can
102 /// unambiguously define the semantic category.
103 ///
104 /// Sadly, that's not 100% true, there are special cases. To make sure that call
105 /// the code handles all the special cases correctly via exhaustive matching, we
106 /// add a [`NameClass`] enum which lists all of them!
107 ///
108 /// A model special case is `None` constant in pattern.
109 #[derive(Debug)]
110 pub enum NameClass {
111     ExternCrate(Crate),
112     Definition(Definition),
113     /// `None` in `if let None = Some(82) {}`.
114     /// Syntactically, it is a name, but semantically it is a reference.
115     ConstReference(Definition),
116     /// `field` in `if let Foo { field } = foo`. Here, `ast::Name` both Here the
117     /// name both introduces a definition into a local scope, and refers to an
118     /// existing definition.
119     PatFieldShorthand {
120         local_def: Local,
121         field_ref: Definition,
122     },
123 }
124
125 impl NameClass {
126     /// `Definition` defined by this name.
127     pub fn defined(self, db: &dyn HirDatabase) -> Option<Definition> {
128         let res = match self {
129             NameClass::ExternCrate(krate) => Definition::ModuleDef(krate.root_module(db).into()),
130             NameClass::Definition(it) => it,
131             NameClass::ConstReference(_) => return None,
132             NameClass::PatFieldShorthand { local_def, field_ref: _ } => {
133                 Definition::Local(local_def)
134             }
135         };
136         Some(res)
137     }
138
139     /// `Definition` referenced or defined by this name.
140     pub fn referenced_or_defined(self, db: &dyn HirDatabase) -> Definition {
141         match self {
142             NameClass::ExternCrate(krate) => Definition::ModuleDef(krate.root_module(db).into()),
143             NameClass::Definition(it) | NameClass::ConstReference(it) => it,
144             NameClass::PatFieldShorthand { local_def: _, field_ref } => field_ref,
145         }
146     }
147
148     pub fn classify(sema: &Semantics<RootDatabase>, name: &ast::Name) -> Option<NameClass> {
149         let _p = profile::span("classify_name");
150
151         let parent = name.syntax().parent()?;
152
153         if let Some(bind_pat) = ast::IdentPat::cast(parent.clone()) {
154             if let Some(def) = sema.resolve_bind_pat_to_const(&bind_pat) {
155                 return Some(NameClass::ConstReference(Definition::ModuleDef(def)));
156             }
157         }
158
159         match_ast! {
160             match parent {
161                 ast::Rename(it) => {
162                     if let Some(use_tree) = it.syntax().parent().and_then(ast::UseTree::cast) {
163                         let path = use_tree.path()?;
164                         let path_segment = path.segment()?;
165                         let name_ref_class = path_segment
166                             .kind()
167                             .and_then(|kind| {
168                                 match kind {
169                                     // The rename might be from a `self` token, so fallback to the name higher
170                                     // in the use tree.
171                                     PathSegmentKind::SelfKw => {
172                                         let use_tree = use_tree
173                                             .syntax()
174                                             .parent()
175                                             .as_ref()
176                                             // Skip over UseTreeList
177                                             .and_then(SyntaxNode::parent)
178                                             .and_then(ast::UseTree::cast)?;
179                                         let path = use_tree.path()?;
180                                         let path_segment = path.segment()?;
181                                         path_segment.name_ref()
182                                     },
183                                     PathSegmentKind::Name(name_ref) => Some(name_ref),
184                                     _ => return None,
185                                 }
186                             })
187                             .and_then(|name_ref| NameRefClass::classify(sema, &name_ref))?;
188
189                         Some(NameClass::Definition(name_ref_class.referenced(sema.db)))
190                     } else {
191                         let extern_crate = it.syntax().parent().and_then(ast::ExternCrate::cast)?;
192                         let resolved = sema.resolve_extern_crate(&extern_crate)?;
193                         Some(NameClass::ExternCrate(resolved))
194                     }
195                 },
196                 ast::IdentPat(it) => {
197                     let local = sema.to_def(&it)?;
198
199                     if let Some(record_pat_field) = it.syntax().parent().and_then(ast::RecordPatField::cast) {
200                         if record_pat_field.name_ref().is_none() {
201                             if let Some(field) = sema.resolve_record_pat_field(&record_pat_field) {
202                                 let field = Definition::Field(field);
203                                 return Some(NameClass::PatFieldShorthand { local_def: local, field_ref: field });
204                             }
205                         }
206                     }
207
208                     Some(NameClass::Definition(Definition::Local(local)))
209                 },
210                 ast::SelfParam(it) => {
211                     let def = sema.to_def(&it)?;
212                     Some(NameClass::Definition(Definition::Local(def)))
213                 },
214                 ast::RecordField(it) => {
215                     let field: hir::Field = sema.to_def(&it)?;
216                     Some(NameClass::Definition(Definition::Field(field)))
217                 },
218                 ast::Module(it) => {
219                     let def = sema.to_def(&it)?;
220                     Some(NameClass::Definition(Definition::ModuleDef(def.into())))
221                 },
222                 ast::Struct(it) => {
223                     let def: hir::Struct = sema.to_def(&it)?;
224                     Some(NameClass::Definition(Definition::ModuleDef(def.into())))
225                 },
226                 ast::Union(it) => {
227                     let def: hir::Union = sema.to_def(&it)?;
228                     Some(NameClass::Definition(Definition::ModuleDef(def.into())))
229                 },
230                 ast::Enum(it) => {
231                     let def: hir::Enum = sema.to_def(&it)?;
232                     Some(NameClass::Definition(Definition::ModuleDef(def.into())))
233                 },
234                 ast::Trait(it) => {
235                     let def: hir::Trait = sema.to_def(&it)?;
236                     Some(NameClass::Definition(Definition::ModuleDef(def.into())))
237                 },
238                 ast::Static(it) => {
239                     let def: hir::Static = sema.to_def(&it)?;
240                     Some(NameClass::Definition(Definition::ModuleDef(def.into())))
241                 },
242                 ast::Variant(it) => {
243                     let def: hir::Variant = sema.to_def(&it)?;
244                     Some(NameClass::Definition(Definition::ModuleDef(def.into())))
245                 },
246                 ast::Fn(it) => {
247                     let def: hir::Function = sema.to_def(&it)?;
248                     Some(NameClass::Definition(Definition::ModuleDef(def.into())))
249                 },
250                 ast::Const(it) => {
251                     let def: hir::Const = sema.to_def(&it)?;
252                     Some(NameClass::Definition(Definition::ModuleDef(def.into())))
253                 },
254                 ast::TypeAlias(it) => {
255                     let def: hir::TypeAlias = sema.to_def(&it)?;
256                     Some(NameClass::Definition(Definition::ModuleDef(def.into())))
257                 },
258                 ast::Macro(it) => {
259                     let def = sema.to_def(&it)?;
260                     Some(NameClass::Definition(Definition::Macro(def)))
261                 },
262                 ast::TypeParam(it) => {
263                     let def = sema.to_def(&it)?;
264                     Some(NameClass::Definition(Definition::GenericParam(def.into())))
265                 },
266                 ast::ConstParam(it) => {
267                     let def = sema.to_def(&it)?;
268                     Some(NameClass::Definition(Definition::GenericParam(def.into())))
269                 },
270                 _ => None,
271             }
272         }
273     }
274
275     pub fn classify_lifetime(
276         sema: &Semantics<RootDatabase>,
277         lifetime: &ast::Lifetime,
278     ) -> Option<NameClass> {
279         let _p = profile::span("classify_lifetime").detail(|| lifetime.to_string());
280         let parent = lifetime.syntax().parent()?;
281
282         match_ast! {
283             match parent {
284                 ast::LifetimeParam(it) => {
285                     let def = sema.to_def(&it)?;
286                     Some(NameClass::Definition(Definition::GenericParam(def.into())))
287                 },
288                 ast::Label(it) => {
289                     let def = sema.to_def(&it)?;
290                     Some(NameClass::Definition(Definition::Label(def)))
291                 },
292                 _ => None,
293             }
294         }
295     }
296 }
297
298 /// This is similar to [`NameClass`], but works for [`ast::NameRef`] rather than
299 /// for [`ast::Name`]. Similarly, what looks like a reference in syntax is a
300 /// reference most of the time, but there are a couple of annoying exceptions.
301 ///
302 /// A model special case is field shorthand syntax, which uses a single
303 /// reference to point to two different defs.
304 #[derive(Debug)]
305 pub enum NameRefClass {
306     ExternCrate(Crate),
307     Definition(Definition),
308     FieldShorthand { local_ref: Local, field_ref: Definition },
309 }
310
311 impl NameRefClass {
312     /// `Definition`, which this name refers to.
313     pub fn referenced(self, db: &dyn HirDatabase) -> Definition {
314         match self {
315             NameRefClass::ExternCrate(krate) => Definition::ModuleDef(krate.root_module(db).into()),
316             NameRefClass::Definition(def) => def,
317             NameRefClass::FieldShorthand { local_ref, field_ref: _ } => {
318                 // FIXME: this is inherently ambiguous -- this name refers to
319                 // two different defs....
320                 Definition::Local(local_ref)
321             }
322         }
323     }
324
325     // Note: we don't have unit-tests for this rather important function.
326     // It is primarily exercised via goto definition tests in `ide`.
327     pub fn classify(
328         sema: &Semantics<RootDatabase>,
329         name_ref: &ast::NameRef,
330     ) -> Option<NameRefClass> {
331         let _p = profile::span("classify_name_ref").detail(|| name_ref.to_string());
332
333         let parent = name_ref.syntax().parent()?;
334
335         if let Some(method_call) = ast::MethodCallExpr::cast(parent.clone()) {
336             if let Some(func) = sema.resolve_method_call(&method_call) {
337                 return Some(NameRefClass::Definition(Definition::ModuleDef(func.into())));
338             }
339         }
340
341         if let Some(field_expr) = ast::FieldExpr::cast(parent.clone()) {
342             if let Some(field) = sema.resolve_field(&field_expr) {
343                 return Some(NameRefClass::Definition(Definition::Field(field)));
344             }
345         }
346
347         if let Some(record_field) = ast::RecordExprField::for_field_name(name_ref) {
348             if let Some((field, local, _)) = sema.resolve_record_field(&record_field) {
349                 let field = Definition::Field(field);
350                 let res = match local {
351                     None => NameRefClass::Definition(field),
352                     Some(local) => {
353                         NameRefClass::FieldShorthand { field_ref: field, local_ref: local }
354                     }
355                 };
356                 return Some(res);
357             }
358         }
359
360         if let Some(record_pat_field) = ast::RecordPatField::cast(parent.clone()) {
361             if let Some(field) = sema.resolve_record_pat_field(&record_pat_field) {
362                 let field = Definition::Field(field);
363                 return Some(NameRefClass::Definition(field));
364             }
365         }
366
367         if let Some(assoc_type_arg) = ast::AssocTypeArg::cast(parent.clone()) {
368             if assoc_type_arg.name_ref().as_ref() == Some(name_ref) {
369                 // `Trait<Assoc = Ty>`
370                 //        ^^^^^
371                 let path = name_ref.syntax().ancestors().find_map(ast::Path::cast)?;
372                 let resolved = sema.resolve_path(&path)?;
373                 if let PathResolution::Def(ModuleDef::Trait(tr)) = resolved {
374                     // FIXME: resolve in supertraits
375                     if let Some(ty) = tr
376                         .items(sema.db)
377                         .iter()
378                         .filter_map(|assoc| match assoc {
379                             hir::AssocItem::TypeAlias(it) => Some(*it),
380                             _ => None,
381                         })
382                         .find(|alias| &alias.name(sema.db).to_string() == &name_ref.text())
383                     {
384                         return Some(NameRefClass::Definition(Definition::ModuleDef(
385                             ModuleDef::TypeAlias(ty),
386                         )));
387                     }
388                 }
389
390                 return None;
391             }
392         }
393
394         if let Some(path) = name_ref.syntax().ancestors().find_map(ast::Path::cast) {
395             if path.qualifier().is_none() {
396                 if let Some(macro_call) = path.syntax().parent().and_then(ast::MacroCall::cast) {
397                     // Only use this to resolve single-segment macro calls like `foo!()`. Multi-segment
398                     // paths are handled below (allowing `log$0::info!` to resolve to the log crate).
399                     if let Some(macro_def) = sema.resolve_macro_call(&macro_call) {
400                         return Some(NameRefClass::Definition(Definition::Macro(macro_def)));
401                     }
402                 }
403             }
404             let top_path = path.top_path();
405             let is_attribute_path = top_path
406                 .syntax()
407                 .ancestors()
408                 .find_map(ast::Attr::cast)
409                 .map(|attr| attr.path().as_ref() == Some(&top_path));
410             return match is_attribute_path {
411                 Some(true) => sema.resolve_path(&path).and_then(|resolved| {
412                     match resolved {
413                         // Don't wanna collide with builtin attributes here like `test` hence guard
414                         // so only resolve to modules that aren't the last segment
415                         PathResolution::Def(module @ ModuleDef::Module(_)) if path != top_path => {
416                             cov_mark::hit!(name_ref_classify_attr_path_qualifier);
417                             Some(NameRefClass::Definition(Definition::ModuleDef(module)))
418                         }
419                         PathResolution::Macro(mac) if mac.kind() == hir::MacroKind::Attr => {
420                             Some(NameRefClass::Definition(Definition::Macro(mac)))
421                         }
422                         _ => None,
423                     }
424                 }),
425                 Some(false) => None,
426                 None => sema.resolve_path(&path).map(Into::into).map(NameRefClass::Definition),
427             };
428         }
429
430         let extern_crate = ast::ExternCrate::cast(parent)?;
431         let resolved = sema.resolve_extern_crate(&extern_crate)?;
432         Some(NameRefClass::ExternCrate(resolved))
433     }
434
435     pub fn classify_lifetime(
436         sema: &Semantics<RootDatabase>,
437         lifetime: &ast::Lifetime,
438     ) -> Option<NameRefClass> {
439         let _p = profile::span("classify_lifetime_ref").detail(|| lifetime.to_string());
440         let parent = lifetime.syntax().parent()?;
441         match parent.kind() {
442             SyntaxKind::BREAK_EXPR | SyntaxKind::CONTINUE_EXPR => {
443                 sema.resolve_label(lifetime).map(Definition::Label).map(NameRefClass::Definition)
444             }
445             SyntaxKind::LIFETIME_ARG
446             | SyntaxKind::SELF_PARAM
447             | SyntaxKind::TYPE_BOUND
448             | SyntaxKind::WHERE_PRED
449             | SyntaxKind::REF_TYPE => sema
450                 .resolve_lifetime_param(lifetime)
451                 .map(GenericParam::LifetimeParam)
452                 .map(Definition::GenericParam)
453                 .map(NameRefClass::Definition),
454             // lifetime bounds, as in the 'b in 'a: 'b aren't wrapped in TypeBound nodes so we gotta check
455             // if our lifetime is in a LifetimeParam without being the constrained lifetime
456             _ if ast::LifetimeParam::cast(parent).and_then(|param| param.lifetime()).as_ref()
457                 != Some(lifetime) =>
458             {
459                 sema.resolve_lifetime_param(lifetime)
460                     .map(GenericParam::LifetimeParam)
461                     .map(Definition::GenericParam)
462                     .map(NameRefClass::Definition)
463             }
464             _ => None,
465         }
466     }
467 }
468
469 impl From<PathResolution> for Definition {
470     fn from(path_resolution: PathResolution) -> Self {
471         match path_resolution {
472             PathResolution::Def(def) => Definition::ModuleDef(def),
473             PathResolution::AssocItem(item) => {
474                 let def = match item {
475                     hir::AssocItem::Function(it) => it.into(),
476                     hir::AssocItem::Const(it) => it.into(),
477                     hir::AssocItem::TypeAlias(it) => it.into(),
478                 };
479                 Definition::ModuleDef(def)
480             }
481             PathResolution::Local(local) => Definition::Local(local),
482             PathResolution::TypeParam(par) => Definition::GenericParam(par.into()),
483             PathResolution::Macro(def) => Definition::Macro(def),
484             PathResolution::SelfType(impl_def) => Definition::SelfType(impl_def),
485             PathResolution::ConstParam(par) => Definition::GenericParam(par.into()),
486         }
487     }
488 }