Skip to content

Commit 2b2e69d

Browse files
authored
lowering: Support let-else. (#7855)
1 parent 396d1d8 commit 2b2e69d

File tree

4 files changed

+259
-34
lines changed

4 files changed

+259
-34
lines changed
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
use cairo_lang_semantic::usage::MemberPath;
2+
use cairo_lang_semantic::{Binding, Expr, Pattern, TypeLongId, VarId};
3+
use cairo_lang_syntax::node::TypedStablePtr;
4+
use cairo_lang_syntax::node::ast::StatementPtr;
5+
use cairo_lang_syntax::node::ids::SyntaxStablePtrId;
6+
use cairo_lang_utils::Intern;
7+
use id_arena::Id;
8+
use itertools::zip_eq;
9+
10+
use super::block_builder::{BlockBuilder, SealedBlockBuilder};
11+
use super::context::{LoweredExpr, LoweringContext, LoweringFlowError, VarRequest};
12+
use super::generators;
13+
use super::lower_match::{MatchArmWrapper, lower_match_arms};
14+
use crate::diagnostic::MatchKind;
15+
16+
/// Lowers a let-else statement.
17+
///
18+
/// For example,
19+
/// ```plain
20+
/// let Some((x, y)) = opt else {
21+
/// return;
22+
/// }
23+
/// ```
24+
///
25+
/// A let-else statement is handled as a match as follows:
26+
///
27+
/// ```plain
28+
/// let (v0, v1, ...) = match expr {
29+
/// pattern => { (v0, v1, ...) }, // Success arm.
30+
/// _ => { else_expr }, // Else arm.
31+
/// };
32+
/// ```
33+
pub fn lower_let_else(
34+
ctx: &mut LoweringContext<'_, '_>,
35+
builder: &mut BlockBuilder,
36+
pattern_id: &Id<Pattern>,
37+
expr: &Id<Expr>,
38+
lowered_expr: LoweredExpr,
39+
else_clause: &Id<Expr>,
40+
stable_ptr: &StatementPtr,
41+
) -> Result<(), LoweringFlowError> {
42+
let pattern = ctx.function_body.arenas.patterns[*pattern_id].clone();
43+
let variables = pattern.variables(&ctx.function_body.arenas.patterns);
44+
45+
// Create a match expression with two arms.
46+
let patterns = &[*pattern_id];
47+
let var_ids_and_stable_ptrs = variables
48+
.iter()
49+
.map(|pattern_var| (VarId::Local(pattern_var.var.id), pattern_var.stable_ptr.untyped()))
50+
.collect();
51+
let arms = vec![
52+
MatchArmWrapper::LetElseSuccess(patterns, var_ids_and_stable_ptrs, stable_ptr.untyped()),
53+
MatchArmWrapper::ElseClause(*else_clause),
54+
];
55+
56+
// Lower the match expression.
57+
// The result is a tuple with the values of the pattern's variables.
58+
let match_lowered = lower_match_arms(
59+
ctx,
60+
builder,
61+
*expr,
62+
lowered_expr,
63+
arms,
64+
ctx.get_location(stable_ptr.untyped()),
65+
MatchKind::Match,
66+
)?;
67+
68+
// Destruct the tuple.
69+
let reqs = variables
70+
.iter()
71+
.map(|pattern_var| VarRequest {
72+
ty: pattern_var.var.ty,
73+
location: ctx.get_location(pattern_var.stable_ptr.untyped()),
74+
})
75+
.collect();
76+
let output_vars = generators::StructDestructure {
77+
input: match_lowered.as_var_usage(ctx, builder)?,
78+
var_reqs: reqs,
79+
}
80+
.add(ctx, &mut builder.statements);
81+
82+
// Bind the values to the variables.
83+
for (pattern_var, output_var) in zip_eq(variables, output_vars) {
84+
let sem_var = Binding::LocalVar(pattern_var.var.clone());
85+
// Override variable location.
86+
ctx.variables.variables[output_var].location =
87+
ctx.get_location(pattern_var.stable_ptr.untyped());
88+
builder.put_semantic(sem_var.id(), output_var);
89+
ctx.semantic_defs.insert(sem_var.id(), sem_var);
90+
}
91+
92+
Ok(())
93+
}
94+
95+
/// Similar to [super::lower_tail_expr] expect that the result is a tuple of the given variables.
96+
///
97+
/// Used in the lowering of the success arm's body in let-else.
98+
pub fn lower_success_arm_body(
99+
ctx: &mut LoweringContext<'_, '_>,
100+
mut builder: BlockBuilder,
101+
vars: &[(VarId, SyntaxStablePtrId)],
102+
stable_ptr: &SyntaxStablePtrId,
103+
) -> SealedBlockBuilder {
104+
log::trace!("Lowering success arm body");
105+
106+
let inputs: Vec<_> = vars
107+
.iter()
108+
.map(|(var_id, stable_ptr)| {
109+
builder
110+
.get_ref_raw(ctx, &MemberPath::Var(*var_id), ctx.get_location(*stable_ptr))
111+
.unwrap()
112+
})
113+
.collect();
114+
115+
let tys = inputs.iter().map(|var_usage| ctx.variables[var_usage.var_id].ty).collect();
116+
let tuple_ty = TypeLongId::Tuple(tys).intern(ctx.db);
117+
let tuple_var_usage = generators::StructConstruct {
118+
inputs,
119+
ty: tuple_ty,
120+
location: ctx.get_location(*stable_ptr),
121+
}
122+
.add(ctx, &mut builder.statements);
123+
124+
builder.goto_callsite(Some(tuple_var_usage))
125+
}

crates/cairo-lang-lowering/src/lower/lower_match.rs

Lines changed: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use cairo_lang_diagnostics::Maybe;
55
use cairo_lang_filesystem::flag::Flag;
66
use cairo_lang_filesystem::ids::FlagId;
77
use cairo_lang_semantic::{
8-
self as semantic, ConcreteEnumId, ConcreteVariant, GenericArgumentId, corelib,
8+
self as semantic, ConcreteEnumId, ConcreteVariant, GenericArgumentId, VarId, corelib,
99
};
1010
use cairo_lang_syntax::node::TypedStablePtr;
1111
use cairo_lang_syntax::node::ids::SyntaxStablePtrId;
@@ -28,6 +28,7 @@ use super::context::{
2828
LoweredExpr, LoweredExprExternEnum, LoweringContext, LoweringFlowError, LoweringResult,
2929
lowering_flow_error_to_sealed_block,
3030
};
31+
use super::lower_let_else::lower_success_arm_body;
3132
use super::{
3233
alloc_empty_block, generators, lower_expr_block, lower_expr_literal, lower_tail_expr,
3334
lowered_expr_to_block_scope_end, recursively_call_loop_func,
@@ -63,6 +64,9 @@ pub enum MatchArmWrapper<'a> {
6364
ElseClause(semantic::ExprId),
6465
/// Default clause when else clause is not provided (if-let/while-let).
6566
DefaultClause,
67+
/// The success arm of a let-else statement. See [super::lower_let_else::lower_let_else] for
68+
/// more details.
69+
LetElseSuccess(&'a [PatternId], Vec<(VarId, SyntaxStablePtrId)>, SyntaxStablePtrId),
6670
}
6771

6872
impl<'a> From<&'a semantic::MatchArm> for MatchArmWrapper<'a> {
@@ -72,21 +76,13 @@ impl<'a> From<&'a semantic::MatchArm> for MatchArmWrapper<'a> {
7276
}
7377

7478
impl MatchArmWrapper<'_> {
75-
/// Returns the expression of the guarded by the match arm.
76-
pub fn expr(&self) -> Option<semantic::ExprId> {
77-
match self {
78-
MatchArmWrapper::Arm(_, expr) => Some(*expr),
79-
MatchArmWrapper::ElseClause(expr) => Some(*expr),
80-
MatchArmWrapper::DefaultClause => None,
81-
}
82-
}
83-
8479
/// Returns the patterns of the match arm.
8580
pub fn patterns(&self) -> Option<&[PatternId]> {
8681
match self {
8782
MatchArmWrapper::Arm(patterns, _) => Some(patterns),
8883
MatchArmWrapper::ElseClause(_) => None,
8984
MatchArmWrapper::DefaultClause => None,
85+
MatchArmWrapper::LetElseSuccess(patterns, _, _) => Some(patterns),
9086
}
9187
}
9288

@@ -180,8 +176,8 @@ fn get_underscore_pattern_path_and_mark_unreachable(
180176
.iter()
181177
.enumerate()
182178
.filter_map(|(arm_index, arm)| {
183-
let pattern_index = match arm {
184-
MatchArmWrapper::Arm(patterns, _) => {
179+
let pattern_index = match arm.patterns() {
180+
Some(patterns) => {
185181
let position = patterns.iter().position(|pattern| {
186182
matches!(
187183
ctx.function_body.arenas.patterns[*pattern],
@@ -190,15 +186,15 @@ fn get_underscore_pattern_path_and_mark_unreachable(
190186
})?;
191187
Some(position)
192188
}
193-
MatchArmWrapper::DefaultClause | MatchArmWrapper::ElseClause(_) => None,
189+
None => None,
194190
};
195191
Some(PatternPath { arm_index, pattern_index })
196192
})
197193
.next()?;
198194

199195
for arm in arms.iter().skip(otherwise_variant.arm_index + 1) {
200196
match arm {
201-
MatchArmWrapper::Arm(patterns, _expr) => {
197+
MatchArmWrapper::Arm(patterns, _) | MatchArmWrapper::LetElseSuccess(patterns, _, _) => {
202198
for pattern in *patterns {
203199
let pattern_ptr = ctx.function_body.arenas.patterns[*pattern].stable_ptr();
204200
ctx.diagnostics.report(
@@ -1176,6 +1172,7 @@ trait EnumVariantScopeBuilder {
11761172
continue;
11771173
}
11781174
MatchArmWrapper::Arm(patterns, _) => patterns,
1175+
MatchArmWrapper::LetElseSuccess(patterns, _, _) => patterns,
11791176
};
11801177
for (pattern_index, pattern) in patterns.iter().copied().enumerate() {
11811178
let pattern_path = PatternPath { arm_index, pattern_index: Some(pattern_index) };
@@ -1347,7 +1344,7 @@ trait EnumVariantScopeBuilder {
13471344

13481345
/// Creates subscopes for match arms and collects them into block builders.
13491346
/// It then merges the blocks and returns the resulting lowered expression.
1350-
///
1347+
///
13511348
/// This function is responsible for:
13521349
/// * Building a pattern tree to track variant coverage
13531350
/// * Creating subscopes for each match variant
@@ -1378,6 +1375,7 @@ trait EnumVariantScopeBuilder {
13781375
);
13791376
},
13801377
MatchArmWrapper::DefaultClause => (),
1378+
MatchArmWrapper::LetElseSuccess(_,_, _) => todo!(),
13811379
}
13821380
}
13831381
return builder_context.builder.merge_and_end_with_match(
@@ -1779,10 +1777,16 @@ fn lower_arm_expr_and_seal(
17791777
arm: &MatchArmWrapper<'_>,
17801778
mut subscope: BlockBuilder,
17811779
) -> Maybe<SealedBlockBuilder> {
1782-
match (arm.expr(), kind) {
1783-
(Some(expr), MatchKind::IfLet | MatchKind::Match) => lower_tail_expr(ctx, subscope, expr),
1784-
(Some(expr), MatchKind::WhileLet(loop_expr_id, stable_ptr)) => {
1785-
let semantic::Expr::Block(expr) = ctx.function_body.arenas.exprs[expr].clone() else {
1780+
match (arm, kind) {
1781+
(
1782+
MatchArmWrapper::Arm(_, expr) | MatchArmWrapper::ElseClause(expr),
1783+
MatchKind::IfLet | MatchKind::Match,
1784+
) => lower_tail_expr(ctx, subscope, *expr),
1785+
(
1786+
MatchArmWrapper::Arm(_, expr) | MatchArmWrapper::ElseClause(expr),
1787+
MatchKind::WhileLet(loop_expr_id, stable_ptr),
1788+
) => {
1789+
let semantic::Expr::Block(expr) = ctx.function_body.arenas.exprs[*expr].clone() else {
17861790
unreachable!("WhileLet expression should be a block");
17871791
};
17881792
let block_expr = (|| {
@@ -1792,7 +1796,13 @@ fn lower_arm_expr_and_seal(
17921796

17931797
lowered_expr_to_block_scope_end(ctx, subscope, block_expr)
17941798
}
1795-
(None, _) => Ok(subscope.goto_callsite(None)),
1799+
(MatchArmWrapper::DefaultClause, _) => Ok(subscope.goto_callsite(None)),
1800+
(MatchArmWrapper::LetElseSuccess(_, vars, stable_ptr), MatchKind::Match) => {
1801+
Ok(lower_success_arm_body(ctx, subscope, vars, stable_ptr))
1802+
}
1803+
(MatchArmWrapper::LetElseSuccess(_, _, _), _) => {
1804+
unreachable!("Invalid MatchKind for LetElseSuccess.")
1805+
}
17961806
}
17971807
}
17981808

crates/cairo-lang-lowering/src/lower/mod.rs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ mod external;
6666
pub mod generators;
6767
mod logical_op;
6868
mod lower_if;
69+
mod lower_let_else;
6970
mod lower_match;
7071
pub mod refs;
7172

@@ -656,12 +657,19 @@ pub fn lower_statement(
656657
}) => {
657658
log::trace!("Lowering a let statement.");
658659
let lowered_expr = lower_expr(ctx, builder, *expr)?;
659-
if else_clause.is_some() {
660-
return Err(LoweringFlowError::Failed(
661-
ctx.diagnostics.report(stable_ptr.untyped(), Unsupported),
662-
));
660+
if let Some(else_clause) = else_clause {
661+
lower_let_else::lower_let_else(
662+
ctx,
663+
builder,
664+
pattern,
665+
expr,
666+
lowered_expr,
667+
else_clause,
668+
stable_ptr,
669+
)?;
670+
} else {
671+
lower_single_pattern(ctx, builder, *pattern, lowered_expr)?;
663672
}
664-
lower_single_pattern(ctx, builder, *pattern, lowered_expr)?
665673
}
666674
semantic::Statement::Continue(semantic::StatementContinue { stable_ptr }) => {
667675
log::trace!("Lowering a continue statement.");

0 commit comments

Comments
 (0)