Skip to content

Commit

Permalink
feat(compiler)!: throw becomes a keyword (#4008)
Browse files Browse the repository at this point in the history
Changes the built-in function "throw" into a dedicated keyword. The rationale for this change (described in #2150) is that throwing is a primary operation that changes the control flow of your program - just like `if`, `while`, `return`, or `try`/`catch` - so it's more natural to have it as a dedicated statement. This means it will receive the same syntax highlighting treatment as `return` and `while` etc.

Closes #2150.

BREAKING CHANGE: `throw` is now a built-in keyword, instead of a global function. Use `throw "blah"` instead of `throw("blah")`

## Checklist

- [x] Title matches [Winglang's style guide](https://www.winglang.io/contributing/start-here/pull_requests#how-are-pull-request-titles-formatted)
- [x] Description explains motivation and solution
- [x] Tests added (always)
- [x] 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)*.
  • Loading branch information
Chriscbr authored Aug 29, 2023
1 parent 881653e commit 7723b06
Show file tree
Hide file tree
Showing 30 changed files with 81 additions and 107 deletions.
2 changes: 1 addition & 1 deletion apps/vscode-wing/syntaxes/wing.tmLanguage.json
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@
"patterns": [
{
"name": "keyword.control.flow.wing",
"match": "\\b(else|elif|if|return|try|catch|finally|bring|as)\\b"
"match": "\\b(else|elif|if|return|throw|try|catch|finally|bring|as)\\b"
},
{
"name": "keyword.control.loop.wing",
Expand Down
18 changes: 15 additions & 3 deletions docs/docs/03-language-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -516,12 +516,10 @@ log("UTC: ${t1.utc.toIso())}"); // output: 2023-02-09T06:21:03.000Z
| Name | Extra information |
| -------- | -------------------------------------------------------- |
| `log` | logs str |
| `throw` | creates and throws an instance of an exception |
| `assert` | checks a condition and _throws_ if evaluated to false |
> ```TS
> log("Hello ${name}");
> throw("a recoverable error occurred");
> assert(x > 0);
> ```
Expand Down Expand Up @@ -1173,7 +1171,7 @@ The loop invariant in for loops is implicitly re-assignable (`var`).
### 2.7 while
**while** statement is used to execute a block of code while a condition is true.
The **while** statement evaluates a condition, and if it is true, a set of statements is repeated until the condition is false.
> ```TS
> // Wing program:
Expand All @@ -1186,6 +1184,20 @@ The loop invariant in for loops is implicitly re-assignable (`var`).
---
### 2.8 throw
The **throw** statement raises a user-defined exception, which must be a string expression.
Execution of the current function will stop (the statements after throw won't be executed), and control will be passed to the first catch block in the call stack.
If no catch block exists among caller functions, the program will terminate.
(An uncaught exception in preflight causes a compilation error, while an uncaught exception in inflight causes a runtime error.)
> ```TS
> // Wing program:
> throw "Username must be at least 3 characters long.";
> ```
[`top`][top]
## 3. Declarations
### 3.1 Structs
Expand Down
6 changes: 3 additions & 3 deletions examples/proposed/counting-semaphore.w
Original file line number Diff line number Diff line change
Expand Up @@ -59,18 +59,18 @@ queue.add_consumer(inflight (message: str) => {
let is_resource_1_acquired = resource_1.try_acquire();
if !is_resource_1_acquired {
// brutally error out to re-enqueue
throw("Failed to acquire resource 1");
throw "Failed to acquire resource 1";
}
let is_resource_2_acquired = resource_2.try_acquire();
if !is_resource_2_acquired {
resource_1.release();
// brutally error out to re-enqueue
throw("Failed to acquire resource 2");
throw "Failed to acquire resource 2";
}

// real work
log("all resources are acquired, processing message: ${message}");

resource_1.release();
resource_2.release();
});
});
4 changes: 2 additions & 2 deletions examples/proposed/task-manager.w
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ resource TaskManager {
try {
return this.bucket.get("${id}/status");
} catch e {
throw("no such key: ${id}");
throw "no such key: ${id}";
}
}

Expand All @@ -65,7 +65,7 @@ resource TaskManager {
try {
return this.bucket.get("${id}/result");
} catch e {
throw("no such id: ${id}");
throw "no such id: ${id}";
}
}

Expand Down
2 changes: 1 addition & 1 deletion examples/tests/error/utilities.w
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
assert(false);
log("W");
throw("me");
throw "me";
3 changes: 3 additions & 0 deletions examples/tests/invalid/throw_non_string.w
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
throw 42;
//^ error: expected expression of type str, but received num

4 changes: 2 additions & 2 deletions examples/tests/sdk_tests/util/wait-until.w
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,10 @@ test "throwing exception from predicate should throw immediately" {
try {
util.waitUntil((): bool => {
invokeCounter.inc();
throw("ERROR");
throw "ERROR";
});
assert(false);
} catch {
assert(invokeCounter.peek() == 1);
}
}
}
2 changes: 1 addition & 1 deletion examples/tests/valid/dynamo.w
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class DynamoTable {
init() {
let target = util.env("WING_TARGET");
if target != "tf-aws" {
throw("Unsupported target: ${target} (expected 'tf-aws')");
throw "Unsupported target: ${target} (expected 'tf-aws')";
}

this.table = new tfaws.dynamodbTable.DynamodbTable(
Expand Down
4 changes: 2 additions & 2 deletions examples/tests/valid/dynamo_awscdk.w
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class DynamoTable {
init() {
let target = util.env("WING_TARGET");
if target != "awscdk" {
throw("Unsupported target: ${target} (expected 'awscdk')");
throw "Unsupported target: ${target} (expected 'awscdk')";
}

this.table = new awscdk.aws_dynamodb.Table(
Expand Down Expand Up @@ -120,4 +120,4 @@ test "cdk table" {
});
assert(c.get("Item").get("Flavor").get("S").asStr() == "Chocolate");
assert(c.get("Item").get("Quantity").get("S").asStr() == "20Kg");
}
}
6 changes: 3 additions & 3 deletions examples/tests/valid/try_catch.w
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ let var x = "";

// Verify throw works and both catch and finally are executed.
try {
throw("hello");
throw "hello";
x = "no way I got here";
} catch e {
assert(e == "hello");
Expand All @@ -27,7 +27,7 @@ assert(x == "finally");
// Verify that finally is executed even if there's no catch block.
try {
try {
throw("hello");
throw "hello";
} finally {
x = "finally with no catch";
}
Expand All @@ -46,6 +46,6 @@ assert(x == "finally with no catch and no exception");
// Verify we can return from a closure in a finally block.
assert((():num => { try {} finally {return 1;}})() == 1);
// Verify we can return from a closure in a catch block.
assert((():num => { try {throw("");} catch {return 2;}})() == 2);
assert((():num => { try {throw "";} catch {return 2;}})() == 2);
// Verify we can return from a closure in a try block.
assert((():num => { try {return 3;} finally {}})() == 3);
6 changes: 5 additions & 1 deletion libs/tree-sitter-wing/grammar.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,8 @@ module.exports = grammar({
$.enum_definition,
$.try_catch_statement,
$.compiler_dbg_env,
$.super_constructor_statement
$.super_constructor_statement,
$.throw_statement,
),

import_statement: ($) =>
Expand Down Expand Up @@ -148,6 +149,9 @@ module.exports = grammar({
return_statement: ($) =>
seq("return", optional(field("expression", $.expression)), $._semicolon),

throw_statement: ($) =>
seq("throw", optional(field("expression", $.expression)), $._semicolon),

variable_assignment_statement: ($) =>
seq(
field("name", alias($.reference, $.lvalue)),
Expand Down
1 change: 1 addition & 0 deletions libs/wingc/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,7 @@ pub enum StmtKind {
Break,
Continue,
Return(Option<Expr>),
Throw(Expr),
Expression(Expr),
Assignment {
variable: Reference,
Expand Down
1 change: 1 addition & 0 deletions libs/wingc/src/fold.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ where
StmtKind::Break => StmtKind::Break,
StmtKind::Continue => StmtKind::Continue,
StmtKind::Return(value) => StmtKind::Return(value.map(|value| f.fold_expr(value))),
StmtKind::Throw(value) => StmtKind::Throw(f.fold_expr(value)),
StmtKind::Expression(expr) => StmtKind::Expression(f.fold_expr(expr)),
StmtKind::Assignment { variable, value } => StmtKind::Assignment {
variable: f.fold_reference(variable),
Expand Down
2 changes: 2 additions & 0 deletions libs/wingc/src/jsify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1039,6 +1039,7 @@ impl<'a> JSifier<'a> {
CodeMaker::one_line("return;")
}
}
StmtKind::Throw(exp) => CodeMaker::one_line(format!("throw new Error({});", self.jsify_expression(exp, ctx))),
StmtKind::Class(class) => self.jsify_class(env, class, ctx),
StmtKind::Interface { .. } => {
// This is a no-op in JS
Expand Down Expand Up @@ -1649,6 +1650,7 @@ fn get_public_symbols(scope: &Scope) -> Vec<Symbol> {
StmtKind::Break => {}
StmtKind::Continue => {}
StmtKind::Return(_) => {}
StmtKind::Throw(_) => {}
StmtKind::Expression(_) => {}
StmtKind::Assignment { .. } => {}
StmtKind::Scope(_) => {}
Expand Down
18 changes: 0 additions & 18 deletions libs/wingc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -242,24 +242,6 @@ pub fn type_check(
scope,
types,
);
add_builtin(
UtilityFunctions::Throw.to_string().as_str(),
Type::Function(FunctionSignature {
this_type: None,
parameters: vec![FunctionParameter {
typeref: types.string(),
name: "message".into(),
docs: Docs::with_summary("The message to throw"),
variadic: false,
}],
return_type: types.void(),
phase: Phase::Independent,
js_override: Some("{((msg) => {throw new Error(msg)})($args$)}".to_string()),
docs: Docs::with_summary("throws an error"),
}),
scope,
types,
);

let mut scope_env = types.get_scope_env(&scope);
let mut tc = TypeChecker::new(types, file_path, jsii_types, jsii_imports);
Expand Down
2 changes: 1 addition & 1 deletion libs/wingc/src/lsp/hover.rs
Original file line number Diff line number Diff line change
Expand Up @@ -673,7 +673,7 @@ assert(true);
r#"
class Foo {
inflight bar() {
throw("hello");
assert(true);
//^
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,18 +37,6 @@ source: libs/wingc/src/lsp/completions.rs
command:
title: triggerParameterHints
command: editor.action.triggerParameterHints
- label: throw
kind: 3
detail: "(message: str): void"
documentation:
kind: markdown
value: "```wing\nthrow: (message: str): void\n```\n---\nthrows an error\n\n### Parameters\n- `message` — The message to throw"
sortText: cc|throw
insertText: throw($0)
insertTextFormat: 2
command:
title: triggerParameterHints
command: editor.action.triggerParameterHints
- label: x
kind: 3
detail: "preflight (arg1: A): void"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,18 +37,6 @@ source: libs/wingc/src/lsp/completions.rs
command:
title: triggerParameterHints
command: editor.action.triggerParameterHints
- label: throw
kind: 3
detail: "(message: str): void"
documentation:
kind: markdown
value: "```wing\nthrow: (message: str): void\n```\n---\nthrows an error\n\n### Parameters\n- `message` — The message to throw"
sortText: cc|throw
insertText: throw($0)
insertTextFormat: 2
command:
title: triggerParameterHints
command: editor.action.triggerParameterHints
- label: x
kind: 3
detail: "preflight (arg1: A): void"
Expand Down
12 changes: 0 additions & 12 deletions libs/wingc/src/lsp/snapshots/completions/empty.snap
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,6 @@ source: libs/wingc/src/lsp/completions.rs
command:
title: triggerParameterHints
command: editor.action.triggerParameterHints
- label: throw
kind: 3
detail: "(message: str): void"
documentation:
kind: markdown
value: "```wing\nthrow: (message: str): void\n```\n---\nthrows an error\n\n### Parameters\n- `message` — The message to throw"
sortText: cc|throw
insertText: throw($0)
insertTextFormat: 2
command:
title: triggerParameterHints
command: editor.action.triggerParameterHints
- label: "inflight () => {}"
kind: 15
sortText: "ll|inflight () => {}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,18 +39,6 @@ source: libs/wingc/src/lsp/completions.rs
command:
title: triggerParameterHints
command: editor.action.triggerParameterHints
- label: throw
kind: 3
detail: "(message: str): void"
documentation:
kind: markdown
value: "```wing\nthrow: (message: str): void\n```\n---\nthrows an error\n\n### Parameters\n- `message` — The message to throw"
sortText: cc|throw
insertText: throw($0)
insertTextFormat: 2
command:
title: triggerParameterHints
command: editor.action.triggerParameterHints
- label: "inflight () => {}"
kind: 15
sortText: "ll|inflight () => {}"
Expand Down
12 changes: 0 additions & 12 deletions libs/wingc/src/lsp/snapshots/completions/struct_literal_value.snap
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,6 @@ source: libs/wingc/src/lsp/completions.rs
command:
title: triggerParameterHints
command: editor.action.triggerParameterHints
- label: throw
kind: 3
detail: "(message: str): void"
documentation:
kind: markdown
value: "```wing\nthrow: (message: str): void\n```\n---\nthrows an error\n\n### Parameters\n- `message` — The message to throw"
sortText: cc|throw
insertText: throw($0)
insertTextFormat: 2
command:
title: triggerParameterHints
command: editor.action.triggerParameterHints
- label: Foo
kind: 22
documentation:
Expand Down
4 changes: 2 additions & 2 deletions libs/wingc/src/lsp/snapshots/hovers/builtin_in_inflight.snap
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ source: libs/wingc/src/lsp/hover.rs
---
contents:
kind: markdown
value: "```wing\nthrow: (message: str): void\n```\n---\nthrows an error\n\n### Parameters\n- `message` — The message to throw"
value: "```wing\nassert: (condition: bool): void\n```\n---\nAsserts that a condition is true\n\n### Parameters\n- `condition` — The condition to assert"
range:
start:
line: 3
character: 4
end:
line: 3
character: 9
character: 10

6 changes: 6 additions & 0 deletions libs/wingc/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,7 @@ impl<'s> Parser<'s> {
"break_statement" => self.build_break_statement(statement_node)?,
"continue_statement" => self.build_continue_statement(statement_node)?,
"return_statement" => self.build_return_statement(statement_node, phase)?,
"throw_statement" => self.build_throw_statement(statement_node, phase)?,
"class_definition" => self.build_class_statement(statement_node, Phase::Inflight)?, // `inflight class` is always "inflight"
"resource_definition" => self.build_class_statement(statement_node, phase)?, // `class` without a modifier inherits from scope
"interface_definition" => self.build_interface_statement(statement_node, phase)?,
Expand Down Expand Up @@ -520,6 +521,11 @@ impl<'s> Parser<'s> {
))
}

fn build_throw_statement(&self, statement_node: &Node, phase: Phase) -> DiagnosticResult<StmtKind> {
let expr = self.build_expression(&statement_node.child_by_field_name("expression").unwrap(), phase)?;
Ok(StmtKind::Throw(expr))
}

/// Builds scope statements for a loop (while/for), and maintains the is_in_loop flag
/// for the duration of the loop. So that later break statements inside can be validated
/// without traversing the AST.
Expand Down
Loading

0 comments on commit 7723b06

Please sign in to comment.