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

feat: var declarations #1180

Merged
merged 17 commits into from Jan 18, 2023
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/wing/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 19 additions & 9 deletions docs/04-reference/winglang-spec.md
Expand Up @@ -402,11 +402,21 @@ Mixing `protected` and `internal` is not allowed.

Re-assignment to variables that are defined with `let` is not allowed in Wing.

Re-assignment to class fields is allowed if field is marked with `readwrite`.
Variables can be reassigned to by adding the `var` modifier:

```ts
// wing
let var sum = 0;
for item in [1,2,3] {
sum = sum + item;
}
```

Re-assignment to class fields is allowed if field is marked with `var`.
Examples in the class section below.

`readwrite` is available in the body of class declarations.
Assigning `readwrite` to immutables of the same type is allowed. That is similar
`var` is available in the body of class declarations.
Assigning `var` to immutables of the same type is allowed. That is similar
to assigning non `readonly`s to `readonly`s in TypeScript.

[`▲ top`][top]
Expand Down Expand Up @@ -833,7 +843,7 @@ The `if` statement is optionally followed by `elif` and `else`.

`for..in` statement is used to iterate over a array or set.
Type annotation after an iteratee (left hand side of `in`) is optional.
The loop invariant in for loops is implicitly `readwrite` and re-assignable.
The loop invariant in for loops is implicitly re-assignable (`var`).

> ```TS
> // Wing program:
Expand Down Expand Up @@ -1147,8 +1157,8 @@ resource Foo {
field6: bool;

// re-assignable class fields, read about them in the mutability section
readwrite field4: num;
readwrite field5: str;
var field4: num;
Chriscbr marked this conversation as resolved.
Show resolved Hide resolved
var field5: str;
}
```

Expand Down Expand Up @@ -1509,7 +1519,7 @@ of the assignment (write) operation. `new` is always the same type as the type
of the property itself.

Both preflight and inflight computed properties are allowed.
Keyword `readwrite` behind computed properties is not allowed.
Keyword `var` behind computed properties is not allowed.
`inflight` computed properties are also allowed.

```TS
Expand All @@ -1520,8 +1530,8 @@ struct Vec2 {
}

class Rect {
readwrite size: Vec2;
readwrite origin: Vec2;
var size: Vec2;
var origin: Vec2;

center: Vec2 {
read {
Expand Down
8 changes: 8 additions & 0 deletions examples/tests/invalid/capture_reassignable.w
@@ -0,0 +1,8 @@
bring cloud;

let var x = 5;

let handler = inflight (m: str): str => {
print("x: ${x}");
// ^ error: cannot capture reassignable variable "x"
};
5 changes: 5 additions & 0 deletions examples/tests/valid/reassignment.w
@@ -0,0 +1,5 @@
let var x = 5;
assert(x == 5);

x = x + 1;
assert(x == 6);
4 changes: 4 additions & 0 deletions libs/tree-sitter-wing/grammar.js
Expand Up @@ -117,9 +117,12 @@ module.exports = grammar({

expression_statement: ($) => seq($.expression, ";"),

reassignable: ($) => "var",

variable_definition_statement: ($) =>
seq(
"let",
optional(field("reassignable", $.reassignable)),
field("name", $.identifier),
optional($._type_annotation),
"=",
Expand Down Expand Up @@ -154,6 +157,7 @@ module.exports = grammar({
class_member: ($) =>
seq(
optional(field("access_modifier", $.access_modifier)),
optional($.reassignable),
field("name", $.identifier),
$._type_annotation,
";"
Expand Down
10 changes: 5 additions & 5 deletions libs/tree-sitter-wing/test/corpus/expressions.txt
Expand Up @@ -379,12 +379,12 @@ let a = new MutMap<num>();

(source
(variable_definition_statement
(identifier)
(new_expression
(mutable_container_type
(builtin_type)
name: (identifier)
value: (new_expression
class: (mutable_container_type
type_parameter: (builtin_type)
)
(argument_list)
args: (argument_list)
)
)
)
Expand Down
13 changes: 13 additions & 0 deletions libs/tree-sitter-wing/test/corpus/statements.txt
Expand Up @@ -22,6 +22,8 @@ Variable assignment
let x: num = 1;
x = 2;

let var y = "hello";

---

(source
Expand All @@ -34,6 +36,11 @@ x = 2;
name: (reference (identifier))
value: (number)
)
(variable_definition_statement
(reassignable)
Chriscbr marked this conversation as resolved.
Show resolved Hide resolved
name: (identifier)
value: (string)
)
)

====================
Expand Down Expand Up @@ -132,6 +139,7 @@ class A {
init() {}
inflight do_something() {}
a_member: str;
var b_member: num;
}

---
Expand All @@ -151,6 +159,11 @@ class A {
name: (identifier)
type: (builtin_type)
)
(class_member
(reassignable)
Chriscbr marked this conversation as resolved.
Show resolved Hide resolved
name: (identifier)
type: (builtin_type)
)
)
)
)
Expand Down
7 changes: 7 additions & 0 deletions libs/wingc/src/ast.rs
Expand Up @@ -181,6 +181,7 @@ pub enum StmtKind {
identifier: Option<Symbol>,
},
VariableDef {
kind: VariableKind,
var_name: Symbol,
initial_value: Expr,
type_: Option<Type>,
Expand Down Expand Up @@ -225,6 +226,12 @@ pub enum StmtKind {
},
}

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum VariableKind {
Let,
Var,
}

#[derive(Debug)]
pub struct ClassMember {
pub name: Symbol,
Expand Down
39 changes: 35 additions & 4 deletions libs/wingc/src/capture.rs
Expand Up @@ -87,6 +87,7 @@ pub fn scan_for_inflights_in_scope(scope: &Scope, diagnostics: &mut Diagnostics)
}
}
StmtKind::VariableDef {
kind: _,
var_name: _,
initial_value,
type_: _,
Expand Down Expand Up @@ -247,11 +248,11 @@ fn scan_captures_in_expression(
Reference::Identifier(symbol) => {
// Lookup the symbol
let x = env.lookup_ext(&symbol, Some(statement_idx));

// we ignore errors here because if the lookup symbol
// wasn't found, a error diagnostic is already emitted
// the type checker.

if x.is_ok() {
let (var, f) = x.unwrap();

Expand All @@ -264,8 +265,17 @@ fn scan_captures_in_expression(
} else {
let t = var.as_variable().unwrap();

// if the identifier represents a preflight object, then capture it
// if the identifier represents a preflight value, then capture it
if f == Phase::Preflight {
if var.is_reassignable() {
diagnostics.push(Diagnostic {
level: DiagnosticLevel::Error,
message: format!("Cannot capture a reassignable variable \"{}\"", symbol.name),
span: Some(symbol.span.clone()),
});
return res;
}

// capture as a resource
if let Some(resource) = t.as_resource() {
// TODO: for now we add all resource client methods to the capture, in the future this should be done based on:
Expand Down Expand Up @@ -328,7 +338,27 @@ fn scan_captures_in_expression(
res.extend(scan_captures_in_expression(lexp, env, statement_idx, diagnostics));
res.extend(scan_captures_in_expression(rexp, env, statement_idx, diagnostics));
}
ExprKind::Literal(_) => {}
ExprKind::Literal(lit) => match lit {
Literal::String(_) => {}
Literal::InterpolatedString(interpolated_str) => {
Chriscbr marked this conversation as resolved.
Show resolved Hide resolved
res.extend(
interpolated_str
.parts
.iter()
.filter_map(|part| match part {
InterpolatedStringPart::Expr(expr) => {
Some(scan_captures_in_expression(expr, env, statement_idx, diagnostics))
}
InterpolatedStringPart::Static(_) => None,
})
.flatten()
.collect::<Vec<_>>(),
);
}
Literal::Number(_) => {}
Literal::Duration(_) => {}
Literal::Boolean(_) => {}
},
ExprKind::ArrayLiteral { items, .. } => {
for v in items {
res.extend(scan_captures_in_expression(&v, env, statement_idx, diagnostics));
Expand Down Expand Up @@ -376,6 +406,7 @@ fn scan_captures_in_inflight_scope(scope: &Scope, diagnostics: &mut Diagnostics)
for s in scope.statements.iter() {
match &s.kind {
StmtKind::VariableDef {
kind: _,
var_name: _,
initial_value,
type_: _,
Expand Down
9 changes: 6 additions & 3 deletions libs/wingc/src/jsify.rs
Expand Up @@ -6,7 +6,7 @@ use sha2::{Digest, Sha256};
use crate::{
ast::{
ArgList, BinaryOperator, ClassMember, Expr, ExprKind, FunctionDefinition, InterpolatedStringPart, Literal, Phase,
Reference, Scope, Stmt, StmtKind, Symbol, Type, UnaryOperator, UtilityFunctions,
Reference, Scope, Stmt, StmtKind, Symbol, Type, UnaryOperator, UtilityFunctions, VariableKind,
},
capture::CaptureKind,
utilities::snake_case_to_camel_case,
Expand Down Expand Up @@ -466,13 +466,16 @@ impl JSifier {
)
}
StmtKind::VariableDef {
kind,
var_name,
initial_value,
type_: _,
} => {
let initial_value = self.jsify_expression(initial_value, phase);
// TODO: decide on `const` vs `let` once we have mutables
format!("const {} = {};", self.jsify_symbol(var_name), initial_value)
return match kind {
VariableKind::Let => format!("const {} = {};", self.jsify_symbol(var_name), initial_value),
VariableKind::Var => format!("let {} = {};", self.jsify_symbol(var_name), initial_value),
};
}
StmtKind::ForLoop {
iterator,
Expand Down
8 changes: 6 additions & 2 deletions libs/wingc/src/lib.rs
@@ -1,7 +1,7 @@
#[macro_use]
extern crate lazy_static;

use ast::{Scope, Stmt, Symbol, UtilityFunctions};
use ast::{Scope, Stmt, Symbol, UtilityFunctions, VariableKind};
use diagnostic::{print_diagnostics, Diagnostic, DiagnosticLevel, Diagnostics, WingSpan};
use jsify::JSifier;
use type_check::symbol_env::StatementIdx;
Expand Down Expand Up @@ -156,7 +156,11 @@ fn add_builtin(name: &str, typ: Type, scope: &mut Scope, types: &mut Types) {
.borrow_mut()
.as_mut()
.unwrap()
.define(&sym, SymbolKind::Variable(types.add_type(typ)), StatementIdx::Top)
.define(
&sym,
SymbolKind::Variable(types.add_type(typ), VariableKind::Let),
StatementIdx::Top,
)
.expect("Failed to add builtin");
}

Expand Down
9 changes: 8 additions & 1 deletion libs/wingc/src/parser.rs
Expand Up @@ -9,7 +9,7 @@ use tree_sitter_traversal::{traverse, Order};
use crate::ast::{
ArgList, BinaryOperator, ClassMember, Constructor, Expr, ExprKind, FunctionDefinition, FunctionSignature,
InterpolatedString, InterpolatedStringPart, Literal, Phase, Reference, Scope, Stmt, StmtKind, Symbol, Type,
UnaryOperator,
UnaryOperator, VariableKind,
};
use crate::diagnostic::{Diagnostic, DiagnosticLevel, DiagnosticResult, Diagnostics, WingSpan};

Expand Down Expand Up @@ -183,7 +183,14 @@ impl Parser<'_> {
None
};

let kind = if let Some(_) = statement_node.child_by_field_name("reassignable") {
VariableKind::Var
} else {
VariableKind::Let
};

StmtKind::VariableDef {
kind,
var_name: self.node_symbol(&statement_node.child_by_field_name("name").unwrap())?,
initial_value: self.build_expression(&statement_node.child_by_field_name("value").unwrap())?,
type_,
Expand Down