aboutsummaryrefslogtreecommitdiff
path: root/crates/ide/src/references.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide/src/references.rs')
-rw-r--r--crates/ide/src/references.rs270
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
12pub(crate) mod rename; 12pub(crate) mod rename;
13 13
14use hir::Semantics; 14use hir::{PathResolution, Semantics};
15use ide_db::{ 15use 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;
22use syntax::{ 22use 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
28use crate::{display::TryToNav, FilePosition, NavigationTarget}; 28use 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
137fn get_struct_def_name_for_struct_literal_search( 148fn 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
164fn get_enum_def_name_for_struct_literal_search( 184fn 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
191fn 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
205fn is_record_lit_name_ref(name_ref: &ast::NameRef) -> bool { 211fn 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
216fn is_enum_lit_name_ref(name_ref: &ast::NameRef) -> bool { 217fn 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#"
320union Foo $0{
321 x: u32
322}
323
324fn 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#"
319enum Foo $0{ 341enum Foo $0{
320 A, 342 A,
321 B, 343 B(),
344 C{},
322} 345}
323fn main() { 346fn 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#"
367enum Foo {
368 A $0{ n: i32 },
369 B,
370}
371fn 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#"
388enum Foo {
389 A$0(i32),
390 B,
391}
392fn 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 }