aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ide/src/references/rename.rs179
-rw-r--r--crates/syntax/src/ast/node_ext.rs5
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
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}
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
4use std::fmt; 4use std::fmt;
5 5
6use ast::AttrsOwner;
7use itertools::Itertools; 6use itertools::Itertools;
8use parser::SyntaxKind; 7use parser::SyntaxKind;
9 8
10use crate::{ 9use 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,