Skip to content

Support for the ECMAScript 'throw' operator #18798

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

Closed
wants to merge 4 commits into from
Closed
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
4 changes: 4 additions & 0 deletions src/compiler/binder.ts
Original file line number Diff line number Diff line change
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
11 changes: 11 additions & 0 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17178,6 +17178,15 @@ namespace ts {
return booleanType;
}

function checkThrowExpression(node: ThrowExpression): Type {
if (!compilerOptions.experimentalThrowExpressions) {
error(node, Diagnostics.Experimental_support_for_throw_expressions_is_a_feature_that_is_subject_to_change_in_a_future_release_Set_the_experimentalThrowExpressions_option_to_remove_this_warning);
}

checkExpression(node.expression);
return neverType;
}

function checkTypeOfExpression(node: TypeOfExpression): Type {
checkExpression(node.expression);
return typeofType;
Expand Down Expand Up @@ -18140,6 +18149,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
6 changes: 6 additions & 0 deletions src/compiler/commandLineParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,12 @@ namespace ts {
category: Diagnostics.Experimental_Options,
description: Diagnostics.Enables_experimental_support_for_emitting_type_metadata_for_decorators
},
{
name: "experimentalThrowExpressions",
type: "boolean",
category: Diagnostics.Experimental_Options,
description: Diagnostics.Enables_experimental_support_for_ECMAScript_Stage_2_throw_expressions
},

// Advanced
{
Expand Down
9 changes: 9 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -907,6 +907,10 @@
"category": "Error",
"code": 1328
},
"Experimental support for 'throw' expressions is a feature that is subject to change in a future release. Set the 'experimentalThrowExpressions' option to remove this warning.": {
"category": "Error",
"code": 1329
},

"Duplicate identifier '{0}'.": {
"category": "Error",
Expand Down Expand Up @@ -3314,6 +3318,11 @@
"category": "Message",
"code": 6185
},
"Enables experimental support for ECMAScript Stage-2 'throw' expressions.": {
"category": "Message",
"code": 6186
},

"Variable '{0}' implicitly has an '{1}' type.": {
"category": "Error",
"code": 7005
Expand Down
7 changes: 7 additions & 0 deletions src/compiler/emitter.ts
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
);
}
}
7 changes: 7 additions & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
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 Expand Up @@ -3652,6 +3658,7 @@ namespace ts {
emitBOM?: boolean;
emitDecoratorMetadata?: boolean;
experimentalDecorators?: boolean;
experimentalThrowExpressions?: boolean;
forceConsistentCasingInFileNames?: boolean;
/*@internal*/help?: boolean;
importHelpers?: boolean;
Expand Down
8 changes: 8 additions & 0 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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))

Loading