Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

merge queue: embarking main (8efecc2) and #3888 together #3965

Closed
wants to merge 14 commits into from
Closed
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
Loading