diff --git a/crates/ide-assists/src/assist_config.rs b/crates/ide-assists/src/assist_config.rs index 57ced8d8534b..afb4c8ba3a2d 100644 --- a/crates/ide-assists/src/assist_config.rs +++ b/crates/ide-assists/src/assist_config.rs @@ -5,6 +5,7 @@ //! assists if we are allowed to. use hir::ImportPathConfig; +use ide_db::rename::RenameConfig; use ide_db::{SnippetCap, assists::ExprFillDefaultMode, imports::insert_use::InsertUseConfig}; use crate::AssistKind; @@ -23,6 +24,7 @@ pub struct AssistConfig { pub code_action_grouping: bool, pub expr_fill_default: ExprFillDefaultMode, pub prefer_self_ty: bool, + pub rename_config: RenameConfig, } impl AssistConfig { diff --git a/crates/ide-assists/src/handlers/remove_underscore.rs b/crates/ide-assists/src/handlers/remove_underscore.rs index a8e27416d5ce..96a5311f2920 100644 --- a/crates/ide-assists/src/handlers/remove_underscore.rs +++ b/crates/ide-assists/src/handlers/remove_underscore.rs @@ -62,7 +62,9 @@ pub(crate) fn remove_underscore(acc: &mut Assists, ctx: &AssistContext<'_>) -> O "Remove underscore from a used variable", text_range, |builder| { - let changes = def.rename(&ctx.sema, new_name, RenameDefinition::Yes).unwrap(); + let changes = def + .rename(&ctx.sema, &ctx.config.rename_config, new_name, RenameDefinition::Yes) + .unwrap(); builder.source_change = changes; }, ) diff --git a/crates/ide-assists/src/tests.rs b/crates/ide-assists/src/tests.rs index cda2ad43278a..c86a86ecddfb 100644 --- a/crates/ide-assists/src/tests.rs +++ b/crates/ide-assists/src/tests.rs @@ -7,6 +7,7 @@ use ide_db::{ assists::ExprFillDefaultMode, base_db::SourceDatabase, imports::insert_use::{ImportGranularity, InsertUseConfig}, + rename::RenameConfig, source_change::FileSystemEdit, }; use stdx::{format_to, trim_indent}; @@ -38,6 +39,7 @@ pub(crate) const TEST_CONFIG: AssistConfig = AssistConfig { code_action_grouping: true, expr_fill_default: ExprFillDefaultMode::Todo, prefer_self_ty: false, + rename_config: RenameConfig { show_conflicts: true }, }; pub(crate) const TEST_CONFIG_NO_GROUPING: AssistConfig = AssistConfig { @@ -59,6 +61,7 @@ pub(crate) const TEST_CONFIG_NO_GROUPING: AssistConfig = AssistConfig { code_action_grouping: false, expr_fill_default: ExprFillDefaultMode::Todo, prefer_self_ty: false, + rename_config: RenameConfig { show_conflicts: true }, }; pub(crate) const TEST_CONFIG_NO_SNIPPET_CAP: AssistConfig = AssistConfig { @@ -80,6 +83,7 @@ pub(crate) const TEST_CONFIG_NO_SNIPPET_CAP: AssistConfig = AssistConfig { code_action_grouping: true, expr_fill_default: ExprFillDefaultMode::Todo, prefer_self_ty: false, + rename_config: RenameConfig { show_conflicts: true }, }; pub(crate) const TEST_CONFIG_IMPORT_ONE: AssistConfig = AssistConfig { @@ -101,6 +105,7 @@ pub(crate) const TEST_CONFIG_IMPORT_ONE: AssistConfig = AssistConfig { code_action_grouping: true, expr_fill_default: ExprFillDefaultMode::Todo, prefer_self_ty: false, + rename_config: RenameConfig { show_conflicts: true }, }; pub(crate) fn with_single_file(text: &str) -> (RootDatabase, EditionedFileId) { diff --git a/crates/ide-db/src/rename.rs b/crates/ide-db/src/rename.rs index 4e737e27f050..b414c5bbf922 100644 --- a/crates/ide-db/src/rename.rs +++ b/crates/ide-db/src/rename.rs @@ -45,6 +45,11 @@ use crate::{ traits::convert_to_def_in_trait, }; +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RenameConfig { + pub show_conflicts: bool, +} + pub type Result = std::result::Result; #[derive(Debug)] @@ -79,6 +84,7 @@ impl Definition { pub fn rename( &self, sema: &Semantics<'_, RootDatabase>, + config: &RenameConfig, new_name: &str, rename_definition: RenameDefinition, ) -> Result { @@ -109,10 +115,15 @@ impl Definition { bail!("Cannot rename a builtin attr.") } Definition::SelfType(_) => bail!("Cannot rename `Self`"), - Definition::Macro(mac) => { - rename_reference(sema, Definition::Macro(mac), new_name, rename_definition, edition) - } - def => rename_reference(sema, def, new_name, rename_definition, edition), + Definition::Macro(mac) => rename_reference( + sema, + config, + Definition::Macro(mac), + new_name, + rename_definition, + edition, + ), + def => rename_reference(sema, config, def, new_name, rename_definition, edition), } } @@ -335,6 +346,7 @@ fn rename_mod( fn rename_reference( sema: &Semantics<'_, RootDatabase>, + config: &RenameConfig, def: Definition, new_name: &str, rename_definition: RenameDefinition, @@ -397,7 +409,8 @@ fn rename_reference( if rename_definition == RenameDefinition::Yes { // This needs to come after the references edits, because we change the annotation of existing edits // if a conflict is detected. - let (file_id, edit) = source_edit_from_def(sema, def, &new_name, &mut source_change)?; + let (file_id, edit) = + source_edit_from_def(sema, config, def, &new_name, &mut source_change)?; source_change.insert_source_edit(file_id, edit); } Ok(source_change) @@ -555,6 +568,7 @@ fn source_edit_from_name_ref( fn source_edit_from_def( sema: &Semantics<'_, RootDatabase>, + config: &RenameConfig, def: Definition, new_name: &Name, source_change: &mut SourceChange, @@ -563,21 +577,22 @@ fn source_edit_from_def( if let Definition::Local(local) = def { let mut file_id = None; - let conflict_annotation = if !sema.rename_conflicts(&local, new_name).is_empty() { - Some( - source_change.insert_annotation(ChangeAnnotation { - label: "This rename will change the program's meaning".to_owned(), - needs_confirmation: true, - description: Some( - "Some variable(s) will shadow the renamed variable \ + let conflict_annotation = + if config.show_conflicts && !sema.rename_conflicts(&local, new_name).is_empty() { + Some( + source_change.insert_annotation(ChangeAnnotation { + label: "This rename will change the program's meaning".to_owned(), + needs_confirmation: true, + description: Some( + "Some variable(s) will shadow the renamed variable \ or be shadowed by it if the rename is performed" - .to_owned(), - ), - }), - ) - } else { - None - }; + .to_owned(), + ), + }), + ) + } else { + None + }; for source in local.sources(sema.db) { let source = match source.source.clone().original_ast_node_rooted(sema.db) { diff --git a/crates/ide-diagnostics/src/handlers/incorrect_case.rs b/crates/ide-diagnostics/src/handlers/incorrect_case.rs index 519ff192799d..e3495595bacc 100644 --- a/crates/ide-diagnostics/src/handlers/incorrect_case.rs +++ b/crates/ide-diagnostics/src/handlers/incorrect_case.rs @@ -44,7 +44,12 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::IncorrectCase) -> Option Cancellable> { - self.with_db(|db| rename::rename(db, position, new_name)) + self.with_db(|db| rename::rename(db, config, position, new_name)) } pub fn prepare_rename( @@ -792,10 +795,11 @@ impl Analysis { pub fn will_rename_file( &self, + config: &RenameConfig, file_id: FileId, new_name_stem: &str, ) -> Cancellable> { - self.with_db(|db| rename::will_rename_file(db, file_id, new_name_stem)) + self.with_db(|db| rename::will_rename_file(db, config, file_id, new_name_stem)) } pub fn structural_search_replace( diff --git a/crates/ide/src/rename.rs b/crates/ide/src/rename.rs index fb84e8e6b474..27f83d5f6170 100644 --- a/crates/ide/src/rename.rs +++ b/crates/ide/src/rename.rs @@ -8,7 +8,10 @@ use hir::{AsAssocItem, InFile, Name, Semantics, sym}; use ide_db::{ FileId, FileRange, RootDatabase, defs::{Definition, NameClass, NameRefClass}, - rename::{IdentifierKind, RenameDefinition, bail, format_err, source_edit_from_references}, + rename::{ + IdentifierKind, RenameConfig, RenameDefinition, bail, format_err, + source_edit_from_references, + }, source_change::SourceChangeBuilder, }; use itertools::Itertools; @@ -78,6 +81,7 @@ pub(crate) fn prepare_rename( // ![Rename](https://user-images.githubusercontent.com/48062697/113065582-055aae80-91b1-11eb-8ade-2b58e6d81883.gif) pub(crate) fn rename( db: &RootDatabase, + config: &RenameConfig, position: FilePosition, new_name: &str, ) -> RenameResult { @@ -145,7 +149,7 @@ pub(crate) fn rename( return rename_to_self(&sema, local); } } - def.rename(&sema, new_name.as_str(), rename_def) + def.rename(&sema, config, new_name.as_str(), rename_def) }) .collect(), }; @@ -159,13 +163,14 @@ pub(crate) fn rename( /// Called by the client when it is about to rename a file. pub(crate) fn will_rename_file( db: &RootDatabase, + config: &RenameConfig, file_id: FileId, new_name_stem: &str, ) -> Option { let sema = Semantics::new(db); let module = sema.file_to_module_def(file_id)?; let def = Definition::Module(module); - let mut change = def.rename(&sema, new_name_stem, RenameDefinition::Yes).ok()?; + let mut change = def.rename(&sema, config, new_name_stem, RenameDefinition::Yes).ok()?; change.file_system_edits.clear(); Some(change) } @@ -493,6 +498,7 @@ fn text_edit_from_self_param(self_param: &ast::SelfParam, new_name: String) -> O #[cfg(test)] mod tests { use expect_test::{Expect, expect}; + use ide_db::rename::RenameConfig; use ide_db::source_change::SourceChange; use ide_db::text_edit::TextEdit; use itertools::Itertools; @@ -503,6 +509,8 @@ mod tests { use super::{RangeInfo, RenameError}; + const TEST_CONFIG: RenameConfig = RenameConfig { show_conflicts: true }; + #[track_caller] fn check( new_name: &str, @@ -517,7 +525,7 @@ mod tests { } } let rename_result = analysis - .rename(position, new_name) + .rename(&TEST_CONFIG, position, new_name) .unwrap_or_else(|err| panic!("Rename to '{new_name}' was cancelled: {err}")); match rename_result { Ok(source_change) => { @@ -549,7 +557,7 @@ mod tests { #[track_caller] fn check_conflicts(new_name: &str, #[rust_analyzer::rust_fixture] ra_fixture: &str) { let (analysis, position, conflicts) = fixture::annotations(ra_fixture); - let source_change = analysis.rename(position, new_name).unwrap().unwrap(); + let source_change = analysis.rename(&TEST_CONFIG, position, new_name).unwrap().unwrap(); let expected_conflicts = conflicts .into_iter() .map(|(file_range, _)| (file_range.file_id, file_range.range)) @@ -576,8 +584,10 @@ mod tests { expect: Expect, ) { let (analysis, position) = fixture::position(ra_fixture); - let source_change = - analysis.rename(position, new_name).unwrap().expect("Expect returned a RenameError"); + let source_change = analysis + .rename(&TEST_CONFIG, position, new_name) + .unwrap() + .expect("Expect returned a RenameError"); expect.assert_eq(&filter_expect(source_change)) } @@ -588,7 +598,7 @@ mod tests { ) { let (analysis, position) = fixture::position(ra_fixture); let source_change = analysis - .will_rename_file(position.file_id, new_name) + .will_rename_file(&TEST_CONFIG, position.file_id, new_name) .unwrap() .expect("Expect returned a RenameError"); expect.assert_eq(&filter_expect(source_change)) diff --git a/crates/rust-analyzer/src/cli/analysis_stats.rs b/crates/rust-analyzer/src/cli/analysis_stats.rs index 0ee01982fea2..1dd4d9312fad 100644 --- a/crates/rust-analyzer/src/cli/analysis_stats.rs +++ b/crates/rust-analyzer/src/cli/analysis_stats.rs @@ -26,6 +26,7 @@ use ide::{ use ide_db::{ EditionedFileId, LineIndexDatabase, SnippetCap, base_db::{SourceDatabase, salsa::Database}, + rename::RenameConfig, }; use itertools::Itertools; use load_cargo::{LoadCargoConfig, ProcMacroServerChoice, load_workspace}; @@ -1140,6 +1141,7 @@ impl flags::AnalysisStats { style_lints: false, term_search_fuel: 400, term_search_borrowck: true, + rename_config: RenameConfig { show_conflicts: true }, }, ide::AssistResolveStrategy::All, analysis.editioned_file_id_to_vfs(file_id), diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 51d4c29aa74e..7f31a2eb522d 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -18,6 +18,7 @@ use ide_db::{ SnippetCap, assists::ExprFillDefaultMode, imports::insert_use::{ImportGranularity, InsertUseConfig, PrefixKind}, + rename::RenameConfig, }; use itertools::{Either, Itertools}; use paths::{Utf8Path, Utf8PathBuf}; @@ -553,6 +554,9 @@ config_data! { imports_prefix: ImportPrefixDef = ImportPrefixDef::ByCrate, /// Whether to prefix external (including std, core) crate imports with `::`. e.g. "use ::std::io::Read;". imports_prefixExternPrelude: bool = false, + + /// Whether to warn when a rename will cause conflicts (change the meaning of the code). + rename_showConflicts: bool = true, } } @@ -1496,6 +1500,10 @@ impl Config { } impl Config { + pub fn rename(&self, source_root: Option) -> RenameConfig { + RenameConfig { show_conflicts: *self.rename_showConflicts(source_root) } + } + pub fn assist(&self, source_root: Option) -> AssistConfig { AssistConfig { snippet_cap: self.snippet_cap(), @@ -1514,6 +1522,7 @@ impl Config { ExprFillDefaultDef::Underscore => ExprFillDefaultMode::Underscore, }, prefer_self_ty: *self.assist_preferSelf(source_root), + rename_config: self.rename(source_root), } } @@ -1608,6 +1617,7 @@ impl Config { style_lints: self.diagnostics_styleLints_enable(source_root).to_owned(), term_search_fuel: self.assist_termSearch_fuel(source_root).to_owned() as u64, term_search_borrowck: self.assist_termSearch_borrowcheck(source_root).to_owned(), + rename_config: self.rename(source_root), } } diff --git a/crates/rust-analyzer/src/handlers/request.rs b/crates/rust-analyzer/src/handlers/request.rs index a76a65220d3b..95ed42b16981 100644 --- a/crates/rust-analyzer/src/handlers/request.rs +++ b/crates/rust-analyzer/src/handlers/request.rs @@ -780,7 +780,11 @@ pub(crate) fn handle_will_rename_files( } }) .filter_map(|(file_id, new_name)| { - snap.analysis.will_rename_file(file_id?, &new_name).ok()? + let file_id = file_id?; + let source_root = snap.analysis.source_root_id(file_id).ok(); + snap.analysis + .will_rename_file(&snap.config.rename(source_root), file_id, &new_name) + .ok()? }) .collect(); @@ -1316,8 +1320,11 @@ pub(crate) fn handle_rename( let _p = tracing::info_span!("handle_rename").entered(); let position = try_default!(from_proto::file_position(&snap, params.text_document_position)?); - let mut change = - snap.analysis.rename(position, ¶ms.new_name)?.map_err(to_proto::rename_error)?; + let source_root = snap.analysis.source_root_id(position.file_id).ok(); + let mut change = snap + .analysis + .rename(&snap.config.rename(source_root), position, ¶ms.new_name)? + .map_err(to_proto::rename_error)?; // this is kind of a hack to prevent double edits from happening when moving files // When a module gets renamed by renaming the mod declaration this causes the file to move diff --git a/crates/rust-analyzer/src/integrated_benchmarks.rs b/crates/rust-analyzer/src/integrated_benchmarks.rs index 84b7888258f8..4c4ae3eaf460 100644 --- a/crates/rust-analyzer/src/integrated_benchmarks.rs +++ b/crates/rust-analyzer/src/integrated_benchmarks.rs @@ -18,6 +18,7 @@ use ide::{ use ide_db::{ SnippetCap, imports::insert_use::{ImportGranularity, InsertUseConfig}, + rename::RenameConfig, }; use project_model::CargoConfig; use test_utils::project_root; @@ -360,6 +361,7 @@ fn integrated_diagnostics_benchmark() { prefer_absolute: false, term_search_fuel: 400, term_search_borrowck: true, + rename_config: RenameConfig { show_conflicts: true }, }; host.analysis() .full_diagnostics(&diagnostics_config, ide::AssistResolveStrategy::None, file_id) diff --git a/docs/book/src/configuration_generated.md b/docs/book/src/configuration_generated.md index ebac26e1d60a..53bb5e0d6440 100644 --- a/docs/book/src/configuration_generated.md +++ b/docs/book/src/configuration_generated.md @@ -1258,6 +1258,13 @@ Default: `false` Exclude tests from find-all-references and call-hierarchy. +## rust-analyzer.rename.showConflicts {#rename.showConflicts} + +Default: `true` + +Whether to warn when a rename will cause conflicts (change the meaning of the code). + + ## rust-analyzer.runnables.command {#runnables.command} Default: `null` diff --git a/editors/code/package.json b/editors/code/package.json index 3cb4c21ee1fb..a5c313c17508 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -2650,6 +2650,16 @@ } } }, + { + "title": "rename", + "properties": { + "rust-analyzer.rename.showConflicts": { + "markdownDescription": "Whether to warn when a rename will cause conflicts (change the meaning of the code).", + "default": true, + "type": "boolean" + } + } + }, { "title": "runnables", "properties": {