From da534bdd074788e47b5dae76998b8f8535ea7257 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Mon, 14 Jun 2021 19:14:34 +0300 Subject: internal: flatten module hierarchy It seems that any crate can be made better by flattening the modules down to a single layer? --- crates/ide/src/rename.rs | 1789 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1789 insertions(+) create mode 100644 crates/ide/src/rename.rs (limited to 'crates/ide/src/rename.rs') diff --git a/crates/ide/src/rename.rs b/crates/ide/src/rename.rs new file mode 100644 index 000000000..41689a939 --- /dev/null +++ b/crates/ide/src/rename.rs @@ -0,0 +1,1789 @@ +//! Renaming functionality. +//! +//! This is mostly front-end for [`ide_db::rename`], but it also includes the +//! tests. This module also implements a couple of magic tricks, like renaming +//! `self` and to `self` (to switch between associated function and method). +use hir::{AsAssocItem, InFile, Semantics}; +use ide_db::{ + base_db::FileId, + defs::{Definition, NameClass, NameRefClass}, + rename::{bail, format_err, source_edit_from_references, IdentifierKind}, + RootDatabase, +}; +use stdx::never; +use syntax::{ast, AstNode, SyntaxNode}; + +use text_edit::TextEdit; + +use crate::{FilePosition, RangeInfo, SourceChange}; + +pub use ide_db::rename::RenameError; + +type RenameResult = Result; + +/// Prepares a rename. The sole job of this function is to return the TextRange of the thing that is +/// being targeted for a rename. +pub(crate) fn prepare_rename( + db: &RootDatabase, + position: FilePosition, +) -> RenameResult> { + let sema = Semantics::new(db); + let source_file = sema.parse(position.file_id); + let syntax = source_file.syntax(); + + let def = find_definition(&sema, syntax, position)?; + let frange = + def.rename_range(&sema).ok_or_else(|| format_err!("No references found at position"))?; + Ok(RangeInfo::new(frange.range, ())) +} + +// Feature: Rename +// +// Renames the item below the cursor and all of its references +// +// |=== +// | Editor | Shortcut +// +// | VS Code | kbd:[F2] +// |=== +// +// image::https://user-images.githubusercontent.com/48062697/113065582-055aae80-91b1-11eb-8ade-2b58e6d81883.gif[] +pub(crate) fn rename( + db: &RootDatabase, + position: FilePosition, + new_name: &str, +) -> RenameResult { + let sema = Semantics::new(db); + rename_with_semantics(&sema, position, new_name) +} + +pub(crate) fn rename_with_semantics( + sema: &Semantics, + position: FilePosition, + new_name: &str, +) -> RenameResult { + let source_file = sema.parse(position.file_id); + let syntax = source_file.syntax(); + + let def = find_definition(sema, syntax, position)?; + + if let Definition::Local(local) = def { + if let Some(self_param) = local.as_self_param(sema.db) { + cov_mark::hit!(rename_self_to_param); + return rename_self_to_param(sema, local, self_param, new_name); + } + if new_name == "self" { + cov_mark::hit!(rename_to_self); + return rename_to_self(sema, local); + } + } + + def.rename(sema, new_name) +} + +/// Called by the client when it is about to rename a file. +pub(crate) fn will_rename_file( + db: &RootDatabase, + file_id: FileId, + new_name_stem: &str, +) -> Option { + let sema = Semantics::new(db); + let module = sema.to_module_def(file_id)?; + let def = Definition::ModuleDef(module.into()); + let mut change = def.rename(&sema, new_name_stem).ok()?; + change.file_system_edits.clear(); + Some(change) +} + +fn find_definition( + sema: &Semantics, + syntax: &SyntaxNode, + position: FilePosition, +) -> RenameResult { + match sema + .find_node_at_offset_with_descend(syntax, position.offset) + .ok_or_else(|| format_err!("No references found at position"))? + { + // renaming aliases would rename the item being aliased as the HIR doesn't track aliases yet + ast::NameLike::Name(name) + if name.syntax().parent().map_or(false, |it| ast::Rename::can_cast(it.kind())) => + { + bail!("Renaming aliases is currently unsupported") + } + ast::NameLike::Name(name) => { + NameClass::classify(sema, &name).map(|class| class.referenced_or_defined(sema.db)) + } + ast::NameLike::NameRef(name_ref) => { + if let Some(def) = + NameRefClass::classify(sema, &name_ref).map(|class| class.referenced(sema.db)) + { + // if the name differs from the definitions name it has to be an alias + if def.name(sema.db).map_or(false, |it| it.to_string() != name_ref.text()) { + bail!("Renaming aliases is currently unsupported"); + } + Some(def) + } else { + None + } + } + ast::NameLike::Lifetime(lifetime) => NameRefClass::classify_lifetime(sema, &lifetime) + .map(|class| NameRefClass::referenced(class, sema.db)) + .or_else(|| { + NameClass::classify_lifetime(sema, &lifetime) + .map(|it| it.referenced_or_defined(sema.db)) + }), + } + .ok_or_else(|| format_err!("No references found at position")) +} + +fn rename_to_self(sema: &Semantics, local: hir::Local) -> RenameResult { + if never!(local.is_self(sema.db)) { + bail!("rename_to_self invoked on self"); + } + + let fn_def = match local.parent(sema.db) { + hir::DefWithBody::Function(func) => func, + _ => bail!("Cannot rename local to self outside of function"), + }; + + if let Some(_) = fn_def.self_param(sema.db) { + bail!("Method already has a self parameter"); + } + + let params = fn_def.assoc_fn_params(sema.db); + let first_param = params + .first() + .ok_or_else(|| format_err!("Cannot rename local to self unless it is a parameter"))?; + if first_param.as_local(sema.db) != local { + bail!("Only the first parameter may be renamed to self"); + } + + let assoc_item = fn_def + .as_assoc_item(sema.db) + .ok_or_else(|| format_err!("Cannot rename parameter to self for free function"))?; + let impl_ = match assoc_item.container(sema.db) { + hir::AssocItemContainer::Trait(_) => { + bail!("Cannot rename parameter to self for trait functions"); + } + hir::AssocItemContainer::Impl(impl_) => impl_, + }; + let first_param_ty = first_param.ty(); + let impl_ty = impl_.self_ty(sema.db); + let (ty, self_param) = if impl_ty.remove_ref().is_some() { + // if the impl is a ref to the type we can just match the `&T` with self directly + (first_param_ty.clone(), "self") + } else { + first_param_ty.remove_ref().map_or((first_param_ty.clone(), "self"), |ty| { + (ty, if first_param_ty.is_mutable_reference() { "&mut self" } else { "&self" }) + }) + }; + + if ty != impl_ty { + bail!("Parameter type differs from impl block type"); + } + + let InFile { file_id, value: param_source } = + first_param.source(sema.db).ok_or_else(|| format_err!("No source for parameter found"))?; + + let def = Definition::Local(local); + let usages = def.usages(sema).all(); + let mut source_change = SourceChange::default(); + source_change.extend(usages.iter().map(|(&file_id, references)| { + (file_id, source_edit_from_references(references, def, "self")) + })); + source_change.insert_source_edit( + file_id.original_file(sema.db), + TextEdit::replace(param_source.syntax().text_range(), String::from(self_param)), + ); + Ok(source_change) +} + +fn rename_self_to_param( + sema: &Semantics, + local: hir::Local, + self_param: hir::SelfParam, + new_name: &str, +) -> RenameResult { + if new_name == "self" { + // Let's do nothing rather than complain. + cov_mark::hit!(rename_self_to_self); + return Ok(SourceChange::default()); + } + + let identifier_kind = IdentifierKind::classify(new_name)?; + + let InFile { file_id, value: self_param } = + self_param.source(sema.db).ok_or_else(|| format_err!("cannot find function source"))?; + + let def = Definition::Local(local); + let usages = def.usages(sema).all(); + let edit = text_edit_from_self_param(&self_param, new_name) + .ok_or_else(|| format_err!("No target type found"))?; + if usages.len() > 1 && identifier_kind == IdentifierKind::Underscore { + bail!("Cannot rename reference to `_` as it is being referenced multiple times"); + } + let mut source_change = SourceChange::default(); + source_change.insert_source_edit(file_id.original_file(sema.db), edit); + source_change.extend(usages.iter().map(|(&file_id, references)| { + (file_id, source_edit_from_references(references, def, new_name)) + })); + Ok(source_change) +} + +fn text_edit_from_self_param(self_param: &ast::SelfParam, new_name: &str) -> Option { + fn target_type_name(impl_def: &ast::Impl) -> Option { + if let Some(ast::Type::PathType(p)) = impl_def.self_ty() { + return Some(p.path()?.segment()?.name_ref()?.text().to_string()); + } + None + } + + let impl_def = self_param.syntax().ancestors().find_map(ast::Impl::cast)?; + let type_name = target_type_name(&impl_def)?; + + let mut replacement_text = String::from(new_name); + replacement_text.push_str(": "); + match (self_param.amp_token(), self_param.mut_token()) { + (Some(_), None) => replacement_text.push('&'), + (Some(_), Some(_)) => replacement_text.push_str("&mut "), + (_, _) => (), + }; + replacement_text.push_str(type_name.as_str()); + + Some(TextEdit::replace(self_param.syntax().text_range(), replacement_text)) +} + +#[cfg(test)] +mod tests { + use expect_test::{expect, Expect}; + use stdx::trim_indent; + use test_utils::assert_eq_text; + use text_edit::TextEdit; + + use crate::{fixture, FileId}; + + use super::{RangeInfo, RenameError}; + + fn check(new_name: &str, ra_fixture_before: &str, ra_fixture_after: &str) { + let ra_fixture_after = &trim_indent(ra_fixture_after); + let (analysis, position) = fixture::position(ra_fixture_before); + let rename_result = analysis + .rename(position, new_name) + .unwrap_or_else(|err| panic!("Rename to '{}' was cancelled: {}", new_name, err)); + match rename_result { + Ok(source_change) => { + let mut text_edit_builder = TextEdit::builder(); + let mut file_id: Option = None; + for edit in source_change.source_file_edits { + file_id = Some(edit.0); + for indel in edit.1.into_iter() { + text_edit_builder.replace(indel.delete, indel.insert); + } + } + if let Some(file_id) = file_id { + let mut result = analysis.file_text(file_id).unwrap().to_string(); + text_edit_builder.finish().apply(&mut result); + assert_eq_text!(ra_fixture_after, &*result); + } + } + Err(err) => { + if ra_fixture_after.starts_with("error:") { + let error_message = ra_fixture_after + .chars() + .into_iter() + .skip("error:".len()) + .collect::(); + assert_eq!(error_message.trim(), err.to_string()); + return; + } else { + panic!("Rename to '{}' failed unexpectedly: {}", new_name, err) + } + } + }; + } + + fn check_expect(new_name: &str, ra_fixture: &str, expect: Expect) { + let (analysis, position) = fixture::position(ra_fixture); + let source_change = + analysis.rename(position, new_name).unwrap().expect("Expect returned a RenameError"); + expect.assert_debug_eq(&source_change) + } + + fn check_prepare(ra_fixture: &str, expect: Expect) { + let (analysis, position) = fixture::position(ra_fixture); + let result = analysis + .prepare_rename(position) + .unwrap_or_else(|err| panic!("PrepareRename was cancelled: {}", err)); + match result { + Ok(RangeInfo { range, info: () }) => { + let source = analysis.file_text(position.file_id).unwrap(); + expect.assert_eq(&format!("{:?}: {}", range, &source[range])) + } + Err(RenameError(err)) => expect.assert_eq(&err), + }; + } + + #[test] + fn test_prepare_rename_namelikes() { + check_prepare(r"fn name$0<'lifetime>() {}", expect![[r#"3..7: name"#]]); + check_prepare(r"fn name<'lifetime$0>() {}", expect![[r#"8..17: 'lifetime"#]]); + check_prepare(r"fn name<'lifetime>() { name$0(); }", expect![[r#"3..7: name"#]]); + } + + #[test] + fn test_prepare_rename_in_macro() { + check_prepare( + r"macro_rules! foo { + ($ident:ident) => { + pub struct $ident; + } +} +foo!(Foo$0);", + expect![[r#"83..86: Foo"#]], + ); + } + + #[test] + fn test_prepare_rename_keyword() { + check_prepare(r"struct$0 Foo;", expect![[r#"No references found at position"#]]); + } + + #[test] + fn test_prepare_rename_tuple_field() { + check_prepare( + r#" +struct Foo(i32); + +fn baz() { + let mut x = Foo(4); + x.0$0 = 5; +} +"#, + expect![[r#"No references found at position"#]], + ); + } + + #[test] + fn test_prepare_rename_builtin() { + check_prepare( + r#" +fn foo() { + let x: i32$0 = 0; +} +"#, + expect![[r#"No references found at position"#]], + ); + } + + #[test] + fn test_prepare_rename_self() { + check_prepare( + r#" +struct Foo {} + +impl Foo { + fn foo(self) -> Self$0 { + self + } +} +"#, + expect![[r#"No references found at position"#]], + ); + } + + #[test] + fn test_rename_to_underscore() { + check("_", r#"fn main() { let i$0 = 1; }"#, r#"fn main() { let _ = 1; }"#); + } + + #[test] + fn test_rename_to_raw_identifier() { + check("r#fn", r#"fn main() { let i$0 = 1; }"#, r#"fn main() { let r#fn = 1; }"#); + } + + #[test] + fn test_rename_to_invalid_identifier1() { + check( + "invalid!", + r#"fn main() { let i$0 = 1; }"#, + "error: Invalid name `invalid!`: not an identifier", + ); + } + + #[test] + fn test_rename_to_invalid_identifier2() { + check( + "multiple tokens", + r#"fn main() { let i$0 = 1; }"#, + "error: Invalid name `multiple tokens`: not an identifier", + ); + } + + #[test] + fn test_rename_to_invalid_identifier3() { + check( + "let", + r#"fn main() { let i$0 = 1; }"#, + "error: Invalid name `let`: not an identifier", + ); + } + + #[test] + fn test_rename_to_invalid_identifier_lifetime() { + cov_mark::check!(rename_not_an_ident_ref); + check( + "'foo", + r#"fn main() { let i$0 = 1; }"#, + "error: Invalid name `'foo`: not an identifier", + ); + } + + #[test] + fn test_rename_to_invalid_identifier_lifetime2() { + cov_mark::check!(rename_not_a_lifetime_ident_ref); + check( + "foo", + r#"fn main<'a>(_: &'a$0 ()) {}"#, + "error: Invalid name `foo`: not a lifetime identifier", + ); + } + + #[test] + fn test_rename_to_underscore_invalid() { + cov_mark::check!(rename_underscore_multiple); + check( + "_", + r#"fn main(foo$0: ()) {foo;}"#, + "error: Cannot rename reference to `_` as it is being referenced multiple times", + ); + } + + #[test] + fn test_rename_mod_invalid() { + check( + "'foo", + r#"mod foo$0 {}"#, + "error: Invalid name `'foo`: cannot rename module to 'foo", + ); + } + + #[test] + fn test_rename_for_local() { + check( + "k", + r#" +fn main() { + let mut i = 1; + let j = 1; + i = i$0 + j; + + { i = 0; } + + i = 5; +} +"#, + r#" +fn main() { + let mut k = 1; + let j = 1; + k = k + j; + + { k = 0; } + + k = 5; +} +"#, + ); + } + + #[test] + fn test_rename_unresolved_reference() { + check( + "new_name", + r#"fn main() { let _ = unresolved_ref$0; }"#, + "error: No references found at position", + ); + } + + #[test] + fn test_rename_for_macro_args() { + check( + "b", + r#" +macro_rules! foo {($i:ident) => {$i} } +fn main() { + let a$0 = "test"; + foo!(a); +} +"#, + r#" +macro_rules! foo {($i:ident) => {$i} } +fn main() { + let b = "test"; + foo!(b); +} +"#, + ); + } + + #[test] + fn test_rename_for_macro_args_rev() { + check( + "b", + r#" +macro_rules! foo {($i:ident) => {$i} } +fn main() { + let a = "test"; + foo!(a$0); +} +"#, + r#" +macro_rules! foo {($i:ident) => {$i} } +fn main() { + let b = "test"; + foo!(b); +} +"#, + ); + } + + #[test] + fn test_rename_for_macro_define_fn() { + check( + "bar", + r#" +macro_rules! define_fn {($id:ident) => { fn $id{} }} +define_fn!(foo); +fn main() { + fo$0o(); +} +"#, + r#" +macro_rules! define_fn {($id:ident) => { fn $id{} }} +define_fn!(bar); +fn main() { + bar(); +} +"#, + ); + } + + #[test] + fn test_rename_for_macro_define_fn_rev() { + check( + "bar", + r#" +macro_rules! define_fn {($id:ident) => { fn $id{} }} +define_fn!(fo$0o); +fn main() { + foo(); +} +"#, + r#" +macro_rules! define_fn {($id:ident) => { fn $id{} }} +define_fn!(bar); +fn main() { + bar(); +} +"#, + ); + } + + #[test] + fn test_rename_for_param_inside() { + check("j", r#"fn foo(i : u32) -> u32 { i$0 }"#, r#"fn foo(j : u32) -> u32 { j }"#); + } + + #[test] + fn test_rename_refs_for_fn_param() { + check("j", r#"fn foo(i$0 : u32) -> u32 { i }"#, r#"fn foo(j : u32) -> u32 { j }"#); + } + + #[test] + fn test_rename_for_mut_param() { + check("j", r#"fn foo(mut i$0 : u32) -> u32 { i }"#, r#"fn foo(mut j : u32) -> u32 { j }"#); + } + + #[test] + fn test_rename_struct_field() { + check( + "j", + r#" +struct Foo { i$0: i32 } + +impl Foo { + fn new(i: i32) -> Self { + Self { i: i } + } +} +"#, + r#" +struct Foo { j: i32 } + +impl Foo { + fn new(i: i32) -> Self { + Self { j: i } + } +} +"#, + ); + } + + #[test] + fn test_rename_field_in_field_shorthand() { + cov_mark::check!(test_rename_field_in_field_shorthand); + check( + "j", + r#" +struct Foo { i$0: i32 } + +impl Foo { + fn new(i: i32) -> Self { + Self { i } + } +} +"#, + r#" +struct Foo { j: i32 } + +impl Foo { + fn new(i: i32) -> Self { + Self { j: i } + } +} +"#, + ); + } + + #[test] + fn test_rename_local_in_field_shorthand() { + cov_mark::check!(test_rename_local_in_field_shorthand); + check( + "j", + r#" +struct Foo { i: i32 } + +impl Foo { + fn new(i$0: i32) -> Self { + Self { i } + } +} +"#, + r#" +struct Foo { i: i32 } + +impl Foo { + fn new(j: i32) -> Self { + Self { i: j } + } +} +"#, + ); + } + + #[test] + fn test_field_shorthand_correct_struct() { + check( + "j", + r#" +struct Foo { i$0: i32 } +struct Bar { i: i32 } + +impl Bar { + fn new(i: i32) -> Self { + Self { i } + } +} +"#, + r#" +struct Foo { j: i32 } +struct Bar { i: i32 } + +impl Bar { + fn new(i: i32) -> Self { + Self { i } + } +} +"#, + ); + } + + #[test] + fn test_shadow_local_for_struct_shorthand() { + check( + "j", + r#" +struct Foo { i: i32 } + +fn baz(i$0: i32) -> Self { + let x = Foo { i }; + { + let i = 0; + Foo { i } + } +} +"#, + r#" +struct Foo { i: i32 } + +fn baz(j: i32) -> Self { + let x = Foo { i: j }; + { + let i = 0; + Foo { i } + } +} +"#, + ); + } + + #[test] + fn test_rename_mod() { + check_expect( + "foo2", + r#" +//- /lib.rs +mod bar; + +//- /bar.rs +mod foo$0; + +//- /bar/foo.rs +// empty +"#, + expect![[r#" + SourceChange { + source_file_edits: { + FileId( + 1, + ): TextEdit { + indels: [ + Indel { + insert: "foo2", + delete: 4..7, + }, + ], + }, + }, + file_system_edits: [ + MoveFile { + src: FileId( + 2, + ), + dst: AnchoredPathBuf { + anchor: FileId( + 2, + ), + path: "foo2.rs", + }, + }, + ], + is_snippet: false, + } + "#]], + ); + } + + #[test] + fn test_rename_mod_in_use_tree() { + check_expect( + "quux", + r#" +//- /main.rs +pub mod foo; +pub mod bar; +fn main() {} + +//- /foo.rs +pub struct FooContent; + +//- /bar.rs +use crate::foo$0::FooContent; +"#, + expect![[r#" + SourceChange { + source_file_edits: { + FileId( + 0, + ): TextEdit { + indels: [ + Indel { + insert: "quux", + delete: 8..11, + }, + ], + }, + FileId( + 2, + ): TextEdit { + indels: [ + Indel { + insert: "quux", + delete: 11..14, + }, + ], + }, + }, + file_system_edits: [ + MoveFile { + src: FileId( + 1, + ), + dst: AnchoredPathBuf { + anchor: FileId( + 1, + ), + path: "quux.rs", + }, + }, + ], + is_snippet: false, + } + "#]], + ); + } + + #[test] + fn test_rename_mod_in_dir() { + check_expect( + "foo2", + r#" +//- /lib.rs +mod fo$0o; +//- /foo/mod.rs +// empty +"#, + expect![[r#" + SourceChange { + source_file_edits: { + FileId( + 0, + ): TextEdit { + indels: [ + Indel { + insert: "foo2", + delete: 4..7, + }, + ], + }, + }, + file_system_edits: [ + MoveFile { + src: FileId( + 1, + ), + dst: AnchoredPathBuf { + anchor: FileId( + 1, + ), + path: "../foo2/mod.rs", + }, + }, + ], + is_snippet: false, + } + "#]], + ); + } + + #[test] + fn test_rename_unusually_nested_mod() { + check_expect( + "bar", + r#" +//- /lib.rs +mod outer { mod fo$0o; } + +//- /outer/foo.rs +// empty +"#, + expect![[r#" + SourceChange { + source_file_edits: { + FileId( + 0, + ): TextEdit { + indels: [ + Indel { + insert: "bar", + delete: 16..19, + }, + ], + }, + }, + file_system_edits: [ + MoveFile { + src: FileId( + 1, + ), + dst: AnchoredPathBuf { + anchor: FileId( + 1, + ), + path: "bar.rs", + }, + }, + ], + is_snippet: false, + } + "#]], + ); + } + + #[test] + fn test_module_rename_in_path() { + check( + "baz", + r#" +mod $0foo { pub fn bar() {} } + +fn main() { foo::bar(); } +"#, + r#" +mod baz { pub fn bar() {} } + +fn main() { baz::bar(); } +"#, + ); + } + + #[test] + fn test_rename_mod_filename_and_path() { + check_expect( + "foo2", + r#" +//- /lib.rs +mod bar; +fn f() { + bar::foo::fun() +} + +//- /bar.rs +pub mod foo$0; + +//- /bar/foo.rs +// pub fn fun() {} +"#, + expect![[r#" + SourceChange { + source_file_edits: { + FileId( + 0, + ): TextEdit { + indels: [ + Indel { + insert: "foo2", + delete: 27..30, + }, + ], + }, + FileId( + 1, + ): TextEdit { + indels: [ + Indel { + insert: "foo2", + delete: 8..11, + }, + ], + }, + }, + file_system_edits: [ + MoveFile { + src: FileId( + 2, + ), + dst: AnchoredPathBuf { + anchor: FileId( + 2, + ), + path: "foo2.rs", + }, + }, + ], + is_snippet: false, + } + "#]], + ); + } + + #[test] + fn test_enum_variant_from_module_1() { + cov_mark::check!(rename_non_local); + check( + "Baz", + r#" +mod foo { + pub enum Foo { Bar$0 } +} + +fn func(f: foo::Foo) { + match f { + foo::Foo::Bar => {} + } +} +"#, + r#" +mod foo { + pub enum Foo { Baz } +} + +fn func(f: foo::Foo) { + match f { + foo::Foo::Baz => {} + } +} +"#, + ); + } + + #[test] + fn test_enum_variant_from_module_2() { + check( + "baz", + r#" +mod foo { + pub struct Foo { pub bar$0: uint } +} + +fn foo(f: foo::Foo) { + let _ = f.bar; +} +"#, + r#" +mod foo { + pub struct Foo { pub baz: uint } +} + +fn foo(f: foo::Foo) { + let _ = f.baz; +} +"#, + ); + } + + #[test] + fn test_parameter_to_self() { + cov_mark::check!(rename_to_self); + check( + "self", + r#" +struct Foo { i: i32 } + +impl Foo { + fn f(foo$0: &mut Foo) -> i32 { + foo.i + } +} +"#, + r#" +struct Foo { i: i32 } + +impl Foo { + fn f(&mut self) -> i32 { + self.i + } +} +"#, + ); + check( + "self", + r#" +struct Foo { i: i32 } + +impl Foo { + fn f(foo$0: Foo) -> i32 { + foo.i + } +} +"#, + r#" +struct Foo { i: i32 } + +impl Foo { + fn f(self) -> i32 { + self.i + } +} +"#, + ); + } + + #[test] + fn test_parameter_to_self_error_no_impl() { + check( + "self", + r#" +struct Foo { i: i32 } + +fn f(foo$0: &mut Foo) -> i32 { + foo.i +} +"#, + "error: Cannot rename parameter to self for free function", + ); + check( + "self", + r#" +struct Foo { i: i32 } +struct Bar; + +impl Bar { + fn f(foo$0: &mut Foo) -> i32 { + foo.i + } +} +"#, + "error: Parameter type differs from impl block type", + ); + } + + #[test] + fn test_parameter_to_self_error_not_first() { + check( + "self", + r#" +struct Foo { i: i32 } +impl Foo { + fn f(x: (), foo$0: &mut Foo) -> i32 { + foo.i + } +} +"#, + "error: Only the first parameter may be renamed to self", + ); + } + + #[test] + fn test_parameter_to_self_impl_ref() { + check( + "self", + r#" +struct Foo { i: i32 } +impl &Foo { + fn f(foo$0: &Foo) -> i32 { + foo.i + } +} +"#, + r#" +struct Foo { i: i32 } +impl &Foo { + fn f(self) -> i32 { + self.i + } +} +"#, + ); + } + + #[test] + fn test_self_to_parameter() { + check( + "foo", + r#" +struct Foo { i: i32 } + +impl Foo { + fn f(&mut $0self) -> i32 { + self.i + } +} +"#, + r#" +struct Foo { i: i32 } + +impl Foo { + fn f(foo: &mut Foo) -> i32 { + foo.i + } +} +"#, + ); + } + + #[test] + fn test_owned_self_to_parameter() { + cov_mark::check!(rename_self_to_param); + check( + "foo", + r#" +struct Foo { i: i32 } + +impl Foo { + fn f($0self) -> i32 { + self.i + } +} +"#, + r#" +struct Foo { i: i32 } + +impl Foo { + fn f(foo: Foo) -> i32 { + foo.i + } +} +"#, + ); + } + + #[test] + fn test_self_in_path_to_parameter() { + check( + "foo", + r#" +struct Foo { i: i32 } + +impl Foo { + fn f(&self) -> i32 { + let self_var = 1; + self$0.i + } +} +"#, + r#" +struct Foo { i: i32 } + +impl Foo { + fn f(foo: &Foo) -> i32 { + let self_var = 1; + foo.i + } +} +"#, + ); + } + + #[test] + fn test_rename_field_put_init_shorthand() { + cov_mark::check!(test_rename_field_put_init_shorthand); + check( + "bar", + r#" +struct Foo { i$0: i32 } + +fn foo(bar: i32) -> Foo { + Foo { i: bar } +} +"#, + r#" +struct Foo { bar: i32 } + +fn foo(bar: i32) -> Foo { + Foo { bar } +} +"#, + ); + } + + #[test] + fn test_rename_local_put_init_shorthand() { + cov_mark::check!(test_rename_local_put_init_shorthand); + check( + "i", + r#" +struct Foo { i: i32 } + +fn foo(bar$0: i32) -> Foo { + Foo { i: bar } +} +"#, + r#" +struct Foo { i: i32 } + +fn foo(i: i32) -> Foo { + Foo { i } +} +"#, + ); + } + + #[test] + fn test_struct_field_pat_into_shorthand() { + cov_mark::check!(test_rename_field_put_init_shorthand_pat); + check( + "baz", + r#" +struct Foo { i$0: i32 } + +fn foo(foo: Foo) { + let Foo { i: ref baz @ qux } = foo; + let _ = qux; +} +"#, + r#" +struct Foo { baz: i32 } + +fn foo(foo: Foo) { + let Foo { ref baz @ qux } = foo; + let _ = qux; +} +"#, + ); + } + + #[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$0; +} +"#, + expected_fixture, + ); + check( + "bar", + r#" +struct Foo { + i: i32, +} + +fn foo(foo: Foo) { + let Foo { i } = foo; + let _ = i$0; +} +"#, + 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$0 +} +"#, + r#" +struct Foo { + i: i32 +} + +fn foo(Foo { i: bar }: foo) -> i32 { + bar +} +"#, + ) + } + + #[test] + fn test_struct_field_complex_ident_pat() { + check( + "baz", + r#" +struct Foo { i$0: i32 } + +fn foo(foo: Foo) { + let Foo { ref i } = foo; +} +"#, + r#" +struct Foo { baz: i32 } + +fn foo(foo: Foo) { + let Foo { baz: ref i } = foo; +} +"#, + ); + } + + #[test] + fn test_rename_lifetimes() { + cov_mark::check!(rename_lifetime); + check( + "'yeeee", + r#" +trait Foo<'a> { + fn foo() -> &'a (); +} +impl<'a> Foo<'a> for &'a () { + fn foo() -> &'a$0 () { + unimplemented!() + } +} +"#, + r#" +trait Foo<'a> { + fn foo() -> &'a (); +} +impl<'yeeee> Foo<'yeeee> for &'yeeee () { + fn foo() -> &'yeeee () { + unimplemented!() + } +} +"#, + ) + } + + #[test] + fn test_rename_bind_pat() { + check( + "new_name", + r#" +fn main() { + enum CustomOption { + None, + Some(T), + } + + let test_variable = CustomOption::Some(22); + + match test_variable { + CustomOption::Some(foo$0) if foo == 11 => {} + _ => (), + } +}"#, + r#" +fn main() { + enum CustomOption { + None, + Some(T), + } + + let test_variable = CustomOption::Some(22); + + match test_variable { + CustomOption::Some(new_name) if new_name == 11 => {} + _ => (), + } +}"#, + ); + } + + #[test] + fn test_rename_label() { + check( + "'foo", + r#" +fn foo<'a>() -> &'a () { + 'a: { + 'b: loop { + break 'a$0; + } + } +} +"#, + r#" +fn foo<'a>() -> &'a () { + 'foo: { + 'b: loop { + break 'foo; + } + } +} +"#, + ) + } + + #[test] + fn test_self_to_self() { + cov_mark::check!(rename_self_to_self); + check( + "self", + r#" +struct Foo; +impl Foo { + fn foo(self$0) {} +} +"#, + r#" +struct Foo; +impl Foo { + fn foo(self) {} +} +"#, + ) + } + + #[test] + fn test_rename_field_in_pat_in_macro_doesnt_shorthand() { + // ideally we would be able to make this emit a short hand, but I doubt this is easily possible + check( + "baz", + r#" +macro_rules! foo { + ($pattern:pat) => { + let $pattern = loop {}; + }; +} +struct Foo { + bar$0: u32, +} +fn foo() { + foo!(Foo { bar: baz }); +} +"#, + r#" +macro_rules! foo { + ($pattern:pat) => { + let $pattern = loop {}; + }; +} +struct Foo { + baz: u32, +} +fn foo() { + foo!(Foo { baz: baz }); +} +"#, + ) + } + + #[test] + fn test_rename_tuple_field() { + check( + "foo", + r#" +struct Foo(i32); + +fn baz() { + let mut x = Foo(4); + x.0$0 = 5; +} +"#, + "error: No identifier available to rename", + ); + } + + #[test] + fn test_rename_builtin() { + check( + "foo", + r#" +fn foo() { + let x: i32$0 = 0; +} +"#, + "error: Cannot rename builtin type", + ); + } + + #[test] + fn test_rename_self() { + check( + "foo", + r#" +struct Foo {} + +impl Foo { + fn foo(self) -> Self$0 { + self + } +} +"#, + "error: Cannot rename `Self`", + ); + } + + #[test] + fn test_rename_ignores_self_ty() { + check( + "Fo0", + r#" +struct $0Foo; + +impl Foo where Self: {} +"#, + r#" +struct Fo0; + +impl Fo0 where Self: {} +"#, + ); + } + + #[test] + fn test_rename_fails_on_aliases() { + check( + "Baz", + r#" +struct Foo; +use Foo as Bar$0; +"#, + "error: Renaming aliases is currently unsupported", + ); + check( + "Baz", + r#" +struct Foo; +use Foo as Bar; +use Bar$0; +"#, + "error: Renaming aliases is currently unsupported", + ); + } + + #[test] + fn test_rename_trait_method() { + let res = r" +trait Foo { + fn foo(&self) { + self.foo(); + } +} + +impl Foo for () { + fn foo(&self) { + self.foo(); + } +}"; + check( + "foo", + r#" +trait Foo { + fn bar$0(&self) { + self.bar(); + } +} + +impl Foo for () { + fn bar(&self) { + self.bar(); + } +}"#, + res, + ); + check( + "foo", + r#" +trait Foo { + fn bar(&self) { + self.bar$0(); + } +} + +impl Foo for () { + fn bar(&self) { + self.bar(); + } +}"#, + res, + ); + check( + "foo", + r#" +trait Foo { + fn bar(&self) { + self.bar(); + } +} + +impl Foo for () { + fn bar$0(&self) { + self.bar(); + } +}"#, + res, + ); + check( + "foo", + r#" +trait Foo { + fn bar(&self) { + self.bar(); + } +} + +impl Foo for () { + fn bar(&self) { + self.bar$0(); + } +}"#, + res, + ); + } + + #[test] + fn test_rename_trait_const() { + let res = r" +trait Foo { + const FOO: (); +} + +impl Foo for () { + const FOO: (); +} +fn f() { <()>::FOO; }"; + check( + "FOO", + r#" +trait Foo { + const BAR$0: (); +} + +impl Foo for () { + const BAR: (); +} +fn f() { <()>::BAR; }"#, + res, + ); + check( + "FOO", + r#" +trait Foo { + const BAR: (); +} + +impl Foo for () { + const BAR$0: (); +} +fn f() { <()>::BAR; }"#, + res, + ); + check( + "FOO", + r#" +trait Foo { + const BAR: (); +} + +impl Foo for () { + const BAR: (); +} +fn f() { <()>::BAR$0; }"#, + res, + ); + } + + #[test] + fn macros_are_broken_lol() { + cov_mark::check!(macros_are_broken_lol); + check( + "lol", + r#" +macro_rules! m { () => { fn f() {} } } +m!(); +fn main() { f$0() } +"#, + r#" +macro_rules! m { () => { fn f() {} } } +lol +fn main() { lol() } +"#, + ) + } +} -- cgit v1.2.3