diff options
Diffstat (limited to 'crates/ide/src')
-rw-r--r-- | crates/ide/src/references.rs | 270 |
1 files changed, 170 insertions, 100 deletions
diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs index 17086f7d4..a83b82f1b 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,40 @@ 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(hir::ModuleDef::Adt(_)) | ||
74 | | Definition::ModuleDef(hir::ModuleDef::Variant(_)) => { | ||
75 | refs.for_each(|it| { | ||
76 | it.retain(|reference| { | ||
77 | reference.name.as_name_ref().map_or(false, is_lit_name_ref) | ||
78 | }) | ||
79 | }); | ||
80 | usages.references.retain(|_, it| !it.is_empty()); | ||
81 | } | ||
82 | _ => {} | ||
83 | } | ||
73 | } | 84 | } |
74 | let nav = def.try_to_nav(sema.db)?; | 85 | let nav = def.try_to_nav(sema.db)?; |
75 | let decl_range = nav.focus_or_full_range(); | 86 | let decl_range = nav.focus_or_full_range(); |
@@ -89,9 +100,9 @@ fn find_def( | |||
89 | sema: &Semantics<RootDatabase>, | 100 | sema: &Semantics<RootDatabase>, |
90 | syntax: &SyntaxNode, | 101 | syntax: &SyntaxNode, |
91 | position: FilePosition, | 102 | position: FilePosition, |
92 | opt_name: Option<ast::Name>, | ||
93 | ) -> Option<Definition> { | 103 | ) -> Option<Definition> { |
94 | if let Some(name) = opt_name { | 104 | if let Some(name) = sema.find_node_at_offset_with_descend::<ast::Name>(&syntax, position.offset) |
105 | { | ||
95 | let class = NameClass::classify(sema, &name)?; | 106 | let class = NameClass::classify(sema, &name)?; |
96 | Some(class.referenced_or_defined(sema.db)) | 107 | Some(class.referenced_or_defined(sema.db)) |
97 | } else if let Some(lifetime) = | 108 | } else if let Some(lifetime) = |
@@ -134,95 +145,85 @@ fn decl_access(def: &Definition, syntax: &SyntaxNode, range: TextRange) -> Optio | |||
134 | None | 145 | None |
135 | } | 146 | } |
136 | 147 | ||
137 | fn get_struct_def_name_for_struct_literal_search( | 148 | fn get_name_of_item_declaration(syntax: &SyntaxNode, position: FilePosition) -> Option<ast::Name> { |
138 | sema: &Semantics<RootDatabase>, | 149 | let token = syntax.token_at_offset(position.offset).right_biased()?; |
139 | syntax: &SyntaxNode, | 150 | let kind = token.kind(); |
140 | position: FilePosition, | 151 | if kind == T![;] { |
141 | ) -> Option<ast::Name> { | 152 | ast::Struct::cast(token.parent()) |
142 | if let TokenAtOffset::Between(ref left, ref right) = syntax.token_at_offset(position.offset) { | 153 | .filter(|struct_| struct_.field_list().is_none()) |
143 | if right.kind() != T!['{'] && right.kind() != T!['('] { | 154 | .and_then(|struct_| struct_.name()) |
144 | return None; | 155 | } else if kind == T!['{'] { |
145 | } | 156 | match_ast! { |
146 | if let Some(name) = | 157 | match (token.parent()) { |
147 | sema.find_node_at_offset_with_descend::<ast::Name>(&syntax, left.text_range().start()) | 158 | ast::RecordFieldList(rfl) => match_ast! { |
148 | { | 159 | match (rfl.syntax().parent()?) { |
149 | return name.syntax().ancestors().find_map(ast::Struct::cast).and_then(|l| l.name()); | 160 | ast::Variant(it) => it.name(), |
161 | ast::Struct(it) => it.name(), | ||
162 | ast::Union(it) => it.name(), | ||
163 | _ => None, | ||
164 | } | ||
165 | }, | ||
166 | ast::VariantList(vl) => ast::Enum::cast(vl.syntax().parent()?)?.name(), | ||
167 | _ => None, | ||
168 | } | ||
150 | } | 169 | } |
151 | if sema | 170 | } else if kind == T!['('] { |
152 | .find_node_at_offset_with_descend::<ast::GenericParamList>( | 171 | let tfl = ast::TupleFieldList::cast(token.parent())?; |
153 | &syntax, | 172 | match_ast! { |
154 | left.text_range().start(), | 173 | match (tfl.syntax().parent()?) { |
155 | ) | 174 | ast::Variant(it) => it.name(), |
156 | .is_some() | 175 | ast::Struct(it) => it.name(), |
157 | { | 176 | _ => None, |
158 | return left.ancestors().find_map(ast::Struct::cast).and_then(|l| l.name()); | 177 | } |
159 | } | 178 | } |
179 | } else { | ||
180 | None | ||
160 | } | 181 | } |
161 | None | ||
162 | } | 182 | } |
163 | 183 | ||
164 | fn get_enum_def_name_for_struct_literal_search( | 184 | fn is_enum_lit_name_ref( |
165 | sema: &Semantics<RootDatabase>, | 185 | sema: &Semantics<RootDatabase>, |
166 | syntax: &SyntaxNode, | 186 | enum_: hir::Enum, |
167 | position: FilePosition, | 187 | name_ref: &ast::NameRef, |
168 | ) -> Option<ast::Name> { | 188 | ) -> bool { |
169 | if let TokenAtOffset::Between(ref left, ref right) = syntax.token_at_offset(position.offset) { | 189 | let path_is_variant_of_enum = |path: ast::Path| { |
170 | if right.kind() != T!['{'] && right.kind() != T!['('] { | 190 | matches!( |
171 | return None; | 191 | sema.resolve_path(&path), |
172 | } | 192 | Some(PathResolution::Def(hir::ModuleDef::Variant(variant))) |
173 | if let Some(name) = | 193 | if variant.parent_enum(sema.db) == enum_ |
174 | sema.find_node_at_offset_with_descend::<ast::Name>(&syntax, left.text_range().start()) | 194 | ) |
175 | { | 195 | }; |
176 | return name.syntax().ancestors().find_map(ast::Enum::cast).and_then(|l| l.name()); | ||
177 | } | ||
178 | if sema | ||
179 | .find_node_at_offset_with_descend::<ast::GenericParamList>( | ||
180 | &syntax, | ||
181 | left.text_range().start(), | ||
182 | ) | ||
183 | .is_some() | ||
184 | { | ||
185 | return left.ancestors().find_map(ast::Enum::cast).and_then(|l| l.name()); | ||
186 | } | ||
187 | } | ||
188 | None | ||
189 | } | ||
190 | |||
191 | fn is_call_expr_name_ref(name_ref: &ast::NameRef) -> bool { | ||
192 | name_ref | 196 | name_ref |
193 | .syntax() | 197 | .syntax() |
194 | .ancestors() | 198 | .ancestors() |
195 | .find_map(ast::CallExpr::cast) | 199 | .find_map(|ancestor| { |
196 | .and_then(|c| match c.expr()? { | 200 | match_ast! { |
197 | ast::Expr::PathExpr(p) => { | 201 | match ancestor { |
198 | Some(p.path()?.segment()?.name_ref().as_ref() == Some(name_ref)) | 202 | ast::PathExpr(path_expr) => path_expr.path().map(path_is_variant_of_enum), |
203 | ast::RecordExpr(record_expr) => record_expr.path().map(path_is_variant_of_enum), | ||
204 | _ => None, | ||
205 | } | ||
199 | } | 206 | } |
200 | _ => None, | ||
201 | }) | 207 | }) |
202 | .unwrap_or(false) | 208 | .unwrap_or(false) |
203 | } | 209 | } |
204 | 210 | ||
205 | fn is_record_lit_name_ref(name_ref: &ast::NameRef) -> bool { | 211 | fn path_ends_with(path: Option<ast::Path>, name_ref: &ast::NameRef) -> bool { |
206 | name_ref | 212 | path.and_then(|path| path.segment()) |
207 | .syntax() | 213 | .and_then(|segment| segment.name_ref()) |
208 | .ancestors() | 214 | .map_or(false, |segment| segment == *name_ref) |
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 | } |
215 | 216 | ||
216 | fn is_enum_lit_name_ref(name_ref: &ast::NameRef) -> bool { | 217 | fn is_lit_name_ref(name_ref: &ast::NameRef) -> bool { |
217 | name_ref | 218 | name_ref.syntax().ancestors().find_map(|ancestor| { |
218 | .syntax() | 219 | match_ast! { |
219 | .ancestors() | 220 | match ancestor { |
220 | .find_map(ast::PathExpr::cast) | 221 | ast::PathExpr(path_expr) => Some(path_ends_with(path_expr.path(), name_ref)), |
221 | .and_then(|p| p.path()) | 222 | ast::RecordExpr(record_expr) => Some(path_ends_with(record_expr.path(), name_ref)), |
222 | .and_then(|p| p.qualifier()) | 223 | _ => None, |
223 | .and_then(|p| p.segment()) | 224 | } |
224 | .map(|p| p.name_ref().as_ref() == Some(name_ref)) | 225 | } |
225 | .unwrap_or(false) | 226 | }).unwrap_or(false) |
226 | } | 227 | } |
227 | 228 | ||
228 | #[cfg(test)] | 229 | #[cfg(test)] |
@@ -313,22 +314,91 @@ fn main() { | |||
313 | } | 314 | } |
314 | 315 | ||
315 | #[test] | 316 | #[test] |
317 | fn test_struct_literal_for_union() { | ||
318 | check( | ||
319 | r#" | ||
320 | union Foo $0{ | ||
321 | x: u32 | ||
322 | } | ||
323 | |||
324 | fn main() { | ||
325 | let f: Foo; | ||
326 | f = Foo { x: 1 }; | ||
327 | } | ||
328 | "#, | ||
329 | expect![[r#" | ||
330 | Foo Union FileId(0) 0..24 6..9 | ||
331 | |||
332 | FileId(0) 62..65 | ||
333 | "#]], | ||
334 | ); | ||
335 | } | ||
336 | |||
337 | #[test] | ||
316 | fn test_enum_after_space() { | 338 | fn test_enum_after_space() { |
317 | check( | 339 | check( |
318 | r#" | 340 | r#" |
319 | enum Foo $0{ | 341 | enum Foo $0{ |
320 | A, | 342 | A, |
321 | B, | 343 | B(), |
344 | C{}, | ||
322 | } | 345 | } |
323 | fn main() { | 346 | fn main() { |
324 | let f: Foo; | 347 | let f: Foo; |
325 | f = Foo::A; | 348 | f = Foo::A; |
349 | f = Foo::B(); | ||
350 | f = Foo::C{}; | ||
326 | } | 351 | } |
327 | "#, | 352 | "#, |
328 | expect![[r#" | 353 | expect![[r#" |
329 | Foo Enum FileId(0) 0..26 5..8 | 354 | Foo Enum FileId(0) 0..37 5..8 |
330 | 355 | ||
331 | FileId(0) 63..66 | 356 | FileId(0) 74..77 |
357 | FileId(0) 90..93 | ||
358 | FileId(0) 108..111 | ||
359 | "#]], | ||
360 | ); | ||
361 | } | ||
362 | |||
363 | #[test] | ||
364 | fn test_variant_record_after_space() { | ||
365 | check( | ||
366 | r#" | ||
367 | enum Foo { | ||
368 | A $0{ n: i32 }, | ||
369 | B, | ||
370 | } | ||
371 | fn main() { | ||
372 | let f: Foo; | ||
373 | f = Foo::B; | ||
374 | f = Foo::A { n: 92 }; | ||
375 | } | ||
376 | "#, | ||
377 | expect![[r#" | ||
378 | A Variant FileId(0) 15..27 15..16 | ||
379 | |||
380 | FileId(0) 95..96 | ||
381 | "#]], | ||
382 | ); | ||
383 | } | ||
384 | #[test] | ||
385 | fn test_variant_tuple_before_paren() { | ||
386 | check( | ||
387 | r#" | ||
388 | enum Foo { | ||
389 | A$0(i32), | ||
390 | B, | ||
391 | } | ||
392 | fn main() { | ||
393 | let f: Foo; | ||
394 | f = Foo::B; | ||
395 | f = Foo::A(92); | ||
396 | } | ||
397 | "#, | ||
398 | expect![[r#" | ||
399 | A Variant FileId(0) 15..21 15..16 | ||
400 | |||
401 | FileId(0) 89..90 | ||
332 | "#]], | 402 | "#]], |
333 | ); | 403 | ); |
334 | } | 404 | } |