Skip to content

Commit

Permalink
Move 'throw' to ParenthesizedExpression
Browse files Browse the repository at this point in the history
  • Loading branch information
rbuckton committed Jan 17, 2024
1 parent 8556f55 commit 4da7901
Show file tree
Hide file tree
Showing 4 changed files with 873 additions and 452 deletions.
81 changes: 47 additions & 34 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ A `throw` expression allows you to throw exceptions in expression contexts. For

* Parameter initializers
```js
function save(filename = throw new TypeError("Argument required")) {
function save(filename = (throw new TypeError("Argument required"))) {
}
```
* Arrow function bodies
```js
lint(ast, {
with: () => throw new Error("avoid using 'with' statements.")
with: () => (throw new Error("avoid using 'with' statements."))
});
```
* Conditional expressions
Expand All @@ -34,67 +34,80 @@ A `throw` expression allows you to throw exceptions in expression contexts. For
const encoder = encoding === "utf8" ? new UTF8Encoder()
: encoding === "utf16le" ? new UTF16Encoder(false)
: encoding === "utf16be" ? new UTF16Encoder(true)
: throw new Error("Unsupported encoding");
: (throw new Error("Unsupported encoding"));
}
```
* Logical operations
```js
class Product {
get id() { return this._id; }
set id(value) { this._id = value || throw new Error("Invalid value"); }
set id(value) { this._id = value || (throw new Error("Invalid value")); }
}
```

A `throw` expression *does not* replace a `throw` statement due to the difference
in the precedence of their values. To maintain the precedence of the `throw` statement,
we must add a lookahead restriction to `ExpressionStatement` to avoid ambiguity.

Due to the difference in precedence between a `throw` expression and a _ThrowStatement_, certain operators to the right
of the expression would parse differently between the two which could cause ambiguity and confusion:
In order to avoid a difference in precedence between `throw` expression and a _ThrowStatement_, we want to ensure that
operators within the expression are parsed in the same way in order to avoid ambiguity and confusion:

```js
throw a ? b : c; // evaluates 'a' and throws either 'b' or 'c'
(throw a ? b : c); // without restriction would throw 'a', so `?` is forbidden
(throw a ? b : c);
throw a, b; // evaluates 'a', throws 'b'
(throw a, b); // would throw 'a', not 'b', so `,` is forbidden
(throw a, b);
throw a && b; // throws 'a' if 'a' is falsy, otherwise throws 'b'
(throw a && b); // would always throw 'a', so `&&` is forbidden
(throw a && b);
throw a || b; // throws 'a' if 'a' is truthy, otherwise throws 'b'
(throw a || b); // would always throw 'a', so `||` is forbidden
(throw a || b);
// ... etc.
```

As a result, all binary operators and the `?` operator are forbidden to the right of a `throw` expression. To use these
operators inside of a `throw` expression, the expression must be surrounded with parentheses:
As a result, `throw` expressions are only parsed inside of a _ParenthesizedExpression_, and thus will always require
outer parentheses to be parsed correctly:

```js
(throw (a, b)); // evaluates 'a', throws 'b'
(throw (a ? b : c)); // evaluates 'a' and throws either 'b' or 'c'
```

However, we do not forbid `:` so that a `throw` expression can still be easily used in a ternary:

```js
const x = a ? throw b : c; // if 'a' then throw 'b', else evaluate 'c'
(throw a, b); // evaluates 'a', throws 'b'
(throw a ? b : c); // evaluates 'a' and throws either 'b' or 'c'
a ? (throw b) : c;
// mostly nonsensical uses, requires extra parenthesis:
if ((throw a)) { }
while ((throw a)) { }
do { } while ((throw a));
for ((throw a);;) { }
for (; (throw a);) {}
for (;; (throw a)) {}
for (let x in (throw a)) { }
switch ((throw a)) { }
switch (a) { case (throw b): }
return (throw a);
throw (throw a);
`${(throw a)}`;
a[(throw b)];
a?.[(throw b)];
```

# Grammar

```diff grammarkdown
++ThrowExpressionInvalidPunctuator : one of
`,` `<` `>` `<=` `>=` `==` `!=` `===` `!==` `+` `-` `*` `/` `%` `**` `<<` `>>` `>>>` `&` `|` `^` `&&` `||` `??`
`=` `+=` `-=` `*=` `%=` `**=` `<<=` `>>=` `>>>=` `&=` `|=` `^=` `&&=` `||=` `??=` `?`
UnaryExpression[Yield, Await] :
++ `throw` UnaryExpression[?Yield, ?Await] [lookahead ∉ ThrowExpressionInvalidPunctuator]
ExpressionStatement[Yield, Await] :
-- [lookahead ∉ {`{`, `function`, `async` [no |LineTerminator| here] `function`, `class`, `let [`}] Expression[+In, ?Yield, ?Await] `;`
++ [lookahead ∉ {`{`, `function`, `async` [no |LineTerminator| here] `function`, `class`, `let [`, `throw`}] Expression[+In, ?Yield, ?Await] `;`
CoverParenthesizedExpressionAndArrowParameterList[Yield, Await] :
`(` Expression[+In, ?Yield, ?Await] `)`
`(` Expression[+In, ?Yield, ?Await] `,` `)`
+ `(` ThrowExpression[?Yield, ?Await] `)`
`(` `)`
`(` `...` BindingIdentifier[?Yield, ?Await] `)`
`(` `...` BindingPattern[?Yield, ?Await] `)`
`(` Expression[+In, ?Yield, ?Await] `,` `...` BindingIdentifier[?Yield, ?Await] `)`
`(` Expression[+In, ?Yield, ?Await] `,` `...` BindingPattern[?Yield, ?Await] `)`
ParenthesizedExpression[Yield, Await] :
`(` Expression[+In, ?Yield, ?Await] `)`
+ `(` ThrowExpression[?Yield, ?Await] `)`
+ ThrowExpression[Yield, Await] :
+ `throw` Expression[+In, ?Yield, ?Await]
```

# Other Notes
Expand Down
61 changes: 37 additions & 24 deletions package-lock.json

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

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@
},
"homepage": "https://github.com/tc39/proposal-throw-expressions#readme",
"devDependencies": {
"@tc39/ecma262-biblio": "^2.0.2322",
"@tc39/ecma262-biblio": "^2.1.2669",
"del": "^6.0.0",
"ecmarkup": "^12.1.0",
"ecmarkup": "^18.1.1",
"gulp": "^4.0.2",
"gulp-emu": "^2.1.0",
"gulp-live-server": "0.0.31",
Expand Down
Loading

0 comments on commit 4da7901

Please sign in to comment.