]> git.lizzy.rs Git - rust.git/blob - crates/ra_ide_api/src/goto_definition.rs
Merge #623
[rust.git] / crates / ra_ide_api / src / goto_definition.rs
1 use ra_db::{FileId, SyntaxDatabase};
2 use ra_syntax::{
3     AstNode, ast,
4     algo::find_node_at_offset,
5 };
6
7 use crate::{FilePosition, NavigationTarget, db::RootDatabase, RangeInfo};
8
9 pub(crate) fn goto_definition(
10     db: &RootDatabase,
11     position: FilePosition,
12 ) -> Option<RangeInfo<Vec<NavigationTarget>>> {
13     let file = db.source_file(position.file_id);
14     let syntax = file.syntax();
15     if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(syntax, position.offset) {
16         let navs = reference_definition(db, position.file_id, name_ref).to_vec();
17         return Some(RangeInfo::new(name_ref.syntax().range(), navs.to_vec()));
18     }
19     if let Some(name) = find_node_at_offset::<ast::Name>(syntax, position.offset) {
20         let navs = name_definition(db, position.file_id, name)?;
21         return Some(RangeInfo::new(name.syntax().range(), navs));
22     }
23     None
24 }
25
26 pub(crate) enum ReferenceResult {
27     Exact(NavigationTarget),
28     Approximate(Vec<NavigationTarget>),
29 }
30
31 impl ReferenceResult {
32     fn to_vec(self) -> Vec<NavigationTarget> {
33         use self::ReferenceResult::*;
34         match self {
35             Exact(target) => vec![target],
36             Approximate(vec) => vec,
37         }
38     }
39 }
40
41 pub(crate) fn reference_definition(
42     db: &RootDatabase,
43     file_id: FileId,
44     name_ref: &ast::NameRef,
45 ) -> ReferenceResult {
46     use self::ReferenceResult::*;
47     if let Some(function) =
48         hir::source_binder::function_from_child_node(db, file_id, name_ref.syntax())
49     {
50         let scope = function.scopes(db);
51         // First try to resolve the symbol locally
52         if let Some(entry) = scope.resolve_local_name(name_ref) {
53             let nav = NavigationTarget::from_scope_entry(file_id, &entry);
54             return Exact(nav);
55         };
56
57         // Next check if it is a method
58         if let Some(method_call) = name_ref
59             .syntax()
60             .parent()
61             .and_then(ast::MethodCallExpr::cast)
62         {
63             let infer_result = function.infer(db);
64             let syntax_mapping = function.body_syntax_mapping(db);
65             let expr = ast::Expr::cast(method_call.syntax()).unwrap();
66             if let Some(func) = syntax_mapping
67                 .node_expr(expr)
68                 .and_then(|it| infer_result.method_resolution(it))
69             {
70                 return Exact(NavigationTarget::from_function(db, func));
71             };
72         }
73     }
74     // Then try module name resolution
75     if let Some(module) = hir::source_binder::module_from_child_node(db, file_id, name_ref.syntax())
76     {
77         if let Some(path) = name_ref
78             .syntax()
79             .ancestors()
80             .find_map(ast::Path::cast)
81             .and_then(hir::Path::from_ast)
82         {
83             let resolved = module.resolve_path(db, &path);
84             if let Some(def_id) = resolved.take_types().or(resolved.take_values()) {
85                 if let Some(target) = NavigationTarget::from_def(db, def_id) {
86                     return Exact(target);
87                 }
88             }
89         }
90     }
91     // If that fails try the index based approach.
92     let navs = db
93         .index_resolve(name_ref)
94         .into_iter()
95         .map(NavigationTarget::from_symbol)
96         .collect();
97     Approximate(navs)
98 }
99
100 fn name_definition(
101     db: &RootDatabase,
102     file_id: FileId,
103     name: &ast::Name,
104 ) -> Option<Vec<NavigationTarget>> {
105     if let Some(module) = name.syntax().parent().and_then(ast::Module::cast) {
106         if module.has_semi() {
107             if let Some(child_module) =
108                 hir::source_binder::module_from_declaration(db, file_id, module)
109             {
110                 let nav = NavigationTarget::from_module(db, child_module);
111                 return Some(vec![nav]);
112             }
113         }
114     }
115     None
116 }
117
118 #[cfg(test)]
119 mod tests {
120     use crate::mock_analysis::analysis_and_position;
121
122     fn check_goto(fixuture: &str, expected: &str) {
123         let (analysis, pos) = analysis_and_position(fixuture);
124
125         let mut navs = analysis.goto_definition(pos).unwrap().unwrap().info;
126         assert_eq!(navs.len(), 1);
127         let nav = navs.pop().unwrap();
128         nav.assert_match(expected);
129     }
130
131     #[test]
132     fn goto_definition_works_in_items() {
133         check_goto(
134             "
135             //- /lib.rs
136             struct Foo;
137             enum E { X(Foo<|>) }
138             ",
139             "Foo STRUCT_DEF FileId(1) [0; 11) [7; 10)",
140         );
141     }
142
143     #[test]
144     fn goto_definition_resolves_correct_name() {
145         check_goto(
146             "
147             //- /lib.rs
148             use a::Foo;
149             mod a;
150             mod b;
151             enum E { X(Foo<|>) }
152             //- /a.rs
153             struct Foo;
154             //- /b.rs
155             struct Foo;
156             ",
157             "Foo STRUCT_DEF FileId(2) [0; 11) [7; 10)",
158         );
159     }
160
161     #[test]
162     fn goto_definition_works_for_module_declaration() {
163         check_goto(
164             "
165             //- /lib.rs
166             mod <|>foo;
167             //- /foo.rs
168             // empty
169             ",
170             "foo SOURCE_FILE FileId(2) [0; 10)",
171         );
172
173         check_goto(
174             "
175             //- /lib.rs
176             mod <|>foo;
177             //- /foo/mod.rs
178             // empty
179             ",
180             "foo SOURCE_FILE FileId(2) [0; 10)",
181         );
182     }
183
184     #[test]
185     fn goto_definition_works_for_methods() {
186         check_goto(
187             "
188             //- /lib.rs
189             struct Foo;
190             impl Foo {
191                 fn frobnicate(&self) {  }
192             }
193
194             fn bar(foo: &Foo) {
195                 foo.frobnicate<|>();
196             }
197             ",
198             "frobnicate FN_DEF FileId(1) [27; 52) [30; 40)",
199         );
200
201         check_goto(
202             "
203             //- /lib.rs
204             mod <|>foo;
205             //- /foo/mod.rs
206             // empty
207             ",
208             "foo SOURCE_FILE FileId(2) [0; 10)",
209         );
210     }
211 }