Skip to content

Commit d6de8e9

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 and raises a warning when needed. commit-id:8eb351cd
1 parent 64b88f0 commit d6de8e9

File tree

6 files changed

+445
-31
lines changed

6 files changed

+445
-31
lines changed

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

+78-10
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
}
@@ -1715,4 +1715,72 @@ error: Unreachable pattern arm.
17151715
^^^^^^^^^
17161716

17171717
//! > lowering_flat
1718-
<Failed lowering function - run with RUST_LOG=warn (or less) to see diagnostics>
1718+
<Failed lowering function - run with RUST_LOG=warn (or less) to see diagnostics>
1719+
1720+
//! > ==========================================================================
1721+
1722+
//! > Test lower match missing args.
1723+
1724+
//! > test_runner_name
1725+
test_function_lowering(expect_diagnostics: true)
1726+
1727+
//! > function
1728+
fn foo(a: felt252) -> felt252 {
1729+
match Some(a) {
1730+
Some => 2,
1731+
_ => 0,
1732+
}
1733+
}
1734+
1735+
//! > function_name
1736+
foo
1737+
1738+
//! > module_code
1739+
1740+
//! > semantic_diagnostics
1741+
warning: Pattern missing subpattern for the payload of variant. Consider using `Some(_)`
1742+
--> lib.cairo:3:9
1743+
Some => 2,
1744+
^^^^
1745+
1746+
//! > lowering_diagnostics
1747+
1748+
//! > lowering_flat
1749+
Parameters: v0: core::felt252
1750+
blk0 (root):
1751+
Statements:
1752+
(v1: core::felt252) <- 2
1753+
End:
1754+
Return(v1)
1755+
1756+
//! > ==========================================================================
1757+
1758+
//! > Test lower match with _ args.
1759+
1760+
//! > test_runner_name
1761+
test_function_lowering(expect_diagnostics: false)
1762+
1763+
//! > function
1764+
fn foo(a: felt252) -> felt252 {
1765+
match Some(a) {
1766+
Some(_) => 2,
1767+
_ => 0,
1768+
}
1769+
}
1770+
1771+
//! > function_name
1772+
foo
1773+
1774+
//! > module_code
1775+
1776+
//! > semantic_diagnostics
1777+
1778+
//! > lowering_diagnostics
1779+
1780+
//! > lowering_flat
1781+
Parameters: v0: core::felt252
1782+
blk0 (root):
1783+
Statements:
1784+
(v1: core::felt252) <- 2
1785+
End:
1786+
Return(v1)

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

+10
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ use cairo_lang_diagnostics::{
1313
ErrorCode, Severity, error_code,
1414
};
1515
use cairo_lang_filesystem::db::Edition;
16+
use cairo_lang_syntax::node::ast;
17+
use cairo_lang_syntax::node::helpers::GetIdentifier;
1618
use cairo_lang_syntax::{self as syntax};
1719
use itertools::Itertools;
1820
use smol_str::SmolStr;
@@ -1011,6 +1013,12 @@ impl DiagnosticEntry for SemanticDiagnostic {
10111013
SemanticDiagnosticKind::TypeConstraintsSyntaxNotEnabled => {
10121014
"Type constraints syntax is not enabled in the current crate.".into()
10131015
}
1016+
SemanticDiagnosticKind::PatternMissingArgs(path) => {
1017+
format!(
1018+
"Pattern missing subpattern for the payload of variant. Consider using `{}(_)`",
1019+
path.elements(db).into_iter().map(|seg| seg.identifier(db)).join("::")
1020+
)
1021+
}
10141022
}
10151023
}
10161024

@@ -1048,6 +1056,7 @@ impl DiagnosticEntry for SemanticDiagnostic {
10481056
| SemanticDiagnosticKind::CallingShadowedFunction { .. }
10491057
| SemanticDiagnosticKind::UnusedConstant
10501058
| SemanticDiagnosticKind::UnusedUse
1059+
| SemanticDiagnosticKind::PatternMissingArgs(_)
10511060
| SemanticDiagnosticKind::UnsupportedAllowAttrArguments => Severity::Warning,
10521061
SemanticDiagnosticKind::PluginDiagnostic(diag) => diag.severity,
10531062
_ => Severity::Error,
@@ -1439,6 +1448,7 @@ pub enum SemanticDiagnosticKind {
14391448
concrete_trait_type_id: ConcreteTraitTypeId,
14401449
},
14411450
TypeConstraintsSyntaxNotEnabled,
1451+
PatternMissingArgs(ast::ExprPath),
14421452
}
14431453

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

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

+52-21
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,46 @@ 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+
let path = match pattern {
2645+
ast::Pattern::Enum(pattern) => pattern.path(db),
2646+
ast::Pattern::Path(p) => p.clone(),
2647+
_ => unreachable!(),
2648+
};
2649+
ctx.diagnostics.report(pattern.stable_ptr(db), PatternMissingArgs(path));
2650+
}
2651+
2652+
let concrete_variant = db
2653+
.concrete_enum_variant(concrete_enum, &generic_variant)
2654+
.map_err(|_| ctx.diagnostics.report(pattern.stable_ptr(db), UnknownEnum))?;
2655+
2656+
Ok((concrete_variant, n_snapshots))
2657+
}
2658+
26282659
/// Creates a local variable pattern.
26292660
fn create_variable_pattern(
26302661
ctx: &mut ComputationContext<'_>,

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

+55
Original file line numberDiff line numberDiff line change
@@ -137,3 +137,58 @@ error[E0006]: Identifier not found.
137137
--> lib.cairo:4:16
138138
return y == 9;
139139
^
140+
141+
//! > ==========================================================================
142+
143+
//! > if_let some no args.
144+
145+
//! > test_runner_name
146+
test_function_diagnostics(expect_diagnostics: true)
147+
148+
//! > function
149+
fn foo() -> bool {
150+
let x = Some(7_u64);
151+
if let Some = x {
152+
return true;
153+
} else {
154+
return false;
155+
}
156+
}
157+
158+
//! > function_name
159+
foo
160+
161+
//! > module_code
162+
163+
//! > expected_diagnostics
164+
warning: Pattern missing subpattern for the payload of variant. Consider using `Some(_)`
165+
--> lib.cairo:3:12
166+
if let Some = x {
167+
^^^^
168+
169+
//! > ==========================================================================
170+
171+
//! > if_let enum unit no args.
172+
173+
//! > test_runner_name
174+
test_function_diagnostics(expect_diagnostics: false)
175+
176+
//! > function
177+
fn foo() -> bool {
178+
let a = A::B(());
179+
if let A::B = a {
180+
return true;
181+
} else {
182+
return false;
183+
}
184+
}
185+
186+
//! > function_name
187+
foo
188+
189+
//! > module_code
190+
enum A {
191+
B: (),
192+
}
193+
194+
//! > expected_diagnostics

0 commit comments

Comments
 (0)