diff options
Diffstat (limited to 'crates/ra_ide_api/src/goto_definition.rs')
-rw-r--r-- | crates/ra_ide_api/src/goto_definition.rs | 150 |
1 files changed, 46 insertions, 104 deletions
diff --git a/crates/ra_ide_api/src/goto_definition.rs b/crates/ra_ide_api/src/goto_definition.rs index 60c1f5085..517dffbca 100644 --- a/crates/ra_ide_api/src/goto_definition.rs +++ b/crates/ra_ide_api/src/goto_definition.rs | |||
@@ -5,7 +5,6 @@ use ra_syntax::{ | |||
5 | SyntaxNode, | 5 | SyntaxNode, |
6 | }; | 6 | }; |
7 | use test_utils::tested_by; | 7 | use test_utils::tested_by; |
8 | use hir::Resolution; | ||
9 | 8 | ||
10 | use crate::{FilePosition, NavigationTarget, db::RootDatabase, RangeInfo}; | 9 | use crate::{FilePosition, NavigationTarget, db::RootDatabase, RangeInfo}; |
11 | 10 | ||
@@ -48,127 +47,70 @@ pub(crate) fn reference_definition( | |||
48 | ) -> ReferenceResult { | 47 | ) -> ReferenceResult { |
49 | use self::ReferenceResult::*; | 48 | use self::ReferenceResult::*; |
50 | 49 | ||
51 | let function = hir::source_binder::function_from_child_node(db, file_id, name_ref.syntax()); | 50 | let analyzer = hir::SourceAnalyzer::new(db, file_id, name_ref.syntax(), None); |
52 | |||
53 | if let Some(function) = function { | ||
54 | // Check if it is a method | ||
55 | if let Some(method_call) = name_ref.syntax().parent().and_then(ast::MethodCallExpr::cast) { | ||
56 | tested_by!(goto_definition_works_for_methods); | ||
57 | let infer_result = function.infer(db); | ||
58 | let source_map = function.body_source_map(db); | ||
59 | let expr = ast::Expr::cast(method_call.syntax()).unwrap(); | ||
60 | if let Some(func) = | ||
61 | source_map.node_expr(expr).and_then(|it| infer_result.method_resolution(it)) | ||
62 | { | ||
63 | return Exact(NavigationTarget::from_function(db, func)); | ||
64 | }; | ||
65 | } | ||
66 | // It could also be a field access | ||
67 | if let Some(field_expr) = name_ref.syntax().parent().and_then(ast::FieldExpr::cast) { | ||
68 | tested_by!(goto_definition_works_for_fields); | ||
69 | let infer_result = function.infer(db); | ||
70 | let source_map = function.body_source_map(db); | ||
71 | let expr = ast::Expr::cast(field_expr.syntax()).unwrap(); | ||
72 | if let Some(field) = | ||
73 | source_map.node_expr(expr).and_then(|it| infer_result.field_resolution(it)) | ||
74 | { | ||
75 | return Exact(NavigationTarget::from_field(db, field)); | ||
76 | }; | ||
77 | } | ||
78 | 51 | ||
79 | // It could also be a named field | 52 | // Special cases: |
80 | if let Some(field_expr) = name_ref.syntax().parent().and_then(ast::NamedField::cast) { | ||
81 | tested_by!(goto_definition_works_for_named_fields); | ||
82 | 53 | ||
83 | let infer_result = function.infer(db); | 54 | // Check if it is a method |
84 | let source_map = function.body_source_map(db); | 55 | if let Some(method_call) = name_ref.syntax().parent().and_then(ast::MethodCallExpr::cast) { |
56 | tested_by!(goto_definition_works_for_methods); | ||
57 | if let Some(func) = analyzer.resolve_method_call(method_call) { | ||
58 | return Exact(NavigationTarget::from_function(db, func)); | ||
59 | } | ||
60 | } | ||
61 | // It could also be a field access | ||
62 | if let Some(field_expr) = name_ref.syntax().parent().and_then(ast::FieldExpr::cast) { | ||
63 | tested_by!(goto_definition_works_for_fields); | ||
64 | if let Some(field) = analyzer.resolve_field(field_expr) { | ||
65 | return Exact(NavigationTarget::from_field(db, field)); | ||
66 | }; | ||
67 | } | ||
85 | 68 | ||
86 | let struct_lit = field_expr.syntax().ancestors().find_map(ast::StructLit::cast); | 69 | // It could also be a named field |
70 | if let Some(field_expr) = name_ref.syntax().parent().and_then(ast::NamedField::cast) { | ||
71 | tested_by!(goto_definition_works_for_named_fields); | ||
87 | 72 | ||
88 | if let Some(expr) = struct_lit.and_then(|lit| source_map.node_expr(lit.into())) { | 73 | let struct_lit = field_expr.syntax().ancestors().find_map(ast::StructLit::cast); |
89 | let ty = infer_result[expr].clone(); | ||
90 | if let Some((hir::AdtDef::Struct(s), _)) = ty.as_adt() { | ||
91 | let hir_path = hir::Path::from_name_ref(name_ref); | ||
92 | let hir_name = hir_path.as_ident().unwrap(); | ||
93 | 74 | ||
94 | if let Some(field) = s.field(db, hir_name) { | 75 | if let Some(ty) = struct_lit.and_then(|lit| analyzer.type_of(db, lit.into())) { |
95 | return Exact(NavigationTarget::from_field(db, field)); | 76 | if let Some((hir::AdtDef::Struct(s), _)) = ty.as_adt() { |
96 | } | 77 | let hir_path = hir::Path::from_name_ref(name_ref); |
78 | let hir_name = hir_path.as_ident().unwrap(); | ||
79 | |||
80 | if let Some(field) = s.field(db, hir_name) { | ||
81 | return Exact(NavigationTarget::from_field(db, field)); | ||
97 | } | 82 | } |
98 | } | 83 | } |
99 | } | 84 | } |
100 | } | 85 | } |
101 | 86 | ||
102 | // Try name resolution | 87 | // General case, a path or a local: |
103 | let resolver = hir::source_binder::resolver_for_node(db, file_id, name_ref.syntax()); | 88 | if let Some(path) = name_ref.syntax().ancestors().find_map(ast::Path::cast) { |
104 | if let Some(path) = | 89 | if let Some(resolved) = analyzer.resolve_path(db, path) { |
105 | name_ref.syntax().ancestors().find_map(ast::Path::cast).and_then(hir::Path::from_ast) | 90 | match resolved { |
106 | { | 91 | hir::PathResolution::Def(def) => return Exact(NavigationTarget::from_def(db, def)), |
107 | let resolved = resolver.resolve_path(db, &path); | 92 | hir::PathResolution::LocalBinding(pat) => { |
108 | match resolved.clone().take_types().or_else(|| resolved.take_values()) { | 93 | let nav = NavigationTarget::from_pat(db, file_id, pat); |
109 | Some(Resolution::Def(def)) => return Exact(NavigationTarget::from_def(db, def)), | 94 | return Exact(nav); |
110 | Some(Resolution::LocalBinding(pat)) => { | ||
111 | let body = resolver.body().expect("no body for local binding"); | ||
112 | let source_map = body.owner().body_source_map(db); | ||
113 | let ptr = source_map.pat_syntax(pat).expect("pattern not found in syntax mapping"); | ||
114 | let name = | ||
115 | path.as_ident().cloned().expect("local binding from a multi-segment path"); | ||
116 | let ptr = ptr.either(|it| it.into(), |it| it.into()); | ||
117 | let nav = NavigationTarget::from_scope_entry(file_id, name, ptr); | ||
118 | return Exact(nav); | ||
119 | } | ||
120 | Some(Resolution::GenericParam(..)) => { | ||
121 | // FIXME: go to the generic param def | ||
122 | } | ||
123 | Some(Resolution::SelfType(impl_block)) => { | ||
124 | let ty = impl_block.target_ty(db); | ||
125 | |||
126 | if let Some((def_id, _)) = ty.as_adt() { | ||
127 | return Exact(NavigationTarget::from_adt_def(db, def_id)); | ||
128 | } | 95 | } |
129 | } | 96 | hir::PathResolution::GenericParam(..) => { |
130 | None => { | 97 | // FIXME: go to the generic param def |
131 | // If we failed to resolve then check associated items | 98 | } |
132 | if let Some(function) = function { | 99 | hir::PathResolution::SelfType(impl_block) => { |
133 | // Resolve associated item for path expressions | 100 | let ty = impl_block.target_ty(db); |
134 | if let Some(path_expr) = | ||
135 | name_ref.syntax().ancestors().find_map(ast::PathExpr::cast) | ||
136 | { | ||
137 | let infer_result = function.infer(db); | ||
138 | let source_map = function.body_source_map(db); | ||
139 | |||
140 | if let Some(expr) = ast::Expr::cast(path_expr.syntax()) { | ||
141 | if let Some(res) = source_map | ||
142 | .node_expr(expr) | ||
143 | .and_then(|it| infer_result.assoc_resolutions_for_expr(it.into())) | ||
144 | { | ||
145 | return Exact(NavigationTarget::from_impl_item(db, res)); | ||
146 | } | ||
147 | } | ||
148 | } | ||
149 | 101 | ||
150 | // Resolve associated item for path patterns | 102 | if let Some((def_id, _)) = ty.as_adt() { |
151 | if let Some(path_pat) = | 103 | return Exact(NavigationTarget::from_adt_def(db, def_id)); |
152 | name_ref.syntax().ancestors().find_map(ast::PathPat::cast) | ||
153 | { | ||
154 | let infer_result = function.infer(db); | ||
155 | let source_map = function.body_source_map(db); | ||
156 | |||
157 | let pat: &ast::Pat = path_pat.into(); | ||
158 | |||
159 | if let Some(res) = source_map | ||
160 | .node_pat(pat) | ||
161 | .and_then(|it| infer_result.assoc_resolutions_for_pat(it.into())) | ||
162 | { | ||
163 | return Exact(NavigationTarget::from_impl_item(db, res)); | ||
164 | } | ||
165 | } | 104 | } |
166 | } | 105 | } |
106 | hir::PathResolution::AssocItem(assoc) => { | ||
107 | return Exact(NavigationTarget::from_impl_item(db, assoc)) | ||
108 | } | ||
167 | } | 109 | } |
168 | } | 110 | } |
169 | } | 111 | } |
170 | 112 | ||
171 | // If that fails try the index based approach. | 113 | // Fallback index based approach: |
172 | let navs = crate::symbol_index::index_resolve(db, name_ref) | 114 | let navs = crate::symbol_index::index_resolve(db, name_ref) |
173 | .into_iter() | 115 | .into_iter() |
174 | .map(NavigationTarget::from_symbol) | 116 | .map(NavigationTarget::from_symbol) |