diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/assist_config.rs b/src/tools/rust-analyzer/crates/ide-assists/src/assist_config.rs index fb569f8cdae0..57ced8d8534b 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/assist_config.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/assist_config.rs @@ -22,6 +22,7 @@ pub struct AssistConfig { pub term_search_borrowck: bool, pub code_action_grouping: bool, pub expr_fill_default: ExprFillDefaultMode, + pub prefer_self_ty: bool, } impl AssistConfig { diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_match_arms.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_match_arms.rs index 858d4369914a..682a8e6c78d3 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_match_arms.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_match_arms.rs @@ -1,12 +1,13 @@ use std::iter::{self, Peekable}; use either::Either; -use hir::{Adt, Crate, HasAttrs, ImportPathConfig, ModuleDef, Semantics, sym}; +use hir::{Adt, AsAssocItem, Crate, HasAttrs, ImportPathConfig, ModuleDef, Semantics, sym}; use ide_db::RootDatabase; use ide_db::assists::ExprFillDefaultMode; use ide_db::syntax_helpers::suggest_name; use ide_db::{famous_defs::FamousDefs, helpers::mod_path_to_ast}; use itertools::Itertools; +use syntax::ToSmolStr; use syntax::ast::edit::IndentLevel; use syntax::ast::edit_in_place::Indent; use syntax::ast::syntax_factory::SyntaxFactory; @@ -79,12 +80,20 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>) let make = SyntaxFactory::with_mappings(); - let module = ctx.sema.scope(expr.syntax())?.module(); + let scope = ctx.sema.scope(expr.syntax())?; + let module = scope.module(); + let self_ty = if ctx.config.prefer_self_ty { + scope + .containing_function() + .and_then(|function| function.as_assoc_item(ctx.db())?.implementing_ty(ctx.db())) + } else { + None + }; let (mut missing_pats, is_non_exhaustive, has_hidden_variants): ( Peekable>>, bool, bool, - ) = if let Some(enum_def) = resolve_enum_def(&ctx.sema, &expr) { + ) = if let Some(enum_def) = resolve_enum_def(&ctx.sema, &expr, self_ty.as_ref()) { let is_non_exhaustive = enum_def.is_non_exhaustive(ctx.db(), module.krate()); let variants = enum_def.variants(ctx.db()); @@ -102,8 +111,9 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>) }) .filter(|(variant_pat, _)| is_variant_missing(&top_lvl_pats, variant_pat)); - let option_enum = FamousDefs(&ctx.sema, module.krate()).core_option_Option().map(lift_enum); - let missing_pats: Box> = if Some(enum_def) == option_enum { + let option_enum = FamousDefs(&ctx.sema, module.krate()).core_option_Option(); + let missing_pats: Box> = if matches!(enum_def, ExtendedEnum::Enum { enum_: e, .. } if Some(e) == option_enum) + { // Match `Some` variant first. cov_mark::hit!(option_order); Box::new(missing_pats.rev()) @@ -111,7 +121,7 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>) Box::new(missing_pats) }; (missing_pats.peekable(), is_non_exhaustive, has_hidden_variants) - } else if let Some(enum_defs) = resolve_tuple_of_enum_def(&ctx.sema, &expr) { + } else if let Some(enum_defs) = resolve_tuple_of_enum_def(&ctx.sema, &expr, self_ty.as_ref()) { let is_non_exhaustive = enum_defs.iter().any(|enum_def| enum_def.is_non_exhaustive(ctx.db(), module.krate())); @@ -159,7 +169,9 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>) is_non_exhaustive, has_hidden_variants, ) - } else if let Some((enum_def, len)) = resolve_array_of_enum_def(&ctx.sema, &expr) { + } else if let Some((enum_def, len)) = + resolve_array_of_enum_def(&ctx.sema, &expr, self_ty.as_ref()) + { let is_non_exhaustive = enum_def.is_non_exhaustive(ctx.db(), module.krate()); let variants = enum_def.variants(ctx.db()); @@ -373,23 +385,23 @@ fn does_pat_match_variant(pat: &Pat, var: &Pat) -> bool { } } -#[derive(Eq, PartialEq, Clone, Copy)] +#[derive(Eq, PartialEq, Clone)] enum ExtendedEnum { Bool, - Enum(hir::Enum), + Enum { enum_: hir::Enum, use_self: bool }, } #[derive(Eq, PartialEq, Clone, Copy, Debug)] enum ExtendedVariant { True, False, - Variant(hir::Variant), + Variant { variant: hir::Variant, use_self: bool }, } impl ExtendedVariant { fn should_be_hidden(self, db: &RootDatabase, krate: Crate) -> bool { match self { - ExtendedVariant::Variant(var) => { + ExtendedVariant::Variant { variant: var, .. } => { var.attrs(db).has_doc_hidden() && var.module(db).krate() != krate } _ => false, @@ -397,25 +409,35 @@ impl ExtendedVariant { } } -fn lift_enum(e: hir::Enum) -> ExtendedEnum { - ExtendedEnum::Enum(e) -} - impl ExtendedEnum { - fn is_non_exhaustive(self, db: &RootDatabase, krate: Crate) -> bool { + fn enum_( + db: &RootDatabase, + enum_: hir::Enum, + enum_ty: &hir::Type, + self_ty: Option<&hir::Type>, + ) -> Self { + ExtendedEnum::Enum { + enum_, + use_self: self_ty.is_some_and(|self_ty| self_ty.could_unify_with_deeply(db, enum_ty)), + } + } + + fn is_non_exhaustive(&self, db: &RootDatabase, krate: Crate) -> bool { match self { - ExtendedEnum::Enum(e) => { + ExtendedEnum::Enum { enum_: e, .. } => { e.attrs(db).by_key(sym::non_exhaustive).exists() && e.module(db).krate() != krate } _ => false, } } - fn variants(self, db: &RootDatabase) -> Vec { - match self { - ExtendedEnum::Enum(e) => { - e.variants(db).into_iter().map(ExtendedVariant::Variant).collect::>() - } + fn variants(&self, db: &RootDatabase) -> Vec { + match *self { + ExtendedEnum::Enum { enum_: e, use_self } => e + .variants(db) + .into_iter() + .map(|variant| ExtendedVariant::Variant { variant, use_self }) + .collect::>(), ExtendedEnum::Bool => { Vec::::from([ExtendedVariant::True, ExtendedVariant::False]) } @@ -423,9 +445,13 @@ impl ExtendedEnum { } } -fn resolve_enum_def(sema: &Semantics<'_, RootDatabase>, expr: &ast::Expr) -> Option { +fn resolve_enum_def( + sema: &Semantics<'_, RootDatabase>, + expr: &ast::Expr, + self_ty: Option<&hir::Type>, +) -> Option { sema.type_of_expr(expr)?.adjusted().autoderef(sema.db).find_map(|ty| match ty.as_adt() { - Some(Adt::Enum(e)) => Some(ExtendedEnum::Enum(e)), + Some(Adt::Enum(e)) => Some(ExtendedEnum::enum_(sema.db, e, &ty, self_ty)), _ => ty.is_bool().then_some(ExtendedEnum::Bool), }) } @@ -433,6 +459,7 @@ fn resolve_enum_def(sema: &Semantics<'_, RootDatabase>, expr: &ast::Expr) -> Opt fn resolve_tuple_of_enum_def( sema: &Semantics<'_, RootDatabase>, expr: &ast::Expr, + self_ty: Option<&hir::Type>, ) -> Option> { sema.type_of_expr(expr)? .adjusted() @@ -441,7 +468,7 @@ fn resolve_tuple_of_enum_def( .map(|ty| { ty.autoderef(sema.db).find_map(|ty| { match ty.as_adt() { - Some(Adt::Enum(e)) => Some(lift_enum(e)), + Some(Adt::Enum(e)) => Some(ExtendedEnum::enum_(sema.db, e, &ty, self_ty)), // For now we only handle expansion for a tuple of enums. Here // we map non-enum items to None and rely on `collect` to // convert Vec> into Option>. @@ -456,10 +483,11 @@ fn resolve_tuple_of_enum_def( fn resolve_array_of_enum_def( sema: &Semantics<'_, RootDatabase>, expr: &ast::Expr, + self_ty: Option<&hir::Type>, ) -> Option<(ExtendedEnum, usize)> { sema.type_of_expr(expr)?.adjusted().as_array(sema.db).and_then(|(ty, len)| { ty.autoderef(sema.db).find_map(|ty| match ty.as_adt() { - Some(Adt::Enum(e)) => Some((lift_enum(e), len)), + Some(Adt::Enum(e)) => Some((ExtendedEnum::enum_(sema.db, e, &ty, self_ty), len)), _ => ty.is_bool().then_some((ExtendedEnum::Bool, len)), }) }) @@ -474,9 +502,21 @@ fn build_pat( ) -> Option { let db = ctx.db(); match var { - ExtendedVariant::Variant(var) => { + ExtendedVariant::Variant { variant: var, use_self } => { let edition = module.krate().edition(db); - let path = mod_path_to_ast(&module.find_path(db, ModuleDef::from(var), cfg)?, edition); + let path = if use_self { + make::path_from_segments( + [ + make::path_segment(make::name_ref_self_ty()), + make::path_segment(make::name_ref( + &var.name(db).display(db, edition).to_smolstr(), + )), + ], + false, + ) + } else { + mod_path_to_ast(&module.find_path(db, ModuleDef::from(var), cfg)?, edition) + }; let fields = var.fields(db); let pat: ast::Pat = match var.kind(db) { hir::StructKind::Tuple => { @@ -509,8 +549,10 @@ fn build_pat( #[cfg(test)] mod tests { + use crate::AssistConfig; use crate::tests::{ - check_assist, check_assist_not_applicable, check_assist_target, check_assist_unresolved, + TEST_CONFIG, check_assist, check_assist_not_applicable, check_assist_target, + check_assist_unresolved, check_assist_with_config, }; use super::add_missing_match_arms; @@ -2095,4 +2137,111 @@ fn f() { "#, ); } + + #[test] + fn prefer_self() { + check_assist_with_config( + add_missing_match_arms, + AssistConfig { prefer_self_ty: true, ..TEST_CONFIG }, + r#" +enum Foo { + Bar, + Baz, +} + +impl Foo { + fn qux(&self) { + match self { + $0_ => {} + } + } +} + "#, + r#" +enum Foo { + Bar, + Baz, +} + +impl Foo { + fn qux(&self) { + match self { + Self::Bar => ${1:todo!()}, + Self::Baz => ${2:todo!()},$0 + } + } +} + "#, + ); + } + + #[test] + fn prefer_self_with_generics() { + check_assist_with_config( + add_missing_match_arms, + AssistConfig { prefer_self_ty: true, ..TEST_CONFIG }, + r#" +enum Foo { + Bar(T), + Baz, +} + +impl Foo { + fn qux(&self) { + match self { + $0_ => {} + } + } +} + "#, + r#" +enum Foo { + Bar(T), + Baz, +} + +impl Foo { + fn qux(&self) { + match self { + Self::Bar(${1:_}) => ${2:todo!()}, + Self::Baz => ${3:todo!()},$0 + } + } +} + "#, + ); + check_assist_with_config( + add_missing_match_arms, + AssistConfig { prefer_self_ty: true, ..TEST_CONFIG }, + r#" +enum Foo { + Bar(T), + Baz, +} + +impl Foo { + fn qux(v: Foo) { + match v { + $0_ => {} + } + } +} + "#, + r#" +enum Foo { + Bar(T), + Baz, +} + +impl Foo { + fn qux(v: Foo) { + match v { + Foo::Bar(${1:_}) => ${2:todo!()}, + Foo::Baz => ${3:todo!()},$0 + } + } +} + "#, + ); + } } diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/tests.rs b/src/tools/rust-analyzer/crates/ide-assists/src/tests.rs index 5e6889792db6..cda2ad43278a 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/tests.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/tests.rs @@ -37,6 +37,7 @@ pub(crate) const TEST_CONFIG: AssistConfig = AssistConfig { term_search_borrowck: true, code_action_grouping: true, expr_fill_default: ExprFillDefaultMode::Todo, + prefer_self_ty: false, }; pub(crate) const TEST_CONFIG_NO_GROUPING: AssistConfig = AssistConfig { @@ -57,6 +58,7 @@ pub(crate) const TEST_CONFIG_NO_GROUPING: AssistConfig = AssistConfig { term_search_borrowck: true, code_action_grouping: false, expr_fill_default: ExprFillDefaultMode::Todo, + prefer_self_ty: false, }; pub(crate) const TEST_CONFIG_NO_SNIPPET_CAP: AssistConfig = AssistConfig { @@ -77,6 +79,7 @@ pub(crate) const TEST_CONFIG_NO_SNIPPET_CAP: AssistConfig = AssistConfig { term_search_borrowck: true, code_action_grouping: true, expr_fill_default: ExprFillDefaultMode::Todo, + prefer_self_ty: false, }; pub(crate) const TEST_CONFIG_IMPORT_ONE: AssistConfig = AssistConfig { @@ -97,6 +100,7 @@ pub(crate) const TEST_CONFIG_IMPORT_ONE: AssistConfig = AssistConfig { term_search_borrowck: true, code_action_grouping: true, expr_fill_default: ExprFillDefaultMode::Todo, + prefer_self_ty: false, }; pub(crate) fn with_single_file(text: &str) -> (RootDatabase, EditionedFileId) { @@ -113,6 +117,23 @@ pub(crate) fn check_assist( check(assist, ra_fixture_before, ExpectedResult::After(&ra_fixture_after), None); } +#[track_caller] +pub(crate) fn check_assist_with_config( + assist: Handler, + config: AssistConfig, + #[rust_analyzer::rust_fixture] ra_fixture_before: &str, + #[rust_analyzer::rust_fixture] ra_fixture_after: &str, +) { + let ra_fixture_after = trim_indent(ra_fixture_after); + check_with_config( + config, + assist, + ra_fixture_before, + ExpectedResult::After(&ra_fixture_after), + None, + ); +} + #[track_caller] pub(crate) fn check_assist_no_snippet_cap( assist: Handler, diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs index 5cbea9c2b3d9..ba5142845d09 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs @@ -452,6 +452,8 @@ config_data! { assist_emitMustUse: bool = false, /// Placeholder expression to use for missing expressions in assists. assist_expressionFillDefault: ExprFillDefaultDef = ExprFillDefaultDef::Todo, + /// When inserting a type (e.g. in "fill match arms" assist), prefer to use `Self` over the type name where possible. + assist_preferSelf: bool = false, /// Enable borrow checking for term search code assists. If set to false, also there will be more suggestions, but some of them may not borrow-check. assist_termSearch_borrowcheck: bool = true, /// Term search fuel in "units of work" for assists (Defaults to 1800). @@ -1501,6 +1503,7 @@ impl Config { ExprFillDefaultDef::Default => ExprFillDefaultMode::Default, ExprFillDefaultDef::Underscore => ExprFillDefaultMode::Underscore, }, + prefer_self_ty: *self.assist_preferSelf(source_root), } } diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/make.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/make.rs index fab4cb287c3d..955aadaa25d3 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ast/make.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ast/make.rs @@ -134,6 +134,13 @@ pub fn name_ref(name_ref: &str) -> ast::NameRef { } } } +pub fn name_ref_self_ty() -> ast::NameRef { + quote! { + NameRef { + [Self] + } + } +} fn raw_ident_esc(ident: &str) -> &'static str { if is_raw_identifier(ident, Edition::CURRENT) { "r#" } else { "" } } diff --git a/src/tools/rust-analyzer/docs/book/src/configuration_generated.md b/src/tools/rust-analyzer/docs/book/src/configuration_generated.md index 0e07dadfb7c9..7c1aafcdfa9c 100644 --- a/src/tools/rust-analyzer/docs/book/src/configuration_generated.md +++ b/src/tools/rust-analyzer/docs/book/src/configuration_generated.md @@ -13,6 +13,13 @@ Default: `"todo"` Placeholder expression to use for missing expressions in assists. +## rust-analyzer.assist.preferSelf {#assist.preferSelf} + +Default: `false` + +When inserting a type (e.g. in "fill match arms" assist), prefer to use `Self` over the type name where possible. + + ## rust-analyzer.assist.termSearch.borrowcheck {#assist.termSearch.borrowcheck} Default: `true` diff --git a/src/tools/rust-analyzer/editors/code/package.json b/src/tools/rust-analyzer/editors/code/package.json index c8c36cd85c8e..398d0c51e9ad 100644 --- a/src/tools/rust-analyzer/editors/code/package.json +++ b/src/tools/rust-analyzer/editors/code/package.json @@ -680,6 +680,16 @@ } } }, + { + "title": "assist", + "properties": { + "rust-analyzer.assist.preferSelf": { + "markdownDescription": "When inserting a type (e.g. in \"fill match arms\" assist), prefer to use `Self` over the type name where possible.", + "default": false, + "type": "boolean" + } + } + }, { "title": "assist", "properties": {