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