Skip to content

Commit 8fa69f9

Browse files
committed
feat: array match
1 parent 3a72713 commit 8fa69f9

File tree

3 files changed

+157
-11
lines changed

3 files changed

+157
-11
lines changed

crates/hir/src/lib.rs

+17-2
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ use hir_def::{
5050
per_ns::PerNs,
5151
resolver::{HasResolver, Resolver},
5252
src::HasSource as _,
53+
type_ref::ConstScalar,
5354
AdtId, AssocItemId, AssocItemLoc, AttrDefId, ConstId, ConstParamId, DefWithBodyId, EnumId,
5455
EnumVariantId, FunctionId, GenericDefId, HasModule, ImplId, ItemContainerId, LifetimeParamId,
5556
LocalEnumVariantId, LocalFieldId, Lookup, MacroExpander, MacroId, ModuleId, StaticId, StructId,
@@ -65,8 +66,9 @@ use hir_ty::{
6566
primitive::UintTy,
6667
traits::FnTrait,
6768
AliasTy, CallableDefId, CallableSig, Canonical, CanonicalVarKinds, Cast, ClosureId,
68-
GenericArgData, Interner, ParamKind, QuantifiedWhereClause, Scalar, Substitution,
69-
TraitEnvironment, TraitRefExt, Ty, TyBuilder, TyDefId, TyExt, TyKind, WhereClause,
69+
ConcreteConst, ConstValue, GenericArgData, Interner, ParamKind, QuantifiedWhereClause, Scalar,
70+
Substitution, TraitEnvironment, TraitRefExt, Ty, TyBuilder, TyDefId, TyExt, TyKind,
71+
WhereClause,
7072
};
7173
use itertools::Itertools;
7274
use nameres::diagnostics::DefDiagnosticKind;
@@ -3232,6 +3234,19 @@ impl Type {
32323234
}
32333235
}
32343236

3237+
pub fn as_array(&self, _db: &dyn HirDatabase) -> Option<(Type, usize)> {
3238+
if let TyKind::Array(ty, len) = &self.ty.kind(Interner) {
3239+
match len.data(Interner).value {
3240+
ConstValue::Concrete(ConcreteConst { interned: ConstScalar::UInt(len) }) => {
3241+
Some((self.derived(ty.clone()), len as usize))
3242+
}
3243+
_ => None,
3244+
}
3245+
} else {
3246+
None
3247+
}
3248+
}
3249+
32353250
pub fn autoderef<'a>(&'a self, db: &'a dyn HirDatabase) -> impl Iterator<Item = Type> + 'a {
32363251
self.autoderef_(db).map(move |ty| self.derived(ty))
32373252
}

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

+131-9
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,31 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>)
140140
})
141141
.filter(|(variant_pat, _)| is_variant_missing(&top_lvl_pats, variant_pat));
142142
((Box::new(missing_pats) as Box<dyn Iterator<Item = _>>).peekable(), is_non_exhaustive)
143+
} else if let Some((enum_def, len)) = resolve_array_of_enum_def(&ctx.sema, &expr) {
144+
let is_non_exhaustive = enum_def.is_non_exhaustive(ctx.db(), module.krate());
145+
let variants = enum_def.variants(ctx.db());
146+
147+
if len.pow(variants.len() as u32) > 256 {
148+
return None;
149+
}
150+
151+
let variants_of_enums = vec![variants.clone(); len];
152+
153+
let missing_pats = variants_of_enums
154+
.into_iter()
155+
.multi_cartesian_product()
156+
.inspect(|_| cov_mark::hit!(add_missing_match_arms_lazy_computation))
157+
.map(|variants| {
158+
let is_hidden = variants
159+
.iter()
160+
.any(|variant| variant.should_be_hidden(ctx.db(), module.krate()));
161+
let patterns = variants.into_iter().filter_map(|variant| {
162+
build_pat(ctx.db(), module, variant.clone(), ctx.config.prefer_no_std)
163+
});
164+
(ast::Pat::from(make::slice_pat(patterns)), is_hidden)
165+
})
166+
.filter(|(variant_pat, _)| is_variant_missing(&top_lvl_pats, variant_pat));
167+
((Box::new(missing_pats) as Box<dyn Iterator<Item = _>>).peekable(), is_non_exhaustive)
143168
} else {
144169
return None;
145170
};
@@ -266,6 +291,9 @@ fn is_variant_missing(existing_pats: &[Pat], var: &Pat) -> bool {
266291
fn does_pat_match_variant(pat: &Pat, var: &Pat) -> bool {
267292
match (pat, var) {
268293
(Pat::WildcardPat(_), _) => true,
294+
(Pat::SlicePat(spat), Pat::SlicePat(svar)) => {
295+
spat.pats().zip(svar.pats()).all(|(p, v)| does_pat_match_variant(&p, &v))
296+
}
269297
(Pat::TuplePat(tpat), Pat::TuplePat(tvar)) => {
270298
tpat.fields().zip(tvar.fields()).all(|(p, v)| does_pat_match_variant(&p, &v))
271299
}
@@ -280,7 +308,7 @@ enum ExtendedEnum {
280308
Enum(hir::Enum),
281309
}
282310

283-
#[derive(Eq, PartialEq, Clone, Copy)]
311+
#[derive(Eq, PartialEq, Clone, Copy, Debug)]
284312
enum ExtendedVariant {
285313
True,
286314
False,
@@ -340,15 +368,30 @@ fn resolve_tuple_of_enum_def(
340368
.tuple_fields(sema.db)
341369
.iter()
342370
.map(|ty| {
343-
ty.autoderef(sema.db).find_map(|ty| match ty.as_adt() {
344-
Some(Adt::Enum(e)) => Some(lift_enum(e)),
345-
// For now we only handle expansion for a tuple of enums. Here
346-
// we map non-enum items to None and rely on `collect` to
347-
// convert Vec<Option<hir::Enum>> into Option<Vec<hir::Enum>>.
348-
_ => ty.is_bool().then_some(ExtendedEnum::Bool),
371+
ty.autoderef(sema.db).find_map(|ty| {
372+
match ty.as_adt() {
373+
Some(Adt::Enum(e)) => Some(lift_enum(e)),
374+
// For now we only handle expansion for a tuple of enums. Here
375+
// we map non-enum items to None and rely on `collect` to
376+
// convert Vec<Option<hir::Enum>> into Option<Vec<hir::Enum>>.
377+
_ => ty.is_bool().then_some(ExtendedEnum::Bool),
378+
}
349379
})
350380
})
351-
.collect()
381+
.collect::<Option<Vec<ExtendedEnum>>>()
382+
.and_then(|list| if list.is_empty() { None } else { Some(list) })
383+
}
384+
385+
fn resolve_array_of_enum_def(
386+
sema: &Semantics<'_, RootDatabase>,
387+
expr: &ast::Expr,
388+
) -> Option<(ExtendedEnum, usize)> {
389+
sema.type_of_expr(expr)?.adjusted().as_array(sema.db).and_then(|(ty, len)| {
390+
ty.autoderef(sema.db).find_map(|ty| match ty.as_adt() {
391+
Some(Adt::Enum(e)) => Some((lift_enum(e), len)),
392+
_ => ty.is_bool().then_some((ExtendedEnum::Bool, len)),
393+
})
394+
})
352395
}
353396

354397
fn build_pat(
@@ -377,7 +420,6 @@ fn build_pat(
377420
}
378421
ast::StructKind::Unit => make::path_pat(path),
379422
};
380-
381423
Some(pat)
382424
}
383425
ExtendedVariant::True => Some(ast::Pat::from(make::literal_pat("true"))),
@@ -573,6 +615,86 @@ fn foo(a: bool) {
573615
)
574616
}
575617

618+
#[test]
619+
fn fill_boolean_array() {
620+
check_assist(
621+
add_missing_match_arms,
622+
r#"
623+
fn foo(a: bool) {
624+
match [a]$0 {
625+
}
626+
}
627+
"#,
628+
r#"
629+
fn foo(a: bool) {
630+
match [a] {
631+
$0[true] => todo!(),
632+
[false] => todo!(),
633+
}
634+
}
635+
"#,
636+
);
637+
638+
check_assist(
639+
add_missing_match_arms,
640+
r#"
641+
fn foo(a: bool) {
642+
match [a,]$0 {
643+
}
644+
}
645+
"#,
646+
r#"
647+
fn foo(a: bool) {
648+
match [a,] {
649+
$0[true] => todo!(),
650+
[false] => todo!(),
651+
}
652+
}
653+
"#,
654+
);
655+
656+
check_assist(
657+
add_missing_match_arms,
658+
r#"
659+
fn foo(a: bool) {
660+
match [a, a]$0 {
661+
[true, true] => todo!(),
662+
}
663+
}
664+
"#,
665+
r#"
666+
fn foo(a: bool) {
667+
match [a, a] {
668+
[true, true] => todo!(),
669+
$0[true, false] => todo!(),
670+
[false, true] => todo!(),
671+
[false, false] => todo!(),
672+
}
673+
}
674+
"#,
675+
);
676+
677+
check_assist(
678+
add_missing_match_arms,
679+
r#"
680+
fn foo(a: bool) {
681+
match [a, a]$0 {
682+
}
683+
}
684+
"#,
685+
r#"
686+
fn foo(a: bool) {
687+
match [a, a] {
688+
$0[true, true] => todo!(),
689+
[true, false] => todo!(),
690+
[false, true] => todo!(),
691+
[false, false] => todo!(),
692+
}
693+
}
694+
"#,
695+
)
696+
}
697+
576698
#[test]
577699
fn partial_fill_boolean_tuple() {
578700
check_assist(

crates/syntax/src/ast/make.rs

+9
Original file line numberDiff line numberDiff line change
@@ -520,6 +520,15 @@ pub fn literal_pat(lit: &str) -> ast::LiteralPat {
520520
}
521521
}
522522

523+
pub fn slice_pat(pats: impl IntoIterator<Item = ast::Pat>) -> ast::SlicePat {
524+
let pats_str = pats.into_iter().join(", ");
525+
return from_text(&format!("[{pats_str}]"));
526+
527+
fn from_text(text: &str) -> ast::SlicePat {
528+
ast_from_text(&format!("fn f() {{ match () {{{text} => ()}} }}"))
529+
}
530+
}
531+
523532
/// Creates a tuple of patterns from an iterator of patterns.
524533
///
525534
/// Invariant: `pats` must be length > 0

0 commit comments

Comments
 (0)