]> git.lizzy.rs Git - rust.git/blob - crates/ide_db/src/defs.rs
Merge #7038
[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, HasVisibility, Impl, Label, LifetimeParam, Local, MacroDef,
10     Module, ModuleDef, Name, PathResolution, Semantics, TypeParam, Visibility,
11 };
12 use syntax::{
13     ast::{self, AstNode},
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     TypeParam(TypeParam),
28     LifetimeParam(LifetimeParam),
29     Label(Label),
30 }
31
32 impl Definition {
33     pub fn module(&self, db: &RootDatabase) -> Option<Module> {
34         match self {
35             Definition::Macro(it) => it.module(db),
36             Definition::Field(it) => Some(it.parent_def(db).module(db)),
37             Definition::ModuleDef(it) => it.module(db),
38             Definition::SelfType(it) => Some(it.module(db)),
39             Definition::Local(it) => Some(it.module(db)),
40             Definition::TypeParam(it) => Some(it.module(db)),
41             Definition::LifetimeParam(it) => Some(it.module(db)),
42             Definition::Label(it) => Some(it.module(db)),
43         }
44     }
45
46     pub fn visibility(&self, db: &RootDatabase) -> Option<Visibility> {
47         match self {
48             Definition::Macro(_) => None,
49             Definition::Field(sf) => Some(sf.visibility(db)),
50             Definition::ModuleDef(def) => def.definition_visibility(db),
51             Definition::SelfType(_) => None,
52             Definition::Local(_) => None,
53             Definition::TypeParam(_) => None,
54             Definition::LifetimeParam(_) => None,
55             Definition::Label(_) => None,
56         }
57     }
58
59     pub fn name(&self, db: &RootDatabase) -> Option<Name> {
60         let name = match self {
61             Definition::Macro(it) => it.name(db)?,
62             Definition::Field(it) => it.name(db),
63             Definition::ModuleDef(def) => match def {
64                 hir::ModuleDef::Module(it) => it.name(db)?,
65                 hir::ModuleDef::Function(it) => it.name(db),
66                 hir::ModuleDef::Adt(def) => match def {
67                     hir::Adt::Struct(it) => it.name(db),
68                     hir::Adt::Union(it) => it.name(db),
69                     hir::Adt::Enum(it) => it.name(db),
70                 },
71                 hir::ModuleDef::Variant(it) => it.name(db),
72                 hir::ModuleDef::Const(it) => it.name(db)?,
73                 hir::ModuleDef::Static(it) => it.name(db)?,
74                 hir::ModuleDef::Trait(it) => it.name(db),
75                 hir::ModuleDef::TypeAlias(it) => it.name(db),
76                 hir::ModuleDef::BuiltinType(_) => return None,
77             },
78             Definition::SelfType(_) => return None,
79             Definition::Local(it) => it.name(db)?,
80             Definition::TypeParam(it) => it.name(db),
81             Definition::LifetimeParam(it) => it.name(db),
82             Definition::Label(it) => it.name(db),
83         };
84         Some(name)
85     }
86 }
87
88 #[derive(Debug)]
89 pub enum NameClass {
90     ExternCrate(Crate),
91     Definition(Definition),
92     /// `None` in `if let None = Some(82) {}`.
93     ConstReference(Definition),
94     /// `field` in `if let Foo { field } = foo`.
95     PatFieldShorthand {
96         local_def: Local,
97         field_ref: Definition,
98     },
99 }
100
101 impl NameClass {
102     /// `Definition` defined by this name.
103     pub fn defined(self, db: &dyn HirDatabase) -> Option<Definition> {
104         let res = match self {
105             NameClass::ExternCrate(krate) => Definition::ModuleDef(krate.root_module(db).into()),
106             NameClass::Definition(it) => it,
107             NameClass::ConstReference(_) => return None,
108             NameClass::PatFieldShorthand { local_def, field_ref: _ } => {
109                 Definition::Local(local_def)
110             }
111         };
112         Some(res)
113     }
114
115     /// `Definition` referenced or defined by this name.
116     pub fn referenced_or_defined(self, db: &dyn HirDatabase) -> Definition {
117         match self {
118             NameClass::ExternCrate(krate) => Definition::ModuleDef(krate.root_module(db).into()),
119             NameClass::Definition(it) | NameClass::ConstReference(it) => it,
120             NameClass::PatFieldShorthand { local_def: _, field_ref } => field_ref,
121         }
122     }
123
124     pub fn classify(sema: &Semantics<RootDatabase>, name: &ast::Name) -> Option<NameClass> {
125         let _p = profile::span("classify_name");
126
127         let parent = name.syntax().parent()?;
128
129         if let Some(bind_pat) = ast::IdentPat::cast(parent.clone()) {
130             if let Some(def) = sema.resolve_bind_pat_to_const(&bind_pat) {
131                 return Some(NameClass::ConstReference(Definition::ModuleDef(def)));
132             }
133         }
134
135         match_ast! {
136             match parent {
137                 ast::Rename(it) => {
138                     if let Some(use_tree) = it.syntax().parent().and_then(ast::UseTree::cast) {
139                         let path = use_tree.path()?;
140                         let path_segment = path.segment()?;
141                         let name_ref_class = path_segment
142                             .name_ref()
143                             // The rename might be from a `self` token, so fallback to the name higher
144                             // in the use tree.
145                             .or_else(||{
146                                 if path_segment.self_token().is_none() {
147                                     return None;
148                                 }
149
150                                 let use_tree = use_tree
151                                     .syntax()
152                                     .parent()
153                                     .as_ref()
154                                     // Skip over UseTreeList
155                                     .and_then(SyntaxNode::parent)
156                                     .and_then(ast::UseTree::cast)?;
157                                 let path = use_tree.path()?;
158                                 let path_segment = path.segment()?;
159                                 path_segment.name_ref()
160                             })
161                             .and_then(|name_ref| NameRefClass::classify(sema, &name_ref))?;
162
163                         Some(NameClass::Definition(name_ref_class.referenced(sema.db)))
164                     } else {
165                         let extern_crate = it.syntax().parent().and_then(ast::ExternCrate::cast)?;
166                         let resolved = sema.resolve_extern_crate(&extern_crate)?;
167                         Some(NameClass::ExternCrate(resolved))
168                     }
169                 },
170                 ast::IdentPat(it) => {
171                     let local = sema.to_def(&it)?;
172
173                     if let Some(record_pat_field) = it.syntax().parent().and_then(ast::RecordPatField::cast) {
174                         if record_pat_field.name_ref().is_none() {
175                             if let Some(field) = sema.resolve_record_pat_field(&record_pat_field) {
176                                 let field = Definition::Field(field);
177                                 return Some(NameClass::PatFieldShorthand { local_def: local, field_ref: field });
178                             }
179                         }
180                     }
181
182                     Some(NameClass::Definition(Definition::Local(local)))
183                 },
184                 ast::RecordField(it) => {
185                     let field: hir::Field = sema.to_def(&it)?;
186                     Some(NameClass::Definition(Definition::Field(field)))
187                 },
188                 ast::Module(it) => {
189                     let def = sema.to_def(&it)?;
190                     Some(NameClass::Definition(Definition::ModuleDef(def.into())))
191                 },
192                 ast::Struct(it) => {
193                     let def: hir::Struct = sema.to_def(&it)?;
194                     Some(NameClass::Definition(Definition::ModuleDef(def.into())))
195                 },
196                 ast::Union(it) => {
197                     let def: hir::Union = sema.to_def(&it)?;
198                     Some(NameClass::Definition(Definition::ModuleDef(def.into())))
199                 },
200                 ast::Enum(it) => {
201                     let def: hir::Enum = sema.to_def(&it)?;
202                     Some(NameClass::Definition(Definition::ModuleDef(def.into())))
203                 },
204                 ast::Trait(it) => {
205                     let def: hir::Trait = sema.to_def(&it)?;
206                     Some(NameClass::Definition(Definition::ModuleDef(def.into())))
207                 },
208                 ast::Static(it) => {
209                     let def: hir::Static = sema.to_def(&it)?;
210                     Some(NameClass::Definition(Definition::ModuleDef(def.into())))
211                 },
212                 ast::Variant(it) => {
213                     let def: hir::Variant = sema.to_def(&it)?;
214                     Some(NameClass::Definition(Definition::ModuleDef(def.into())))
215                 },
216                 ast::Fn(it) => {
217                     let def: hir::Function = sema.to_def(&it)?;
218                     Some(NameClass::Definition(Definition::ModuleDef(def.into())))
219                 },
220                 ast::Const(it) => {
221                     let def: hir::Const = sema.to_def(&it)?;
222                     Some(NameClass::Definition(Definition::ModuleDef(def.into())))
223                 },
224                 ast::TypeAlias(it) => {
225                     let def: hir::TypeAlias = sema.to_def(&it)?;
226                     Some(NameClass::Definition(Definition::ModuleDef(def.into())))
227                 },
228                 ast::MacroRules(it) => {
229                     let def = sema.to_def(&it)?;
230                     Some(NameClass::Definition(Definition::Macro(def)))
231                 },
232                 ast::TypeParam(it) => {
233                     let def = sema.to_def(&it)?;
234                     Some(NameClass::Definition(Definition::TypeParam(def)))
235                 },
236                 _ => None,
237             }
238         }
239     }
240
241     pub fn classify_lifetime(
242         sema: &Semantics<RootDatabase>,
243         lifetime: &ast::Lifetime,
244     ) -> Option<NameClass> {
245         let _p = profile::span("classify_lifetime").detail(|| lifetime.to_string());
246         let parent = lifetime.syntax().parent()?;
247
248         match_ast! {
249             match parent {
250                 ast::LifetimeParam(it) => {
251                     let def = sema.to_def(&it)?;
252                     Some(NameClass::Definition(Definition::LifetimeParam(def)))
253                 },
254                 ast::Label(it) => {
255                     let def = sema.to_def(&it)?;
256                     Some(NameClass::Definition(Definition::Label(def)))
257                 },
258                 _ => None,
259             }
260         }
261     }
262 }
263
264 #[derive(Debug)]
265 pub enum NameRefClass {
266     ExternCrate(Crate),
267     Definition(Definition),
268     FieldShorthand { local_ref: Local, field_ref: Definition },
269 }
270
271 impl NameRefClass {
272     /// `Definition`, which this name refers to.
273     pub fn referenced(self, db: &dyn HirDatabase) -> Definition {
274         match self {
275             NameRefClass::ExternCrate(krate) => Definition::ModuleDef(krate.root_module(db).into()),
276             NameRefClass::Definition(def) => def,
277             NameRefClass::FieldShorthand { local_ref, field_ref: _ } => {
278                 // FIXME: this is inherently ambiguous -- this name refers to
279                 // two different defs....
280                 Definition::Local(local_ref)
281             }
282         }
283     }
284
285     // Note: we don't have unit-tests for this rather important function.
286     // It is primarily exercised via goto definition tests in `ide`.
287     pub fn classify(
288         sema: &Semantics<RootDatabase>,
289         name_ref: &ast::NameRef,
290     ) -> Option<NameRefClass> {
291         let _p = profile::span("classify_name_ref").detail(|| name_ref.to_string());
292
293         let parent = name_ref.syntax().parent()?;
294
295         if let Some(method_call) = ast::MethodCallExpr::cast(parent.clone()) {
296             if let Some(func) = sema.resolve_method_call(&method_call) {
297                 return Some(NameRefClass::Definition(Definition::ModuleDef(func.into())));
298             }
299         }
300
301         if let Some(field_expr) = ast::FieldExpr::cast(parent.clone()) {
302             if let Some(field) = sema.resolve_field(&field_expr) {
303                 return Some(NameRefClass::Definition(Definition::Field(field)));
304             }
305         }
306
307         if let Some(record_field) = ast::RecordExprField::for_field_name(name_ref) {
308             if let Some((field, local)) = sema.resolve_record_field(&record_field) {
309                 let field = Definition::Field(field);
310                 let res = match local {
311                     None => NameRefClass::Definition(field),
312                     Some(local) => {
313                         NameRefClass::FieldShorthand { field_ref: field, local_ref: local }
314                     }
315                 };
316                 return Some(res);
317             }
318         }
319
320         if let Some(record_pat_field) = ast::RecordPatField::cast(parent.clone()) {
321             if let Some(field) = sema.resolve_record_pat_field(&record_pat_field) {
322                 let field = Definition::Field(field);
323                 return Some(NameRefClass::Definition(field));
324             }
325         }
326
327         if ast::AssocTypeArg::cast(parent.clone()).is_some() {
328             // `Trait<Assoc = Ty>`
329             //        ^^^^^
330             let path = name_ref.syntax().ancestors().find_map(ast::Path::cast)?;
331             let resolved = sema.resolve_path(&path)?;
332             if let PathResolution::Def(ModuleDef::Trait(tr)) = resolved {
333                 if let Some(ty) = tr
334                     .items(sema.db)
335                     .iter()
336                     .filter_map(|assoc| match assoc {
337                         hir::AssocItem::TypeAlias(it) => Some(*it),
338                         _ => None,
339                     })
340                     .find(|alias| alias.name(sema.db).to_string() == **name_ref.text())
341                 {
342                     return Some(NameRefClass::Definition(Definition::ModuleDef(
343                         ModuleDef::TypeAlias(ty),
344                     )));
345                 }
346             }
347         }
348
349         if let Some(macro_call) = parent.ancestors().find_map(ast::MacroCall::cast) {
350             if let Some(path) = macro_call.path() {
351                 if path.qualifier().is_none() {
352                     // Only use this to resolve single-segment macro calls like `foo!()`. Multi-segment
353                     // paths are handled below (allowing `log<|>::info!` to resolve to the log crate).
354                     if let Some(macro_def) = sema.resolve_macro_call(&macro_call) {
355                         return Some(NameRefClass::Definition(Definition::Macro(macro_def)));
356                     }
357                 }
358             }
359         }
360
361         if let Some(path) = name_ref.syntax().ancestors().find_map(ast::Path::cast) {
362             if let Some(resolved) = sema.resolve_path(&path) {
363                 return Some(NameRefClass::Definition(resolved.into()));
364             }
365         }
366
367         let extern_crate = ast::ExternCrate::cast(parent)?;
368         let resolved = sema.resolve_extern_crate(&extern_crate)?;
369         Some(NameRefClass::ExternCrate(resolved))
370     }
371
372     pub fn classify_lifetime(
373         sema: &Semantics<RootDatabase>,
374         lifetime: &ast::Lifetime,
375     ) -> Option<NameRefClass> {
376         let _p = profile::span("classify_lifetime_ref").detail(|| lifetime.to_string());
377         let parent = lifetime.syntax().parent()?;
378         match parent.kind() {
379             SyntaxKind::BREAK_EXPR | SyntaxKind::CONTINUE_EXPR => {
380                 sema.resolve_label(lifetime).map(Definition::Label).map(NameRefClass::Definition)
381             }
382             SyntaxKind::LIFETIME_ARG
383             | SyntaxKind::SELF_PARAM
384             | SyntaxKind::TYPE_BOUND
385             | SyntaxKind::WHERE_PRED
386             | SyntaxKind::REF_TYPE => sema
387                 .resolve_lifetime_param(lifetime)
388                 .map(Definition::LifetimeParam)
389                 .map(NameRefClass::Definition),
390             // lifetime bounds, as in the 'b in 'a: 'b aren't wrapped in TypeBound nodes so we gotta check
391             // if our lifetime is in a LifetimeParam without being the constrained lifetime
392             _ if ast::LifetimeParam::cast(parent).and_then(|param| param.lifetime()).as_ref()
393                 != Some(lifetime) =>
394             {
395                 sema.resolve_lifetime_param(lifetime)
396                     .map(Definition::LifetimeParam)
397                     .map(NameRefClass::Definition)
398             }
399             _ => None,
400         }
401     }
402 }
403
404 impl From<PathResolution> for Definition {
405     fn from(path_resolution: PathResolution) -> Self {
406         match path_resolution {
407             PathResolution::Def(def) => Definition::ModuleDef(def),
408             PathResolution::AssocItem(item) => {
409                 let def = match item {
410                     hir::AssocItem::Function(it) => it.into(),
411                     hir::AssocItem::Const(it) => it.into(),
412                     hir::AssocItem::TypeAlias(it) => it.into(),
413                 };
414                 Definition::ModuleDef(def)
415             }
416             PathResolution::Local(local) => Definition::Local(local),
417             PathResolution::TypeParam(par) => Definition::TypeParam(par),
418             PathResolution::Macro(def) => Definition::Macro(def),
419             PathResolution::SelfType(impl_def) => Definition::SelfType(impl_def),
420         }
421     }
422 }