]> git.lizzy.rs Git - rust.git/blob - crates/ra_ide_api/src/goto_definition.rs
Merge #521
[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(fn_descr) =
51         hir::source_binder::function_from_child_node(db, file_id, name_ref.syntax())?
52     {
53         let scope = fn_descr.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     // Then try module name resolution
61     if let Some(module) =
62         hir::source_binder::module_from_child_node(db, file_id, name_ref.syntax())?
63     {
64         if let Some(path) = name_ref
65             .syntax()
66             .ancestors()
67             .find_map(ast::Path::cast)
68             .and_then(hir::Path::from_ast)
69         {
70             let resolved = module.resolve_path(db, &path)?;
71             if let Some(def_id) = resolved.take_types().or(resolved.take_values()) {
72                 if let Some(target) = NavigationTarget::from_def(db, def_id.resolve(db)?)? {
73                     return Ok(Exact(target));
74                 }
75             }
76         }
77     }
78     // If that fails try the index based approach.
79     let navs = db
80         .index_resolve(name_ref)?
81         .into_iter()
82         .map(NavigationTarget::from_symbol)
83         .collect();
84     Ok(Approximate(navs))
85 }
86
87 fn name_definition(
88     db: &RootDatabase,
89     file_id: FileId,
90     name: &ast::Name,
91 ) -> Cancelable<Option<Vec<NavigationTarget>>> {
92     if let Some(module) = name.syntax().parent().and_then(ast::Module::cast) {
93         if module.has_semi() {
94             if let Some(child_module) =
95                 hir::source_binder::module_from_declaration(db, file_id, module)?
96             {
97                 let nav = NavigationTarget::from_module(db, child_module)?;
98                 return Ok(Some(vec![nav]));
99             }
100         }
101     }
102     Ok(None)
103 }
104
105 #[cfg(test)]
106 mod tests {
107     use crate::mock_analysis::analysis_and_position;
108
109     fn check_goto(fixuture: &str, expected: &str) {
110         let (analysis, pos) = analysis_and_position(fixuture);
111
112         let mut navs = analysis.goto_definition(pos).unwrap().unwrap().info;
113         assert_eq!(navs.len(), 1);
114         let nav = navs.pop().unwrap();
115         nav.assert_match(expected);
116     }
117
118     #[test]
119     fn goto_definition_works_in_items() {
120         check_goto(
121             "
122             //- /lib.rs
123             struct Foo;
124             enum E { X(Foo<|>) }
125             ",
126             "Foo STRUCT_DEF FileId(1) [0; 11) [7; 10)",
127         );
128     }
129
130     #[test]
131     fn goto_definition_resolves_correct_name() {
132         check_goto(
133             "
134             //- /lib.rs
135             use a::Foo;
136             mod a;
137             mod b;
138             enum E { X(Foo<|>) }
139             //- /a.rs
140             struct Foo;
141             //- /b.rs
142             struct Foo;
143             ",
144             "Foo STRUCT_DEF FileId(2) [0; 11) [7; 10)",
145         );
146     }
147
148     #[test]
149     fn goto_definition_works_for_module_declaration() {
150         check_goto(
151             "
152             //- /lib.rs
153             mod <|>foo;
154             //- /foo.rs
155             // empty
156             ",
157             "foo SOURCE_FILE FileId(2) [0; 10)",
158         );
159
160         check_goto(
161             "
162             //- /lib.rs
163             mod <|>foo;
164             //- /foo/mod.rs
165             // empty
166             ",
167             "foo SOURCE_FILE FileId(2) [0; 10)",
168         );
169     }
170 }