Skip to content

Commit 8d7eea9

Browse files
bors[bot]Veykril
andauthored
Merge #9929
9929: fix: Handle all rename special cases for record pattern fields r=Veykril a=Veykril Co-authored-by: Lukas Wirth <[email protected]>
2 parents dcbaa75 + daf3094 commit 8d7eea9

File tree

4 files changed

+178
-65
lines changed

4 files changed

+178
-65
lines changed

crates/ide/src/rename.rs

Lines changed: 67 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,7 @@ mod tests {
274274

275275
use super::{RangeInfo, RenameError};
276276

277+
#[track_caller]
277278
fn check(new_name: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
278279
let ra_fixture_after = &trim_indent(ra_fixture_after);
279280
let (analysis, position) = fixture::position(ra_fixture_before);
@@ -1332,9 +1333,71 @@ fn foo(foo: Foo) {
13321333
struct Foo { baz: i32 }
13331334
13341335
fn foo(foo: Foo) {
1335-
let Foo { ref baz @ qux } = foo;
1336+
let Foo { baz: ref baz @ qux } = foo;
13361337
let _ = qux;
13371338
}
1339+
"#,
1340+
);
1341+
check(
1342+
"baz",
1343+
r#"
1344+
struct Foo { i$0: i32 }
1345+
1346+
fn foo(foo: Foo) {
1347+
let Foo { i: ref baz } = foo;
1348+
let _ = qux;
1349+
}
1350+
"#,
1351+
r#"
1352+
struct Foo { baz: i32 }
1353+
1354+
fn foo(foo: Foo) {
1355+
let Foo { ref baz } = foo;
1356+
let _ = qux;
1357+
}
1358+
"#,
1359+
);
1360+
}
1361+
1362+
#[test]
1363+
fn test_struct_local_pat_into_shorthand() {
1364+
cov_mark::check!(test_rename_local_put_init_shorthand_pat);
1365+
check(
1366+
"field",
1367+
r#"
1368+
struct Foo { field: i32 }
1369+
1370+
fn foo(foo: Foo) {
1371+
let Foo { field: qux$0 } = foo;
1372+
let _ = qux;
1373+
}
1374+
"#,
1375+
r#"
1376+
struct Foo { field: i32 }
1377+
1378+
fn foo(foo: Foo) {
1379+
let Foo { field } = foo;
1380+
let _ = field;
1381+
}
1382+
"#,
1383+
);
1384+
check(
1385+
"field",
1386+
r#"
1387+
struct Foo { field: i32 }
1388+
1389+
fn foo(foo: Foo) {
1390+
let Foo { field: x @ qux$0 } = foo;
1391+
let _ = qux;
1392+
}
1393+
"#,
1394+
r#"
1395+
struct Foo { field: i32 }
1396+
1397+
fn foo(foo: Foo) {
1398+
let Foo { field: x @ field } = foo;
1399+
let _ = field;
1400+
}
13381401
"#,
13391402
);
13401403
}
@@ -1390,7 +1453,7 @@ struct Foo {
13901453
i: i32
13911454
}
13921455
1393-
fn foo(Foo { i }: foo) -> i32 {
1456+
fn foo(Foo { i }: Foo) -> i32 {
13941457
i$0
13951458
}
13961459
"#,
@@ -1399,7 +1462,7 @@ struct Foo {
13991462
i: i32
14001463
}
14011464
1402-
fn foo(Foo { i: bar }: foo) -> i32 {
1465+
fn foo(Foo { i: bar }: Foo) -> i32 {
14031466
bar
14041467
}
14051468
"#,
@@ -1408,6 +1471,7 @@ fn foo(Foo { i: bar }: foo) -> i32 {
14081471

14091472
#[test]
14101473
fn test_struct_field_complex_ident_pat() {
1474+
cov_mark::check!(rename_record_pat_field_name_split);
14111475
check(
14121476
"baz",
14131477
r#"

crates/ide_db/src/helpers/import_assets.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,11 +120,11 @@ impl ImportAssets {
120120
}
121121

122122
pub fn for_ident_pat(pat: &ast::IdentPat, sema: &Semantics<RootDatabase>) -> Option<Self> {
123-
let name = pat.name()?;
124-
let candidate_node = pat.syntax().clone();
125123
if !pat.is_simple_ident() {
126124
return None;
127125
}
126+
let name = pat.name()?;
127+
let candidate_node = pat.syntax().clone();
128128
Some(Self {
129129
import_candidate: ImportCandidate::for_name(sema, &name)?,
130130
module_with_candidate: sema.scope(&candidate_node).module()?,

crates/ide_db/src/rename.rs

Lines changed: 106 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ use syntax::{
3030
ast::{self, NameOwner},
3131
lex_single_syntax_kind, AstNode, SyntaxKind, TextRange, T,
3232
};
33-
use text_edit::TextEdit;
33+
use text_edit::{TextEdit, TextEditBuilder};
3434

3535
use crate::{
3636
defs::Definition,
@@ -303,141 +303,187 @@ pub fn source_edit_from_references(
303303
) -> TextEdit {
304304
let mut edit = TextEdit::builder();
305305
for reference in references {
306-
let (range, replacement) = match &reference.name {
306+
let has_emitted_edit = match &reference.name {
307307
// if the ranges differ then the node is inside a macro call, we can't really attempt
308308
// to make special rewrites like shorthand syntax and such, so just rename the node in
309309
// the macro input
310310
ast::NameLike::NameRef(name_ref)
311311
if name_ref.syntax().text_range() == reference.range =>
312312
{
313-
source_edit_from_name_ref(name_ref, new_name, def)
313+
source_edit_from_name_ref(&mut edit, name_ref, new_name, def)
314314
}
315315
ast::NameLike::Name(name) if name.syntax().text_range() == reference.range => {
316-
source_edit_from_name(name, new_name)
316+
source_edit_from_name(&mut edit, name, new_name)
317317
}
318-
_ => None,
318+
_ => false,
319+
};
320+
if !has_emitted_edit {
321+
edit.replace(reference.range, new_name.to_string());
319322
}
320-
.unwrap_or_else(|| (reference.range, new_name.to_string()));
321-
edit.replace(range, replacement);
322323
}
324+
323325
edit.finish()
324326
}
325327

326-
fn source_edit_from_name(name: &ast::Name, new_name: &str) -> Option<(TextRange, String)> {
328+
fn source_edit_from_name(edit: &mut TextEditBuilder, name: &ast::Name, new_name: &str) -> bool {
327329
if let Some(_) = ast::RecordPatField::for_field_name(name) {
328-
// FIXME: instead of splitting the shorthand, recursively trigger a rename of the
329-
// other name https://github.com/rust-analyzer/rust-analyzer/issues/6547
330330
if let Some(ident_pat) = name.syntax().parent().and_then(ast::IdentPat::cast) {
331-
return Some((
332-
TextRange::empty(ident_pat.syntax().text_range().start()),
333-
[new_name, ": "].concat(),
334-
));
331+
cov_mark::hit!(rename_record_pat_field_name_split);
332+
// Foo { ref mut field } -> Foo { new_name: ref mut field }
333+
// ^ insert `new_name: `
334+
335+
// FIXME: instead of splitting the shorthand, recursively trigger a rename of the
336+
// other name https://github.com/rust-analyzer/rust-analyzer/issues/6547
337+
edit.insert(ident_pat.syntax().text_range().start(), format!("{}: ", new_name));
338+
return true;
335339
}
336340
}
337-
None
341+
342+
false
338343
}
339344

340345
fn source_edit_from_name_ref(
346+
edit: &mut TextEditBuilder,
341347
name_ref: &ast::NameRef,
342348
new_name: &str,
343349
def: Definition,
344-
) -> Option<(TextRange, String)> {
350+
) -> bool {
345351
if let Some(record_field) = ast::RecordExprField::for_name_ref(name_ref) {
346352
let rcf_name_ref = record_field.name_ref();
347353
let rcf_expr = record_field.expr();
348-
match (rcf_name_ref, rcf_expr.and_then(|it| it.name_ref())) {
354+
match &(rcf_name_ref, rcf_expr.and_then(|it| it.name_ref())) {
349355
// field: init-expr, check if we can use a field init shorthand
350356
(Some(field_name), Some(init)) => {
351-
if field_name == *name_ref {
357+
if field_name == name_ref {
352358
if init.text() == new_name {
353359
cov_mark::hit!(test_rename_field_put_init_shorthand);
360+
// Foo { field: local } -> Foo { local }
361+
// ^^^^^^^ delete this
362+
354363
// same names, we can use a shorthand here instead.
355364
// we do not want to erase attributes hence this range start
356365
let s = field_name.syntax().text_range().start();
357-
let e = record_field.syntax().text_range().end();
358-
return Some((TextRange::new(s, e), new_name.to_owned()));
366+
let e = init.syntax().text_range().start();
367+
edit.delete(TextRange::new(s, e));
368+
return true;
359369
}
360-
} else if init == *name_ref {
370+
} else if init == name_ref {
361371
if field_name.text() == new_name {
362372
cov_mark::hit!(test_rename_local_put_init_shorthand);
373+
// Foo { field: local } -> Foo { field }
374+
// ^^^^^^^ delete this
375+
363376
// same names, we can use a shorthand here instead.
364377
// we do not want to erase attributes hence this range start
365-
let s = field_name.syntax().text_range().start();
366-
let e = record_field.syntax().text_range().end();
367-
return Some((TextRange::new(s, e), new_name.to_owned()));
378+
let s = field_name.syntax().text_range().end();
379+
let e = init.syntax().text_range().end();
380+
edit.delete(TextRange::new(s, e));
381+
return true;
368382
}
369383
}
370-
None
371384
}
372385
// init shorthand
373386
(None, Some(_)) if matches!(def, Definition::Field(_)) => {
374387
cov_mark::hit!(test_rename_field_in_field_shorthand);
375-
let s = name_ref.syntax().text_range().start();
376-
Some((TextRange::empty(s), format!("{}: ", new_name)))
388+
// Foo { field } -> Foo { new_name: field }
389+
// ^ insert `new_name: `
390+
let offset = name_ref.syntax().text_range().start();
391+
edit.insert(offset, format!("{}: ", new_name));
392+
return true;
377393
}
378394
(None, Some(_)) if matches!(def, Definition::Local(_)) => {
379395
cov_mark::hit!(test_rename_local_in_field_shorthand);
380-
let s = name_ref.syntax().text_range().end();
381-
Some((TextRange::empty(s), format!(": {}", new_name)))
396+
// Foo { field } -> Foo { field: new_name }
397+
// ^ insert `: new_name`
398+
let offset = name_ref.syntax().text_range().end();
399+
edit.insert(offset, format!(": {}", new_name));
400+
return true;
382401
}
383-
_ => None,
402+
_ => (),
384403
}
385404
} else if let Some(record_field) = ast::RecordPatField::for_field_name_ref(name_ref) {
386405
let rcf_name_ref = record_field.name_ref();
387406
let rcf_pat = record_field.pat();
388407
match (rcf_name_ref, rcf_pat) {
389408
// field: rename
390-
(Some(field_name), Some(ast::Pat::IdentPat(pat))) if field_name == *name_ref => {
409+
(Some(field_name), Some(ast::Pat::IdentPat(pat)))
410+
if field_name == *name_ref && pat.at_token().is_none() =>
411+
{
391412
// field name is being renamed
392-
if pat.name().map_or(false, |it| it.text() == new_name) {
393-
cov_mark::hit!(test_rename_field_put_init_shorthand_pat);
394-
// same names, we can use a shorthand here instead/
395-
// we do not want to erase attributes hence this range start
396-
let s = field_name.syntax().text_range().start();
397-
let e = record_field.syntax().text_range().end();
398-
Some((TextRange::new(s, e), pat.to_string()))
399-
} else {
400-
None
413+
if let Some(name) = pat.name() {
414+
if name.text() == new_name {
415+
cov_mark::hit!(test_rename_field_put_init_shorthand_pat);
416+
// Foo { field: ref mut local } -> Foo { ref mut field }
417+
// ^^^^^^^ delete this
418+
// ^^^^^ replace this with `field`
419+
420+
// same names, we can use a shorthand here instead/
421+
// we do not want to erase attributes hence this range start
422+
let s = field_name.syntax().text_range().start();
423+
let e = pat.syntax().text_range().start();
424+
edit.delete(TextRange::new(s, e));
425+
edit.replace(name.syntax().text_range(), new_name.to_string());
426+
return true;
427+
}
401428
}
402429
}
403-
_ => None,
430+
_ => (),
404431
}
405-
} else {
406-
None
407432
}
433+
false
408434
}
409435

410436
fn source_edit_from_def(
411437
sema: &Semantics<RootDatabase>,
412438
def: Definition,
413439
new_name: &str,
414440
) -> Result<(FileId, TextEdit)> {
415-
let frange = def
441+
let FileRange { file_id, range } = def
416442
.range_for_rename(sema)
417443
.ok_or_else(|| format_err!("No identifier available to rename"))?;
418444

419-
let mut replacement_text = String::new();
420-
let mut repl_range = frange.range;
445+
let mut edit = TextEdit::builder();
421446
if let Definition::Local(local) = def {
422447
if let Either::Left(pat) = local.source(sema.db).value {
423-
if matches!(
424-
pat.syntax().parent().and_then(ast::RecordPatField::cast),
425-
Some(pat_field) if pat_field.name_ref().is_none()
426-
) {
427-
replacement_text.push_str(": ");
428-
replacement_text.push_str(new_name);
429-
repl_range = TextRange::new(
430-
pat.syntax().text_range().end(),
431-
pat.syntax().text_range().end(),
432-
);
448+
// special cases required for renaming fields/locals in Record patterns
449+
if let Some(pat_field) = pat.syntax().parent().and_then(ast::RecordPatField::cast) {
450+
let name_range = pat.name().unwrap().syntax().text_range();
451+
if let Some(name_ref) = pat_field.name_ref() {
452+
if new_name == name_ref.text() && pat.at_token().is_none() {
453+
// Foo { field: ref mut local } -> Foo { ref mut field }
454+
// ^^^^^^ delete this
455+
// ^^^^^ replace this with `field`
456+
cov_mark::hit!(test_rename_local_put_init_shorthand_pat);
457+
edit.delete(
458+
name_ref
459+
.syntax()
460+
.text_range()
461+
.cover_offset(pat.syntax().text_range().start()),
462+
);
463+
edit.replace(name_range, name_ref.text().to_string());
464+
} else {
465+
// Foo { field: ref mut local @ local 2} -> Foo { field: ref mut new_name @ local2 }
466+
// Foo { field: ref mut local } -> Foo { field: ref mut new_name }
467+
// ^^^^^ replace this with `new_name`
468+
edit.replace(name_range, new_name.to_string());
469+
}
470+
} else {
471+
// Foo { ref mut field } -> Foo { field: ref mut new_name }
472+
// ^ insert `field: `
473+
// ^^^^^ replace this with `new_name`
474+
edit.insert(
475+
pat.syntax().text_range().start(),
476+
format!("{}: ", pat_field.field_name().unwrap()),
477+
);
478+
edit.replace(name_range, new_name.to_string());
479+
}
433480
}
434481
}
435482
}
436-
if replacement_text.is_empty() {
437-
replacement_text.push_str(new_name);
483+
if edit.is_empty() {
484+
edit.replace(range, new_name.to_string());
438485
}
439-
let edit = TextEdit::replace(repl_range, replacement_text);
440-
Ok((frange.file_id, edit))
486+
Ok((file_id, edit.finish()))
441487
}
442488

443489
#[derive(Copy, Clone, Debug, PartialEq)]

crates/text_edit/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,9 @@ impl<'a> IntoIterator for &'a TextEdit {
159159
}
160160

161161
impl TextEditBuilder {
162+
pub fn is_empty(&self) -> bool {
163+
self.indels.is_empty()
164+
}
162165
pub fn replace(&mut self, range: TextRange, replace_with: String) {
163166
self.indel(Indel::replace(range, replace_with))
164167
}

0 commit comments

Comments
 (0)