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