diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index a256b632fac..447a7625d38 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -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); } @@ -2421,6 +2421,9 @@ namespace ts { case SyntaxKind.HeritageClause: return computeHeritageClause(node, subtreeFlags); + case SyntaxKind.CatchClause: + return computeCatchClause(node, subtreeFlags); + case SyntaxKind.ExpressionWithTypeArguments: return computeExpressionWithTypeArguments(node, subtreeFlags); @@ -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. diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 5b85a932c05..772a0e3b5d5 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -3308,7 +3308,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 @@ -16928,22 +16928,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 = (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); + } } } } diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 0c92f0acd7f..526019e4908 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -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 diff --git a/src/compiler/transformers/es2015.ts b/src/compiler/transformers/es2015.ts index 9972f339c16..b48539e8722 100644 --- a/src/compiler/transformers/es2015.ts +++ b/src/compiler/transformers/es2015.ts @@ -362,6 +362,9 @@ namespace ts { case SyntaxKind.ObjectLiteralExpression: return visitObjectLiteralExpression(node); + case SyntaxKind.CatchClause: + return visitCatchClause(node); + case SyntaxKind.ShorthandPropertyAssignment: return visitShorthandPropertyAssignment(node); @@ -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. diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 87f6cd5e4aa..3a8d09f113d 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -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 { @@ -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 "[]", where is the source // text of the expression in the computed property. diff --git a/tests/baselines/reference/catchClauseWithBindingPattern1.errors.txt b/tests/baselines/reference/catchClauseWithBindingPattern1.errors.txt deleted file mode 100644 index 0b7482850e5..00000000000 --- a/tests/baselines/reference/catchClauseWithBindingPattern1.errors.txt +++ /dev/null @@ -1,10 +0,0 @@ -tests/cases/compiler/catchClauseWithBindingPattern1.ts(3,8): error TS1195: Catch clause variable name must be an identifier. - - -==== tests/cases/compiler/catchClauseWithBindingPattern1.ts (1 errors) ==== - try { - } - catch ({a}) { - ~ -!!! error TS1195: Catch clause variable name must be an identifier. - } \ No newline at end of file diff --git a/tests/baselines/reference/catchClauseWithBindingPattern1.js b/tests/baselines/reference/catchClauseWithBindingPattern1.js deleted file mode 100644 index bbdc259946c..00000000000 --- a/tests/baselines/reference/catchClauseWithBindingPattern1.js +++ /dev/null @@ -1,11 +0,0 @@ -//// [catchClauseWithBindingPattern1.ts] -try { -} -catch ({a}) { -} - -//// [catchClauseWithBindingPattern1.js] -try { -} -catch (a = (void 0).a) { -} diff --git a/tests/baselines/reference/destructuringCatch.js b/tests/baselines/reference/destructuringCatch.js new file mode 100644 index 00000000000..4593b22f127 --- /dev/null +++ b/tests/baselines/reference/destructuringCatch.js @@ -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]; +} diff --git a/tests/baselines/reference/destructuringCatch.symbols b/tests/baselines/reference/destructuringCatch.symbols new file mode 100644 index 00000000000..1d0a954b377 --- /dev/null +++ b/tests/baselines/reference/destructuringCatch.symbols @@ -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)) + +} + diff --git a/tests/baselines/reference/destructuringCatch.types b/tests/baselines/reference/destructuringCatch.types new file mode 100644 index 00000000000..3f057e64e4e --- /dev/null +++ b/tests/baselines/reference/destructuringCatch.types @@ -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 + +} + diff --git a/tests/baselines/reference/redeclareParameterInCatchBlock.errors.txt b/tests/baselines/reference/redeclareParameterInCatchBlock.errors.txt index bd307bb89ae..9fd4c63f02f 100644 --- a/tests/baselines/reference/redeclareParameterInCatchBlock.errors.txt +++ b/tests/baselines/reference/redeclareParameterInCatchBlock.errors.txt @@ -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 { @@ -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; } } - \ No newline at end of file diff --git a/tests/baselines/reference/redeclareParameterInCatchBlock.js b/tests/baselines/reference/redeclareParameterInCatchBlock.js index 704d56676bf..9fafe0b17be 100644 --- a/tests/baselines/reference/redeclareParameterInCatchBlock.js +++ b/tests/baselines/reference/redeclareParameterInCatchBlock.js @@ -14,12 +14,23 @@ try { try { +} catch ([a, b]) { + const [c, b] = [0, 1]; +} + +try { + +} catch ({ a: x, b: x }) { + +} + +try { + } catch(e) { function test() { let e; } } - //// [redeclareParameterInCatchBlock.js] @@ -35,6 +46,15 @@ catch (e) { } try { } +catch ([a, b]) { + const [c, b] = [0, 1]; +} +try { +} +catch ({ a: x, b: x }) { +} +try { +} catch (e) { function test() { let e; diff --git a/tests/cases/compiler/catchClauseWithBindingPattern1.ts b/tests/cases/compiler/catchClauseWithBindingPattern1.ts deleted file mode 100644 index dbd0f81c576..00000000000 --- a/tests/cases/compiler/catchClauseWithBindingPattern1.ts +++ /dev/null @@ -1,4 +0,0 @@ -try { -} -catch ({a}) { -} \ No newline at end of file diff --git a/tests/cases/compiler/redeclareParameterInCatchBlock.ts b/tests/cases/compiler/redeclareParameterInCatchBlock.ts index 37ca5fc3a9e..7f98d68b29d 100644 --- a/tests/cases/compiler/redeclareParameterInCatchBlock.ts +++ b/tests/cases/compiler/redeclareParameterInCatchBlock.ts @@ -14,9 +14,20 @@ try { try { +} catch ([a, b]) { + const [c, b] = [0, 1]; +} + +try { + +} catch ({ a: x, b: x }) { + +} + +try { + } catch(e) { function test() { let e; } } - diff --git a/tests/cases/conformance/es6/destructuring/destructuringCatch.ts b/tests/cases/conformance/es6/destructuring/destructuringCatch.ts new file mode 100644 index 00000000000..31afd672084 --- /dev/null +++ b/tests/cases/conformance/es6/destructuring/destructuringCatch.ts @@ -0,0 +1,29 @@ +// @noImplicitAny: true + +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]) { + +}