Skip to content

Commit fab0859

Browse files
author
Andy Hanson
committed
Allow destructuring in catch clauses
1 parent 3f234f2 commit fab0859

15 files changed

+310
-53
lines changed

src/compiler/binder.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1007,7 +1007,7 @@ namespace ts {
10071007
currentFlow = finishFlowLabel(preFinallyLabel);
10081008
bind(node.finallyBlock);
10091009
// if flow after finally is unreachable - keep it
1010-
// otherwise check if flows after try and after catch are unreachable
1010+
// otherwise check if flows after try and after catch are unreachable
10111011
// if yes - convert current flow to unreachable
10121012
// i.e.
10131013
// try { return "1" } finally { console.log(1); }
@@ -2421,6 +2421,9 @@ namespace ts {
24212421
case SyntaxKind.HeritageClause:
24222422
return computeHeritageClause(<HeritageClause>node, subtreeFlags);
24232423

2424+
case SyntaxKind.CatchClause:
2425+
return computeCatchClause(<CatchClause>node, subtreeFlags);
2426+
24242427
case SyntaxKind.ExpressionWithTypeArguments:
24252428
return computeExpressionWithTypeArguments(<ExpressionWithTypeArguments>node, subtreeFlags);
24262429

@@ -2650,6 +2653,17 @@ namespace ts {
26502653
return transformFlags & ~TransformFlags.NodeExcludes;
26512654
}
26522655

2656+
function computeCatchClause(node: CatchClause, subtreeFlags: TransformFlags) {
2657+
let transformFlags = subtreeFlags;
2658+
2659+
if (node.variableDeclaration && isBindingPattern(node.variableDeclaration.name)) {
2660+
transformFlags |= TransformFlags.AssertES2015;
2661+
}
2662+
2663+
node.transformFlags = transformFlags | TransformFlags.HasComputedFlags;
2664+
return transformFlags & ~TransformFlags.NodeExcludes;
2665+
}
2666+
26532667
function computeExpressionWithTypeArguments(node: ExpressionWithTypeArguments, subtreeFlags: TransformFlags) {
26542668
// An ExpressionWithTypeArguments is ES6 syntax, as it is used in the
26552669
// extends clause of a class.

src/compiler/checker.ts

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3304,7 +3304,7 @@ namespace ts {
33043304
}
33053305
// Handle catch clause variables
33063306
const declaration = symbol.valueDeclaration;
3307-
if (declaration.parent.kind === SyntaxKind.CatchClause) {
3307+
if (isCatchClauseVariableDeclarationOrBindingElement(declaration)) {
33083308
return links.type = anyType;
33093309
}
33103310
// Handle export default expressions
@@ -16906,22 +16906,20 @@ namespace ts {
1690616906
if (catchClause) {
1690716907
// Grammar checking
1690816908
if (catchClause.variableDeclaration) {
16909-
if (catchClause.variableDeclaration.name.kind !== SyntaxKind.Identifier) {
16910-
grammarErrorOnFirstToken(catchClause.variableDeclaration.name, Diagnostics.Catch_clause_variable_name_must_be_an_identifier);
16911-
}
16912-
else if (catchClause.variableDeclaration.type) {
16909+
if (catchClause.variableDeclaration.type) {
1691316910
grammarErrorOnFirstToken(catchClause.variableDeclaration.type, Diagnostics.Catch_clause_variable_cannot_have_a_type_annotation);
1691416911
}
1691516912
else if (catchClause.variableDeclaration.initializer) {
1691616913
grammarErrorOnFirstToken(catchClause.variableDeclaration.initializer, Diagnostics.Catch_clause_variable_cannot_have_an_initializer);
1691716914
}
1691816915
else {
16919-
const identifierName = (<Identifier>catchClause.variableDeclaration.name).text;
16920-
const locals = catchClause.block.locals;
16921-
if (locals) {
16922-
const localSymbol = locals[identifierName];
16923-
if (localSymbol && (localSymbol.flags & SymbolFlags.BlockScopedVariable) !== 0) {
16924-
grammarErrorOnNode(localSymbol.valueDeclaration, Diagnostics.Cannot_redeclare_identifier_0_in_catch_clause, identifierName);
16916+
const blockLocals = catchClause.block.locals;
16917+
if (blockLocals) {
16918+
for (const caughtName in catchClause.locals) {
16919+
const blockLocal = blockLocals[caughtName];
16920+
if (blockLocal && (blockLocal.flags & SymbolFlags.BlockScopedVariable) !== 0) {
16921+
grammarErrorOnNode(blockLocal.valueDeclaration, Diagnostics.Cannot_redeclare_identifier_0_in_catch_clause, caughtName);
16922+
}
1692516923
}
1692616924
}
1692716925
}

src/compiler/diagnosticMessages.json

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -603,10 +603,6 @@
603603
"category": "Error",
604604
"code": 1194
605605
},
606-
"Catch clause variable name must be an identifier.": {
607-
"category": "Error",
608-
"code": 1195
609-
},
610606
"Catch clause variable cannot have a type annotation.": {
611607
"category": "Error",
612608
"code": 1196

src/compiler/transformers/es2015.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,9 @@ namespace ts {
362362
case SyntaxKind.ObjectLiteralExpression:
363363
return visitObjectLiteralExpression(<ObjectLiteralExpression>node);
364364

365+
case SyntaxKind.CatchClause:
366+
return visitCatchClause(<CatchClause>node);
367+
365368
case SyntaxKind.ShorthandPropertyAssignment:
366369
return visitShorthandPropertyAssignment(<ShorthandPropertyAssignment>node);
367370

@@ -2622,6 +2625,24 @@ namespace ts {
26222625
return expression;
26232626
}
26242627

2628+
function visitCatchClause(node: CatchClause): CatchClause {
2629+
Debug.assert(isBindingPattern(node.variableDeclaration.name));
2630+
2631+
const temp = createTempVariable(undefined);
2632+
const newVariableDeclaration = createVariableDeclaration(temp, undefined, undefined, node.variableDeclaration);
2633+
2634+
const vars = flattenVariableDestructuring(node.variableDeclaration, temp, visitor);
2635+
const list = createVariableDeclarationList(vars, /*location*/node.variableDeclaration, /*flags*/node.variableDeclaration.flags);
2636+
const destructure = createVariableStatement(undefined, list);
2637+
2638+
return updateCatchClause(node, newVariableDeclaration, addStatementToStartOfBlock(node.block, destructure));
2639+
}
2640+
2641+
function addStatementToStartOfBlock(block: Block, statement: Statement): Block {
2642+
const transformedStatements = visitNodes(block.statements, visitor, isStatement);
2643+
return updateBlock(block, [statement].concat(transformedStatements));
2644+
}
2645+
26252646
/**
26262647
* Visits a MethodDeclaration of an ObjectLiteralExpression and transforms it into a
26272648
* PropertyAssignment.

src/compiler/utilities.ts

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,12 @@ namespace ts {
406406

407407
export function isBlockOrCatchScoped(declaration: Declaration) {
408408
return (getCombinedNodeFlags(declaration) & NodeFlags.BlockScoped) !== 0 ||
409-
isCatchClauseVariableDeclaration(declaration);
409+
isCatchClauseVariableDeclarationOrBindingElement(declaration);
410+
}
411+
412+
export function isCatchClauseVariableDeclarationOrBindingElement(declaration: Declaration) {
413+
const node = getRootDeclaration(declaration);
414+
return node.kind === SyntaxKind.VariableDeclaration && node.parent.kind === SyntaxKind.CatchClause;
410415
}
411416

412417
export function isAmbientModule(node: Node): boolean {
@@ -489,13 +494,6 @@ namespace ts {
489494
}
490495
}
491496

492-
export function isCatchClauseVariableDeclaration(declaration: Declaration) {
493-
return declaration &&
494-
declaration.kind === SyntaxKind.VariableDeclaration &&
495-
declaration.parent &&
496-
declaration.parent.kind === SyntaxKind.CatchClause;
497-
}
498-
499497
// Return display name of an identifier
500498
// Computed property names will just be emitted as "[<expr>]", where <expr> is the source
501499
// text of the expression in the computed property.

tests/baselines/reference/catchClauseWithBindingPattern1.errors.txt

Lines changed: 0 additions & 10 deletions
This file was deleted.

tests/baselines/reference/catchClauseWithBindingPattern1.js

Lines changed: 0 additions & 11 deletions
This file was deleted.
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
//// [destructuringCatch.ts]
2+
3+
try {
4+
throw [0, 1];
5+
}
6+
catch ([a, b]) {
7+
a + b;
8+
}
9+
10+
try {
11+
throw { a: 0, b: 1 };
12+
}
13+
catch ({a, b}) {
14+
a + b;
15+
}
16+
17+
try {
18+
throw [{ x: [0], z: 1 }];
19+
}
20+
catch ([{x: [y], z}]) {
21+
y + z;
22+
}
23+
24+
// Test of comment ranges. A fix to GH#11755 should update this.
25+
try {
26+
}
27+
catch (/*Test comment ranges*/[/*a*/a]) {
28+
29+
}
30+
31+
32+
//// [destructuringCatch.js]
33+
try {
34+
throw [0, 1];
35+
}
36+
catch (_a) {
37+
var a = _a[0], b = _a[1];
38+
a + b;
39+
}
40+
try {
41+
throw { a: 0, b: 1 };
42+
}
43+
catch (_b) {
44+
var a = _b.a, b = _b.b;
45+
a + b;
46+
}
47+
try {
48+
throw [{ x: [0], z: 1 }];
49+
}
50+
catch (_c) {
51+
var _d = _c[0], y = _d.x[0], z = _d.z;
52+
y + z;
53+
}
54+
// Test of comment ranges. A fix to GH#11755 should update this.
55+
try {
56+
}
57+
catch (_e) {
58+
var /*a*/ a = _e[0];
59+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
=== tests/cases/conformance/es6/destructuring/destructuringCatch.ts ===
2+
3+
try {
4+
throw [0, 1];
5+
}
6+
catch ([a, b]) {
7+
>a : Symbol(a, Decl(destructuringCatch.ts, 4, 8))
8+
>b : Symbol(b, Decl(destructuringCatch.ts, 4, 10))
9+
10+
a + b;
11+
>a : Symbol(a, Decl(destructuringCatch.ts, 4, 8))
12+
>b : Symbol(b, Decl(destructuringCatch.ts, 4, 10))
13+
}
14+
15+
try {
16+
throw { a: 0, b: 1 };
17+
>a : Symbol(a, Decl(destructuringCatch.ts, 9, 11))
18+
>b : Symbol(b, Decl(destructuringCatch.ts, 9, 17))
19+
}
20+
catch ({a, b}) {
21+
>a : Symbol(a, Decl(destructuringCatch.ts, 11, 8))
22+
>b : Symbol(b, Decl(destructuringCatch.ts, 11, 10))
23+
24+
a + b;
25+
>a : Symbol(a, Decl(destructuringCatch.ts, 11, 8))
26+
>b : Symbol(b, Decl(destructuringCatch.ts, 11, 10))
27+
}
28+
29+
try {
30+
throw [{ x: [0], z: 1 }];
31+
>x : Symbol(x, Decl(destructuringCatch.ts, 16, 12))
32+
>z : Symbol(z, Decl(destructuringCatch.ts, 16, 20))
33+
}
34+
catch ([{x: [y], z}]) {
35+
>x : Symbol(x)
36+
>y : Symbol(y, Decl(destructuringCatch.ts, 18, 13))
37+
>z : Symbol(z, Decl(destructuringCatch.ts, 18, 16))
38+
39+
y + z;
40+
>y : Symbol(y, Decl(destructuringCatch.ts, 18, 13))
41+
>z : Symbol(z, Decl(destructuringCatch.ts, 18, 16))
42+
}
43+
44+
// Test of comment ranges. A fix to GH#11755 should update this.
45+
try {
46+
}
47+
catch (/*Test comment ranges*/[/*a*/a]) {
48+
>a : Symbol(a, Decl(destructuringCatch.ts, 25, 31))
49+
50+
}
51+
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
=== tests/cases/conformance/es6/destructuring/destructuringCatch.ts ===
2+
3+
try {
4+
throw [0, 1];
5+
>[0, 1] : number[]
6+
>0 : 0
7+
>1 : 1
8+
}
9+
catch ([a, b]) {
10+
>a : any
11+
>b : any
12+
13+
a + b;
14+
>a + b : any
15+
>a : any
16+
>b : any
17+
}
18+
19+
try {
20+
throw { a: 0, b: 1 };
21+
>{ a: 0, b: 1 } : { a: number; b: number; }
22+
>a : number
23+
>0 : 0
24+
>b : number
25+
>1 : 1
26+
}
27+
catch ({a, b}) {
28+
>a : any
29+
>b : any
30+
31+
a + b;
32+
>a + b : any
33+
>a : any
34+
>b : any
35+
}
36+
37+
try {
38+
throw [{ x: [0], z: 1 }];
39+
>[{ x: [0], z: 1 }] : { x: number[]; z: number; }[]
40+
>{ x: [0], z: 1 } : { x: number[]; z: number; }
41+
>x : number[]
42+
>[0] : number[]
43+
>0 : 0
44+
>z : number
45+
>1 : 1
46+
}
47+
catch ([{x: [y], z}]) {
48+
>x : any
49+
>y : any
50+
>z : any
51+
52+
y + z;
53+
>y + z : any
54+
>y : any
55+
>z : any
56+
}
57+
58+
// Test of comment ranges. A fix to GH#11755 should update this.
59+
try {
60+
}
61+
catch (/*Test comment ranges*/[/*a*/a]) {
62+
>a : any
63+
64+
}
65+

tests/baselines/reference/redeclareParameterInCatchBlock.errors.txt

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
tests/cases/compiler/redeclareParameterInCatchBlock.ts(5,11): error TS2492: Cannot redeclare identifier 'e' in catch clause
22
tests/cases/compiler/redeclareParameterInCatchBlock.ts(11,9): error TS2492: Cannot redeclare identifier 'e' in catch clause
3+
tests/cases/compiler/redeclareParameterInCatchBlock.ts(17,15): error TS2492: Cannot redeclare identifier 'b' in catch clause
4+
tests/cases/compiler/redeclareParameterInCatchBlock.ts(22,15): error TS2451: Cannot redeclare block-scoped variable 'x'.
5+
tests/cases/compiler/redeclareParameterInCatchBlock.ts(22,21): error TS2451: Cannot redeclare block-scoped variable 'x'.
36

47

5-
==== tests/cases/compiler/redeclareParameterInCatchBlock.ts (2 errors) ====
8+
==== tests/cases/compiler/redeclareParameterInCatchBlock.ts (5 errors) ====
69

710
try {
811

@@ -22,10 +25,27 @@ tests/cases/compiler/redeclareParameterInCatchBlock.ts(11,9): error TS2492: Cann
2225

2326
try {
2427

28+
} catch ([a, b]) {
29+
const [c, b] = [0, 1];
30+
~
31+
!!! error TS2492: Cannot redeclare identifier 'b' in catch clause
32+
}
33+
34+
try {
35+
36+
} catch ({ a: x, b: x }) {
37+
~
38+
!!! error TS2451: Cannot redeclare block-scoped variable 'x'.
39+
~
40+
!!! error TS2451: Cannot redeclare block-scoped variable 'x'.
41+
42+
}
43+
44+
try {
45+
2546
} catch(e) {
2647
function test() {
2748
let e;
2849
}
2950
}
30-
3151

0 commit comments

Comments
 (0)