From 55faa2daa3fc8bd213038a012b1c5e9ad5fd3736 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 16 Dec 2020 21:35:15 +0100 Subject: Lifetime reference search --- crates/ide/src/references/rename.rs | 104 +++++++++++++++++++++++++++++++++--- 1 file changed, 98 insertions(+), 6 deletions(-) (limited to 'crates/ide/src/references/rename.rs') diff --git a/crates/ide/src/references/rename.rs b/crates/ide/src/references/rename.rs index 44081f210..56e923841 100644 --- a/crates/ide/src/references/rename.rs +++ b/crates/ide/src/references/rename.rs @@ -35,6 +35,29 @@ impl fmt::Display for RenameError { impl Error for RenameError {} +pub(crate) fn prepare_rename( + db: &RootDatabase, + position: FilePosition, +) -> Result, RenameError> { + let sema = Semantics::new(db); + let source_file = sema.parse(position.file_id); + let syntax = source_file.syntax(); + if let Some(module) = find_module_at_offset(&sema, position, syntax) { + rename_mod(&sema, position, module, "dummy") + } else if let Some(self_token) = + syntax.token_at_offset(position.offset).find(|t| t.kind() == SyntaxKind::SELF_KW) + { + rename_self_to_param(&sema, position, self_token, "dummy") + } else { + let range = match find_all_refs(&sema, position, None) { + Some(RangeInfo { range, .. }) => range, + None => return Err(RenameError("No references found at position".to_string())), + }; + Ok(RangeInfo::new(range, SourceChange::from(vec![]))) + } + .map(|info| RangeInfo::new(info.range, ())) +} + pub(crate) fn rename( db: &RootDatabase, position: FilePosition, @@ -49,11 +72,18 @@ pub(crate) fn rename_with_semantics( position: FilePosition, new_name: &str, ) -> Result, RenameError> { - match lex_single_syntax_kind(new_name) { + let is_lifetime_name = match lex_single_syntax_kind(new_name) { Some(res) => match res { - (SyntaxKind::IDENT, _) => (), - (SyntaxKind::UNDERSCORE, _) => (), + (SyntaxKind::IDENT, _) => false, + (SyntaxKind::UNDERSCORE, _) => false, (SyntaxKind::SELF_KW, _) => return rename_to_self(&sema, position), + (SyntaxKind::LIFETIME_IDENT, _) if new_name != "'static" && new_name != "'_" => true, + (SyntaxKind::LIFETIME_IDENT, _) => { + return Err(RenameError(format!( + "Invalid name `{0}`: Cannot rename lifetime to {0}", + new_name + ))) + } (_, Some(syntax_error)) => { return Err(RenameError(format!("Invalid name `{}`: {}", new_name, syntax_error))) } @@ -62,18 +92,21 @@ pub(crate) fn rename_with_semantics( } }, None => return Err(RenameError(format!("Invalid name `{}`: not an identifier", new_name))), - } + }; let source_file = sema.parse(position.file_id); let syntax = source_file.syntax(); - if let Some(module) = find_module_at_offset(&sema, position, syntax) { + // this is here to prevent lifetime renames from happening on modules and self + if is_lifetime_name { + rename_reference(&sema, position, new_name, is_lifetime_name) + } else if let Some(module) = find_module_at_offset(&sema, position, syntax) { rename_mod(&sema, position, module, new_name) } else if let Some(self_token) = syntax.token_at_offset(position.offset).find(|t| t.kind() == SyntaxKind::SELF_KW) { rename_self_to_param(&sema, position, self_token, new_name) } else { - rename_reference(&sema, position, new_name) + rename_reference(&sema, position, new_name, is_lifetime_name) } } @@ -355,12 +388,26 @@ fn rename_reference( sema: &Semantics, position: FilePosition, new_name: &str, + is_lifetime_name: bool, ) -> Result, RenameError> { let RangeInfo { range, info: refs } = match find_all_refs(sema, position, None) { Some(range_info) => range_info, None => return Err(RenameError("No references found at position".to_string())), }; + match (refs.declaration.kind == ReferenceKind::Lifetime, is_lifetime_name) { + (true, false) => { + return Err(RenameError(format!( + "Invalid name `{}`: not a lifetime identifier", + new_name + ))) + } + (false, true) => { + return Err(RenameError(format!("Invalid name `{}`: not an identifier", new_name))) + } + _ => (), + } + let edit = refs .into_iter() .map(|reference| source_edit_from_reference(sema, reference, new_name)) @@ -464,6 +511,24 @@ mod tests { ); } + #[test] + fn test_rename_to_invalid_identifier_lifetime() { + check( + "'foo", + r#"fn main() { let i<|> = 1; }"#, + "error: Invalid name `'foo`: not an identifier", + ); + } + + #[test] + fn test_rename_to_invalid_identifier_lifetime2() { + check( + "foo", + r#"fn main<'a>(_: &'a<|> ()) {}"#, + "error: Invalid name `foo`: not a lifetime identifier", + ); + } + #[test] fn test_rename_for_local() { check( @@ -1393,6 +1458,33 @@ struct Foo { fn foo(Foo { i: bar }: foo) -> i32 { bar } +"#, + ) + } + + #[test] + fn test_rename_lifetimes() { + check( + "'yeeee", + r#" +trait Foo<'a> { + fn foo() -> &'a (); +} +impl<'a> Foo<'a> for &'a () { + fn foo() -> &'a<|> () { + unimplemented!() + } +} +"#, + r#" +trait Foo<'a> { + fn foo() -> &'a (); +} +impl<'yeeee> Foo<'yeeee> for &'yeeee () { + fn foo() -> &'yeeee () { + unimplemented!() + } +} "#, ) } -- cgit v1.2.3