Skip to content

Commit d0b74a4

Browse files
Check on enum variant type matching in patterns
When building a semantic model a check is expected that pattern is legal. When we match an option, we expect a case `Some` to be illegal, as it ignores the parameter. The expected pattern would be `Some(_)`. This PR adds that check. commit-id:8eb351cd
1 parent 64b88f0 commit d0b74a4

4 files changed

Lines changed: 238 additions & 31 deletions

File tree

crates/cairo-lang-lowering/src/test_data/match

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1126,10 +1126,10 @@ fn foo(a: A, b: A) -> felt252 {
11261126
let x = (@a, b);
11271127
let y = @x;
11281128
match y {
1129-
(A::Two((t, _)), A::One) => **t + 3,
1130-
(A::Two, _) => 2,
1129+
(A::Two((t, _)), A::One(_)) => **t + 3,
1130+
(A::Two(_), _) => 2,
11311131
(A::One(_), A::One(_)) => 1,
1132-
(_, A::Three(_)) => 3,
1132+
(_, A::Three) => 3,
11331133
(_, _) => 6,
11341134
}
11351135
}
@@ -1270,10 +1270,10 @@ test_function_lowering(expect_diagnostics: true)
12701270
//! > function
12711271
fn foo(a: A, b: A) -> felt252 {
12721272
match (a, b) {
1273-
(A::Two, A::One) => 5,
1274-
(A::Two, _) => 2,
1273+
(A::Two(_), A::One(_)) => 5,
1274+
(A::Two(_), _) => 2,
12751275
(A::One(_), A::One(_)) => 1,
1276-
(_, A::Three(_)) => 3,
1276+
(_, A::Three) => 3,
12771277
(_, A::Four) => 4,
12781278
}
12791279
}
@@ -1331,7 +1331,7 @@ test_function_lowering(expect_diagnostics: true)
13311331
//! > function
13321332
fn foo(a: A, b: A) -> felt252 {
13331333
match (a, (a, b)) {
1334-
(A::Two, (A::One, A::One)) => 8,
1334+
(A::Two(_), (A::One(_), A::One(_))) => 8,
13351335
_ => 4,
13361336
}
13371337
}
@@ -1369,8 +1369,8 @@ test_function_lowering(expect_diagnostics: false)
13691369
//! > function
13701370
fn foo(a: A) -> felt252 {
13711371
match (a, get_bool()) {
1372-
(A::Two, true) => 5,
1373-
(A::Two, _) => 2,
1372+
(A::Two(_), true) => 5,
1373+
(A::Two(_), _) => 2,
13741374
(A::One(_), false) => 1,
13751375
(_, _) => 6,
13761376
}

crates/cairo-lang-semantic/src/diagnostic.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use cairo_lang_defs::diagnostic_utils::StableLocation;
55
use cairo_lang_defs::ids::{
66
EnumId, FunctionTitleId, GenericKind, ImplDefId, ImplFunctionId, ModuleId, ModuleItemId,
77
NamedLanguageElementId, StructId, TopLevelLanguageElementId, TraitFunctionId, TraitId,
8-
TraitImplId, TraitItemId, UseId,
8+
TraitImplId, TraitItemId, UseId, VariantId,
99
};
1010
use cairo_lang_defs::plugin::PluginDiagnostic;
1111
use cairo_lang_diagnostics::{
@@ -1011,6 +1011,14 @@ impl DiagnosticEntry for SemanticDiagnostic {
10111011
SemanticDiagnosticKind::TypeConstraintsSyntaxNotEnabled => {
10121012
"Type constraints syntax is not enabled in the current crate.".into()
10131013
}
1014+
SemanticDiagnosticKind::PatternMissingArgs(variant_id) => {
1015+
format!(
1016+
"Pattern missing subpattern for the parameter of variant `{}`. Consider using \
1017+
`{}(_)`",
1018+
variant_id.full_path(db),
1019+
variant_id.full_path(db)
1020+
)
1021+
}
10141022
}
10151023
}
10161024

@@ -1439,6 +1447,7 @@ pub enum SemanticDiagnosticKind {
14391447
concrete_trait_type_id: ConcreteTraitTypeId,
14401448
},
14411449
TypeConstraintsSyntaxNotEnabled,
1450+
PatternMissingArgs(VariantId),
14421451
}
14431452

14441453
/// The kind of an expression with multiple possible return types.

crates/cairo-lang-semantic/src/expr/compute.rs

Lines changed: 49 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,8 @@ use crate::types::{
8383
};
8484
use crate::usage::Usages;
8585
use crate::{
86-
ConcreteEnumId, GenericArgumentId, GenericParam, LocalItem, Member, Mutability, Parameter,
87-
PatternStringLiteral, PatternStruct, Signature, StatementItemKind,
86+
ConcreteEnumId, ConcreteVariant, GenericArgumentId, GenericParam, LocalItem, Member,
87+
Mutability, Parameter, PatternStringLiteral, PatternStruct, Signature, StatementItemKind,
8888
};
8989

9090
/// Expression with its id.
@@ -2219,18 +2219,13 @@ fn maybe_compute_pattern_semantic(
22192219
let generic_variant = try_extract_matches!(item, ResolvedGenericItem::Variant)
22202220
.ok_or_else(|| ctx.diagnostics.report(path.stable_ptr(db), NotAVariant))?;
22212221

2222-
let (concrete_enum, n_snapshots) = extract_concrete_enum_from_pattern_and_validate(
2223-
ctx,
2224-
pattern_syntax,
2225-
ty,
2226-
generic_variant.enum_id,
2227-
)?;
2228-
2229-
// TODO(lior): Should we report a diagnostic here?
2230-
let concrete_variant = ctx
2231-
.db
2232-
.concrete_enum_variant(concrete_enum, &generic_variant)
2233-
.map_err(|_| ctx.diagnostics.report(path.stable_ptr(db), UnknownEnum))?;
2222+
let (concrete_variant, n_snapshots) =
2223+
extract_concrete_variant_from_pattern_and_validate(
2224+
ctx,
2225+
pattern_syntax,
2226+
ty,
2227+
generic_variant,
2228+
)?;
22342229

22352230
// Compute inner pattern.
22362231
let inner_ty = wrap_in_snapshots(ctx.db, concrete_variant.ty, n_snapshots);
@@ -2266,17 +2261,13 @@ fn maybe_compute_pattern_semantic(
22662261
if let Some(generic_variant) =
22672262
try_extract_matches!(item, ResolvedGenericItem::Variant)
22682263
{
2269-
let (concrete_enum, _n_snapshots) =
2270-
extract_concrete_enum_from_pattern_and_validate(
2264+
let (concrete_variant, _n_snapshots) =
2265+
extract_concrete_variant_from_pattern_and_validate(
22712266
ctx,
22722267
pattern_syntax,
22732268
ty,
2274-
generic_variant.enum_id,
2269+
generic_variant,
22752270
)?;
2276-
let concrete_variant = ctx
2277-
.db
2278-
.concrete_enum_variant(concrete_enum, &generic_variant)
2279-
.map_err(|_| ctx.diagnostics.report(path.stable_ptr(db), UnknownEnum))?;
22802271
return Ok(Pattern::EnumVariant(PatternEnumVariant {
22812272
variant: concrete_variant,
22822273
inner_pattern: None,
@@ -2625,6 +2616,43 @@ fn extract_concrete_enum_from_pattern_and_validate(
26252616
Ok((concrete_enum, n_snapshots))
26262617
}
26272618

2619+
/// Validates that the semantic type of an enum pattern is an enum.
2620+
/// After that finds the concrete variant in the patter, and verifies it has args if needed.
2621+
/// Returns the concrete variant and the number of snapshots.
2622+
fn extract_concrete_variant_from_pattern_and_validate(
2623+
ctx: &mut ComputationContext<'_>,
2624+
pattern: &ast::Pattern,
2625+
ty: TypeId,
2626+
generic_variant: semantic::Variant,
2627+
) -> Maybe<(ConcreteVariant, usize)> {
2628+
let (concrete_enum, n_snapshots) =
2629+
extract_concrete_enum_from_pattern_and_validate(ctx, pattern, ty, generic_variant.enum_id)?;
2630+
let db = ctx.db;
2631+
2632+
// TODO(lior): Should we report a diagnostic here?
2633+
let needs_args = generic_variant.ty != unit_ty(db);
2634+
let has_args = matches!(
2635+
pattern,
2636+
ast::Pattern::Enum(inner)
2637+
if matches!(
2638+
inner.pattern(db),
2639+
ast::OptionPatternEnumInnerPattern::PatternEnumInnerPattern(_)
2640+
)
2641+
);
2642+
2643+
if needs_args && !has_args {
2644+
return Err(ctx
2645+
.diagnostics
2646+
.report(pattern.stable_ptr(db), PatternMissingArgs(generic_variant.id)));
2647+
}
2648+
2649+
let concrete_variant = db
2650+
.concrete_enum_variant(concrete_enum, &generic_variant)
2651+
.map_err(|_| ctx.diagnostics.report(pattern.stable_ptr(db), UnknownEnum))?;
2652+
2653+
Ok((concrete_variant, n_snapshots))
2654+
}
2655+
26282656
/// Creates a local variable pattern.
26292657
fn create_variable_pattern(
26302658
ctx: &mut ComputationContext<'_>,

crates/cairo-lang-semantic/src/expr/test_data/match

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,3 +254,173 @@ error: Unexpected argument type. Expected: "test::MyEnum::<core::integer::u32>",
254254
--> lib.cairo:11:9
255255
bar(a);
256256
^
257+
258+
//! > ==========================================================================
259+
260+
//! > Test match on variant without args.
261+
262+
//! > test_runner_name
263+
test_function_diagnostics(expect_diagnostics: true)
264+
265+
//! > function
266+
fn foo(a: A, b: A) -> felt252 {
267+
match (a, b) {
268+
(A::One, _) => 2,
269+
(_, _) => 6,
270+
}
271+
}
272+
273+
//! > function_name
274+
foo
275+
276+
//! > module_code
277+
#[derive(Copy, Drop)]
278+
enum A {
279+
One: felt252,
280+
}
281+
282+
//! > expected_diagnostics
283+
error: Pattern missing subpattern for the parameter of variant `test::A::One`. Consider using `test::A::One(_)`
284+
--> lib.cairo:7:10
285+
(A::One, _) => 2,
286+
^^^^^^
287+
288+
//! > ==========================================================================
289+
290+
//! > Test match on unit variant without args.
291+
292+
//! > test_runner_name
293+
test_function_diagnostics(expect_diagnostics: false)
294+
295+
//! > function
296+
fn foo(a: A, b: A) -> felt252 {
297+
match (a, b) {
298+
(A::One(_), _) => 2,
299+
(A::Two, _) => 6,
300+
}
301+
}
302+
303+
//! > function_name
304+
foo
305+
306+
//! > module_code
307+
#[derive(Copy, Drop)]
308+
enum A {
309+
One: felt252,
310+
Two: (),
311+
}
312+
313+
//! > expected_diagnostics
314+
315+
//! > ==========================================================================
316+
317+
//! > Test match on unit variant with args.
318+
319+
//! > test_runner_name
320+
test_function_diagnostics(expect_diagnostics: false)
321+
322+
//! > function
323+
fn foo(a: A, b: A) -> felt252 {
324+
match (a, b) {
325+
(A::One(_), _) => 2,
326+
(A::Two(_), _) => 6,
327+
}
328+
}
329+
330+
//! > function_name
331+
foo
332+
333+
//! > module_code
334+
#[derive(Copy, Drop)]
335+
enum A {
336+
One: felt252,
337+
Two: (),
338+
}
339+
340+
//! > expected_diagnostics
341+
342+
//! > ==========================================================================
343+
344+
//! > Test match on Some without args.
345+
346+
//! > test_runner_name
347+
test_function_diagnostics(expect_diagnostics: true)
348+
349+
//! > function
350+
fn foo<T>(a: Option<T>) -> felt252 {
351+
match a {
352+
Some => 2,
353+
None => 6,
354+
}
355+
}
356+
fn bar() -> felt252 {
357+
let a = Some(());
358+
foo(a)
359+
}
360+
361+
//! > function_name
362+
bar
363+
364+
//! > module_code
365+
366+
//! > expected_diagnostics
367+
error: Pattern missing subpattern for the parameter of variant `core::option::Option::Some`. Consider using `core::option::Option::Some(_)`
368+
--> lib.cairo:3:9
369+
Some => 2,
370+
^^^^
371+
372+
//! > ==========================================================================
373+
374+
//! > Test match on Path no args.
375+
376+
//! > test_runner_name
377+
test_function_diagnostics(expect_diagnostics: false)
378+
379+
//! > function
380+
fn foo(a: A, b: A) -> felt252 {
381+
match (a, b) {
382+
(A::One(_), _) => 2,
383+
(Two, _) => 6,
384+
}
385+
}
386+
387+
//! > function_name
388+
foo
389+
390+
//! > module_code
391+
#[derive(Copy, Drop)]
392+
enum A {
393+
One: felt252,
394+
Two: (),
395+
}
396+
use A::Two;
397+
398+
//! > expected_diagnostics
399+
400+
//! > ==========================================================================
401+
402+
//! > Test match on infered unit Some without args.
403+
404+
//! > test_runner_name
405+
test_function_diagnostics(expect_diagnostics: true)
406+
407+
//! > function
408+
fn fb() -> Option<()> {
409+
let x = None;
410+
match x {
411+
Some => 2,
412+
None => 6,
413+
}
414+
return x;
415+
}
416+
417+
//! > function_name
418+
fb
419+
420+
//! > module_code
421+
422+
//! > expected_diagnostics
423+
error: Pattern missing subpattern for the parameter of variant `core::option::Option::Some`. Consider using `core::option::Option::Some(_)`
424+
--> lib.cairo:4:9
425+
Some => 2,
426+
^^^^

0 commit comments

Comments
 (0)