diff options
Diffstat (limited to 'crates/ide/src')
-rw-r--r-- | crates/ide/src/references.rs | 294 |
1 files changed, 189 insertions, 105 deletions
diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs index c7cefb3b6..8a491f077 100644 --- a/crates/ide/src/references.rs +++ b/crates/ide/src/references.rs | |||
@@ -11,7 +11,7 @@ | |||
11 | 11 | ||
12 | pub(crate) mod rename; | 12 | pub(crate) mod rename; |
13 | 13 | ||
14 | use hir::Semantics; | 14 | use hir::{PathResolution, Semantics}; |
15 | use ide_db::{ | 15 | use ide_db::{ |
16 | base_db::FileId, | 16 | base_db::FileId, |
17 | defs::{Definition, NameClass, NameRefClass}, | 17 | defs::{Definition, NameClass, NameRefClass}, |
@@ -22,7 +22,7 @@ use rustc_hash::FxHashMap; | |||
22 | use syntax::{ | 22 | use syntax::{ |
23 | algo::find_node_at_offset, | 23 | algo::find_node_at_offset, |
24 | ast::{self, NameOwner}, | 24 | ast::{self, NameOwner}, |
25 | AstNode, SyntaxNode, TextRange, TokenAtOffset, T, | 25 | match_ast, AstNode, SyntaxNode, TextRange, T, |
26 | }; | 26 | }; |
27 | 27 | ||
28 | use crate::{display::TryToNav, FilePosition, NavigationTarget}; | 28 | use crate::{display::TryToNav, FilePosition, NavigationTarget}; |
@@ -47,29 +47,43 @@ pub(crate) fn find_all_refs( | |||
47 | let _p = profile::span("find_all_refs"); | 47 | let _p = profile::span("find_all_refs"); |
48 | let syntax = sema.parse(position.file_id).syntax().clone(); | 48 | let syntax = sema.parse(position.file_id).syntax().clone(); |
49 | 49 | ||
50 | let (opt_name, ctor_filter): (_, Option<fn(&_) -> bool>) = if let Some(name) = | 50 | let (def, is_literal_search) = |
51 | get_struct_def_name_for_struct_literal_search(&sema, &syntax, position) | 51 | if let Some(name) = get_name_of_item_declaration(&syntax, position) { |
52 | { | 52 | (NameClass::classify(sema, &name)?.referenced_or_defined(sema.db), true) |
53 | ( | 53 | } else { |
54 | Some(name), | 54 | (find_def(&sema, &syntax, position)?, false) |
55 | Some(|name_ref| is_record_lit_name_ref(name_ref) || is_call_expr_name_ref(name_ref)), | 55 | }; |
56 | ) | ||
57 | } else if let Some(name) = get_enum_def_name_for_struct_literal_search(&sema, &syntax, position) | ||
58 | { | ||
59 | (Some(name), Some(is_enum_lit_name_ref)) | ||
60 | } else { | ||
61 | (sema.find_node_at_offset_with_descend::<ast::Name>(&syntax, position.offset), None) | ||
62 | }; | ||
63 | |||
64 | let def = find_def(&sema, &syntax, position, opt_name)?; | ||
65 | 56 | ||
66 | let mut usages = def.usages(sema).set_scope(search_scope).all(); | 57 | let mut usages = def.usages(sema).set_scope(search_scope).all(); |
67 | if let Some(ctor_filter) = ctor_filter { | 58 | if is_literal_search { |
68 | // filter for constructor-literals | 59 | // filter for constructor-literals |
69 | usages.references.values_mut().for_each(|it| { | 60 | let refs = usages.references.values_mut(); |
70 | it.retain(|reference| reference.name.as_name_ref().map_or(false, ctor_filter)); | 61 | match def { |
71 | }); | 62 | Definition::ModuleDef(hir::ModuleDef::Adt(hir::Adt::Enum(enum_))) => { |
72 | usages.references.retain(|_, it| !it.is_empty()); | 63 | refs.for_each(|it| { |
64 | it.retain(|reference| { | ||
65 | reference | ||
66 | .name | ||
67 | .as_name_ref() | ||
68 | .map_or(false, |name_ref| is_enum_lit_name_ref(sema, enum_, name_ref)) | ||
69 | }) | ||
70 | }); | ||
71 | usages.references.retain(|_, it| !it.is_empty()); | ||
72 | } | ||
73 | Definition::ModuleDef(def @ hir::ModuleDef::Adt(_)) | ||
74 | | Definition::ModuleDef(def @ hir::ModuleDef::Variant(_)) => { | ||
75 | refs.for_each(|it| { | ||
76 | it.retain(|reference| { | ||
77 | reference | ||
78 | .name | ||
79 | .as_name_ref() | ||
80 | .map_or(false, |name_ref| is_lit_name_ref(sema, def, name_ref)) | ||
81 | }) | ||
82 | }); | ||
83 | usages.references.retain(|_, it| !it.is_empty()); | ||
84 | } | ||
85 | _ => {} | ||
86 | } | ||
73 | } | 87 | } |
74 | let nav = def.try_to_nav(sema.db)?; | 88 | let nav = def.try_to_nav(sema.db)?; |
75 | let decl_range = nav.focus_or_full_range(); | 89 | let decl_range = nav.focus_or_full_range(); |
@@ -89,9 +103,9 @@ fn find_def( | |||
89 | sema: &Semantics<RootDatabase>, | 103 | sema: &Semantics<RootDatabase>, |
90 | syntax: &SyntaxNode, | 104 | syntax: &SyntaxNode, |
91 | position: FilePosition, | 105 | position: FilePosition, |
92 | opt_name: Option<ast::Name>, | ||
93 | ) -> Option<Definition> { | 106 | ) -> Option<Definition> { |
94 | if let Some(name) = opt_name { | 107 | if let Some(name) = sema.find_node_at_offset_with_descend::<ast::Name>(&syntax, position.offset) |
108 | { | ||
95 | let class = NameClass::classify(sema, &name)?; | 109 | let class = NameClass::classify(sema, &name)?; |
96 | Some(class.referenced_or_defined(sema.db)) | 110 | Some(class.referenced_or_defined(sema.db)) |
97 | } else if let Some(lifetime) = | 111 | } else if let Some(lifetime) = |
@@ -134,95 +148,96 @@ fn decl_access(def: &Definition, syntax: &SyntaxNode, range: TextRange) -> Optio | |||
134 | None | 148 | None |
135 | } | 149 | } |
136 | 150 | ||
137 | fn get_struct_def_name_for_struct_literal_search( | 151 | fn get_name_of_item_declaration(syntax: &SyntaxNode, position: FilePosition) -> Option<ast::Name> { |
138 | sema: &Semantics<RootDatabase>, | 152 | let token = syntax.token_at_offset(position.offset).right_biased()?; |
139 | syntax: &SyntaxNode, | 153 | let kind = token.kind(); |
140 | position: FilePosition, | 154 | if kind == T![;] { |
141 | ) -> Option<ast::Name> { | 155 | ast::Struct::cast(token.parent()) |
142 | if let TokenAtOffset::Between(ref left, ref right) = syntax.token_at_offset(position.offset) { | 156 | .filter(|struct_| struct_.field_list().is_none()) |
143 | if right.kind() != T!['{'] && right.kind() != T!['('] { | 157 | .and_then(|struct_| struct_.name()) |
144 | return None; | 158 | } else if kind == T!['{'] { |
145 | } | 159 | match_ast! { |
146 | if let Some(name) = | 160 | match (token.parent()) { |
147 | sema.find_node_at_offset_with_descend::<ast::Name>(&syntax, left.text_range().start()) | 161 | ast::RecordFieldList(rfl) => match_ast! { |
148 | { | 162 | match (rfl.syntax().parent()?) { |
149 | return name.syntax().ancestors().find_map(ast::Struct::cast).and_then(|l| l.name()); | 163 | ast::Variant(it) => it.name(), |
164 | ast::Struct(it) => it.name(), | ||
165 | ast::Union(it) => it.name(), | ||
166 | _ => None, | ||
167 | } | ||
168 | }, | ||
169 | ast::VariantList(vl) => ast::Enum::cast(vl.syntax().parent()?)?.name(), | ||
170 | _ => None, | ||
171 | } | ||
150 | } | 172 | } |
151 | if sema | 173 | } else if kind == T!['('] { |
152 | .find_node_at_offset_with_descend::<ast::GenericParamList>( | 174 | let tfl = ast::TupleFieldList::cast(token.parent())?; |
153 | &syntax, | 175 | match_ast! { |
154 | left.text_range().start(), | 176 | match (tfl.syntax().parent()?) { |
155 | ) | 177 | ast::Variant(it) => it.name(), |
156 | .is_some() | 178 | ast::Struct(it) => it.name(), |
157 | { | 179 | _ => None, |
158 | return left.ancestors().find_map(ast::Struct::cast).and_then(|l| l.name()); | 180 | } |
159 | } | 181 | } |
182 | } else { | ||
183 | None | ||
160 | } | 184 | } |
161 | None | ||
162 | } | 185 | } |
163 | 186 | ||
164 | fn get_enum_def_name_for_struct_literal_search( | 187 | fn is_enum_lit_name_ref( |
165 | sema: &Semantics<RootDatabase>, | 188 | sema: &Semantics<RootDatabase>, |
166 | syntax: &SyntaxNode, | 189 | enum_: hir::Enum, |
167 | position: FilePosition, | 190 | name_ref: &ast::NameRef, |
168 | ) -> Option<ast::Name> { | 191 | ) -> bool { |
169 | if let TokenAtOffset::Between(ref left, ref right) = syntax.token_at_offset(position.offset) { | 192 | for ancestor in name_ref.syntax().ancestors() { |
170 | if right.kind() != T!['{'] && right.kind() != T!['('] { | 193 | match_ast! { |
171 | return None; | 194 | match ancestor { |
172 | } | 195 | ast::PathExpr(path_expr) => { |
173 | if let Some(name) = | 196 | return matches!( |
174 | sema.find_node_at_offset_with_descend::<ast::Name>(&syntax, left.text_range().start()) | 197 | path_expr.path().and_then(|p| sema.resolve_path(&p)), |
175 | { | 198 | Some(PathResolution::Def(hir::ModuleDef::Variant(variant))) |
176 | return name.syntax().ancestors().find_map(ast::Enum::cast).and_then(|l| l.name()); | 199 | if variant.parent_enum(sema.db) == enum_ |
177 | } | 200 | ) |
178 | if sema | 201 | }, |
179 | .find_node_at_offset_with_descend::<ast::GenericParamList>( | 202 | ast::RecordExpr(record_expr) => { |
180 | &syntax, | 203 | return matches!( |
181 | left.text_range().start(), | 204 | record_expr.path().and_then(|p| sema.resolve_path(&p)), |
182 | ) | 205 | Some(PathResolution::Def(hir::ModuleDef::Variant(variant))) |
183 | .is_some() | 206 | if variant.parent_enum(sema.db) == enum_ |
184 | { | 207 | ) |
185 | return left.ancestors().find_map(ast::Enum::cast).and_then(|l| l.name()); | 208 | }, |
209 | _ => (), | ||
210 | } | ||
186 | } | 211 | } |
187 | } | 212 | } |
188 | None | 213 | false |
189 | } | 214 | } |
190 | 215 | ||
191 | fn is_call_expr_name_ref(name_ref: &ast::NameRef) -> bool { | 216 | fn is_lit_name_ref( |
192 | name_ref | 217 | sema: &Semantics<RootDatabase>, |
193 | .syntax() | 218 | def: hir::ModuleDef, |
194 | .ancestors() | 219 | name_ref: &ast::NameRef, |
195 | .find_map(ast::CallExpr::cast) | 220 | ) -> bool { |
196 | .and_then(|c| match c.expr()? { | 221 | for ancestor in name_ref.syntax().ancestors() { |
197 | ast::Expr::PathExpr(p) => { | 222 | match_ast! { |
198 | Some(p.path()?.segment()?.name_ref().as_ref() == Some(name_ref)) | 223 | match ancestor { |
224 | ast::PathExpr(path_expr) => { | ||
225 | return matches!( | ||
226 | path_expr.path().and_then(|p| sema.resolve_path(&p)), | ||
227 | Some(PathResolution::Def(def2)) if def == def2 | ||
228 | ) | ||
229 | }, | ||
230 | ast::RecordExpr(record_expr) => { | ||
231 | return matches!( | ||
232 | record_expr.path().and_then(|p| sema.resolve_path(&p)), | ||
233 | Some(PathResolution::Def(def2)) if def == def2 | ||
234 | ) | ||
235 | }, | ||
236 | _ => (), | ||
199 | } | 237 | } |
200 | _ => None, | 238 | } |
201 | }) | 239 | } |
202 | .unwrap_or(false) | 240 | false |
203 | } | ||
204 | |||
205 | fn is_record_lit_name_ref(name_ref: &ast::NameRef) -> bool { | ||
206 | name_ref | ||
207 | .syntax() | ||
208 | .ancestors() | ||
209 | .find_map(ast::RecordExpr::cast) | ||
210 | .and_then(|l| l.path()) | ||
211 | .and_then(|p| p.segment()) | ||
212 | .map(|p| p.name_ref().as_ref() == Some(name_ref)) | ||
213 | .unwrap_or(false) | ||
214 | } | ||
215 | |||
216 | fn is_enum_lit_name_ref(name_ref: &ast::NameRef) -> bool { | ||
217 | name_ref | ||
218 | .syntax() | ||
219 | .ancestors() | ||
220 | .find_map(ast::PathExpr::cast) | ||
221 | .and_then(|p| p.path()) | ||
222 | .and_then(|p| p.qualifier()) | ||
223 | .and_then(|p| p.segment()) | ||
224 | .map(|p| p.name_ref().as_ref() == Some(name_ref)) | ||
225 | .unwrap_or(false) | ||
226 | } | 241 | } |
227 | 242 | ||
228 | #[cfg(test)] | 243 | #[cfg(test)] |
@@ -313,22 +328,91 @@ fn main() { | |||
313 | } | 328 | } |
314 | 329 | ||
315 | #[test] | 330 | #[test] |
331 | fn test_struct_literal_for_union() { | ||
332 | check( | ||
333 | r#" | ||
334 | union Foo $0{ | ||
335 | x: u32 | ||
336 | } | ||
337 | |||
338 | fn main() { | ||
339 | let f: Foo; | ||
340 | f = Foo { x: 1 }; | ||
341 | } | ||
342 | "#, | ||
343 | expect![[r#" | ||
344 | Foo Union FileId(0) 0..24 6..9 | ||
345 | |||
346 | FileId(0) 62..65 | ||
347 | "#]], | ||
348 | ); | ||
349 | } | ||
350 | |||
351 | #[test] | ||
316 | fn test_enum_after_space() { | 352 | fn test_enum_after_space() { |
317 | check( | 353 | check( |
318 | r#" | 354 | r#" |
319 | enum Foo $0{ | 355 | enum Foo $0{ |
320 | A, | 356 | A, |
321 | B, | 357 | B(), |
358 | C{}, | ||
322 | } | 359 | } |
323 | fn main() { | 360 | fn main() { |
324 | let f: Foo; | 361 | let f: Foo; |
325 | f = Foo::A; | 362 | f = Foo::A; |
363 | f = Foo::B(); | ||
364 | f = Foo::C{}; | ||
326 | } | 365 | } |
327 | "#, | 366 | "#, |
328 | expect![[r#" | 367 | expect![[r#" |
329 | Foo Enum FileId(0) 0..26 5..8 | 368 | Foo Enum FileId(0) 0..37 5..8 |
330 | 369 | ||
331 | FileId(0) 63..66 | 370 | FileId(0) 74..77 |
371 | FileId(0) 90..93 | ||
372 | FileId(0) 108..111 | ||
373 | "#]], | ||
374 | ); | ||
375 | } | ||
376 | |||
377 | #[test] | ||
378 | fn test_variant_record_after_space() { | ||
379 | check( | ||
380 | r#" | ||
381 | enum Foo { | ||
382 | A $0{ n: i32 }, | ||
383 | B, | ||
384 | } | ||
385 | fn main() { | ||
386 | let f: Foo; | ||
387 | f = Foo::B; | ||
388 | f = Foo::A { n: 92 }; | ||
389 | } | ||
390 | "#, | ||
391 | expect![[r#" | ||
392 | A Variant FileId(0) 15..27 15..16 | ||
393 | |||
394 | FileId(0) 95..96 | ||
395 | "#]], | ||
396 | ); | ||
397 | } | ||
398 | #[test] | ||
399 | fn test_variant_tuple_before_paren() { | ||
400 | check( | ||
401 | r#" | ||
402 | enum Foo { | ||
403 | A$0(i32), | ||
404 | B, | ||
405 | } | ||
406 | fn main() { | ||
407 | let f: Foo; | ||
408 | f = Foo::B; | ||
409 | f = Foo::A(92); | ||
410 | } | ||
411 | "#, | ||
412 | expect![[r#" | ||
413 | A Variant FileId(0) 15..21 15..16 | ||
414 | |||
415 | FileId(0) 89..90 | ||
332 | "#]], | 416 | "#]], |
333 | ); | 417 | ); |
334 | } | 418 | } |