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

Move 'throw' to ParenthesizedExpression #22

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading
Loading