diff options
-rw-r--r-- | crates/ide/src/references/rename.rs | 179 | ||||
-rw-r--r-- | crates/syntax/src/ast/node_ext.rs | 5 |
2 files changed, 133 insertions, 51 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 | } |
diff --git a/crates/syntax/src/ast/node_ext.rs b/crates/syntax/src/ast/node_ext.rs index b105cb0e0..307e150e9 100644 --- a/crates/syntax/src/ast/node_ext.rs +++ b/crates/syntax/src/ast/node_ext.rs | |||
@@ -3,12 +3,11 @@ | |||
3 | 3 | ||
4 | use std::fmt; | 4 | use std::fmt; |
5 | 5 | ||
6 | use ast::AttrsOwner; | ||
7 | use itertools::Itertools; | 6 | use itertools::Itertools; |
8 | use parser::SyntaxKind; | 7 | use parser::SyntaxKind; |
9 | 8 | ||
10 | use crate::{ | 9 | use crate::{ |
11 | ast::{self, support, AstNode, AstToken, NameOwner, SyntaxNode}, | 10 | ast::{self, support, AstNode, AstToken, AttrsOwner, NameOwner, SyntaxNode}, |
12 | SmolStr, SyntaxElement, SyntaxToken, T, | 11 | SmolStr, SyntaxElement, SyntaxToken, T, |
13 | }; | 12 | }; |
14 | 13 | ||
@@ -324,7 +323,7 @@ impl ast::RecordPatField { | |||
324 | 323 | ||
325 | pub fn for_field_name(field_name: &ast::Name) -> Option<ast::RecordPatField> { | 324 | pub fn for_field_name(field_name: &ast::Name) -> Option<ast::RecordPatField> { |
326 | let candidate = | 325 | let candidate = |
327 | field_name.syntax().ancestors().nth(3).and_then(ast::RecordPatField::cast)?; | 326 | field_name.syntax().ancestors().nth(2).and_then(ast::RecordPatField::cast)?; |
328 | match candidate.field_name()? { | 327 | match candidate.field_name()? { |
329 | NameOrNameRef::Name(name) if name == *field_name => Some(candidate), | 328 | NameOrNameRef::Name(name) if name == *field_name => Some(candidate), |
330 | _ => None, | 329 | _ => None, |