diff options
Diffstat (limited to 'crates/ra_ide_api/src/references/classify.rs')
-rw-r--r-- | crates/ra_ide_api/src/references/classify.rs | 179 |
1 files changed, 179 insertions, 0 deletions
diff --git a/crates/ra_ide_api/src/references/classify.rs b/crates/ra_ide_api/src/references/classify.rs new file mode 100644 index 000000000..c8daff9b1 --- /dev/null +++ b/crates/ra_ide_api/src/references/classify.rs | |||
@@ -0,0 +1,179 @@ | |||
1 | //! Functions that are used to classify an element from its definition or reference. | ||
2 | |||
3 | use hir::{Either, FromSource, Module, ModuleSource, Path, PathResolution, Source, SourceAnalyzer}; | ||
4 | use ra_db::FileId; | ||
5 | use ra_syntax::{ast, match_ast, AstNode, AstPtr}; | ||
6 | use test_utils::tested_by; | ||
7 | |||
8 | use super::{ | ||
9 | name_definition::{from_assoc_item, from_module_def, from_pat, from_struct_field}, | ||
10 | NameDefinition, NameKind, | ||
11 | }; | ||
12 | use crate::db::RootDatabase; | ||
13 | |||
14 | pub(crate) fn classify_name( | ||
15 | db: &RootDatabase, | ||
16 | file_id: FileId, | ||
17 | name: &ast::Name, | ||
18 | ) -> Option<NameDefinition> { | ||
19 | let parent = name.syntax().parent()?; | ||
20 | let file_id = file_id.into(); | ||
21 | |||
22 | // FIXME: add ast::MacroCall(it) | ||
23 | match_ast! { | ||
24 | match parent { | ||
25 | ast::BindPat(it) => { | ||
26 | from_pat(db, file_id, AstPtr::new(&it)) | ||
27 | }, | ||
28 | ast::RecordFieldDef(it) => { | ||
29 | let ast = hir::FieldSource::Named(it); | ||
30 | let src = hir::Source { file_id, ast }; | ||
31 | let field = hir::StructField::from_source(db, src)?; | ||
32 | Some(from_struct_field(db, field)) | ||
33 | }, | ||
34 | ast::Module(it) => { | ||
35 | let def = { | ||
36 | if !it.has_semi() { | ||
37 | let ast = hir::ModuleSource::Module(it); | ||
38 | let src = hir::Source { file_id, ast }; | ||
39 | hir::Module::from_definition(db, src) | ||
40 | } else { | ||
41 | let src = hir::Source { file_id, ast: it }; | ||
42 | hir::Module::from_declaration(db, src) | ||
43 | } | ||
44 | }?; | ||
45 | Some(from_module_def(db, def.into(), None)) | ||
46 | }, | ||
47 | ast::StructDef(it) => { | ||
48 | let src = hir::Source { file_id, ast: it }; | ||
49 | let def = hir::Struct::from_source(db, src)?; | ||
50 | Some(from_module_def(db, def.into(), None)) | ||
51 | }, | ||
52 | ast::EnumDef(it) => { | ||
53 | let src = hir::Source { file_id, ast: it }; | ||
54 | let def = hir::Enum::from_source(db, src)?; | ||
55 | Some(from_module_def(db, def.into(), None)) | ||
56 | }, | ||
57 | ast::TraitDef(it) => { | ||
58 | let src = hir::Source { file_id, ast: it }; | ||
59 | let def = hir::Trait::from_source(db, src)?; | ||
60 | Some(from_module_def(db, def.into(), None)) | ||
61 | }, | ||
62 | ast::StaticDef(it) => { | ||
63 | let src = hir::Source { file_id, ast: it }; | ||
64 | let def = hir::Static::from_source(db, src)?; | ||
65 | Some(from_module_def(db, def.into(), None)) | ||
66 | }, | ||
67 | ast::EnumVariant(it) => { | ||
68 | let src = hir::Source { file_id, ast: it }; | ||
69 | let def = hir::EnumVariant::from_source(db, src)?; | ||
70 | Some(from_module_def(db, def.into(), None)) | ||
71 | }, | ||
72 | ast::FnDef(it) => { | ||
73 | let src = hir::Source { file_id, ast: it }; | ||
74 | let def = hir::Function::from_source(db, src)?; | ||
75 | if parent.parent().and_then(ast::ItemList::cast).is_some() { | ||
76 | Some(from_assoc_item(db, def.into())) | ||
77 | } else { | ||
78 | Some(from_module_def(db, def.into(), None)) | ||
79 | } | ||
80 | }, | ||
81 | ast::ConstDef(it) => { | ||
82 | let src = hir::Source { file_id, ast: it }; | ||
83 | let def = hir::Const::from_source(db, src)?; | ||
84 | if parent.parent().and_then(ast::ItemList::cast).is_some() { | ||
85 | Some(from_assoc_item(db, def.into())) | ||
86 | } else { | ||
87 | Some(from_module_def(db, def.into(), None)) | ||
88 | } | ||
89 | }, | ||
90 | ast::TypeAliasDef(it) => { | ||
91 | let src = hir::Source { file_id, ast: it }; | ||
92 | let def = hir::TypeAlias::from_source(db, src)?; | ||
93 | if parent.parent().and_then(ast::ItemList::cast).is_some() { | ||
94 | Some(from_assoc_item(db, def.into())) | ||
95 | } else { | ||
96 | Some(from_module_def(db, def.into(), None)) | ||
97 | } | ||
98 | }, | ||
99 | _ => None, | ||
100 | } | ||
101 | } | ||
102 | } | ||
103 | |||
104 | pub(crate) fn classify_name_ref( | ||
105 | db: &RootDatabase, | ||
106 | file_id: FileId, | ||
107 | name_ref: &ast::NameRef, | ||
108 | ) -> Option<NameDefinition> { | ||
109 | use PathResolution::*; | ||
110 | |||
111 | let parent = name_ref.syntax().parent()?; | ||
112 | let analyzer = SourceAnalyzer::new(db, file_id, name_ref.syntax(), None); | ||
113 | |||
114 | if let Some(method_call) = ast::MethodCallExpr::cast(parent.clone()) { | ||
115 | tested_by!(goto_definition_works_for_methods); | ||
116 | if let Some(func) = analyzer.resolve_method_call(&method_call) { | ||
117 | return Some(from_assoc_item(db, func.into())); | ||
118 | } | ||
119 | } | ||
120 | |||
121 | if let Some(field_expr) = ast::FieldExpr::cast(parent.clone()) { | ||
122 | tested_by!(goto_definition_works_for_fields); | ||
123 | if let Some(field) = analyzer.resolve_field(&field_expr) { | ||
124 | return Some(from_struct_field(db, field)); | ||
125 | } | ||
126 | } | ||
127 | |||
128 | if let Some(record_field) = ast::RecordField::cast(parent.clone()) { | ||
129 | tested_by!(goto_definition_works_for_record_fields); | ||
130 | if let Some(record_lit) = record_field.syntax().ancestors().find_map(ast::RecordLit::cast) { | ||
131 | let variant_def = analyzer.resolve_record_literal(&record_lit)?; | ||
132 | let hir_path = Path::from_name_ref(name_ref); | ||
133 | let hir_name = hir_path.as_ident()?; | ||
134 | let field = variant_def.field(db, hir_name)?; | ||
135 | return Some(from_struct_field(db, field)); | ||
136 | } | ||
137 | } | ||
138 | |||
139 | let ast = ModuleSource::from_child_node(db, file_id, &parent); | ||
140 | let file_id = file_id.into(); | ||
141 | // FIXME: find correct container and visibility for each case | ||
142 | let container = Module::from_definition(db, Source { file_id, ast })?; | ||
143 | let visibility = None; | ||
144 | |||
145 | if let Some(macro_call) = parent.ancestors().find_map(ast::MacroCall::cast) { | ||
146 | tested_by!(goto_definition_works_for_macros); | ||
147 | if let Some(macro_def) = analyzer.resolve_macro_call(db, ¯o_call) { | ||
148 | let kind = NameKind::Macro(macro_def); | ||
149 | return Some(NameDefinition { kind, container, visibility }); | ||
150 | } | ||
151 | } | ||
152 | |||
153 | let path = name_ref.syntax().ancestors().find_map(ast::Path::cast)?; | ||
154 | let resolved = analyzer.resolve_path(db, &path)?; | ||
155 | match resolved { | ||
156 | Def(def) => Some(from_module_def(db, def, Some(container))), | ||
157 | AssocItem(item) => Some(from_assoc_item(db, item)), | ||
158 | LocalBinding(Either::A(pat)) => from_pat(db, file_id, pat), | ||
159 | LocalBinding(Either::B(par)) => { | ||
160 | let kind = NameKind::SelfParam(par); | ||
161 | Some(NameDefinition { kind, container, visibility }) | ||
162 | } | ||
163 | GenericParam(par) => { | ||
164 | // FIXME: get generic param def | ||
165 | let kind = NameKind::GenericParam(par); | ||
166 | Some(NameDefinition { kind, container, visibility }) | ||
167 | } | ||
168 | Macro(def) => { | ||
169 | let kind = NameKind::Macro(def); | ||
170 | Some(NameDefinition { kind, container, visibility }) | ||
171 | } | ||
172 | SelfType(impl_block) => { | ||
173 | let ty = impl_block.target_ty(db); | ||
174 | let kind = NameKind::SelfType(ty); | ||
175 | let container = impl_block.module(); | ||
176 | Some(NameDefinition { kind, container, visibility }) | ||
177 | } | ||
178 | } | ||
179 | } | ||