Skip to content

Allow destructuring in catch clauses #11757

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

Merged
1 commit merged into from
Oct 24, 2016
Merged
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
16 changes: 15 additions & 1 deletion src/compiler/binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1007,7 +1007,7 @@ namespace ts {
currentFlow = finishFlowLabel(preFinallyLabel);
bind(node.finallyBlock);
// if flow after finally is unreachable - keep it
// otherwise check if flows after try and after catch are unreachable
// otherwise check if flows after try and after catch are unreachable
// if yes - convert current flow to unreachable
// i.e.
// try { return "1" } finally { console.log(1); }
Expand Down Expand Up @@ -2421,6 +2421,9 @@ namespace ts {
case SyntaxKind.HeritageClause:
return computeHeritageClause(<HeritageClause>node, subtreeFlags);

case SyntaxKind.CatchClause:
return computeCatchClause(<CatchClause>node, subtreeFlags);

case SyntaxKind.ExpressionWithTypeArguments:
return computeExpressionWithTypeArguments(<ExpressionWithTypeArguments>node, subtreeFlags);

Expand Down Expand Up @@ -2650,6 +2653,17 @@ namespace ts {
return transformFlags & ~TransformFlags.NodeExcludes;
}

function computeCatchClause(node: CatchClause, subtreeFlags: TransformFlags) {
let transformFlags = subtreeFlags;

if (node.variableDeclaration && isBindingPattern(node.variableDeclaration.name)) {
transformFlags |= TransformFlags.AssertES2015;
}

node.transformFlags = transformFlags | TransformFlags.HasComputedFlags;
return transformFlags & ~TransformFlags.NodeExcludes;
}

function computeExpressionWithTypeArguments(node: ExpressionWithTypeArguments, subtreeFlags: TransformFlags) {
// An ExpressionWithTypeArguments is ES6 syntax, as it is used in the
// extends clause of a class.
Expand Down
20 changes: 9 additions & 11 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3304,7 +3304,7 @@ namespace ts {
}
// Handle catch clause variables
const declaration = symbol.valueDeclaration;
if (declaration.parent.kind === SyntaxKind.CatchClause) {
if (isCatchClauseVariableDeclarationOrBindingElement(declaration)) {
return links.type = anyType;
}
// Handle export default expressions
Expand Down Expand Up @@ -16906,22 +16906,20 @@ namespace ts {
if (catchClause) {
// Grammar checking
if (catchClause.variableDeclaration) {
if (catchClause.variableDeclaration.name.kind !== SyntaxKind.Identifier) {
grammarErrorOnFirstToken(catchClause.variableDeclaration.name, Diagnostics.Catch_clause_variable_name_must_be_an_identifier);
}
else if (catchClause.variableDeclaration.type) {
if (catchClause.variableDeclaration.type) {
grammarErrorOnFirstToken(catchClause.variableDeclaration.type, Diagnostics.Catch_clause_variable_cannot_have_a_type_annotation);
}
else if (catchClause.variableDeclaration.initializer) {
grammarErrorOnFirstToken(catchClause.variableDeclaration.initializer, Diagnostics.Catch_clause_variable_cannot_have_an_initializer);
}
else {
const identifierName = (<Identifier>catchClause.variableDeclaration.name).text;
const locals = catchClause.block.locals;
if (locals) {
const localSymbol = locals[identifierName];
if (localSymbol && (localSymbol.flags & SymbolFlags.BlockScopedVariable) !== 0) {
grammarErrorOnNode(localSymbol.valueDeclaration, Diagnostics.Cannot_redeclare_identifier_0_in_catch_clause, identifierName);
const blockLocals = catchClause.block.locals;
if (blockLocals) {
for (const caughtName in catchClause.locals) {
const blockLocal = blockLocals[caughtName];
if (blockLocal && (blockLocal.flags & SymbolFlags.BlockScopedVariable) !== 0) {
grammarErrorOnNode(blockLocal.valueDeclaration, Diagnostics.Cannot_redeclare_identifier_0_in_catch_clause, caughtName);
}
}
}
}
Expand Down
4 changes: 0 additions & 4 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -603,10 +603,6 @@
"category": "Error",
"code": 1194
},
"Catch clause variable name must be an identifier.": {
"category": "Error",
"code": 1195
},
"Catch clause variable cannot have a type annotation.": {
"category": "Error",
"code": 1196
Expand Down
21 changes: 21 additions & 0 deletions src/compiler/transformers/es2015.ts
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,9 @@ namespace ts {
case SyntaxKind.ObjectLiteralExpression:
return visitObjectLiteralExpression(<ObjectLiteralExpression>node);

case SyntaxKind.CatchClause:
return visitCatchClause(<CatchClause>node);

case SyntaxKind.ShorthandPropertyAssignment:
return visitShorthandPropertyAssignment(<ShorthandPropertyAssignment>node);

Expand Down Expand Up @@ -2622,6 +2625,24 @@ namespace ts {
return expression;
}

function visitCatchClause(node: CatchClause): CatchClause {
Debug.assert(isBindingPattern(node.variableDeclaration.name));

const temp = createTempVariable(undefined);
const newVariableDeclaration = createVariableDeclaration(temp, undefined, undefined, node.variableDeclaration);

const vars = flattenVariableDestructuring(node.variableDeclaration, temp, visitor);
const list = createVariableDeclarationList(vars, /*location*/node.variableDeclaration, /*flags*/node.variableDeclaration.flags);
const destructure = createVariableStatement(undefined, list);

return updateCatchClause(node, newVariableDeclaration, addStatementToStartOfBlock(node.block, destructure));
}

function addStatementToStartOfBlock(block: Block, statement: Statement): Block {
const transformedStatements = visitNodes(block.statements, visitor, isStatement);
return updateBlock(block, [statement].concat(transformedStatements));
}

/**
* Visits a MethodDeclaration of an ObjectLiteralExpression and transforms it into a
* PropertyAssignment.
Expand Down
14 changes: 6 additions & 8 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,12 @@ namespace ts {

export function isBlockOrCatchScoped(declaration: Declaration) {
return (getCombinedNodeFlags(declaration) & NodeFlags.BlockScoped) !== 0 ||
isCatchClauseVariableDeclaration(declaration);
isCatchClauseVariableDeclarationOrBindingElement(declaration);
}

export function isCatchClauseVariableDeclarationOrBindingElement(declaration: Declaration) {
const node = getRootDeclaration(declaration);
return node.kind === SyntaxKind.VariableDeclaration && node.parent.kind === SyntaxKind.CatchClause;
}

export function isAmbientModule(node: Node): boolean {
Expand Down Expand Up @@ -489,13 +494,6 @@ namespace ts {
}
}

export function isCatchClauseVariableDeclaration(declaration: Declaration) {
return declaration &&
declaration.kind === SyntaxKind.VariableDeclaration &&
declaration.parent &&
declaration.parent.kind === SyntaxKind.CatchClause;
}

// Return display name of an identifier
// Computed property names will just be emitted as "[<expr>]", where <expr> is the source
// text of the expression in the computed property.
Expand Down

This file was deleted.

11 changes: 0 additions & 11 deletions tests/baselines/reference/catchClauseWithBindingPattern1.js

This file was deleted.

59 changes: 59 additions & 0 deletions tests/baselines/reference/destructuringCatch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
//// [destructuringCatch.ts]

try {
throw [0, 1];
}
catch ([a, b]) {
a + b;
}

try {
throw { a: 0, b: 1 };
}
catch ({a, b}) {
a + b;
}

try {
throw [{ x: [0], z: 1 }];
}
catch ([{x: [y], z}]) {
y + z;
}

// Test of comment ranges. A fix to GH#11755 should update this.
try {
}
catch (/*Test comment ranges*/[/*a*/a]) {

}


//// [destructuringCatch.js]
try {
throw [0, 1];
}
catch (_a) {
var a = _a[0], b = _a[1];
a + b;
}
try {
throw { a: 0, b: 1 };
}
catch (_b) {
var a = _b.a, b = _b.b;
a + b;
}
try {
throw [{ x: [0], z: 1 }];
}
catch (_c) {
var _d = _c[0], y = _d.x[0], z = _d.z;
y + z;
}
// Test of comment ranges. A fix to GH#11755 should update this.
try {
}
catch (_e) {
var /*a*/ a = _e[0];
}
51 changes: 51 additions & 0 deletions tests/baselines/reference/destructuringCatch.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
=== tests/cases/conformance/es6/destructuring/destructuringCatch.ts ===

try {
throw [0, 1];
}
catch ([a, b]) {
>a : Symbol(a, Decl(destructuringCatch.ts, 4, 8))
>b : Symbol(b, Decl(destructuringCatch.ts, 4, 10))

a + b;
>a : Symbol(a, Decl(destructuringCatch.ts, 4, 8))
>b : Symbol(b, Decl(destructuringCatch.ts, 4, 10))
}

try {
throw { a: 0, b: 1 };
>a : Symbol(a, Decl(destructuringCatch.ts, 9, 11))
>b : Symbol(b, Decl(destructuringCatch.ts, 9, 17))
}
catch ({a, b}) {
>a : Symbol(a, Decl(destructuringCatch.ts, 11, 8))
>b : Symbol(b, Decl(destructuringCatch.ts, 11, 10))

a + b;
>a : Symbol(a, Decl(destructuringCatch.ts, 11, 8))
>b : Symbol(b, Decl(destructuringCatch.ts, 11, 10))
}

try {
throw [{ x: [0], z: 1 }];
>x : Symbol(x, Decl(destructuringCatch.ts, 16, 12))
>z : Symbol(z, Decl(destructuringCatch.ts, 16, 20))
}
catch ([{x: [y], z}]) {
>x : Symbol(x)
>y : Symbol(y, Decl(destructuringCatch.ts, 18, 13))
>z : Symbol(z, Decl(destructuringCatch.ts, 18, 16))

y + z;
>y : Symbol(y, Decl(destructuringCatch.ts, 18, 13))
>z : Symbol(z, Decl(destructuringCatch.ts, 18, 16))
}

// Test of comment ranges. A fix to GH#11755 should update this.
try {
}
catch (/*Test comment ranges*/[/*a*/a]) {
>a : Symbol(a, Decl(destructuringCatch.ts, 25, 31))

}

65 changes: 65 additions & 0 deletions tests/baselines/reference/destructuringCatch.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
=== tests/cases/conformance/es6/destructuring/destructuringCatch.ts ===

try {
throw [0, 1];
>[0, 1] : number[]
>0 : 0
>1 : 1
}
catch ([a, b]) {
>a : any
>b : any

a + b;
>a + b : any
>a : any
>b : any
}

try {
throw { a: 0, b: 1 };
>{ a: 0, b: 1 } : { a: number; b: number; }
>a : number
>0 : 0
>b : number
>1 : 1
}
catch ({a, b}) {
>a : any
>b : any

a + b;
>a + b : any
>a : any
>b : any
}

try {
throw [{ x: [0], z: 1 }];
>[{ x: [0], z: 1 }] : { x: number[]; z: number; }[]
>{ x: [0], z: 1 } : { x: number[]; z: number; }
>x : number[]
>[0] : number[]
>0 : 0
>z : number
>1 : 1
}
catch ([{x: [y], z}]) {
>x : any
>y : any
>z : any

y + z;
>y + z : any
>y : any
>z : any
}

// Test of comment ranges. A fix to GH#11755 should update this.
try {
}
catch (/*Test comment ranges*/[/*a*/a]) {
>a : any

}

Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
tests/cases/compiler/redeclareParameterInCatchBlock.ts(5,11): error TS2492: Cannot redeclare identifier 'e' in catch clause
tests/cases/compiler/redeclareParameterInCatchBlock.ts(11,9): error TS2492: Cannot redeclare identifier 'e' in catch clause
tests/cases/compiler/redeclareParameterInCatchBlock.ts(17,15): error TS2492: Cannot redeclare identifier 'b' in catch clause
tests/cases/compiler/redeclareParameterInCatchBlock.ts(22,15): error TS2451: Cannot redeclare block-scoped variable 'x'.
tests/cases/compiler/redeclareParameterInCatchBlock.ts(22,21): error TS2451: Cannot redeclare block-scoped variable 'x'.


==== tests/cases/compiler/redeclareParameterInCatchBlock.ts (2 errors) ====
==== tests/cases/compiler/redeclareParameterInCatchBlock.ts (5 errors) ====

try {

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

try {

} catch ([a, b]) {
const [c, b] = [0, 1];
~
!!! error TS2492: Cannot redeclare identifier 'b' in catch clause
}

try {

} catch ({ a: x, b: x }) {
~
!!! error TS2451: Cannot redeclare block-scoped variable 'x'.
~
!!! error TS2451: Cannot redeclare block-scoped variable 'x'.

}

try {

} catch(e) {
function test() {
let e;
}
}


Loading