Proposal for ECMAScript 'throw' expressions
532
stars
24
commits
JavaScript
primary language
Feb 7, 2024
updated
throw expressionsThis proposal defines new syntax to throw exceptions from within an expression context.
Stage: 2
Champion: Ron Buckton (@rbuckton)
For more information see the TC39 proposal process.
A throw expression allows you to throw exceptions in expression contexts. For example:
function save(filename = throw new TypeError("Argument required")) {
}
lint(ast, {
with: () => throw new Error("avoid using 'with' statements.")
});
function getEncoder(encoding) {
const encoder = encoding === "utf8" ? new UTF8Encoder()
: encoding === "utf16le" ? new UTF16Encoder(false)
: encoding === "utf16be" ? new UTF16Encoder(true)
: throw new Error("Unsupported encoding");
}
class Product {
get id() { return this._id; }
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:
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; // evaluates 'a', throws 'b'
(throw a, b); // would throw 'a', not 'b', so `,` is forbidden
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; // throws 'a' if 'a' is truthy, otherwise throws 'b'
(throw a || b); // would always throw 'a', so `||` is forbidden
// ... 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:
(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:
const x = a ? throw b : c; // if 'a' then throw 'b', else evaluate 'c'
++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] `;`
A throw expression can be approximated in ECMAScript using something like the following definition:
const __throw = err => { throw err; };
// via helper...
function getEncoder1(encoding) {
const encoder = encoding === "utf8" ? new UTF8Encoder()
: encoding === "utf16le" ? new UTF16Encoder(false)
: encoding === "utf16be" ? new UTF16Encoder(true)
: __throw(new Error("Unsupported encoding"));
}
// via arrow...
function getEncoder2(encoding) {
const encoder = encoding === "utf8" ? new UTF8Encoder()
: encoding === "utf16le" ? new UTF16Encoder(false)
: encoding === "utf16be" ? new UTF16Encoder(true)
: (() => { throw new Error("Unsupported encoding"); })();
}
However, this has several downsides compared to a native implementation:
__throw helper will appear in err.stack in a host environment.
Error.captureStackTracethrow is not local to the function.The following is a high-level list of tasks to progress through each stage of the TC39 proposal process:
23 commits
1 commits
JavaScript
100.0%
Proposal for ECMAScript 'throw' expressions
532
stars
24
commits
JavaScript
primary language
Feb 7, 2024
updated
throw expressionsThis proposal defines new syntax to throw exceptions from within an expression context.
Stage: 2
Champion: Ron Buckton (@rbuckton)
For more information see the TC39 proposal process.
A throw expression allows you to throw exceptions in expression contexts. For example:
function save(filename = throw new TypeError("Argument required")) {
}
lint(ast, {
with: () => throw new Error("avoid using 'with' statements.")
});
function getEncoder(encoding) {
const encoder = encoding === "utf8" ? new UTF8Encoder()
: encoding === "utf16le" ? new UTF16Encoder(false)
: encoding === "utf16be" ? new UTF16Encoder(true)
: throw new Error("Unsupported encoding");
}
class Product {
get id() { return this._id; }
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:
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; // evaluates 'a', throws 'b'
(throw a, b); // would throw 'a', not 'b', so `,` is forbidden
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; // throws 'a' if 'a' is truthy, otherwise throws 'b'
(throw a || b); // would always throw 'a', so `||` is forbidden
// ... 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:
(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:
const x = a ? throw b : c; // if 'a' then throw 'b', else evaluate 'c'
++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] `;`
A throw expression can be approximated in ECMAScript using something like the following definition:
const __throw = err => { throw err; };
// via helper...
function getEncoder1(encoding) {
const encoder = encoding === "utf8" ? new UTF8Encoder()
: encoding === "utf16le" ? new UTF16Encoder(false)
: encoding === "utf16be" ? new UTF16Encoder(true)
: __throw(new Error("Unsupported encoding"));
}
// via arrow...
function getEncoder2(encoding) {
const encoder = encoding === "utf8" ? new UTF8Encoder()
: encoding === "utf16le" ? new UTF16Encoder(false)
: encoding === "utf16be" ? new UTF16Encoder(true)
: (() => { throw new Error("Unsupported encoding"); })();
}
However, this has several downsides compared to a native implementation:
__throw helper will appear in err.stack in a host environment.
Error.captureStackTracethrow is not local to the function.The following is a high-level list of tasks to progress through each stage of the TC39 proposal process:
23 commits
1 commits
JavaScript
100.0%