Skip to content

Commit 8661c59

Browse files
authored
Merge pull request #19939 from ChayimFriedman2/fill-arms-self
feat: In "Fill match arms", allow users to prefer `Self` to the enum name when possible
2 parents eb25f5e + 25a7b24 commit 8661c59

File tree

7 files changed

+227
-29
lines changed

7 files changed

+227
-29
lines changed

crates/ide-assists/src/assist_config.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ pub struct AssistConfig {
2222
pub term_search_borrowck: bool,
2323
pub code_action_grouping: bool,
2424
pub expr_fill_default: ExprFillDefaultMode,
25+
pub prefer_self_ty: bool,
2526
}
2627

2728
impl AssistConfig {

crates/ide-assists/src/handlers/add_missing_match_arms.rs

Lines changed: 178 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
use std::iter::{self, Peekable};
22

33
use either::Either;
4-
use hir::{Adt, Crate, HasAttrs, ImportPathConfig, ModuleDef, Semantics, sym};
4+
use hir::{Adt, AsAssocItem, Crate, HasAttrs, ImportPathConfig, ModuleDef, Semantics, sym};
55
use ide_db::RootDatabase;
66
use ide_db::assists::ExprFillDefaultMode;
77
use ide_db::syntax_helpers::suggest_name;
88
use ide_db::{famous_defs::FamousDefs, helpers::mod_path_to_ast};
99
use itertools::Itertools;
10+
use syntax::ToSmolStr;
1011
use syntax::ast::edit::IndentLevel;
1112
use syntax::ast::edit_in_place::Indent;
1213
use syntax::ast::syntax_factory::SyntaxFactory;
@@ -79,12 +80,20 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>)
7980

8081
let make = SyntaxFactory::with_mappings();
8182

82-
let module = ctx.sema.scope(expr.syntax())?.module();
83+
let scope = ctx.sema.scope(expr.syntax())?;
84+
let module = scope.module();
85+
let self_ty = if ctx.config.prefer_self_ty {
86+
scope
87+
.containing_function()
88+
.and_then(|function| function.as_assoc_item(ctx.db())?.implementing_ty(ctx.db()))
89+
} else {
90+
None
91+
};
8392
let (mut missing_pats, is_non_exhaustive, has_hidden_variants): (
8493
Peekable<Box<dyn Iterator<Item = (ast::Pat, bool)>>>,
8594
bool,
8695
bool,
87-
) = if let Some(enum_def) = resolve_enum_def(&ctx.sema, &expr) {
96+
) = if let Some(enum_def) = resolve_enum_def(&ctx.sema, &expr, self_ty.as_ref()) {
8897
let is_non_exhaustive = enum_def.is_non_exhaustive(ctx.db(), module.krate());
8998

9099
let variants = enum_def.variants(ctx.db());
@@ -102,16 +111,17 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>)
102111
})
103112
.filter(|(variant_pat, _)| is_variant_missing(&top_lvl_pats, variant_pat));
104113

105-
let option_enum = FamousDefs(&ctx.sema, module.krate()).core_option_Option().map(lift_enum);
106-
let missing_pats: Box<dyn Iterator<Item = _>> = if Some(enum_def) == option_enum {
114+
let option_enum = FamousDefs(&ctx.sema, module.krate()).core_option_Option();
115+
let missing_pats: Box<dyn Iterator<Item = _>> = if matches!(enum_def, ExtendedEnum::Enum { enum_: e, .. } if Some(e) == option_enum)
116+
{
107117
// Match `Some` variant first.
108118
cov_mark::hit!(option_order);
109119
Box::new(missing_pats.rev())
110120
} else {
111121
Box::new(missing_pats)
112122
};
113123
(missing_pats.peekable(), is_non_exhaustive, has_hidden_variants)
114-
} else if let Some(enum_defs) = resolve_tuple_of_enum_def(&ctx.sema, &expr) {
124+
} else if let Some(enum_defs) = resolve_tuple_of_enum_def(&ctx.sema, &expr, self_ty.as_ref()) {
115125
let is_non_exhaustive =
116126
enum_defs.iter().any(|enum_def| enum_def.is_non_exhaustive(ctx.db(), module.krate()));
117127

@@ -159,7 +169,9 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>)
159169
is_non_exhaustive,
160170
has_hidden_variants,
161171
)
162-
} else if let Some((enum_def, len)) = resolve_array_of_enum_def(&ctx.sema, &expr) {
172+
} else if let Some((enum_def, len)) =
173+
resolve_array_of_enum_def(&ctx.sema, &expr, self_ty.as_ref())
174+
{
163175
let is_non_exhaustive = enum_def.is_non_exhaustive(ctx.db(), module.krate());
164176
let variants = enum_def.variants(ctx.db());
165177

@@ -373,66 +385,81 @@ fn does_pat_match_variant(pat: &Pat, var: &Pat) -> bool {
373385
}
374386
}
375387

376-
#[derive(Eq, PartialEq, Clone, Copy)]
388+
#[derive(Eq, PartialEq, Clone)]
377389
enum ExtendedEnum {
378390
Bool,
379-
Enum(hir::Enum),
391+
Enum { enum_: hir::Enum, use_self: bool },
380392
}
381393

382394
#[derive(Eq, PartialEq, Clone, Copy, Debug)]
383395
enum ExtendedVariant {
384396
True,
385397
False,
386-
Variant(hir::Variant),
398+
Variant { variant: hir::Variant, use_self: bool },
387399
}
388400

389401
impl ExtendedVariant {
390402
fn should_be_hidden(self, db: &RootDatabase, krate: Crate) -> bool {
391403
match self {
392-
ExtendedVariant::Variant(var) => {
404+
ExtendedVariant::Variant { variant: var, .. } => {
393405
var.attrs(db).has_doc_hidden() && var.module(db).krate() != krate
394406
}
395407
_ => false,
396408
}
397409
}
398410
}
399411

400-
fn lift_enum(e: hir::Enum) -> ExtendedEnum {
401-
ExtendedEnum::Enum(e)
402-
}
403-
404412
impl ExtendedEnum {
405-
fn is_non_exhaustive(self, db: &RootDatabase, krate: Crate) -> bool {
413+
fn enum_(
414+
db: &RootDatabase,
415+
enum_: hir::Enum,
416+
enum_ty: &hir::Type,
417+
self_ty: Option<&hir::Type>,
418+
) -> Self {
419+
ExtendedEnum::Enum {
420+
enum_,
421+
use_self: self_ty.is_some_and(|self_ty| self_ty.could_unify_with_deeply(db, enum_ty)),
422+
}
423+
}
424+
425+
fn is_non_exhaustive(&self, db: &RootDatabase, krate: Crate) -> bool {
406426
match self {
407-
ExtendedEnum::Enum(e) => {
427+
ExtendedEnum::Enum { enum_: e, .. } => {
408428
e.attrs(db).by_key(sym::non_exhaustive).exists() && e.module(db).krate() != krate
409429
}
410430
_ => false,
411431
}
412432
}
413433

414-
fn variants(self, db: &RootDatabase) -> Vec<ExtendedVariant> {
415-
match self {
416-
ExtendedEnum::Enum(e) => {
417-
e.variants(db).into_iter().map(ExtendedVariant::Variant).collect::<Vec<_>>()
418-
}
434+
fn variants(&self, db: &RootDatabase) -> Vec<ExtendedVariant> {
435+
match *self {
436+
ExtendedEnum::Enum { enum_: e, use_self } => e
437+
.variants(db)
438+
.into_iter()
439+
.map(|variant| ExtendedVariant::Variant { variant, use_self })
440+
.collect::<Vec<_>>(),
419441
ExtendedEnum::Bool => {
420442
Vec::<ExtendedVariant>::from([ExtendedVariant::True, ExtendedVariant::False])
421443
}
422444
}
423445
}
424446
}
425447

426-
fn resolve_enum_def(sema: &Semantics<'_, RootDatabase>, expr: &ast::Expr) -> Option<ExtendedEnum> {
448+
fn resolve_enum_def(
449+
sema: &Semantics<'_, RootDatabase>,
450+
expr: &ast::Expr,
451+
self_ty: Option<&hir::Type>,
452+
) -> Option<ExtendedEnum> {
427453
sema.type_of_expr(expr)?.adjusted().autoderef(sema.db).find_map(|ty| match ty.as_adt() {
428-
Some(Adt::Enum(e)) => Some(ExtendedEnum::Enum(e)),
454+
Some(Adt::Enum(e)) => Some(ExtendedEnum::enum_(sema.db, e, &ty, self_ty)),
429455
_ => ty.is_bool().then_some(ExtendedEnum::Bool),
430456
})
431457
}
432458

433459
fn resolve_tuple_of_enum_def(
434460
sema: &Semantics<'_, RootDatabase>,
435461
expr: &ast::Expr,
462+
self_ty: Option<&hir::Type>,
436463
) -> Option<Vec<ExtendedEnum>> {
437464
sema.type_of_expr(expr)?
438465
.adjusted()
@@ -441,7 +468,7 @@ fn resolve_tuple_of_enum_def(
441468
.map(|ty| {
442469
ty.autoderef(sema.db).find_map(|ty| {
443470
match ty.as_adt() {
444-
Some(Adt::Enum(e)) => Some(lift_enum(e)),
471+
Some(Adt::Enum(e)) => Some(ExtendedEnum::enum_(sema.db, e, &ty, self_ty)),
445472
// For now we only handle expansion for a tuple of enums. Here
446473
// we map non-enum items to None and rely on `collect` to
447474
// convert Vec<Option<hir::Enum>> into Option<Vec<hir::Enum>>.
@@ -456,10 +483,11 @@ fn resolve_tuple_of_enum_def(
456483
fn resolve_array_of_enum_def(
457484
sema: &Semantics<'_, RootDatabase>,
458485
expr: &ast::Expr,
486+
self_ty: Option<&hir::Type>,
459487
) -> Option<(ExtendedEnum, usize)> {
460488
sema.type_of_expr(expr)?.adjusted().as_array(sema.db).and_then(|(ty, len)| {
461489
ty.autoderef(sema.db).find_map(|ty| match ty.as_adt() {
462-
Some(Adt::Enum(e)) => Some((lift_enum(e), len)),
490+
Some(Adt::Enum(e)) => Some((ExtendedEnum::enum_(sema.db, e, &ty, self_ty), len)),
463491
_ => ty.is_bool().then_some((ExtendedEnum::Bool, len)),
464492
})
465493
})
@@ -474,9 +502,21 @@ fn build_pat(
474502
) -> Option<ast::Pat> {
475503
let db = ctx.db();
476504
match var {
477-
ExtendedVariant::Variant(var) => {
505+
ExtendedVariant::Variant { variant: var, use_self } => {
478506
let edition = module.krate().edition(db);
479-
let path = mod_path_to_ast(&module.find_path(db, ModuleDef::from(var), cfg)?, edition);
507+
let path = if use_self {
508+
make::path_from_segments(
509+
[
510+
make::path_segment(make::name_ref_self_ty()),
511+
make::path_segment(make::name_ref(
512+
&var.name(db).display(db, edition).to_smolstr(),
513+
)),
514+
],
515+
false,
516+
)
517+
} else {
518+
mod_path_to_ast(&module.find_path(db, ModuleDef::from(var), cfg)?, edition)
519+
};
480520
let fields = var.fields(db);
481521
let pat: ast::Pat = match var.kind(db) {
482522
hir::StructKind::Tuple => {
@@ -509,8 +549,10 @@ fn build_pat(
509549

510550
#[cfg(test)]
511551
mod tests {
552+
use crate::AssistConfig;
512553
use crate::tests::{
513-
check_assist, check_assist_not_applicable, check_assist_target, check_assist_unresolved,
554+
TEST_CONFIG, check_assist, check_assist_not_applicable, check_assist_target,
555+
check_assist_unresolved, check_assist_with_config,
514556
};
515557

516558
use super::add_missing_match_arms;
@@ -2095,4 +2137,111 @@ fn f() {
20952137
"#,
20962138
);
20972139
}
2140+
2141+
#[test]
2142+
fn prefer_self() {
2143+
check_assist_with_config(
2144+
add_missing_match_arms,
2145+
AssistConfig { prefer_self_ty: true, ..TEST_CONFIG },
2146+
r#"
2147+
enum Foo {
2148+
Bar,
2149+
Baz,
2150+
}
2151+
2152+
impl Foo {
2153+
fn qux(&self) {
2154+
match self {
2155+
$0_ => {}
2156+
}
2157+
}
2158+
}
2159+
"#,
2160+
r#"
2161+
enum Foo {
2162+
Bar,
2163+
Baz,
2164+
}
2165+
2166+
impl Foo {
2167+
fn qux(&self) {
2168+
match self {
2169+
Self::Bar => ${1:todo!()},
2170+
Self::Baz => ${2:todo!()},$0
2171+
}
2172+
}
2173+
}
2174+
"#,
2175+
);
2176+
}
2177+
2178+
#[test]
2179+
fn prefer_self_with_generics() {
2180+
check_assist_with_config(
2181+
add_missing_match_arms,
2182+
AssistConfig { prefer_self_ty: true, ..TEST_CONFIG },
2183+
r#"
2184+
enum Foo<T> {
2185+
Bar(T),
2186+
Baz,
2187+
}
2188+
2189+
impl<T> Foo<T> {
2190+
fn qux(&self) {
2191+
match self {
2192+
$0_ => {}
2193+
}
2194+
}
2195+
}
2196+
"#,
2197+
r#"
2198+
enum Foo<T> {
2199+
Bar(T),
2200+
Baz,
2201+
}
2202+
2203+
impl<T> Foo<T> {
2204+
fn qux(&self) {
2205+
match self {
2206+
Self::Bar(${1:_}) => ${2:todo!()},
2207+
Self::Baz => ${3:todo!()},$0
2208+
}
2209+
}
2210+
}
2211+
"#,
2212+
);
2213+
check_assist_with_config(
2214+
add_missing_match_arms,
2215+
AssistConfig { prefer_self_ty: true, ..TEST_CONFIG },
2216+
r#"
2217+
enum Foo<T> {
2218+
Bar(T),
2219+
Baz,
2220+
}
2221+
2222+
impl<T> Foo<T> {
2223+
fn qux(v: Foo<i32>) {
2224+
match v {
2225+
$0_ => {}
2226+
}
2227+
}
2228+
}
2229+
"#,
2230+
r#"
2231+
enum Foo<T> {
2232+
Bar(T),
2233+
Baz,
2234+
}
2235+
2236+
impl<T> Foo<T> {
2237+
fn qux(v: Foo<i32>) {
2238+
match v {
2239+
Foo::Bar(${1:_}) => ${2:todo!()},
2240+
Foo::Baz => ${3:todo!()},$0
2241+
}
2242+
}
2243+
}
2244+
"#,
2245+
);
2246+
}
20982247
}

0 commit comments

Comments
 (0)