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