Skip to content

Commit

Permalink
Merge of #3888
Browse files Browse the repository at this point in the history
  • Loading branch information
mergify[bot] authored Aug 25, 2023
2 parents 8efecc2 + 117d693 commit 052c1fe
Show file tree
Hide file tree
Showing 16 changed files with 388 additions and 171 deletions.
13 changes: 13 additions & 0 deletions examples/tests/valid/optionals.w
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,19 @@ let tryParseName = (fullName: str): Name? => {
};
};

let json_obj = Json { ghost: "spooky" };
let var something_else = false;
if let y = json_obj.tryAsBool() {
assert(y == true || y == false);
} elif let y = json_obj.tryAsNum() {
assert(y + 0 == y);
} elif let y = json_obj.tryAsStr() {
assert(y.length >= 0);
} else {
something_else = true;
}
assert(something_else);

// if lets reassignable
let a: num? = 1;
if let var z = a {
Expand Down
12 changes: 12 additions & 0 deletions libs/tree-sitter-wing/grammar.js
Original file line number Diff line number Diff line change
Expand Up @@ -281,9 +281,21 @@ module.exports = grammar({
"=",
field("value", $.expression),
field("block", $.block),
repeat(field("elif_let_block", $.elif_let_block)),
optional(seq("else", field("else_block", $.block)))
),

elif_let_block: ($) =>
seq(
"elif",
"let",
optional(field("reassignable", $.reassignable)),
field("name", $.identifier),
"=",
field("value", $.expression),
field("block", $.block)
),

if_statement: ($) =>
seq(
"if",
Expand Down
21 changes: 21 additions & 0 deletions libs/tree-sitter-wing/test/corpus/statements/statements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,27 @@ if let x = y {} else {}
block: (block)
else_block: (block)))

================================================================================
If Let Elif Let Else
================================================================================

if let x = y {} elif let x = z {} else {}

--------------------------------------------------------------------------------

(source
(if_let_statement
name: (identifier)
value: (reference
(reference_identifier))
block: (block)
elif_let_block: (elif_let_block
name: (identifier)
value: (reference
(reference_identifier))
block: (block))
else_block: (block)))

================================================================================
If Let Var
================================================================================
Expand Down
9 changes: 9 additions & 0 deletions libs/wingc/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,14 @@ pub struct ElifBlock {
pub statements: Scope,
}

#[derive(Debug)]
pub struct ElifLetBlock {
pub reassignable: bool,
pub var_name: Symbol,
pub value: Expr,
pub statements: Scope,
}

#[derive(Debug)]
pub struct Class {
pub name: Symbol,
Expand Down Expand Up @@ -449,6 +457,7 @@ pub enum StmtKind {
var_name: Symbol,
value: Expr,
statements: Scope,
elif_statements: Vec<ElifLetBlock>,
else_statements: Option<Scope>,
},
If {
Expand Down
18 changes: 14 additions & 4 deletions libs/wingc/src/fold.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use crate::{
ast::{
ArgList, BringSource, CalleeKind, CatchBlock, Class, ClassField, ElifBlock, Expr, ExprKind, FunctionBody,
FunctionDefinition, FunctionParameter, FunctionSignature, Interface, InterpolatedString, InterpolatedStringPart,
Literal, NewExpr, Reference, Scope, Stmt, StmtKind, StructField, Symbol, TypeAnnotation, TypeAnnotationKind,
UserDefinedType,
ArgList, BringSource, CalleeKind, CatchBlock, Class, ClassField, ElifBlock, ElifLetBlock, Expr, ExprKind,
FunctionBody, FunctionDefinition, FunctionParameter, FunctionSignature, Interface, InterpolatedString,
InterpolatedStringPart, Literal, NewExpr, Reference, Scope, Stmt, StmtKind, StructField, Symbol, TypeAnnotation,
TypeAnnotationKind, UserDefinedType,
},
dbg_panic,
};
Expand Down Expand Up @@ -118,12 +118,22 @@ where
statements,
reassignable,
var_name,
elif_statements,
else_statements,
} => StmtKind::IfLet {
value: f.fold_expr(value),
statements: f.fold_scope(statements),
reassignable,
var_name: f.fold_symbol(var_name),
elif_statements: elif_statements
.into_iter()
.map(|elif_let_block| ElifLetBlock {
reassignable: elif_let_block.reassignable,
statements: f.fold_scope(elif_let_block.statements),
value: f.fold_expr(elif_let_block.value),
var_name: f.fold_symbol(elif_let_block.var_name),
})
.collect(),
else_statements: else_statements.map(|statements| f.fold_scope(statements)),
},
StmtKind::If {
Expand Down
84 changes: 81 additions & 3 deletions libs/wingc/src/jsify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use std::{

use crate::{
ast::{
ArgList, BinaryOperator, BringSource, CalleeKind, Class as AstClass, Expr, ExprKind, FunctionBody,
ArgList, BinaryOperator, BringSource, CalleeKind, Class as AstClass, ElifLetBlock, Expr, ExprKind, FunctionBody,
FunctionDefinition, InterpolatedStringPart, Literal, NewExpr, Phase, Reference, Scope, Stmt, StmtKind, StructField,
Symbol, TypeAnnotationKind, UnaryOperator, UserDefinedType,
},
Expand Down Expand Up @@ -765,6 +765,78 @@ impl<'a> JSifier<'a> {
code
}

// To avoid a performance penalty when evaluating assignments made in the elif statement,
// it was necessary to nest the if statements.
//
// Thus, this code in Wing:
//
// if let x = tryA() {
// ...
// } elif let x = tryB() {
// ...
// } elif let x = TryC() {
// ...
// } else {
// ...
// }
//
// In JavaScript, will become this:
//
// const $if_let_value = tryA();
// if ($if_let_value !== undefined) {
// ...
// } else {
// let $elif_let_value0 = tryB();
// if ($elif_let_value0 !== undefined) {
// ...
// } else {
// let $elif_let_value1 = tryC();
// if ($elif_let_value1 !== undefined) {
// ...
// } else {
// ...
// }
// }
// }
fn jsify_elif_statements(
&self,
code: &mut CodeMaker,
elif_statements: &Vec<ElifLetBlock>,
index: usize,
else_statements: &Option<Scope>,
ctx: &mut JSifyContext,
) {
let elif_let_value = "$elif_let_value";

let value = format!("{}{}", elif_let_value, index);
code.line(format!(
"const {} = {};",
value,
self.jsify_expression(&elif_statements.get(index).unwrap().value, ctx)
));
let value = format!("{}{}", elif_let_value, index);
code.open(format!("if ({value} != undefined) {{"));
let elif_block = elif_statements.get(index).unwrap();
if elif_block.reassignable {
code.line(format!("let {} = {};", elif_block.var_name, value));
} else {
code.line(format!("const {} = {};", elif_block.var_name, value));
}
code.add_code(self.jsify_scope_body(&elif_block.statements, ctx));
code.close("}");

if index < elif_statements.len() - 1 {
code.open("else {");
self.jsify_elif_statements(code, elif_statements, index + 1, else_statements, ctx);
code.close("}");
} else if let Some(else_scope) = else_statements {
code.open("else {");
code.add_code(self.jsify_scope_body(else_scope, ctx));
code.close("}");
}
return;
}

fn jsify_statement(&self, env: &SymbolEnv, statement: &Stmt, ctx: &mut JSifyContext) -> CodeMaker {
CompilationContext::set(CompilationPhase::Jsifying, &statement.span);
match &statement.kind {
Expand Down Expand Up @@ -836,6 +908,7 @@ impl<'a> JSifier<'a> {
value,
statements,
var_name,
elif_statements,
else_statements,
} => {
let mut code = CodeMaker::default();
Expand Down Expand Up @@ -867,12 +940,13 @@ impl<'a> JSifier<'a> {
// The temporary scope is created so that intermediate variables created by consecutive `if let` clauses
// do not interfere with each other.
code.open("{");
let if_let_value = "$IF_LET_VALUE".to_string();
let if_let_value = "$if_let_value".to_string();
code.line(format!(
"const {} = {};",
if_let_value,
self.jsify_expression(value, ctx)
));

code.open(format!("if ({if_let_value} != undefined) {{"));
if *reassignable {
code.line(format!("let {} = {};", var_name, if_let_value));
Expand All @@ -882,7 +956,11 @@ impl<'a> JSifier<'a> {
code.add_code(self.jsify_scope_body(statements, ctx));
code.close("}");

if let Some(else_scope) = else_statements {
if elif_statements.len() > 0 {
code.open("else {");
self.jsify_elif_statements(&mut code, elif_statements, 0, else_statements, ctx);
code.close("}");
} else if let Some(else_scope) = else_statements {
code.open("else {");
code.add_code(self.jsify_scope_body(else_scope, ctx));
code.close("}");
Expand Down
8 changes: 8 additions & 0 deletions libs/wingc/src/lsp/hover.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,13 +169,21 @@ impl<'a> Visit<'a> for HoverVisitor<'a> {
value,
statements,
reassignable: _,
elif_statements,
else_statements,
} => {
self.with_scope(statements, |v| {
v.visit_symbol(var_name);
});
self.visit_expr(value);
self.visit_scope(statements);
for elif in elif_statements {
self.with_scope(&elif.statements, |v| {
v.visit_symbol(&elif.var_name);
});
self.visit_expr(&elif.value);
self.visit_scope(&elif.statements);
}
if let Some(else_statements) = else_statements {
self.visit_scope(else_statements);
}
Expand Down
21 changes: 19 additions & 2 deletions libs/wingc/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ use tree_sitter::Node;
use tree_sitter_traversal::{traverse, Order};

use crate::ast::{
ArgList, BinaryOperator, BringSource, CalleeKind, CatchBlock, Class, ClassField, ElifBlock, Expr, ExprKind,
FunctionBody, FunctionDefinition, FunctionParameter, FunctionSignature, Interface, InterpolatedString,
ArgList, BinaryOperator, BringSource, CalleeKind, CatchBlock, Class, ClassField, ElifBlock, ElifLetBlock, Expr,
ExprKind, FunctionBody, FunctionDefinition, FunctionParameter, FunctionSignature, Interface, InterpolatedString,
InterpolatedStringPart, Literal, NewExpr, Phase, Reference, Scope, Stmt, StmtKind, StructField, Symbol,
TypeAnnotation, TypeAnnotationKind, UnaryOperator, UserDefinedType,
};
Expand Down Expand Up @@ -571,6 +571,22 @@ impl<'s> Parser<'s> {
let reassignable = statement_node.child_by_field_name("reassignable").is_some();
let value = self.build_expression(&statement_node.child_by_field_name("value").unwrap(), phase)?;
let name = self.check_reserved_symbol(&statement_node.child_by_field_name("name").unwrap())?;

let mut elif_vec = vec![];
let mut cursor = statement_node.walk();
for node in statement_node.children_by_field_name("elif_let_block", &mut cursor) {
let statements = self.build_scope(&node.child_by_field_name("block").unwrap(), phase);
let value = self.build_expression(&node.child_by_field_name("value").unwrap(), phase)?;
let name = self.check_reserved_symbol(&statement_node.child_by_field_name("name").unwrap())?;
let elif = ElifLetBlock {
reassignable: node.child_by_field_name("reassignable").is_some(),
statements: statements,
value: value,
var_name: name,
};
elif_vec.push(elif);
}

let else_block = if let Some(else_block) = statement_node.child_by_field_name("else_block") {
Some(self.build_scope(&else_block, phase))
} else {
Expand All @@ -581,6 +597,7 @@ impl<'s> Parser<'s> {
reassignable,
value,
statements: if_block,
elif_statements: elif_vec,
else_statements: else_block,
})
}
Expand Down
Loading

0 comments on commit 052c1fe

Please sign in to comment.