diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2021-02-14 15:29:00 +0000 |
---|---|---|
committer | GitHub <[email protected]> | 2021-02-14 15:29:00 +0000 |
commit | 63c5c928561b45dcef5c13881f09e5bc98c1eb9a (patch) | |
tree | e338540418eeff1c317d9781ae17dd32c987e9a2 /crates/ide | |
parent | 8638bcbfe79a19f3c2a75bd35054e90a245af3af (diff) | |
parent | 7b64622780bfa33c593ba856bdb6cfc31b220265 (diff) |
Merge #7668
7668: Finalize rename infra rewrite r=matklad a=Veykril
This should be the final PR in regards to rewriting rename stuff, #4290.
It addresses 3 things:
- Currently renaming import aliases causes some undesired behavior(see #5198) which is why this PR causes us to just return an error if an attempt at renaming an alias is made for the time being. Though this only prevents it from happening when the alias import is renamed, so its not too helpful.
- Fixes #6898
- If we are inside a macro file simply rename the input name node as there isn't really a way to do any of the fancy shorthand renames and similar things as for that we would have to exactly know what the macro generates and what not.
Co-authored-by: Lukas Wirth <[email protected]>
Diffstat (limited to 'crates/ide')
-rw-r--r-- | crates/ide/src/references/rename.rs | 179 |
1 files changed, 131 insertions, 48 deletions
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 | } |