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

Support for the ECMAScript 'throw' operator #18798

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
4 changes: 4 additions & 0 deletions src/compiler/binder.ts
Expand Up @@ -3255,6 +3255,10 @@ namespace ts {
let excludeFlags = TransformFlags.NodeExcludes;

switch (kind) {
case SyntaxKind.ThrowExpression:
transformFlags |= TransformFlags.AssertESNext;
break;

case SyntaxKind.AsyncKeyword:
case SyntaxKind.AwaitExpression:
// async/await is ES2017 syntax, but may be ESNext syntax (for async generators)
Expand Down
7 changes: 7 additions & 0 deletions src/compiler/checker.ts
Expand Up @@ -17178,6 +17178,11 @@ namespace ts {
return booleanType;
}

function checkThrowExpression(node: ThrowExpression): Type {
checkExpression(node.expression);
return neverType;
}

function checkTypeOfExpression(node: TypeOfExpression): Type {
checkExpression(node.expression);
return typeofType;
Expand Down Expand Up @@ -18140,6 +18145,8 @@ namespace ts {
return checkMetaProperty(<MetaProperty>node);
case SyntaxKind.DeleteExpression:
return checkDeleteExpression(<DeleteExpression>node);
case SyntaxKind.ThrowExpression:
return checkThrowExpression(<ThrowExpression>node);
case SyntaxKind.VoidExpression:
return checkVoidExpression(<VoidExpression>node);
case SyntaxKind.AwaitExpression:
Expand Down
7 changes: 7 additions & 0 deletions src/compiler/emitter.ts
Expand Up @@ -784,6 +784,8 @@ namespace ts {
return emitArrowFunction(<ArrowFunction>node);
case SyntaxKind.DeleteExpression:
return emitDeleteExpression(<DeleteExpression>node);
case SyntaxKind.ThrowExpression:
return emitThrowExpression(<ThrowExpression>node);
case SyntaxKind.TypeOfExpression:
return emitTypeOfExpression(<TypeOfExpression>node);
case SyntaxKind.VoidExpression:
Expand Down Expand Up @@ -1300,6 +1302,11 @@ namespace ts {
emitExpression(node.expression);
}

function emitThrowExpression(node: ThrowExpression) {
write("throw ");
emitExpression(node.expression);
}

function emitTypeOfExpression(node: TypeOfExpression) {
write("typeof ");
emitExpression(node.expression);
Expand Down
12 changes: 12 additions & 0 deletions src/compiler/factory.ts
Expand Up @@ -1074,6 +1074,18 @@ namespace ts {
: node;
}

export function createThrowExpression(expression: Expression) {
const node = <ThrowExpression>createSynthesizedNode(SyntaxKind.ThrowExpression);
node.expression = parenthesizePrefixOperand(expression);
return node;
}

export function updateThrowExpression(node: ThrowExpression, expression: Expression) {
return node.expression !== expression
? updateNode(createThrowExpression(expression), node)
: node;
}

export function createTypeOf(expression: Expression) {
const node = <TypeOfExpression>createSynthesizedNode(SyntaxKind.TypeOfExpression);
node.expression = parenthesizePrefixOperand(expression);
Expand Down
29 changes: 28 additions & 1 deletion src/compiler/parser.ts
Expand Up @@ -185,6 +185,8 @@ namespace ts {
return visitNode(cbNode, (<ParenthesizedExpression>node).expression);
case SyntaxKind.DeleteExpression:
return visitNode(cbNode, (<DeleteExpression>node).expression);
case SyntaxKind.ThrowExpression:
return visitNode(cbNode, (<ThrowExpression>node).expression);
case SyntaxKind.TypeOfExpression:
return visitNode(cbNode, (<TypeOfExpression>node).expression);
case SyntaxKind.VoidExpression:
Expand Down Expand Up @@ -2962,6 +2964,7 @@ namespace ts {
case SyntaxKind.ExclamationToken:
case SyntaxKind.DeleteKeyword:
case SyntaxKind.TypeOfKeyword:
case SyntaxKind.ThrowKeyword:
case SyntaxKind.VoidKeyword:
case SyntaxKind.PlusPlusToken:
case SyntaxKind.MinusMinusToken:
Expand All @@ -2986,11 +2989,21 @@ namespace ts {
}

function isStartOfExpressionStatement(): boolean {
// As per the grammar, none of '{' or 'function' or 'class' can start an expression statement.
// As per the grammar, none of '{', 'function', 'async [no LineTerminator here] function', 'class', 'let [', or 'throw' can start an expression statement:
//
// An |ExpressionStatement| cannot start with a U+007B (LEFT CURLY BRACKET) because that might make it ambiguous with a |Block|.
// An |ExpressionStatement| cannot start with the `function` or `class` keywords because that would make it ambiguous with a |FunctionDeclaration|, a |GeneratorDeclaration|, or a |ClassDeclaration|.
// An |ExpressionStatement| cannot start with `async` `function` because that would make it ambiguous with an |AsyncFunctionDeclaration|.
// An |ExpressionStatement| cannot start with the two token sequence `let` `[` because that would make it ambiguous with a `let` |LexicalDeclaration| whose first |LexicalBinding| was an |ArrayBindingPattern|.
// An |ExpressionStatement| cannot start with `throw` because that would make it ambiguous with a |ThrowStatement|.
//
return token() !== SyntaxKind.OpenBraceToken &&
token() !== SyntaxKind.FunctionKeyword &&
token() !== SyntaxKind.ClassKeyword &&
token() !== SyntaxKind.ThrowKeyword &&
token() !== SyntaxKind.AtToken &&
(token() !== SyntaxKind.AsyncKeyword || !lookAhead(nextTokenIsFunctionKeywordOnSameLine)) &&
(token() !== SyntaxKind.LetKeyword || !lookAhead(nextTokenIsOpenBracket)) &&
isStartOfExpression();
}

Expand Down Expand Up @@ -3624,6 +3637,13 @@ namespace ts {
return finishNode(node);
}

function parseThrowExpression() {
const node = <ThrowExpression>createNode(SyntaxKind.ThrowExpression);
nextToken();
node.expression = parseSimpleUnaryExpression();
return finishNode(node);
}

function parseTypeOfExpression() {
const node = <TypeOfExpression>createNode(SyntaxKind.TypeOfExpression);
nextToken();
Expand Down Expand Up @@ -3730,6 +3750,8 @@ namespace ts {
return parsePrefixUnaryExpression();
case SyntaxKind.DeleteKeyword:
return parseDeleteExpression();
case SyntaxKind.ThrowKeyword:
return parseThrowExpression();
case SyntaxKind.TypeOfKeyword:
return parseTypeOfExpression();
case SyntaxKind.VoidKeyword:
Expand Down Expand Up @@ -3768,6 +3790,7 @@ namespace ts {
case SyntaxKind.TildeToken:
case SyntaxKind.ExclamationToken:
case SyntaxKind.DeleteKeyword:
case SyntaxKind.ThrowKeyword:
case SyntaxKind.TypeOfKeyword:
case SyntaxKind.VoidKeyword:
case SyntaxKind.AwaitKeyword:
Expand Down Expand Up @@ -5774,6 +5797,10 @@ namespace ts {
return nextToken() === SyntaxKind.OpenParenToken;
}

function nextTokenIsOpenBracket() {
return nextToken() === SyntaxKind.OpenBracketToken;
}

function nextTokenIsSlash() {
return nextToken() === SyntaxKind.SlashToken;
}
Expand Down
24 changes: 24 additions & 0 deletions src/compiler/transformers/esnext.ts
Expand Up @@ -79,6 +79,8 @@ namespace ts {
return visitForOfStatement(node as ForOfStatement, /*outermostLabeledStatement*/ undefined);
case SyntaxKind.ForStatement:
return visitForStatement(node as ForStatement);
case SyntaxKind.ThrowExpression:
return visitThrowExpression(node as ThrowExpression);
case SyntaxKind.VoidExpression:
return visitVoidExpression(node as VoidExpression);
case SyntaxKind.Constructor:
Expand Down Expand Up @@ -278,6 +280,10 @@ namespace ts {
);
}

function visitThrowExpression(node: ThrowExpression) {
return createThrowHelper(context, visitNode(node.expression, visitor, isExpression), node);
}

function visitVoidExpression(node: VoidExpression) {
return visitEachChild(node, visitorNoDestructuringValue, context);
}
Expand Down Expand Up @@ -987,4 +993,22 @@ namespace ts {
location
);
}

const throwHelper: EmitHelper = {
name: "typescript:throw",
scoped: false,
text: `var __throw = (this && this.__throw) || function (e) { throw e; };`
};

function createThrowHelper(context: TransformationContext, expression: Expression, location?: TextRange) {
context.requestEmitHelper(throwHelper);
return setTextRange(
createCall(
getHelperName("__throw"),
/*typeArguments*/ undefined,
[expression]
),
location
);
}
}
6 changes: 6 additions & 0 deletions src/compiler/types.ts
Expand Up @@ -257,6 +257,7 @@ namespace ts {
FunctionExpression,
ArrowFunction,
DeleteExpression,
ThrowExpression,
TypeOfExpression,
VoidExpression,
AwaitExpression,
Expand Down Expand Up @@ -1155,6 +1156,11 @@ namespace ts {
expression: UnaryExpression;
}

export interface ThrowExpression extends UnaryExpression {
kind: SyntaxKind.ThrowExpression;
expression: UnaryExpression;
}

export interface TypeOfExpression extends UnaryExpression {
kind: SyntaxKind.TypeOfExpression;
expression: UnaryExpression;
Expand Down
8 changes: 8 additions & 0 deletions src/compiler/utilities.ts
Expand Up @@ -1227,6 +1227,7 @@ namespace ts {
case SyntaxKind.ArrowFunction:
case SyntaxKind.VoidExpression:
case SyntaxKind.DeleteExpression:
case SyntaxKind.ThrowExpression:
case SyntaxKind.TypeOfExpression:
case SyntaxKind.PrefixUnaryExpression:
case SyntaxKind.PostfixUnaryExpression:
Expand Down Expand Up @@ -2129,6 +2130,7 @@ namespace ts {
case SyntaxKind.PrefixUnaryExpression:
case SyntaxKind.TypeOfExpression:
case SyntaxKind.VoidExpression:
case SyntaxKind.ThrowExpression:
case SyntaxKind.DeleteExpression:
case SyntaxKind.AwaitExpression:
case SyntaxKind.ConditionalExpression:
Expand Down Expand Up @@ -2216,6 +2218,7 @@ namespace ts {
case SyntaxKind.PrefixUnaryExpression:
case SyntaxKind.TypeOfExpression:
case SyntaxKind.VoidExpression:
case SyntaxKind.ThrowExpression:
case SyntaxKind.DeleteExpression:
case SyntaxKind.AwaitExpression:
return 15;
Expand Down Expand Up @@ -4294,6 +4297,10 @@ namespace ts {
return node.kind === SyntaxKind.DeleteExpression;
}

export function isThrowExpression(node: Node): node is ThrowExpression {
return node.kind === SyntaxKind.ThrowExpression;
}

export function isTypeOfExpression(node: Node): node is TypeOfExpression {
return node.kind === SyntaxKind.AwaitExpression;
}
Expand Down Expand Up @@ -5112,6 +5119,7 @@ namespace ts {
case SyntaxKind.PrefixUnaryExpression:
case SyntaxKind.PostfixUnaryExpression:
case SyntaxKind.DeleteExpression:
case SyntaxKind.ThrowExpression:
case SyntaxKind.TypeOfExpression:
case SyntaxKind.VoidExpression:
case SyntaxKind.AwaitExpression:
Expand Down
7 changes: 6 additions & 1 deletion src/compiler/visitor.ts
Expand Up @@ -495,6 +495,10 @@ namespace ts {
return updateDelete(<DeleteExpression>node,
visitNode((<DeleteExpression>node).expression, visitor, isExpression));

case SyntaxKind.ThrowExpression:
return updateThrowExpression(<ThrowExpression>node,
visitNode((<ThrowExpression>node).expression, visitor, isExpression));

case SyntaxKind.TypeOfExpression:
return updateTypeOf(<TypeOfExpression>node,
visitNode((<TypeOfExpression>node).expression, visitor, isExpression));
Expand Down Expand Up @@ -1104,13 +1108,14 @@ namespace ts {

case SyntaxKind.ParenthesizedExpression:
case SyntaxKind.DeleteExpression:
case SyntaxKind.ThrowExpression:
case SyntaxKind.TypeOfExpression:
case SyntaxKind.VoidExpression:
case SyntaxKind.AwaitExpression:
case SyntaxKind.YieldExpression:
case SyntaxKind.SpreadElement:
case SyntaxKind.NonNullExpression:
result = reduceNode((<ParenthesizedExpression | DeleteExpression | TypeOfExpression | VoidExpression | AwaitExpression | YieldExpression | SpreadElement | NonNullExpression>node).expression, cbNode, result);
result = reduceNode((<ParenthesizedExpression | DeleteExpression | ThrowExpression | TypeOfExpression | VoidExpression | AwaitExpression | YieldExpression | SpreadElement | NonNullExpression>node).expression, cbNode, result);
break;

case SyntaxKind.PrefixUnaryExpression:
Expand Down
3 changes: 2 additions & 1 deletion src/services/utilities.ts
Expand Up @@ -535,12 +535,13 @@ namespace ts {
case SyntaxKind.TypeQuery:
return isCompletedNode((<TypeQueryNode>n).exprName, sourceFile);

case SyntaxKind.ThrowExpression:
case SyntaxKind.TypeOfExpression:
case SyntaxKind.DeleteExpression:
case SyntaxKind.VoidExpression:
case SyntaxKind.YieldExpression:
case SyntaxKind.SpreadElement:
const unaryWordExpression = n as (TypeOfExpression | DeleteExpression | VoidExpression | YieldExpression | SpreadElement);
const unaryWordExpression = n as (ThrowExpression | TypeOfExpression | DeleteExpression | VoidExpression | YieldExpression | SpreadElement);
return isCompletedNode(unaryWordExpression.expression, sourceFile);

case SyntaxKind.TaggedTemplateExpression:
Expand Down
16 changes: 16 additions & 0 deletions tests/baselines/reference/throwExpressions.es2015.js
@@ -0,0 +1,16 @@
//// [throwExpressions.es2015.ts]
declare const condition: boolean;
const a = condition ? 1 : throw new Error();
const b = condition || throw new Error();
function c(d = throw new TypeError()) { }

const x = "x", y = "y", z = "z";
const w = condition ? throw true ? x : y : z;

//// [throwExpressions.es2015.js]
var __throw = (this && this.__throw) || function (e) { throw e; };
const a = condition ? 1 : __throw(new Error());
const b = condition || __throw(new Error());
function c(d = __throw(new TypeError())) { }
const x = "x", y = "y", z = "z";
const w = condition ? __throw(true) ? x : y : z;
31 changes: 31 additions & 0 deletions tests/baselines/reference/throwExpressions.es2015.symbols
@@ -0,0 +1,31 @@
=== tests/cases/conformance/expressions/throwExpressions/throwExpressions.es2015.ts ===
declare const condition: boolean;
>condition : Symbol(condition, Decl(throwExpressions.es2015.ts, 0, 13))

const a = condition ? 1 : throw new Error();
>a : Symbol(a, Decl(throwExpressions.es2015.ts, 1, 5))
>condition : Symbol(condition, Decl(throwExpressions.es2015.ts, 0, 13))
>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))

const b = condition || throw new Error();
>b : Symbol(b, Decl(throwExpressions.es2015.ts, 2, 5))
>condition : Symbol(condition, Decl(throwExpressions.es2015.ts, 0, 13))
>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))

function c(d = throw new TypeError()) { }
>c : Symbol(c, Decl(throwExpressions.es2015.ts, 2, 41))
>d : Symbol(d, Decl(throwExpressions.es2015.ts, 3, 11))
>TypeError : Symbol(TypeError, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))

const x = "x", y = "y", z = "z";
>x : Symbol(x, Decl(throwExpressions.es2015.ts, 5, 5))
>y : Symbol(y, Decl(throwExpressions.es2015.ts, 5, 14))
>z : Symbol(z, Decl(throwExpressions.es2015.ts, 5, 23))

const w = condition ? throw true ? x : y : z;
>w : Symbol(w, Decl(throwExpressions.es2015.ts, 6, 5))
>condition : Symbol(condition, Decl(throwExpressions.es2015.ts, 0, 13))
>x : Symbol(x, Decl(throwExpressions.es2015.ts, 5, 5))
>y : Symbol(y, Decl(throwExpressions.es2015.ts, 5, 14))
>z : Symbol(z, Decl(throwExpressions.es2015.ts, 5, 23))

47 changes: 47 additions & 0 deletions tests/baselines/reference/throwExpressions.es2015.types
@@ -0,0 +1,47 @@
=== tests/cases/conformance/expressions/throwExpressions/throwExpressions.es2015.ts ===
declare const condition: boolean;
>condition : boolean

const a = condition ? 1 : throw new Error();
>a : 1
>condition ? 1 : throw new Error() : 1
>condition : boolean
>1 : 1
>throw new Error() : never
>new Error() : Error
>Error : ErrorConstructor

const b = condition || throw new Error();
>b : true
>condition || throw new Error() : true
>condition : true
>throw new Error() : never
>new Error() : Error
>Error : ErrorConstructor

function c(d = throw new TypeError()) { }
>c : (d?: never) => void
>d : never
>throw new TypeError() : never
>new TypeError() : TypeError
>TypeError : TypeErrorConstructor

const x = "x", y = "y", z = "z";
>x : "x"
>"x" : "x"
>y : "y"
>"y" : "y"
>z : "z"
>"z" : "z"

const w = condition ? throw true ? x : y : z;
>w : "x" | "y" | "z"
>condition ? throw true ? x : y : z : "x" | "y" | "z"
>condition : true
>throw true ? x : y : "x" | "y"
>throw true : never
>true : true
>x : "x"
>y : "y"
>z : "z"