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