aboutsummaryrefslogtreecommitdiff
path: root/crates/ide/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide/src')
-rw-r--r--crates/ide/src/annotations.rs40
-rw-r--r--crates/ide/src/references.rs305
-rw-r--r--crates/ide/src/references/rename.rs179
3 files changed, 356 insertions, 168 deletions
diff --git a/crates/ide/src/annotations.rs b/crates/ide/src/annotations.rs
index 414a60bed..2e8e82b70 100644
--- a/crates/ide/src/annotations.rs
+++ b/crates/ide/src/annotations.rs
@@ -57,19 +57,19 @@ pub(crate) fn annotations(
57 let action = runnable.action(); 57 let action = runnable.action();
58 let range = runnable.nav.full_range; 58 let range = runnable.nav.full_range;
59 59
60 if action.debugee && config.debug { 60 if config.run {
61 annotations.push(Annotation { 61 annotations.push(Annotation {
62 range, 62 range,
63 63
64 // FIXME: This one allocates without reason if run is enabled, but debug is disabled 64 // FIXME: This one allocates without reason if run is enabled, but debug is disabled
65 kind: AnnotationKind::Runnable { debug: true, runnable: runnable.clone() }, 65 kind: AnnotationKind::Runnable { debug: false, runnable: runnable.clone() },
66 }); 66 });
67 } 67 }
68 68
69 if config.run { 69 if action.debugee && config.debug {
70 annotations.push(Annotation { 70 annotations.push(Annotation {
71 range, 71 range,
72 kind: AnnotationKind::Runnable { debug: false, runnable }, 72 kind: AnnotationKind::Runnable { debug: true, runnable },
73 }); 73 });
74 } 74 }
75 } 75 }
@@ -199,7 +199,7 @@ fn main() {
199 Annotation { 199 Annotation {
200 range: 50..85, 200 range: 50..85,
201 kind: Runnable { 201 kind: Runnable {
202 debug: true, 202 debug: false,
203 runnable: Runnable { 203 runnable: Runnable {
204 nav: NavigationTarget { 204 nav: NavigationTarget {
205 file_id: FileId( 205 file_id: FileId(
@@ -218,7 +218,7 @@ fn main() {
218 Annotation { 218 Annotation {
219 range: 50..85, 219 range: 50..85,
220 kind: Runnable { 220 kind: Runnable {
221 debug: false, 221 debug: true,
222 runnable: Runnable { 222 runnable: Runnable {
223 nav: NavigationTarget { 223 nav: NavigationTarget {
224 file_id: FileId( 224 file_id: FileId(
@@ -303,7 +303,7 @@ fn main() {
303 Annotation { 303 Annotation {
304 range: 14..48, 304 range: 14..48,
305 kind: Runnable { 305 kind: Runnable {
306 debug: true, 306 debug: false,
307 runnable: Runnable { 307 runnable: Runnable {
308 nav: NavigationTarget { 308 nav: NavigationTarget {
309 file_id: FileId( 309 file_id: FileId(
@@ -322,7 +322,7 @@ fn main() {
322 Annotation { 322 Annotation {
323 range: 14..48, 323 range: 14..48,
324 kind: Runnable { 324 kind: Runnable {
325 debug: false, 325 debug: true,
326 runnable: Runnable { 326 runnable: Runnable {
327 nav: NavigationTarget { 327 nav: NavigationTarget {
328 file_id: FileId( 328 file_id: FileId(
@@ -411,7 +411,7 @@ fn main() {
411 Annotation { 411 Annotation {
412 range: 66..100, 412 range: 66..100,
413 kind: Runnable { 413 kind: Runnable {
414 debug: true, 414 debug: false,
415 runnable: Runnable { 415 runnable: Runnable {
416 nav: NavigationTarget { 416 nav: NavigationTarget {
417 file_id: FileId( 417 file_id: FileId(
@@ -430,7 +430,7 @@ fn main() {
430 Annotation { 430 Annotation {
431 range: 66..100, 431 range: 66..100,
432 kind: Runnable { 432 kind: Runnable {
433 debug: false, 433 debug: true,
434 runnable: Runnable { 434 runnable: Runnable {
435 nav: NavigationTarget { 435 nav: NavigationTarget {
436 file_id: FileId( 436 file_id: FileId(
@@ -572,7 +572,7 @@ fn main() {}
572 Annotation { 572 Annotation {
573 range: 0..12, 573 range: 0..12,
574 kind: Runnable { 574 kind: Runnable {
575 debug: true, 575 debug: false,
576 runnable: Runnable { 576 runnable: Runnable {
577 nav: NavigationTarget { 577 nav: NavigationTarget {
578 file_id: FileId( 578 file_id: FileId(
@@ -591,7 +591,7 @@ fn main() {}
591 Annotation { 591 Annotation {
592 range: 0..12, 592 range: 0..12,
593 kind: Runnable { 593 kind: Runnable {
594 debug: false, 594 debug: true,
595 runnable: Runnable { 595 runnable: Runnable {
596 nav: NavigationTarget { 596 nav: NavigationTarget {
597 file_id: FileId( 597 file_id: FileId(
@@ -645,7 +645,7 @@ fn main() {
645 Annotation { 645 Annotation {
646 range: 58..95, 646 range: 58..95,
647 kind: Runnable { 647 kind: Runnable {
648 debug: true, 648 debug: false,
649 runnable: Runnable { 649 runnable: Runnable {
650 nav: NavigationTarget { 650 nav: NavigationTarget {
651 file_id: FileId( 651 file_id: FileId(
@@ -664,7 +664,7 @@ fn main() {
664 Annotation { 664 Annotation {
665 range: 58..95, 665 range: 58..95,
666 kind: Runnable { 666 kind: Runnable {
667 debug: false, 667 debug: true,
668 runnable: Runnable { 668 runnable: Runnable {
669 nav: NavigationTarget { 669 nav: NavigationTarget {
670 file_id: FileId( 670 file_id: FileId(
@@ -787,7 +787,7 @@ mod tests {
787 Annotation { 787 Annotation {
788 range: 0..12, 788 range: 0..12,
789 kind: Runnable { 789 kind: Runnable {
790 debug: true, 790 debug: false,
791 runnable: Runnable { 791 runnable: Runnable {
792 nav: NavigationTarget { 792 nav: NavigationTarget {
793 file_id: FileId( 793 file_id: FileId(
@@ -806,7 +806,7 @@ mod tests {
806 Annotation { 806 Annotation {
807 range: 0..12, 807 range: 0..12,
808 kind: Runnable { 808 kind: Runnable {
809 debug: false, 809 debug: true,
810 runnable: Runnable { 810 runnable: Runnable {
811 nav: NavigationTarget { 811 nav: NavigationTarget {
812 file_id: FileId( 812 file_id: FileId(
@@ -825,7 +825,7 @@ mod tests {
825 Annotation { 825 Annotation {
826 range: 14..64, 826 range: 14..64,
827 kind: Runnable { 827 kind: Runnable {
828 debug: true, 828 debug: false,
829 runnable: Runnable { 829 runnable: Runnable {
830 nav: NavigationTarget { 830 nav: NavigationTarget {
831 file_id: FileId( 831 file_id: FileId(
@@ -846,7 +846,7 @@ mod tests {
846 Annotation { 846 Annotation {
847 range: 14..64, 847 range: 14..64,
848 kind: Runnable { 848 kind: Runnable {
849 debug: false, 849 debug: true,
850 runnable: Runnable { 850 runnable: Runnable {
851 nav: NavigationTarget { 851 nav: NavigationTarget {
852 file_id: FileId( 852 file_id: FileId(
@@ -867,7 +867,7 @@ mod tests {
867 Annotation { 867 Annotation {
868 range: 30..62, 868 range: 30..62,
869 kind: Runnable { 869 kind: Runnable {
870 debug: true, 870 debug: false,
871 runnable: Runnable { 871 runnable: Runnable {
872 nav: NavigationTarget { 872 nav: NavigationTarget {
873 file_id: FileId( 873 file_id: FileId(
@@ -893,7 +893,7 @@ mod tests {
893 Annotation { 893 Annotation {
894 range: 30..62, 894 range: 30..62,
895 kind: Runnable { 895 kind: Runnable {
896 debug: false, 896 debug: true,
897 runnable: Runnable { 897 runnable: Runnable {
898 nav: NavigationTarget { 898 nav: NavigationTarget {
899 file_id: FileId( 899 file_id: FileId(
diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs
index c7cefb3b6..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 }
@@ -1127,4 +1197,39 @@ impl Foo {
1127 "#]], 1197 "#]],
1128 ); 1198 );
1129 } 1199 }
1200
1201 #[test]
1202 fn test_attr_differs_from_fn_with_same_name() {
1203 check(
1204 r#"
1205#[test]
1206fn test$0() {
1207 test();
1208}
1209"#,
1210 expect![[r#"
1211 test Function FileId(0) 0..33 11..15
1212
1213 FileId(0) 24..28
1214 "#]],
1215 );
1216 }
1217
1218 #[test]
1219 fn test_attr_matches_proc_macro_fn() {
1220 check(
1221 r#"
1222#[proc_macro_attribute]
1223fn my_proc_macro() {}
1224
1225#[my_proc_macro$0]
1226fn test() {}
1227"#,
1228 expect![[r#"
1229 my_proc_macro Function FileId(0) 0..45 27..40
1230
1231 FileId(0) 49..62
1232 "#]],
1233 );
1234 }
1130} 1235}
diff --git a/crates/ide/src/references/rename.rs b/crates/ide/src/references/rename.rs
index b04214291..08f16b54d 100644
--- a/crates/ide/src/references/rename.rs
+++ b/crates/ide/src/references/rename.rs
@@ -75,8 +75,7 @@ pub(crate) fn rename_with_semantics(
75 let source_file = sema.parse(position.file_id); 75 let source_file = sema.parse(position.file_id);
76 let syntax = source_file.syntax(); 76 let syntax = source_file.syntax();
77 77
78 let def = find_definition(sema, syntax, position) 78 let def = find_definition(sema, syntax, position)?;
79 .ok_or_else(|| format_err!("No references found at position"))?;
80 match def { 79 match def {
81 Definition::ModuleDef(ModuleDef::Module(module)) => rename_mod(&sema, module, new_name), 80 Definition::ModuleDef(ModuleDef::Module(module)) => rename_mod(&sema, module, new_name),
82 def => rename_reference(sema, def, new_name), 81 def => rename_reference(sema, def, new_name),
@@ -149,18 +148,30 @@ fn find_definition(
149 sema: &Semantics<RootDatabase>, 148 sema: &Semantics<RootDatabase>,
150 syntax: &SyntaxNode, 149 syntax: &SyntaxNode,
151 position: FilePosition, 150 position: FilePosition,
152) -> Option<Definition> { 151) -> RenameResult<Definition> {
153 let def = match find_name_like(sema, syntax, position)? { 152 match find_name_like(sema, syntax, position)
154 NameLike::Name(name) => NameClass::classify(sema, &name)?.referenced_or_defined(sema.db), 153 .ok_or_else(|| format_err!("No references found at position"))?
155 NameLike::NameRef(name_ref) => NameRefClass::classify(sema, &name_ref)?.referenced(sema.db), 154 {
155 // renaming aliases would rename the item being aliased as the HIR doesn't track aliases yet
156 NameLike::Name(name)
157 if name.syntax().parent().map_or(false, |it| ast::Rename::can_cast(it.kind())) =>
158 {
159 bail!("Renaming aliases is currently unsupported")
160 }
161 NameLike::Name(name) => {
162 NameClass::classify(sema, &name).map(|class| class.referenced_or_defined(sema.db))
163 }
164 NameLike::NameRef(name_ref) => {
165 NameRefClass::classify(sema, &name_ref).map(|class| class.referenced(sema.db))
166 }
156 NameLike::Lifetime(lifetime) => NameRefClass::classify_lifetime(sema, &lifetime) 167 NameLike::Lifetime(lifetime) => NameRefClass::classify_lifetime(sema, &lifetime)
157 .map(|class| NameRefClass::referenced(class, sema.db)) 168 .map(|class| NameRefClass::referenced(class, sema.db))
158 .or_else(|| { 169 .or_else(|| {
159 NameClass::classify_lifetime(sema, &lifetime) 170 NameClass::classify_lifetime(sema, &lifetime)
160 .map(|it| it.referenced_or_defined(sema.db)) 171 .map(|it| it.referenced_or_defined(sema.db))
161 })?, 172 }),
162 }; 173 }
163 Some(def) 174 .ok_or_else(|| format_err!("No references found at position"))
164} 175}
165 176
166fn source_edit_from_references( 177fn source_edit_from_references(
@@ -173,21 +184,40 @@ fn source_edit_from_references(
173 let mut edit = TextEdit::builder(); 184 let mut edit = TextEdit::builder();
174 for reference in references { 185 for reference in references {
175 let (range, replacement) = match &reference.name { 186 let (range, replacement) = match &reference.name {
176 NameLike::Name(_) => (None, format!("{}", new_name)), 187 // if the ranges differ then the node is inside a macro call, we can't really attempt
177 NameLike::NameRef(name_ref) => source_edit_from_name_ref(name_ref, new_name, def), 188 // to make special rewrites like shorthand syntax and such, so just rename the node in
178 NameLike::Lifetime(_) => (None, format!("{}", new_name)), 189 // the macro input
179 }; 190 NameLike::NameRef(name_ref) if name_ref.syntax().text_range() == reference.range => {
180 // FIXME: Some(range) will be incorrect when we are inside macros 191 source_edit_from_name_ref(name_ref, new_name, def)
181 edit.replace(range.unwrap_or(reference.range), replacement); 192 }
193 NameLike::Name(name) if name.syntax().text_range() == reference.range => {
194 source_edit_from_name(name, new_name)
195 }
196 _ => None,
197 }
198 .unwrap_or_else(|| (reference.range, new_name.to_string()));
199 edit.replace(range, replacement);
182 } 200 }
183 (file_id, edit.finish()) 201 (file_id, edit.finish())
184} 202}
185 203
204fn source_edit_from_name(name: &ast::Name, new_name: &str) -> Option<(TextRange, String)> {
205 if let Some(_) = ast::RecordPatField::for_field_name(name) {
206 if let Some(ident_pat) = name.syntax().parent().and_then(ast::IdentPat::cast) {
207 return Some((
208 TextRange::empty(ident_pat.syntax().text_range().start()),
209 format!("{}: ", new_name),
210 ));
211 }
212 }
213 None
214}
215
186fn source_edit_from_name_ref( 216fn source_edit_from_name_ref(
187 name_ref: &ast::NameRef, 217 name_ref: &ast::NameRef,
188 new_name: &str, 218 new_name: &str,
189 def: Definition, 219 def: Definition,
190) -> (Option<TextRange>, String) { 220) -> Option<(TextRange, String)> {
191 if let Some(record_field) = ast::RecordExprField::for_name_ref(name_ref) { 221 if let Some(record_field) = ast::RecordExprField::for_name_ref(name_ref) {
192 let rcf_name_ref = record_field.name_ref(); 222 let rcf_name_ref = record_field.name_ref();
193 let rcf_expr = record_field.expr(); 223 let rcf_expr = record_field.expr();
@@ -197,45 +227,40 @@ fn source_edit_from_name_ref(
197 if field_name == *name_ref { 227 if field_name == *name_ref {
198 if init.text() == new_name { 228 if init.text() == new_name {
199 mark::hit!(test_rename_field_put_init_shorthand); 229 mark::hit!(test_rename_field_put_init_shorthand);
200 // same names, we can use a shorthand here instead 230 // same names, we can use a shorthand here instead.
201 // we do not want to erase attributes hence this range start 231 // we do not want to erase attributes hence this range start
202 let s = field_name.syntax().text_range().start(); 232 let s = field_name.syntax().text_range().start();
203 let e = record_field.syntax().text_range().end(); 233 let e = record_field.syntax().text_range().end();
204 return (Some(TextRange::new(s, e)), format!("{}", new_name)); 234 return Some((TextRange::new(s, e), new_name.to_owned()));
205 } 235 }
206 } else if init == *name_ref { 236 } else if init == *name_ref {
207 if field_name.text() == new_name { 237 if field_name.text() == new_name {
208 mark::hit!(test_rename_local_put_init_shorthand); 238 mark::hit!(test_rename_local_put_init_shorthand);
209 // same names, we can use a shorthand here instead 239 // same names, we can use a shorthand here instead.
210 // we do not want to erase attributes hence this range start 240 // we do not want to erase attributes hence this range start
211 let s = field_name.syntax().text_range().start(); 241 let s = field_name.syntax().text_range().start();
212 let e = record_field.syntax().text_range().end(); 242 let e = record_field.syntax().text_range().end();
213 return (Some(TextRange::new(s, e)), format!("{}", new_name)); 243 return Some((TextRange::new(s, e), new_name.to_owned()));
214 } 244 }
215 } 245 }
246 None
216 } 247 }
217 // init shorthand 248 // init shorthand
218 (None, Some(_)) => { 249 // FIXME: instead of splitting the shorthand, recursively trigger a rename of the
219 // FIXME: instead of splitting the shorthand, recursively trigger a rename of the 250 // other name https://github.com/rust-analyzer/rust-analyzer/issues/6547
220 // other name https://github.com/rust-analyzer/rust-analyzer/issues/6547 251 (None, Some(_)) if matches!(def, Definition::Field(_)) => {
221 match def { 252 mark::hit!(test_rename_field_in_field_shorthand);
222 Definition::Field(_) => { 253 let s = name_ref.syntax().text_range().start();
223 mark::hit!(test_rename_field_in_field_shorthand); 254 Some((TextRange::empty(s), format!("{}: ", new_name)))
224 let s = name_ref.syntax().text_range().start();
225 return (Some(TextRange::empty(s)), format!("{}: ", new_name));
226 }
227 Definition::Local(_) => {
228 mark::hit!(test_rename_local_in_field_shorthand);
229 let s = name_ref.syntax().text_range().end();
230 return (Some(TextRange::empty(s)), format!(": {}", new_name));
231 }
232 _ => {}
233 }
234 } 255 }
235 _ => {} 256 (None, Some(_)) if matches!(def, Definition::Local(_)) => {
257 mark::hit!(test_rename_local_in_field_shorthand);
258 let s = name_ref.syntax().text_range().end();
259 Some((TextRange::empty(s), format!(": {}", new_name)))
260 }
261 _ => None,
236 } 262 }
237 } 263 } else if let Some(record_field) = ast::RecordPatField::for_field_name_ref(name_ref) {
238 if let Some(record_field) = ast::RecordPatField::for_field_name_ref(name_ref) {
239 let rcf_name_ref = record_field.name_ref(); 264 let rcf_name_ref = record_field.name_ref();
240 let rcf_pat = record_field.pat(); 265 let rcf_pat = record_field.pat();
241 match (rcf_name_ref, rcf_pat) { 266 match (rcf_name_ref, rcf_pat) {
@@ -244,17 +269,20 @@ fn source_edit_from_name_ref(
244 // field name is being renamed 269 // field name is being renamed
245 if pat.name().map_or(false, |it| it.text() == new_name) { 270 if pat.name().map_or(false, |it| it.text() == new_name) {
246 mark::hit!(test_rename_field_put_init_shorthand_pat); 271 mark::hit!(test_rename_field_put_init_shorthand_pat);
247 // same names, we can use a shorthand here instead 272 // same names, we can use a shorthand here instead/
248 // we do not want to erase attributes hence this range start 273 // we do not want to erase attributes hence this range start
249 let s = field_name.syntax().text_range().start(); 274 let s = field_name.syntax().text_range().start();
250 let e = record_field.syntax().text_range().end(); 275 let e = record_field.syntax().text_range().end();
251 return (Some(TextRange::new(s, e)), format!("{}", new_name)); 276 Some((TextRange::new(s, e), pat.to_string()))
277 } else {
278 None
252 } 279 }
253 } 280 }
254 _ => {} 281 _ => None,
255 } 282 }
283 } else {
284 None
256 } 285 }
257 (None, format!("{}", new_name))
258} 286}
259 287
260fn rename_mod( 288fn rename_mod(
@@ -1477,7 +1505,7 @@ fn foo(i: i32) -> Foo {
1477 } 1505 }
1478 1506
1479 #[test] 1507 #[test]
1480 fn test_struct_field_destructure_into_shorthand() { 1508 fn test_struct_field_pat_into_shorthand() {
1481 mark::check!(test_rename_field_put_init_shorthand_pat); 1509 mark::check!(test_rename_field_put_init_shorthand_pat);
1482 check( 1510 check(
1483 "baz", 1511 "baz",
@@ -1485,16 +1513,16 @@ fn foo(i: i32) -> Foo {
1485struct Foo { i$0: i32 } 1513struct Foo { i$0: i32 }
1486 1514
1487fn foo(foo: Foo) { 1515fn foo(foo: Foo) {
1488 let Foo { i: baz } = foo; 1516 let Foo { i: ref baz @ qux } = foo;
1489 let _ = baz; 1517 let _ = qux;
1490} 1518}
1491"#, 1519"#,
1492 r#" 1520 r#"
1493struct Foo { baz: i32 } 1521struct Foo { baz: i32 }
1494 1522
1495fn foo(foo: Foo) { 1523fn foo(foo: Foo) {
1496 let Foo { baz } = foo; 1524 let Foo { ref baz @ qux } = foo;
1497 let _ = baz; 1525 let _ = qux;
1498} 1526}
1499"#, 1527"#,
1500 ); 1528 );
@@ -1568,6 +1596,27 @@ fn foo(Foo { i: bar }: foo) -> i32 {
1568 } 1596 }
1569 1597
1570 #[test] 1598 #[test]
1599 fn test_struct_field_complex_ident_pat() {
1600 check(
1601 "baz",
1602 r#"
1603struct Foo { i$0: i32 }
1604
1605fn foo(foo: Foo) {
1606 let Foo { ref i } = foo;
1607}
1608"#,
1609 r#"
1610struct Foo { baz: i32 }
1611
1612fn foo(foo: Foo) {
1613 let Foo { baz: ref i } = foo;
1614}
1615"#,
1616 );
1617 }
1618
1619 #[test]
1571 fn test_rename_lifetimes() { 1620 fn test_rename_lifetimes() {
1572 mark::check!(rename_lifetime); 1621 mark::check!(rename_lifetime);
1573 check( 1622 check(
@@ -1674,4 +1723,38 @@ impl Foo {
1674"#, 1723"#,
1675 ) 1724 )
1676 } 1725 }
1726
1727 #[test]
1728 fn test_rename_field_in_pat_in_macro_doesnt_shorthand() {
1729 // ideally we would be able to make this emit a short hand, but I doubt this is easily possible
1730 check(
1731 "baz",
1732 r#"
1733macro_rules! foo {
1734 ($pattern:pat) => {
1735 let $pattern = loop {};
1736 };
1737}
1738struct Foo {
1739 bar$0: u32,
1740}
1741fn foo() {
1742 foo!(Foo { bar: baz });
1743}
1744"#,
1745 r#"
1746macro_rules! foo {
1747 ($pattern:pat) => {
1748 let $pattern = loop {};
1749 };
1750}
1751struct Foo {
1752 baz: u32,
1753}
1754fn foo() {
1755 foo!(Foo { baz: baz });
1756}
1757"#,
1758 )
1759 }
1677} 1760}