Ignored catch parameter#17517
Conversation
4d29920 to
58c8a2c
Compare
| "category": "Error", | ||
| "code": 2713 | ||
| }, | ||
| "Duplicate identifier '_ignoredCatchParameter'. Compiler uses the parameter declaration '_ignoredCatchParameter' to bind ignored catched exceptions.": { |
There was a problem hiding this comment.
If you still need this:
Compiler uses the declaration '_ignoredCatchBinding` to bind ignored 'catch' clause parameters.
|
|
||
| function visitCatchClause(node: CatchClause): CatchClause { | ||
| if (!node.variableDeclaration) { | ||
| return updateCatchClause(node, createVariableDeclaration("_ignoredCatchParameter"), node.block); |
There was a problem hiding this comment.
Instead of createVariableDeclaration, we have a createUniqueName that you can call, which relieves you of having to write the error out. I think @RyanCavanaugh was just joking about reserving a variable name (but I'm not sure 😄 ).
There was a problem hiding this comment.
Yes, you should either use createUniqueName or createTempVariable.
There was a problem hiding this comment.
Added createTempVariable(undefined) here.
|
Looks great other than the unique identifier change I suggested (which may lead you to remove some tests relating to reserving these identifiers). I think @rbuckton may want to review the changes. |
|
(Also, thanks for the PR @tinganho, long time no see! 😄) |
| if (!node.variableDeclaration) { | ||
| transformFlags |= TransformFlags.AssertESNext; | ||
| } | ||
| else if (/* node.variableDeclaration && */ isBindingPattern(node.variableDeclaration.name)) { |
There was a problem hiding this comment.
The comment is unnecessary
| else if (/* !catchClause.variableDeclaration && */ languageVersion < ScriptTarget.ESNext) { | ||
| const blockLocals = catchClause.block.locals; | ||
| if (blockLocals) { | ||
| forEachKey(blockLocals, caughtName => { |
There was a problem hiding this comment.
Do we need a name? We can generate a name in the transforms.
There was a problem hiding this comment.
I removed this block of change. Since we don't need check and error on a named variable anymore.
| const ancestorFacts = enterSubtree(HierarchyFacts.BlockScopeExcludes, HierarchyFacts.BlockScopeIncludes); | ||
| let updated: CatchClause; | ||
| if (isBindingPattern(node.variableDeclaration.name)) { | ||
| if (node.variableDeclaration && isBindingPattern(node.variableDeclaration.name)) { |
There was a problem hiding this comment.
By the time we get to the es2015 transform we must already have a variableDeclaration. Filling in the missing binding should be handled in the esnext transform. At best we should have a debug assertion.
There was a problem hiding this comment.
Agreed!
Debug.assert(!!node.variableDeclaration, "Catch clauses should always be present when downleveling ES2015 code.");|
|
||
| function visitCatchClause(node: CatchClause): CatchClause { | ||
| if (!node.variableDeclaration) { | ||
| return updateCatchClause(node, createVariableDeclaration("_ignoredCatchParameter"), node.block); |
There was a problem hiding this comment.
Yes, you should either use createUniqueName or createTempVariable.
| kind: SyntaxKind.CatchClause; | ||
| parent?: TryStatement; | ||
| variableDeclaration: VariableDeclaration; | ||
| parent?: TryStatement; // We parse missing try statements |
There was a problem hiding this comment.
What does this comment mean? This property is present to reduce costs when walking up the AST.
There was a problem hiding this comment.
It wasn't apparent to me that the parent can be missing in a catch clause.
|
Sounds like you want |
|
Yes, specifically |
|
(Thanks @DanielRosenwasser , I have been quite busy for a while, so I haven't have time to participate so much here. Though, working with TS projects all day 😄 ) |
|
I now, removed the named variable |
weswigham
left a comment
There was a problem hiding this comment.
I also bemoan apparently having no coverage in the conformance suite for
try {} catch() {}which is an error both before and after this change.
|
|
||
| function visitCatchClause(node: CatchClause): CatchClause { | ||
| if (!node.variableDeclaration) { | ||
| return updateCatchClause(node, createVariableDeclaration(createTempVariable(/*recordTempVariable*/ undefined)), node.block); |
There was a problem hiding this comment.
node.block still needs to be recursively visited here, no?
function* doFoo(foo: any) {
try {
throw "whatever";
}
catch {
try {
throw "again"
}
catch {
for await (const c of foo) {
c;
}
}
}
}There was a problem hiding this comment.
@weswigham is correct, you should recursively visit the block of the catch clause.
|
@weswigham I think I know why, it messes up checking with the lines after it: |
|
I updated the code with recursive visits. |
| if (node.variableDeclaration) { | ||
| writeToken(SyntaxKind.OpenParenToken, openParenPos); | ||
| emit(node.variableDeclaration); | ||
| writeToken(SyntaxKind.CloseParenToken, node.variableDeclaration ? node.variableDeclaration.end : openParenPos); |
There was a problem hiding this comment.
The conditional on this line is no longer required since you are checking node.variableDeclaration on 2134.
| if (parseExpected(SyntaxKind.OpenParenToken)) { | ||
|
|
||
| if (parseOptional(SyntaxKind.OpenParenToken)) { | ||
| result.variableDeclaration = parseVariableDeclaration(); |
There was a problem hiding this comment.
Conditionally setting result.variableDeclaration introduces polymorphism as we now have two CatchClause shapes, one with the property and one without. This can cause performance degradation in V8 (NodeJS). I would recommend you add an else clause that sets result.variableDeclaration = undefined to keep the shape of CatchClause the same regardless of which form the user writes.
| function visitCatchClause(node: CatchClause): CatchClause { | ||
| const ancestorFacts = enterSubtree(HierarchyFacts.BlockScopeExcludes, HierarchyFacts.BlockScopeIncludes); | ||
| let updated: CatchClause; | ||
| Debug.assert(!!node.variableDeclaration, "Catch clauses should always be present when downleveling ES2015 code."); |
There was a problem hiding this comment.
How about "Catch clause variable should always be present when downleveling ES2015"?
| kind: SyntaxKind.CatchClause; | ||
| parent?: TryStatement; | ||
| variableDeclaration: VariableDeclaration; | ||
| parent?: TryStatement; // We make this optional to parse missing try statements |
There was a problem hiding this comment.
The comment is unnecessary. We only re-introduce parent here to refine its type for methods in the checker. parent is optional because it parent pointers are not always set, especially during tree transformations.
| parent?: TryStatement; | ||
| variableDeclaration: VariableDeclaration; | ||
| parent?: TryStatement; // We make this optional to parse missing try statements | ||
| variableDeclaration?: VariableDeclaration; |
There was a problem hiding this comment.
Can you update createCatchClause and updateCatchClause in factory.ts and add | undefined to the variableDeclaration parameters in both cases? This is needed for those using the Compiler API with -strictNullChecks enabled for their project.
invalidTryStatements.ts covers grammar errors handled in the checker. invalidTryStatements2.ts covers parse errors. I think invalidTryStatements2.ts is a better place for |
I added both the test cases |
|
Looks great! Thanks for the contribution! |
|
Thanks for the great review! |

Fixes #17467