]> git.lizzy.rs Git - rust.git/blob - crates/ra_ide_api/src/goto_definition.rs
rename syntax_mapping as well
[rust.git] / crates / ra_ide_api / src / goto_definition.rs
1 use ra_db::{FileId, SourceDatabase};
2 use ra_syntax::{
3     AstNode, ast,
4     algo::{find_node_at_offset, visit::{visitor, Visitor}},
5     SyntaxNode,
6 };
7 use test_utils::tested_by;
8 use hir::Resolution;
9
10 use crate::{FilePosition, NavigationTarget, db::RootDatabase, RangeInfo};
11
12 pub(crate) fn goto_definition(
13     db: &RootDatabase,
14     position: FilePosition,
15 ) -> Option<RangeInfo<Vec<NavigationTarget>>> {
16     let file = db.parse(position.file_id);
17     let syntax = file.syntax();
18     if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(syntax, position.offset) {
19         let navs = reference_definition(db, position.file_id, name_ref).to_vec();
20         return Some(RangeInfo::new(name_ref.syntax().range(), navs.to_vec()));
21     }
22     if let Some(name) = find_node_at_offset::<ast::Name>(syntax, position.offset) {
23         let navs = name_definition(db, position.file_id, name)?;
24         return Some(RangeInfo::new(name.syntax().range(), navs));
25     }
26     None
27 }
28
29 pub(crate) enum ReferenceResult {
30     Exact(NavigationTarget),
31     Approximate(Vec<NavigationTarget>),
32 }
33
34 impl ReferenceResult {
35     fn to_vec(self) -> Vec<NavigationTarget> {
36         use self::ReferenceResult::*;
37         match self {
38             Exact(target) => vec![target],
39             Approximate(vec) => vec,
40         }
41     }
42 }
43
44 pub(crate) fn reference_definition(
45     db: &RootDatabase,
46     file_id: FileId,
47     name_ref: &ast::NameRef,
48 ) -> ReferenceResult {
49     use self::ReferenceResult::*;
50     if let Some(function) =
51         hir::source_binder::function_from_child_node(db, file_id, name_ref.syntax())
52     {
53         // Check if it is a method
54         if let Some(method_call) = name_ref.syntax().parent().and_then(ast::MethodCallExpr::cast) {
55             tested_by!(goto_definition_works_for_methods);
56             let infer_result = function.infer(db);
57             let source_map = function.body_source_map(db);
58             let expr = ast::Expr::cast(method_call.syntax()).unwrap();
59             if let Some(func) =
60                 source_map.node_expr(expr).and_then(|it| infer_result.method_resolution(it))
61             {
62                 return Exact(NavigationTarget::from_function(db, func));
63             };
64         }
65         // It could also be a field access
66         if let Some(field_expr) = name_ref.syntax().parent().and_then(ast::FieldExpr::cast) {
67             tested_by!(goto_definition_works_for_fields);
68             let infer_result = function.infer(db);
69             let source_map = function.body_source_map(db);
70             let expr = ast::Expr::cast(field_expr.syntax()).unwrap();
71             if let Some(field) =
72                 source_map.node_expr(expr).and_then(|it| infer_result.field_resolution(it))
73             {
74                 return Exact(NavigationTarget::from_field(db, field));
75             };
76         }
77
78         // It could also be a named field
79         if let Some(field_expr) = name_ref.syntax().parent().and_then(ast::NamedField::cast) {
80             tested_by!(goto_definition_works_for_named_fields);
81
82             let infer_result = function.infer(db);
83             let source_map = function.body_source_map(db);
84
85             let struct_lit = field_expr.syntax().ancestors().find_map(ast::StructLit::cast);
86
87             if let Some(expr) = struct_lit.and_then(|lit| source_map.node_expr(lit.into())) {
88                 let ty = infer_result[expr].clone();
89                 if let hir::Ty::Adt { def_id, .. } = ty {
90                     if let hir::AdtDef::Struct(s) = def_id {
91                         let hir_path = hir::Path::from_name_ref(name_ref);
92                         let hir_name = hir_path.as_ident().unwrap();
93
94                         if let Some(field) = s.field(db, hir_name) {
95                             return Exact(NavigationTarget::from_field(db, field));
96                         }
97                     }
98                 }
99             }
100         }
101     }
102     // Try name resolution
103     let resolver = hir::source_binder::resolver_for_node(db, file_id, name_ref.syntax());
104     if let Some(path) =
105         name_ref.syntax().ancestors().find_map(ast::Path::cast).and_then(hir::Path::from_ast)
106     {
107         let resolved = resolver.resolve_path(db, &path);
108         match resolved.clone().take_types().or_else(|| resolved.take_values()) {
109             Some(Resolution::Def(def)) => return Exact(NavigationTarget::from_def(db, def)),
110             Some(Resolution::LocalBinding(pat)) => {
111                 let body = resolver.body().expect("no body for local binding");
112                 let source_map = body.source_map(db);
113                 let ptr = source_map.pat_syntax(pat).expect("pattern not found in syntax mapping");
114                 let name =
115                     path.as_ident().cloned().expect("local binding from a multi-segment path");
116                 let nav = NavigationTarget::from_scope_entry(file_id, name, ptr);
117                 return Exact(nav);
118             }
119             Some(Resolution::GenericParam(..)) => {
120                 // TODO: go to the generic param def
121             }
122             Some(Resolution::SelfType(_impl_block)) => {
123                 // TODO: go to the implemented type
124             }
125             None => {}
126         }
127     }
128     // If that fails try the index based approach.
129     let navs = crate::symbol_index::index_resolve(db, name_ref)
130         .into_iter()
131         .map(NavigationTarget::from_symbol)
132         .collect();
133     Approximate(navs)
134 }
135
136 pub(crate) fn name_definition(
137     db: &RootDatabase,
138     file_id: FileId,
139     name: &ast::Name,
140 ) -> Option<Vec<NavigationTarget>> {
141     let parent = name.syntax().parent()?;
142
143     if let Some(module) = ast::Module::cast(&parent) {
144         if module.has_semi() {
145             if let Some(child_module) =
146                 hir::source_binder::module_from_declaration(db, file_id, module)
147             {
148                 let nav = NavigationTarget::from_module(db, child_module);
149                 return Some(vec![nav]);
150             }
151         }
152     }
153
154     if let Some(nav) = named_target(file_id, &parent) {
155         return Some(vec![nav]);
156     }
157
158     None
159 }
160
161 fn named_target(file_id: FileId, node: &SyntaxNode) -> Option<NavigationTarget> {
162     visitor()
163         .visit(|node: &ast::StructDef| NavigationTarget::from_named(file_id, node))
164         .visit(|node: &ast::EnumDef| NavigationTarget::from_named(file_id, node))
165         .visit(|node: &ast::EnumVariant| NavigationTarget::from_named(file_id, node))
166         .visit(|node: &ast::FnDef| NavigationTarget::from_named(file_id, node))
167         .visit(|node: &ast::TypeAliasDef| NavigationTarget::from_named(file_id, node))
168         .visit(|node: &ast::ConstDef| NavigationTarget::from_named(file_id, node))
169         .visit(|node: &ast::StaticDef| NavigationTarget::from_named(file_id, node))
170         .visit(|node: &ast::TraitDef| NavigationTarget::from_named(file_id, node))
171         .visit(|node: &ast::NamedFieldDef| NavigationTarget::from_named(file_id, node))
172         .visit(|node: &ast::Module| NavigationTarget::from_named(file_id, node))
173         .accept(node)
174 }
175
176 #[cfg(test)]
177 mod tests {
178     use test_utils::covers;
179
180     use crate::mock_analysis::analysis_and_position;
181
182     fn check_goto(fixture: &str, expected: &str) {
183         let (analysis, pos) = analysis_and_position(fixture);
184
185         let mut navs = analysis.goto_definition(pos).unwrap().unwrap().info;
186         assert_eq!(navs.len(), 1);
187         let nav = navs.pop().unwrap();
188         nav.assert_match(expected);
189     }
190
191     #[test]
192     fn goto_definition_works_in_items() {
193         check_goto(
194             "
195             //- /lib.rs
196             struct Foo;
197             enum E { X(Foo<|>) }
198             ",
199             "Foo STRUCT_DEF FileId(1) [0; 11) [7; 10)",
200         );
201     }
202
203     #[test]
204     fn goto_definition_resolves_correct_name() {
205         check_goto(
206             "
207             //- /lib.rs
208             use a::Foo;
209             mod a;
210             mod b;
211             enum E { X(Foo<|>) }
212             //- /a.rs
213             struct Foo;
214             //- /b.rs
215             struct Foo;
216             ",
217             "Foo STRUCT_DEF FileId(2) [0; 11) [7; 10)",
218         );
219     }
220
221     #[test]
222     fn goto_definition_works_for_module_declaration() {
223         check_goto(
224             "
225             //- /lib.rs
226             mod <|>foo;
227             //- /foo.rs
228             // empty
229             ",
230             "foo SOURCE_FILE FileId(2) [0; 10)",
231         );
232
233         check_goto(
234             "
235             //- /lib.rs
236             mod <|>foo;
237             //- /foo/mod.rs
238             // empty
239             ",
240             "foo SOURCE_FILE FileId(2) [0; 10)",
241         );
242     }
243
244     #[test]
245     fn goto_definition_works_for_methods() {
246         covers!(goto_definition_works_for_methods);
247         check_goto(
248             "
249             //- /lib.rs
250             struct Foo;
251             impl Foo {
252                 fn frobnicate(&self) {  }
253             }
254
255             fn bar(foo: &Foo) {
256                 foo.frobnicate<|>();
257             }
258             ",
259             "frobnicate FN_DEF FileId(1) [27; 52) [30; 40)",
260         );
261     }
262
263     #[test]
264     fn goto_definition_works_for_fields() {
265         covers!(goto_definition_works_for_fields);
266         check_goto(
267             "
268             //- /lib.rs
269             struct Foo {
270                 spam: u32,
271             }
272
273             fn bar(foo: &Foo) {
274                 foo.spam<|>;
275             }
276             ",
277             "spam NAMED_FIELD_DEF FileId(1) [17; 26) [17; 21)",
278         );
279     }
280
281     #[test]
282     fn goto_definition_works_for_named_fields() {
283         covers!(goto_definition_works_for_named_fields);
284         check_goto(
285             "
286             //- /lib.rs
287             struct Foo {
288                 spam: u32,
289             }
290
291             fn bar() -> Foo {
292                 Foo {
293                     spam<|>: 0,
294                 }
295             }
296             ",
297             "spam NAMED_FIELD_DEF FileId(1) [17; 26) [17; 21)",
298         );
299     }
300
301     #[test]
302     fn goto_definition_works_when_used_on_definition_name_itself() {
303         check_goto(
304             "
305             //- /lib.rs
306             struct Foo<|> { value: u32 }
307             ",
308             "Foo STRUCT_DEF FileId(1) [0; 25) [7; 10)",
309         );
310
311         check_goto(
312             r#"
313             //- /lib.rs
314             struct Foo {
315                 field<|>: string,
316             }
317             "#,
318             "field NAMED_FIELD_DEF FileId(1) [17; 30) [17; 22)",
319         );
320
321         check_goto(
322             "
323             //- /lib.rs
324             fn foo_test<|>() {
325             }
326             ",
327             "foo_test FN_DEF FileId(1) [0; 17) [3; 11)",
328         );
329
330         check_goto(
331             "
332             //- /lib.rs
333             enum Foo<|> {
334                 Variant,
335             }
336             ",
337             "Foo ENUM_DEF FileId(1) [0; 25) [5; 8)",
338         );
339
340         check_goto(
341             "
342             //- /lib.rs
343             enum Foo {
344                 Variant1,
345                 Variant2<|>,
346                 Variant3,
347             }
348             ",
349             "Variant2 ENUM_VARIANT FileId(1) [29; 37) [29; 37)",
350         );
351
352         check_goto(
353             r#"
354             //- /lib.rs
355             static inner<|>: &str = "";
356             "#,
357             "inner STATIC_DEF FileId(1) [0; 24) [7; 12)",
358         );
359
360         check_goto(
361             r#"
362             //- /lib.rs
363             const inner<|>: &str = "";
364             "#,
365             "inner CONST_DEF FileId(1) [0; 23) [6; 11)",
366         );
367
368         check_goto(
369             r#"
370             //- /lib.rs
371             type Thing<|> = Option<()>;
372             "#,
373             "Thing TYPE_ALIAS_DEF FileId(1) [0; 24) [5; 10)",
374         );
375
376         check_goto(
377             r#"
378             //- /lib.rs
379             trait Foo<|> {
380             }
381             "#,
382             "Foo TRAIT_DEF FileId(1) [0; 13) [6; 9)",
383         );
384
385         check_goto(
386             r#"
387             //- /lib.rs
388             mod bar<|> {
389             }
390             "#,
391             "bar MODULE FileId(1) [0; 11) [4; 7)",
392         );
393     }
394 }