]> git.lizzy.rs Git - rust.git/blob - crates/ra_ide_api/src/goto_definition.rs
Merge #754
[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,
5 };
6 use test_utils::tested_by;
7 use hir::Resolution;
8
9 use crate::{FilePosition, NavigationTarget, db::RootDatabase, RangeInfo};
10
11 pub(crate) fn goto_definition(
12     db: &RootDatabase,
13     position: FilePosition,
14 ) -> Option<RangeInfo<Vec<NavigationTarget>>> {
15     let file = db.parse(position.file_id);
16     let syntax = file.syntax();
17     if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(syntax, position.offset) {
18         let navs = reference_definition(db, position.file_id, name_ref).to_vec();
19         return Some(RangeInfo::new(name_ref.syntax().range(), navs.to_vec()));
20     }
21     if let Some(name) = find_node_at_offset::<ast::Name>(syntax, position.offset) {
22         let navs = name_definition(db, position.file_id, name)?;
23         return Some(RangeInfo::new(name.syntax().range(), navs));
24     }
25     None
26 }
27
28 pub(crate) enum ReferenceResult {
29     Exact(NavigationTarget),
30     Approximate(Vec<NavigationTarget>),
31 }
32
33 impl ReferenceResult {
34     fn to_vec(self) -> Vec<NavigationTarget> {
35         use self::ReferenceResult::*;
36         match self {
37             Exact(target) => vec![target],
38             Approximate(vec) => vec,
39         }
40     }
41 }
42
43 pub(crate) fn reference_definition(
44     db: &RootDatabase,
45     file_id: FileId,
46     name_ref: &ast::NameRef,
47 ) -> ReferenceResult {
48     use self::ReferenceResult::*;
49     if let Some(function) =
50         hir::source_binder::function_from_child_node(db, file_id, name_ref.syntax())
51     {
52         // Check if it is a method
53         if let Some(method_call) = name_ref
54             .syntax()
55             .parent()
56             .and_then(ast::MethodCallExpr::cast)
57         {
58             tested_by!(goto_definition_works_for_methods);
59             let infer_result = function.infer(db);
60             let syntax_mapping = function.body_syntax_mapping(db);
61             let expr = ast::Expr::cast(method_call.syntax()).unwrap();
62             if let Some(func) = syntax_mapping
63                 .node_expr(expr)
64                 .and_then(|it| infer_result.method_resolution(it))
65             {
66                 return Exact(NavigationTarget::from_function(db, func));
67             };
68         }
69         // It could also be a field access
70         if let Some(field_expr) = name_ref.syntax().parent().and_then(ast::FieldExpr::cast) {
71             tested_by!(goto_definition_works_for_fields);
72             let infer_result = function.infer(db);
73             let syntax_mapping = function.body_syntax_mapping(db);
74             let expr = ast::Expr::cast(field_expr.syntax()).unwrap();
75             if let Some(field) = syntax_mapping
76                 .node_expr(expr)
77                 .and_then(|it| infer_result.field_resolution(it))
78             {
79                 return Exact(NavigationTarget::from_field(db, field));
80             };
81         }
82     }
83     // Try name resolution
84     let resolver = hir::source_binder::resolver_for_node(db, file_id, name_ref.syntax());
85     if let Some(path) = name_ref
86         .syntax()
87         .ancestors()
88         .find_map(ast::Path::cast)
89         .and_then(hir::Path::from_ast)
90     {
91         let resolved = resolver.resolve_path(db, &path);
92         match resolved
93             .clone()
94             .take_types()
95             .or_else(|| resolved.take_values())
96         {
97             Some(Resolution::Def(def)) => return Exact(NavigationTarget::from_def(db, def)),
98             Some(Resolution::LocalBinding(pat)) => {
99                 let body = resolver.body().expect("no body for local binding");
100                 let syntax_mapping = body.syntax_mapping(db);
101                 let ptr = syntax_mapping
102                     .pat_syntax(pat)
103                     .expect("pattern not found in syntax mapping");
104                 let name = path
105                     .as_ident()
106                     .cloned()
107                     .expect("local binding from a multi-segment path");
108                 let nav = NavigationTarget::from_scope_entry(file_id, name, ptr);
109                 return Exact(nav);
110             }
111             Some(Resolution::GenericParam(..)) => {
112                 // TODO go to the generic param def
113             }
114             Some(Resolution::SelfType(_impl_block)) => {
115                 // TODO go to the implemented type
116             }
117             None => {}
118         }
119     }
120     // If that fails try the index based approach.
121     let navs = db
122         .index_resolve(name_ref)
123         .into_iter()
124         .map(NavigationTarget::from_symbol)
125         .collect();
126     Approximate(navs)
127 }
128
129 fn name_definition(
130     db: &RootDatabase,
131     file_id: FileId,
132     name: &ast::Name,
133 ) -> Option<Vec<NavigationTarget>> {
134     if let Some(module) = name.syntax().parent().and_then(ast::Module::cast) {
135         if module.has_semi() {
136             if let Some(child_module) =
137                 hir::source_binder::module_from_declaration(db, file_id, module)
138             {
139                 let nav = NavigationTarget::from_module(db, child_module);
140                 return Some(vec![nav]);
141             }
142         }
143     }
144     None
145 }
146
147 #[cfg(test)]
148 mod tests {
149     use test_utils::covers;
150
151     use crate::mock_analysis::analysis_and_position;
152
153     fn check_goto(fixuture: &str, expected: &str) {
154         let (analysis, pos) = analysis_and_position(fixuture);
155
156         let mut navs = analysis.goto_definition(pos).unwrap().unwrap().info;
157         assert_eq!(navs.len(), 1);
158         let nav = navs.pop().unwrap();
159         nav.assert_match(expected);
160     }
161
162     #[test]
163     fn goto_definition_works_in_items() {
164         check_goto(
165             "
166             //- /lib.rs
167             struct Foo;
168             enum E { X(Foo<|>) }
169             ",
170             "Foo STRUCT_DEF FileId(1) [0; 11) [7; 10)",
171         );
172     }
173
174     #[test]
175     fn goto_definition_resolves_correct_name() {
176         check_goto(
177             "
178             //- /lib.rs
179             use a::Foo;
180             mod a;
181             mod b;
182             enum E { X(Foo<|>) }
183             //- /a.rs
184             struct Foo;
185             //- /b.rs
186             struct Foo;
187             ",
188             "Foo STRUCT_DEF FileId(2) [0; 11) [7; 10)",
189         );
190     }
191
192     #[test]
193     fn goto_definition_works_for_module_declaration() {
194         check_goto(
195             "
196             //- /lib.rs
197             mod <|>foo;
198             //- /foo.rs
199             // empty
200             ",
201             "foo SOURCE_FILE FileId(2) [0; 10)",
202         );
203
204         check_goto(
205             "
206             //- /lib.rs
207             mod <|>foo;
208             //- /foo/mod.rs
209             // empty
210             ",
211             "foo SOURCE_FILE FileId(2) [0; 10)",
212         );
213     }
214
215     #[test]
216     fn goto_definition_works_for_methods() {
217         covers!(goto_definition_works_for_methods);
218         check_goto(
219             "
220             //- /lib.rs
221             struct Foo;
222             impl Foo {
223                 fn frobnicate(&self) {  }
224             }
225
226             fn bar(foo: &Foo) {
227                 foo.frobnicate<|>();
228             }
229             ",
230             "frobnicate FN_DEF FileId(1) [27; 52) [30; 40)",
231         );
232     }
233
234     #[test]
235     fn goto_definition_works_for_fields() {
236         covers!(goto_definition_works_for_fields);
237         check_goto(
238             "
239             //- /lib.rs
240             struct Foo {
241                 spam: u32,
242             }
243
244             fn bar(foo: &Foo) {
245                 foo.spam<|>;
246             }
247             ",
248             "spam NAMED_FIELD_DEF FileId(1) [17; 26) [17; 21)",
249         );
250     }
251 }