Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Properly handle shorthands in destructure patterns when renaming #6552

Merged
merged 4 commits into from
Nov 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 17 additions & 8 deletions crates/ide/src/references.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,14 +110,23 @@ pub(crate) fn find_all_refs(
.filter(|r| search_kind == ReferenceKind::Other || search_kind == r.kind)
.collect();

let decl_range = def.try_to_nav(sema.db)?.focus_or_full_range();

let declaration = Declaration {
nav: def.try_to_nav(sema.db)?,
kind: ReferenceKind::Other,
access: decl_access(&def, &syntax, decl_range),
let nav = def.try_to_nav(sema.db)?;
let decl_range = nav.focus_or_full_range();

let mut kind = ReferenceKind::Other;
if let Definition::Local(local) = def {
if let either::Either::Left(pat) = local.source(sema.db).value {
if matches!(
pat.syntax().parent().and_then(ast::RecordPatField::cast),
Some(pat_field) if pat_field.name_ref().is_none()
) {
kind = ReferenceKind::FieldShorthandForLocal;
}
}
};

let declaration = Declaration { nav, kind, access: decl_access(&def, &syntax, decl_range) };

Some(RangeInfo::new(range, ReferenceSearchResult { declaration, references }))
}

Expand Down Expand Up @@ -613,7 +622,7 @@ fn foo() {
expect![[r#"
f RECORD_FIELD FileId(0) 15..21 15..16 Other

FileId(0) 55..56 Other Read
FileId(0) 55..56 RecordFieldExprOrPat Read
FileId(0) 68..69 Other Write
"#]],
);
Expand Down Expand Up @@ -748,7 +757,7 @@ fn f() -> m::En {
expect![[r#"
field RECORD_FIELD FileId(0) 56..65 56..61 Other

FileId(0) 125..130 Other Read
FileId(0) 125..130 RecordFieldExprOrPat Read
"#]],
);
}
Expand Down
165 changes: 158 additions & 7 deletions crates/ide/src/references/rename.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! FIXME: write short doc here

use hir::{Module, ModuleDef, ModuleSource, Semantics};
use ide_db::base_db::SourceDatabaseExt;
use ide_db::base_db::{FileRange, SourceDatabaseExt};
use ide_db::{
defs::{Definition, NameClass, NameRefClass},
RootDatabase,
Expand Down Expand Up @@ -106,9 +106,12 @@ fn find_module_at_offset(
Some(module)
}

fn source_edit_from_reference(reference: Reference, new_name: &str) -> SourceFileEdit {
fn source_edit_from_reference(
sema: &Semantics<RootDatabase>,
reference: Reference,
new_name: &str,
) -> SourceFileEdit {
let mut replacement_text = String::new();
let file_id = reference.file_range.file_id;
let range = match reference.kind {
ReferenceKind::FieldShorthandForField => {
mark::hit!(test_rename_struct_field_for_shorthand);
Expand All @@ -122,12 +125,48 @@ fn source_edit_from_reference(reference: Reference, new_name: &str) -> SourceFil
replacement_text.push_str(new_name);
TextRange::new(reference.file_range.range.end(), reference.file_range.range.end())
}
ReferenceKind::RecordFieldExprOrPat => {
mark::hit!(test_rename_field_expr_pat);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

replacement_text.push_str(new_name);
edit_text_range_for_record_field_expr_or_pat(sema, reference.file_range, new_name)
}
_ => {
replacement_text.push_str(new_name);
reference.file_range.range
}
};
SourceFileEdit { file_id, edit: TextEdit::replace(range, replacement_text) }
SourceFileEdit {
file_id: reference.file_range.file_id,
edit: TextEdit::replace(range, replacement_text),
}
}

fn edit_text_range_for_record_field_expr_or_pat(
Veykril marked this conversation as resolved.
Show resolved Hide resolved
sema: &Semantics<RootDatabase>,
file_range: FileRange,
new_name: &str,
) -> TextRange {
let source_file = sema.parse(file_range.file_id);
let file_syntax = source_file.syntax();
let original_range = file_range.range;

syntax::algo::find_node_at_range::<ast::RecordExprField>(file_syntax, original_range)
.and_then(|field_expr| match field_expr.expr().and_then(|e| e.name_ref()) {
Some(name) if &name.to_string() == new_name => Some(field_expr.syntax().text_range()),
_ => None,
})
.or_else(|| {
syntax::algo::find_node_at_range::<ast::RecordPatField>(file_syntax, original_range)
.and_then(|field_pat| match field_pat.pat() {
Some(ast::Pat::IdentPat(pat))
if pat.name().map(|n| n.to_string()).as_deref() == Some(new_name) =>
{
Some(field_pat.syntax().text_range())
}
_ => None,
})
})
.unwrap_or(original_range)
}

fn rename_mod(
Expand Down Expand Up @@ -170,7 +209,7 @@ fn rename_mod(
let ref_edits = refs
.references
.into_iter()
.map(|reference| source_edit_from_reference(reference, new_name));
.map(|reference| source_edit_from_reference(sema, reference, new_name));
source_file_edits.extend(ref_edits);

Ok(RangeInfo::new(range, SourceChange::from_edits(source_file_edits, file_system_edits)))
Expand Down Expand Up @@ -211,7 +250,7 @@ fn rename_to_self(

let mut edits = usages
.into_iter()
.map(|reference| source_edit_from_reference(reference, "self"))
.map(|reference| source_edit_from_reference(sema, reference, "self"))
.collect::<Vec<_>>();

edits.push(SourceFileEdit {
Expand Down Expand Up @@ -300,7 +339,7 @@ fn rename_reference(

let edit = refs
.into_iter()
.map(|reference| source_edit_from_reference(reference, new_name))
.map(|reference| source_edit_from_reference(sema, reference, new_name))
.collect::<Vec<_>>();

if edit.is_empty() {
Expand Down Expand Up @@ -1097,4 +1136,116 @@ impl Foo {
"#,
);
}

#[test]
fn test_initializer_use_field_init_shorthand() {
mark::check!(test_rename_field_expr_pat);
check(
"bar",
r#"
struct Foo { i<|>: i32 }

fn foo(bar: i32) -> Foo {
Foo { i: bar }
}
"#,
r#"
struct Foo { bar: i32 }

fn foo(bar: i32) -> Foo {
Foo { bar }
}
"#,
);
}

#[test]
fn test_struct_field_destructure_into_shorthand() {
check(
"baz",
r#"
struct Foo { i<|>: i32 }

fn foo(foo: Foo) {
let Foo { i: baz } = foo;
let _ = baz;
}
"#,
r#"
struct Foo { baz: i32 }

fn foo(foo: Foo) {
let Foo { baz } = foo;
let _ = baz;
}
"#,
);
}

#[test]
fn test_rename_binding_in_destructure_pat() {
let expected_fixture = r#"
struct Foo {
i: i32,
}

fn foo(foo: Foo) {
let Foo { i: bar } = foo;
let _ = bar;
}
"#;
check(
"bar",
r#"
struct Foo {
i: i32,
}

fn foo(foo: Foo) {
let Foo { i: b } = foo;
let _ = b<|>;
}
"#,
expected_fixture,
);
check(
"bar",
r#"
struct Foo {
i: i32,
}

fn foo(foo: Foo) {
let Foo { i } = foo;
let _ = i<|>;
}
"#,
expected_fixture,
);
}

#[test]
fn test_rename_binding_in_destructure_param_pat() {
check(
"bar",
r#"
struct Foo {
i: i32
}

fn foo(Foo { i }: foo) -> i32 {
i<|>
}
"#,
r#"
struct Foo {
i: i32
}

fn foo(Foo { i: bar }: foo) -> i32 {
bar
}
"#,
)
}
}
20 changes: 18 additions & 2 deletions crates/ide_db/src/search.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ pub enum ReferenceKind {
FieldShorthandForField,
FieldShorthandForLocal,
StructLiteral,
RecordFieldExprOrPat,
Other,
}

Expand Down Expand Up @@ -278,8 +279,9 @@ impl<'a> FindUsages<'a> {
) -> bool {
match NameRefClass::classify(self.sema, &name_ref) {
Some(NameRefClass::Definition(def)) if &def == self.def => {
let kind = if is_record_lit_name_ref(&name_ref) || is_call_expr_name_ref(&name_ref)
{
let kind = if is_record_field_expr_or_pat(&name_ref) {
ReferenceKind::RecordFieldExprOrPat
} else if is_record_lit_name_ref(&name_ref) || is_call_expr_name_ref(&name_ref) {
ReferenceKind::StructLiteral
} else {
ReferenceKind::Other
Expand Down Expand Up @@ -385,3 +387,17 @@ fn is_record_lit_name_ref(name_ref: &ast::NameRef) -> bool {
.map(|p| p.name_ref().as_ref() == Some(name_ref))
.unwrap_or(false)
}

fn is_record_field_expr_or_pat(name_ref: &ast::NameRef) -> bool {
if let Some(parent) = name_ref.syntax().parent() {
match_ast! {
match parent {
ast::RecordExprField(it) => true,
ast::RecordPatField(_it) => true,
_ => false,
}
}
} else {
false
}
}
12 changes: 12 additions & 0 deletions crates/syntax/src/ast/expr_ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,18 @@ impl ast::Expr {
_ => false,
}
}

pub fn name_ref(&self) -> Option<ast::NameRef> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same thing here -- this methong is non-primitives -- it's a convenience function which reaches deep into the node. We should have such functions, but not as inherent methods on AST nodes.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the same -- see my recent comment on another PR :-)

if let ast::Expr::PathExpr(expr) = self {
let path = expr.path()?;
let segment = path.segment()?;
let name_ref = segment.name_ref()?;
if path.qualifier().is_none() {
return Some(name_ref);
}
}
None
}
}

#[derive(Debug, Clone, PartialEq, Eq)]
Expand Down
10 changes: 1 addition & 9 deletions crates/syntax/src/ast/node_ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,15 +203,7 @@ impl ast::RecordExprField {
if let Some(name_ref) = self.name_ref() {
return Some(name_ref);
}
if let Some(ast::Expr::PathExpr(expr)) = self.expr() {
let path = expr.path()?;
let segment = path.segment()?;
let name_ref = segment.name_ref()?;
if path.qualifier().is_none() {
return Some(name_ref);
}
}
None
self.expr()?.name_ref()
}
}

Expand Down