diff options
Diffstat (limited to 'crates/ide/src')
-rw-r--r-- | crates/ide/src/annotations.rs | 40 | ||||
-rw-r--r-- | crates/ide/src/references.rs | 305 | ||||
-rw-r--r-- | crates/ide/src/references/rename.rs | 179 |
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 | ||
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 | } |
@@ -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] | ||
1206 | fn 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] | ||
1223 | fn my_proc_macro() {} | ||
1224 | |||
1225 | #[my_proc_macro$0] | ||
1226 | fn 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 | ||
166 | fn source_edit_from_references( | 177 | fn 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 | ||
204 | fn 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 | |||
186 | fn source_edit_from_name_ref( | 216 | fn 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 | ||
260 | fn rename_mod( | 288 | fn 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 { | |||
1485 | struct Foo { i$0: i32 } | 1513 | struct Foo { i$0: i32 } |
1486 | 1514 | ||
1487 | fn foo(foo: Foo) { | 1515 | fn 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#" |
1493 | struct Foo { baz: i32 } | 1521 | struct Foo { baz: i32 } |
1494 | 1522 | ||
1495 | fn foo(foo: Foo) { | 1523 | fn 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#" | ||
1603 | struct Foo { i$0: i32 } | ||
1604 | |||
1605 | fn foo(foo: Foo) { | ||
1606 | let Foo { ref i } = foo; | ||
1607 | } | ||
1608 | "#, | ||
1609 | r#" | ||
1610 | struct Foo { baz: i32 } | ||
1611 | |||
1612 | fn 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#" | ||
1733 | macro_rules! foo { | ||
1734 | ($pattern:pat) => { | ||
1735 | let $pattern = loop {}; | ||
1736 | }; | ||
1737 | } | ||
1738 | struct Foo { | ||
1739 | bar$0: u32, | ||
1740 | } | ||
1741 | fn foo() { | ||
1742 | foo!(Foo { bar: baz }); | ||
1743 | } | ||
1744 | "#, | ||
1745 | r#" | ||
1746 | macro_rules! foo { | ||
1747 | ($pattern:pat) => { | ||
1748 | let $pattern = loop {}; | ||
1749 | }; | ||
1750 | } | ||
1751 | struct Foo { | ||
1752 | baz: u32, | ||
1753 | } | ||
1754 | fn foo() { | ||
1755 | foo!(Foo { baz: baz }); | ||
1756 | } | ||
1757 | "#, | ||
1758 | ) | ||
1759 | } | ||
1677 | } | 1760 | } |