Skip to content

Commit d4ae09d

Browse files
authored
feat(compiler): elif let statement (#3888)
## Checklist - [x] Title matches [Winglang's style guide](https://www.winglang.io/contributing/start-here/pull_requests#how-are-pull-request-titles-formatted) - [ ] Description explains motivation and solution - [x] Tests added (always) - [ ] Docs updated (only required for features) - [ ] Added `pr/e2e-full` label if this feature requires end-to-end testing *By submitting this pull request, I confirm that my contribution is made under the terms of the [Wing Cloud Contribution License](https://github.com/winglang/wing/blob/main/CONTRIBUTION_LICENSE.md)*.
1 parent dd9055c commit d4ae09d

16 files changed

Lines changed: 391 additions & 174 deletions

File tree

examples/tests/valid/optionals.w

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,19 @@ let tryParseName = (fullName: str): Name? => {
5858
};
5959
};
6060

61+
let json_obj = Json { ghost: "spooky" };
62+
let var something_else = false;
63+
if let y = json_obj.tryAsBool() {
64+
assert(y == true || y == false);
65+
} elif let y = json_obj.tryAsNum() {
66+
assert(y + 0 == y);
67+
} elif let y = json_obj.tryAsStr() {
68+
assert(y.length >= 0);
69+
} else {
70+
something_else = true;
71+
}
72+
assert(something_else);
73+
6174
// if lets reassignable
6275
let a: num? = 1;
6376
if let var z = a {

libs/tree-sitter-wing/grammar.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,9 +281,21 @@ module.exports = grammar({
281281
"=",
282282
field("value", $.expression),
283283
field("block", $.block),
284+
repeat(field("elif_let_block", $.elif_let_block)),
284285
optional(seq("else", field("else_block", $.block)))
285286
),
286287

288+
elif_let_block: ($) =>
289+
seq(
290+
"elif",
291+
"let",
292+
optional(field("reassignable", $.reassignable)),
293+
field("name", $.identifier),
294+
"=",
295+
field("value", $.expression),
296+
field("block", $.block)
297+
),
298+
287299
if_statement: ($) =>
288300
seq(
289301
"if",

libs/tree-sitter-wing/test/corpus/statements/statements.txt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,27 @@ if let x = y {} else {}
401401
block: (block)
402402
else_block: (block)))
403403

404+
================================================================================
405+
If Let Elif Let Else
406+
================================================================================
407+
408+
if let x = y {} elif let x = z {} else {}
409+
410+
--------------------------------------------------------------------------------
411+
412+
(source
413+
(if_let_statement
414+
name: (identifier)
415+
value: (reference
416+
(reference_identifier))
417+
block: (block)
418+
elif_let_block: (elif_let_block
419+
name: (identifier)
420+
value: (reference
421+
(reference_identifier))
422+
block: (block))
423+
else_block: (block)))
424+
404425
================================================================================
405426
If Let Var
406427
================================================================================

libs/wingc/src/ast.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,14 @@ pub struct ElifBlock {
331331
pub statements: Scope,
332332
}
333333

334+
#[derive(Debug)]
335+
pub struct ElifLetBlock {
336+
pub reassignable: bool,
337+
pub var_name: Symbol,
338+
pub value: Expr,
339+
pub statements: Scope,
340+
}
341+
334342
#[derive(Debug)]
335343
pub struct Class {
336344
pub name: Symbol,
@@ -449,6 +457,7 @@ pub enum StmtKind {
449457
var_name: Symbol,
450458
value: Expr,
451459
statements: Scope,
460+
elif_statements: Vec<ElifLetBlock>,
452461
else_statements: Option<Scope>,
453462
},
454463
If {

libs/wingc/src/fold.rs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
use crate::{
22
ast::{
3-
ArgList, BringSource, CalleeKind, CatchBlock, Class, ClassField, ElifBlock, Expr, ExprKind, FunctionBody,
4-
FunctionDefinition, FunctionParameter, FunctionSignature, Interface, InterpolatedString, InterpolatedStringPart,
5-
Literal, NewExpr, Reference, Scope, Stmt, StmtKind, StructField, Symbol, TypeAnnotation, TypeAnnotationKind,
6-
UserDefinedType,
3+
ArgList, BringSource, CalleeKind, CatchBlock, Class, ClassField, ElifBlock, ElifLetBlock, Expr, ExprKind,
4+
FunctionBody, FunctionDefinition, FunctionParameter, FunctionSignature, Interface, InterpolatedString,
5+
InterpolatedStringPart, Literal, NewExpr, Reference, Scope, Stmt, StmtKind, StructField, Symbol, TypeAnnotation,
6+
TypeAnnotationKind, UserDefinedType,
77
},
88
dbg_panic,
99
};
@@ -118,12 +118,22 @@ where
118118
statements,
119119
reassignable,
120120
var_name,
121+
elif_statements,
121122
else_statements,
122123
} => StmtKind::IfLet {
123124
value: f.fold_expr(value),
124125
statements: f.fold_scope(statements),
125126
reassignable,
126127
var_name: f.fold_symbol(var_name),
128+
elif_statements: elif_statements
129+
.into_iter()
130+
.map(|elif_let_block| ElifLetBlock {
131+
reassignable: elif_let_block.reassignable,
132+
statements: f.fold_scope(elif_let_block.statements),
133+
value: f.fold_expr(elif_let_block.value),
134+
var_name: f.fold_symbol(elif_let_block.var_name),
135+
})
136+
.collect(),
127137
else_statements: else_statements.map(|statements| f.fold_scope(statements)),
128138
},
129139
StmtKind::If {

libs/wingc/src/jsify.rs

Lines changed: 81 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use std::{
1616

1717
use crate::{
1818
ast::{
19-
ArgList, BinaryOperator, BringSource, CalleeKind, Class as AstClass, Expr, ExprKind, FunctionBody,
19+
ArgList, BinaryOperator, BringSource, CalleeKind, Class as AstClass, ElifLetBlock, Expr, ExprKind, FunctionBody,
2020
FunctionDefinition, InterpolatedStringPart, Literal, NewExpr, Phase, Reference, Scope, Stmt, StmtKind, StructField,
2121
Symbol, TypeAnnotationKind, UnaryOperator, UserDefinedType,
2222
},
@@ -765,6 +765,78 @@ impl<'a> JSifier<'a> {
765765
code
766766
}
767767

768+
// To avoid a performance penalty when evaluating assignments made in the elif statement,
769+
// it was necessary to nest the if statements.
770+
//
771+
// Thus, this code in Wing:
772+
//
773+
// if let x = tryA() {
774+
// ...
775+
// } elif let x = tryB() {
776+
// ...
777+
// } elif let x = TryC() {
778+
// ...
779+
// } else {
780+
// ...
781+
// }
782+
//
783+
// In JavaScript, will become this:
784+
//
785+
// const $if_let_value = tryA();
786+
// if ($if_let_value !== undefined) {
787+
// ...
788+
// } else {
789+
// let $elif_let_value0 = tryB();
790+
// if ($elif_let_value0 !== undefined) {
791+
// ...
792+
// } else {
793+
// let $elif_let_value1 = tryC();
794+
// if ($elif_let_value1 !== undefined) {
795+
// ...
796+
// } else {
797+
// ...
798+
// }
799+
// }
800+
// }
801+
fn jsify_elif_statements(
802+
&self,
803+
code: &mut CodeMaker,
804+
elif_statements: &Vec<ElifLetBlock>,
805+
index: usize,
806+
else_statements: &Option<Scope>,
807+
ctx: &mut JSifyContext,
808+
) {
809+
let elif_let_value = "$elif_let_value";
810+
811+
let value = format!("{}{}", elif_let_value, index);
812+
code.line(format!(
813+
"const {} = {};",
814+
value,
815+
self.jsify_expression(&elif_statements.get(index).unwrap().value, ctx)
816+
));
817+
let value = format!("{}{}", elif_let_value, index);
818+
code.open(format!("if ({value} != undefined) {{"));
819+
let elif_block = elif_statements.get(index).unwrap();
820+
if elif_block.reassignable {
821+
code.line(format!("let {} = {};", elif_block.var_name, value));
822+
} else {
823+
code.line(format!("const {} = {};", elif_block.var_name, value));
824+
}
825+
code.add_code(self.jsify_scope_body(&elif_block.statements, ctx));
826+
code.close("}");
827+
828+
if index < elif_statements.len() - 1 {
829+
code.open("else {");
830+
self.jsify_elif_statements(code, elif_statements, index + 1, else_statements, ctx);
831+
code.close("}");
832+
} else if let Some(else_scope) = else_statements {
833+
code.open("else {");
834+
code.add_code(self.jsify_scope_body(else_scope, ctx));
835+
code.close("}");
836+
}
837+
return;
838+
}
839+
768840
fn jsify_statement(&self, env: &SymbolEnv, statement: &Stmt, ctx: &mut JSifyContext) -> CodeMaker {
769841
CompilationContext::set(CompilationPhase::Jsifying, &statement.span);
770842
match &statement.kind {
@@ -836,6 +908,7 @@ impl<'a> JSifier<'a> {
836908
value,
837909
statements,
838910
var_name,
911+
elif_statements,
839912
else_statements,
840913
} => {
841914
let mut code = CodeMaker::default();
@@ -867,12 +940,13 @@ impl<'a> JSifier<'a> {
867940
// The temporary scope is created so that intermediate variables created by consecutive `if let` clauses
868941
// do not interfere with each other.
869942
code.open("{");
870-
let if_let_value = "$IF_LET_VALUE".to_string();
943+
let if_let_value = "$if_let_value".to_string();
871944
code.line(format!(
872945
"const {} = {};",
873946
if_let_value,
874947
self.jsify_expression(value, ctx)
875948
));
949+
876950
code.open(format!("if ({if_let_value} != undefined) {{"));
877951
if *reassignable {
878952
code.line(format!("let {} = {};", var_name, if_let_value));
@@ -882,7 +956,11 @@ impl<'a> JSifier<'a> {
882956
code.add_code(self.jsify_scope_body(statements, ctx));
883957
code.close("}");
884958

885-
if let Some(else_scope) = else_statements {
959+
if elif_statements.len() > 0 {
960+
code.open("else {");
961+
self.jsify_elif_statements(&mut code, elif_statements, 0, else_statements, ctx);
962+
code.close("}");
963+
} else if let Some(else_scope) = else_statements {
886964
code.open("else {");
887965
code.add_code(self.jsify_scope_body(else_scope, ctx));
888966
code.close("}");

libs/wingc/src/lsp/hover.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,13 +169,21 @@ impl<'a> Visit<'a> for HoverVisitor<'a> {
169169
value,
170170
statements,
171171
reassignable: _,
172+
elif_statements,
172173
else_statements,
173174
} => {
174175
self.with_scope(statements, |v| {
175176
v.visit_symbol(var_name);
176177
});
177178
self.visit_expr(value);
178179
self.visit_scope(statements);
180+
for elif in elif_statements {
181+
self.with_scope(&elif.statements, |v| {
182+
v.visit_symbol(&elif.var_name);
183+
});
184+
self.visit_expr(&elif.value);
185+
self.visit_scope(&elif.statements);
186+
}
179187
if let Some(else_statements) = else_statements {
180188
self.visit_scope(else_statements);
181189
}

libs/wingc/src/parser.rs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ use tree_sitter::Node;
88
use tree_sitter_traversal::{traverse, Order};
99

1010
use crate::ast::{
11-
ArgList, BinaryOperator, BringSource, CalleeKind, CatchBlock, Class, ClassField, ElifBlock, Expr, ExprKind,
12-
FunctionBody, FunctionDefinition, FunctionParameter, FunctionSignature, Interface, InterpolatedString,
11+
ArgList, BinaryOperator, BringSource, CalleeKind, CatchBlock, Class, ClassField, ElifBlock, ElifLetBlock, Expr,
12+
ExprKind, FunctionBody, FunctionDefinition, FunctionParameter, FunctionSignature, Interface, InterpolatedString,
1313
InterpolatedStringPart, Literal, NewExpr, Phase, Reference, Scope, Stmt, StmtKind, StructField, Symbol,
1414
TypeAnnotation, TypeAnnotationKind, UnaryOperator, UserDefinedType,
1515
};
@@ -571,6 +571,22 @@ impl<'s> Parser<'s> {
571571
let reassignable = statement_node.child_by_field_name("reassignable").is_some();
572572
let value = self.build_expression(&statement_node.child_by_field_name("value").unwrap(), phase)?;
573573
let name = self.check_reserved_symbol(&statement_node.child_by_field_name("name").unwrap())?;
574+
575+
let mut elif_vec = vec![];
576+
let mut cursor = statement_node.walk();
577+
for node in statement_node.children_by_field_name("elif_let_block", &mut cursor) {
578+
let statements = self.build_scope(&node.child_by_field_name("block").unwrap(), phase);
579+
let value = self.build_expression(&node.child_by_field_name("value").unwrap(), phase)?;
580+
let name = self.check_reserved_symbol(&statement_node.child_by_field_name("name").unwrap())?;
581+
let elif = ElifLetBlock {
582+
reassignable: node.child_by_field_name("reassignable").is_some(),
583+
statements: statements,
584+
value: value,
585+
var_name: name,
586+
};
587+
elif_vec.push(elif);
588+
}
589+
574590
let else_block = if let Some(else_block) = statement_node.child_by_field_name("else_block") {
575591
Some(self.build_scope(&else_block, phase))
576592
} else {
@@ -581,6 +597,7 @@ impl<'s> Parser<'s> {
581597
reassignable,
582598
value,
583599
statements: if_block,
600+
elif_statements: elif_vec,
584601
else_statements: else_block,
585602
})
586603
}

0 commit comments

Comments
 (0)