]> git.lizzy.rs Git - rust.git/blob - crates/ra_ide_api/src/goto_definition.rs
Merge #693
[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.clone().take_types().or(resolved.take_values()) {
93             Some(Resolution::Def(def)) => return Exact(NavigationTarget::from_def(db, def)),
94             Some(Resolution::LocalBinding(pat)) => {
95                 let body = resolver.body().expect("no body for local binding");
96                 let syntax_mapping = body.syntax_mapping(db);
97                 let ptr = syntax_mapping
98                     .pat_syntax(pat)
99                     .expect("pattern not found in syntax mapping");
100                 let name = path
101                     .as_ident()
102                     .cloned()
103                     .expect("local binding from a multi-segment path");
104                 let nav = NavigationTarget::from_scope_entry(file_id, name, ptr);
105                 return Exact(nav);
106             }
107             Some(Resolution::GenericParam(..)) => {
108                 // TODO go to the generic param def
109             }
110             Some(Resolution::SelfType(_impl_block)) => {
111                 // TODO go to the implemented type
112             }
113             None => {}
114         }
115     }
116     // If that fails try the index based approach.
117     let navs = db
118         .index_resolve(name_ref)
119         .into_iter()
120         .map(NavigationTarget::from_symbol)
121         .collect();
122     Approximate(navs)
123 }
124
125 fn name_definition(
126     db: &RootDatabase,
127     file_id: FileId,
128     name: &ast::Name,
129 ) -> Option<Vec<NavigationTarget>> {
130     if let Some(module) = name.syntax().parent().and_then(ast::Module::cast) {
131         if module.has_semi() {
132             if let Some(child_module) =
133                 hir::source_binder::module_from_declaration(db, file_id, module)
134             {
135                 let nav = NavigationTarget::from_module(db, child_module);
136                 return Some(vec![nav]);
137             }
138         }
139     }
140     None
141 }
142
143 #[cfg(test)]
144 mod tests {
145     use test_utils::covers;
146
147     use crate::mock_analysis::analysis_and_position;
148
149     fn check_goto(fixuture: &str, expected: &str) {
150         let (analysis, pos) = analysis_and_position(fixuture);
151
152         let mut navs = analysis.goto_definition(pos).unwrap().unwrap().info;
153         assert_eq!(navs.len(), 1);
154         let nav = navs.pop().unwrap();
155         nav.assert_match(expected);
156     }
157
158     #[test]
159     fn goto_definition_works_in_items() {
160         check_goto(
161             "
162             //- /lib.rs
163             struct Foo;
164             enum E { X(Foo<|>) }
165             ",
166             "Foo STRUCT_DEF FileId(1) [0; 11) [7; 10)",
167         );
168     }
169
170     #[test]
171     fn goto_definition_resolves_correct_name() {
172         check_goto(
173             "
174             //- /lib.rs
175             use a::Foo;
176             mod a;
177             mod b;
178             enum E { X(Foo<|>) }
179             //- /a.rs
180             struct Foo;
181             //- /b.rs
182             struct Foo;
183             ",
184             "Foo STRUCT_DEF FileId(2) [0; 11) [7; 10)",
185         );
186     }
187
188     #[test]
189     fn goto_definition_works_for_module_declaration() {
190         check_goto(
191             "
192             //- /lib.rs
193             mod <|>foo;
194             //- /foo.rs
195             // empty
196             ",
197             "foo SOURCE_FILE FileId(2) [0; 10)",
198         );
199
200         check_goto(
201             "
202             //- /lib.rs
203             mod <|>foo;
204             //- /foo/mod.rs
205             // empty
206             ",
207             "foo SOURCE_FILE FileId(2) [0; 10)",
208         );
209     }
210
211     #[test]
212     fn goto_definition_works_for_methods() {
213         covers!(goto_definition_works_for_methods);
214         check_goto(
215             "
216             //- /lib.rs
217             struct Foo;
218             impl Foo {
219                 fn frobnicate(&self) {  }
220             }
221
222             fn bar(foo: &Foo) {
223                 foo.frobnicate<|>();
224             }
225             ",
226             "frobnicate FN_DEF FileId(1) [27; 52) [30; 40)",
227         );
228     }
229
230     #[test]
231     fn goto_definition_works_for_fields() {
232         covers!(goto_definition_works_for_fields);
233         check_goto(
234             "
235             //- /lib.rs
236             struct Foo {
237                 spam: u32,
238             }
239
240             fn bar(foo: &Foo) {
241                 foo.spam<|>;
242             }
243             ",
244             "spam NAMED_FIELD_DEF FileId(1) [17; 26) [17; 21)",
245         );
246     }
247 }