Skip to content

Commit b7f619d

Browse files
authored
Make catch clause checking consistent with variable declarations (#52240)
1 parent ecaf6d9 commit b7f619d

File tree

30 files changed

+1719
-42
lines changed

30 files changed

+1719
-42
lines changed

Diff for: src/compiler/checker.ts

+15-13
Original file line numberDiff line numberDiff line change
@@ -10219,6 +10219,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1021910219

1022010220
// Use type from type annotation if one is present
1022110221
const declaredType = tryGetTypeFromEffectiveTypeNode(declaration);
10222+
if (isCatchClauseVariableDeclarationOrBindingElement(declaration)) {
10223+
if (declaredType) {
10224+
// If the catch clause is explicitly annotated with any or unknown, accept it, otherwise error.
10225+
return isTypeAny(declaredType) || declaredType === unknownType ? declaredType : errorType;
10226+
}
10227+
// If the catch clause is not explicitly annotated, treat it as though it were explicitly
10228+
// annotated with unknown or any, depending on useUnknownInCatchVariables.
10229+
return useUnknownInCatchVariables ? unknownType : anyType;
10230+
}
1022210231
if (declaredType) {
1022310232
return addOptionality(declaredType, isProperty, isOptional);
1022410233
}
@@ -10877,18 +10886,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1087710886
members.set("exports" as __String, result);
1087810887
return createAnonymousType(symbol, members, emptyArray, emptyArray, emptyArray);
1087910888
}
10880-
// Handle catch clause variables
1088110889
Debug.assertIsDefined(symbol.valueDeclaration);
1088210890
const declaration = symbol.valueDeclaration;
10883-
if (isCatchClauseVariableDeclarationOrBindingElement(declaration)) {
10884-
const typeNode = getEffectiveTypeAnnotationNode(declaration);
10885-
if (typeNode === undefined) {
10886-
return useUnknownInCatchVariables ? unknownType : anyType;
10887-
}
10888-
const type = getTypeOfNode(typeNode);
10889-
// an errorType will make `checkTryStatement` issue an error
10890-
return isTypeAny(type) || type === unknownType ? type : errorType;
10891-
}
1089210891
// Handle export default expressions
1089310892
if (isSourceFile(declaration) && isJsonSourceFile(declaration)) {
1089410893
if (!declaration.statements.length) {
@@ -27079,6 +27078,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2707927078
isFunctionLike(node) && !getImmediatelyInvokedFunctionExpression(node) ||
2708027079
node.kind === SyntaxKind.ModuleBlock ||
2708127080
node.kind === SyntaxKind.SourceFile ||
27081+
node.kind === SyntaxKind.CatchClause ||
2708227082
node.kind === SyntaxKind.PropertyDeclaration)!;
2708327083
}
2708427084

@@ -27451,6 +27451,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2745127451
const isParameter = getRootDeclaration(declaration).kind === SyntaxKind.Parameter;
2745227452
const declarationContainer = getControlFlowContainer(declaration);
2745327453
let flowContainer = getControlFlowContainer(node);
27454+
const isCatch = flowContainer.kind === SyntaxKind.CatchClause;
2745427455
const isOuterVariable = flowContainer !== declarationContainer;
2745527456
const isSpreadDestructuringAssignmentTarget = node.parent && node.parent.parent && isSpreadAssignment(node.parent) && isDestructuringAssignmentTarget(node.parent.parent);
2745627457
const isModuleExports = symbol.flags & SymbolFlags.ModuleExports;
@@ -27465,7 +27466,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2746527466
// We only look for uninitialized variables in strict null checking mode, and only when we can analyze
2746627467
// the entire control flow graph from the variable's declaration (i.e. when the flow container and
2746727468
// declaration container are the same).
27468-
const assumeInitialized = isParameter || isAlias || isOuterVariable || isSpreadDestructuringAssignmentTarget || isModuleExports || isSameScopedBindingElement(node, declaration) ||
27469+
const assumeInitialized = isParameter || isCatch || isAlias || isOuterVariable || isSpreadDestructuringAssignmentTarget || isModuleExports || isSameScopedBindingElement(node, declaration) ||
2746927470
type !== autoType && type !== autoArrayType && (!strictNullChecks || (type.flags & (TypeFlags.AnyOrUnknown | TypeFlags.Void)) !== 0 ||
2747027471
isInTypeQuery(node) || isInAmbientOrTypeNode(node) || node.parent.kind === SyntaxKind.ExportSpecifier) ||
2747127472
node.parent.kind === SyntaxKind.NonNullExpression ||
@@ -41230,9 +41231,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
4123041231
// Grammar checking
4123141232
if (catchClause.variableDeclaration) {
4123241233
const declaration = catchClause.variableDeclaration;
41233-
const typeNode = getEffectiveTypeAnnotationNode(getRootDeclaration(declaration));
41234+
checkVariableLikeDeclaration(declaration);
41235+
const typeNode = getEffectiveTypeAnnotationNode(declaration);
4123441236
if (typeNode) {
41235-
const type = getTypeForVariableLikeDeclaration(declaration, /*includeOptionality*/ false, CheckMode.Normal);
41237+
const type = getTypeFromTypeNode(typeNode);
4123641238
if (type && !(type.flags & TypeFlags.AnyOrUnknown)) {
4123741239
grammarErrorOnFirstToken(typeNode, Diagnostics.Catch_clause_variable_type_annotation_must_be_any_or_unknown_if_specified);
4123841240
}

Diff for: tests/baselines/reference/catchClauseWithTypeAnnotation.errors.txt

+9-3
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@ tests/cases/conformance/statements/tryStatements/catchClauseWithTypeAnnotation.t
44
tests/cases/conformance/statements/tryStatements/catchClauseWithTypeAnnotation.ts(20,23): error TS1196: Catch clause variable type annotation must be 'any' or 'unknown' if specified.
55
tests/cases/conformance/statements/tryStatements/catchClauseWithTypeAnnotation.ts(29,29): error TS2492: Cannot redeclare identifier 'x' in catch clause.
66
tests/cases/conformance/statements/tryStatements/catchClauseWithTypeAnnotation.ts(30,29): error TS2403: Subsequent variable declarations must have the same type. Variable 'x' must be of type 'boolean', but here has type 'string'.
7+
tests/cases/conformance/statements/tryStatements/catchClauseWithTypeAnnotation.ts(36,22): error TS2339: Property 'x' does not exist on type '{}'.
8+
tests/cases/conformance/statements/tryStatements/catchClauseWithTypeAnnotation.ts(37,22): error TS2339: Property 'x' does not exist on type '{}'.
79
tests/cases/conformance/statements/tryStatements/catchClauseWithTypeAnnotation.ts(38,27): error TS1196: Catch clause variable type annotation must be 'any' or 'unknown' if specified.
810
tests/cases/conformance/statements/tryStatements/catchClauseWithTypeAnnotation.ts(39,27): error TS1196: Catch clause variable type annotation must be 'any' or 'unknown' if specified.
911

1012

11-
==== tests/cases/conformance/statements/tryStatements/catchClauseWithTypeAnnotation.ts (8 errors) ====
13+
==== tests/cases/conformance/statements/tryStatements/catchClauseWithTypeAnnotation.ts (10 errors) ====
1214
type any1 = any;
1315
type unknown1 = unknown;
1416

@@ -57,8 +59,12 @@ tests/cases/conformance/statements/tryStatements/catchClauseWithTypeAnnotation.t
5759
try { } catch ({ x }) { } // should be OK
5860
try { } catch ({ x }: any) { x.foo; } // should be OK
5961
try { } catch ({ x }: any1) { x.foo;} // should be OK
60-
try { } catch ({ x }: unknown) { console.log(x); } // should be OK
61-
try { } catch ({ x }: unknown1) { console.log(x); } // should be OK
62+
try { } catch ({ x }: unknown) { console.log(x); } // error in the destructure
63+
~
64+
!!! error TS2339: Property 'x' does not exist on type '{}'.
65+
try { } catch ({ x }: unknown1) { console.log(x); } // error in the destructure
66+
~
67+
!!! error TS2339: Property 'x' does not exist on type '{}'.
6268
try { } catch ({ x }: object) { } // error in the type
6369
~~~~~~
6470
!!! error TS1196: Catch clause variable type annotation must be 'any' or 'unknown' if specified.

Diff for: tests/baselines/reference/catchClauseWithTypeAnnotation.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ function fn(x: boolean) {
3434
try { } catch ({ x }) { } // should be OK
3535
try { } catch ({ x }: any) { x.foo; } // should be OK
3636
try { } catch ({ x }: any1) { x.foo;} // should be OK
37-
try { } catch ({ x }: unknown) { console.log(x); } // should be OK
38-
try { } catch ({ x }: unknown1) { console.log(x); } // should be OK
37+
try { } catch ({ x }: unknown) { console.log(x); } // error in the destructure
38+
try { } catch ({ x }: unknown1) { console.log(x); } // error in the destructure
3939
try { } catch ({ x }: object) { } // error in the type
4040
try { } catch ({ x }: Error) { } // error in the type
4141
}
@@ -124,12 +124,12 @@ function fn(x) {
124124
catch (_d) {
125125
var x_5 = _d.x;
126126
console.log(x_5);
127-
} // should be OK
127+
} // error in the destructure
128128
try { }
129129
catch (_e) {
130130
var x_6 = _e.x;
131131
console.log(x_6);
132-
} // should be OK
132+
} // error in the destructure
133133
try { }
134134
catch (_f) {
135135
var x_7 = _f.x;

Diff for: tests/baselines/reference/catchClauseWithTypeAnnotation.symbols

+2-2
Original file line numberDiff line numberDiff line change
@@ -112,14 +112,14 @@ function fn(x: boolean) {
112112
>any1 : Symbol(any1, Decl(catchClauseWithTypeAnnotation.ts, 0, 0))
113113
>x : Symbol(x, Decl(catchClauseWithTypeAnnotation.ts, 34, 20))
114114

115-
try { } catch ({ x }: unknown) { console.log(x); } // should be OK
115+
try { } catch ({ x }: unknown) { console.log(x); } // error in the destructure
116116
>x : Symbol(x, Decl(catchClauseWithTypeAnnotation.ts, 35, 20))
117117
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
118118
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
119119
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
120120
>x : Symbol(x, Decl(catchClauseWithTypeAnnotation.ts, 35, 20))
121121

122-
try { } catch ({ x }: unknown1) { console.log(x); } // should be OK
122+
try { } catch ({ x }: unknown1) { console.log(x); } // error in the destructure
123123
>x : Symbol(x, Decl(catchClauseWithTypeAnnotation.ts, 36, 20))
124124
>unknown1 : Symbol(unknown1, Decl(catchClauseWithTypeAnnotation.ts, 0, 16))
125125
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))

Diff for: tests/baselines/reference/catchClauseWithTypeAnnotation.types

+2-2
Original file line numberDiff line numberDiff line change
@@ -126,15 +126,15 @@ function fn(x: boolean) {
126126
>x : any
127127
>foo : any
128128

129-
try { } catch ({ x }: unknown) { console.log(x); } // should be OK
129+
try { } catch ({ x }: unknown) { console.log(x); } // error in the destructure
130130
>x : any
131131
>console.log(x) : void
132132
>console.log : (...data: any[]) => void
133133
>console : Console
134134
>log : (...data: any[]) => void
135135
>x : any
136136

137-
try { } catch ({ x }: unknown1) { console.log(x); } // should be OK
137+
try { } catch ({ x }: unknown1) { console.log(x); } // error in the destructure
138138
>x : any
139139
>console.log(x) : void
140140
>console.log : (...data: any[]) => void
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
tests/cases/compiler/destructureCatchClause.ts(26,17): error TS2339: Property 'x' does not exist on type '{}'.
2+
tests/cases/compiler/destructureCatchClause.ts(27,15): error TS2461: Type 'unknown' is not an array type.
3+
tests/cases/compiler/destructureCatchClause.ts(29,17): error TS2339: Property 'a' does not exist on type '{}'.
4+
tests/cases/compiler/destructureCatchClause.ts(30,17): error TS2339: Property 'a' does not exist on type '{}'.
5+
tests/cases/compiler/destructureCatchClause.ts(32,15): error TS2461: Type 'unknown' is not an array type.
6+
tests/cases/compiler/destructureCatchClause.ts(33,15): error TS2461: Type 'unknown' is not an array type.
7+
tests/cases/compiler/destructureCatchClause.ts(35,17): error TS2339: Property 'a' does not exist on type '{}'.
8+
9+
10+
==== tests/cases/compiler/destructureCatchClause.ts (7 errors) ====
11+
// These are okay with useUnknownInCatchVariables=false, but not okay with useUnknownInCatchVariables=true.
12+
try {} catch ({ x }) { x }
13+
try {} catch ([ x ]) { x }
14+
15+
try {} catch ({ a: { x } }) { x }
16+
try {} catch ({ a: [ x ] }) { x }
17+
18+
try {} catch ([{ x }]) { x }
19+
try {} catch ([[ x ]]) { x }
20+
21+
try {} catch ({ a: { b: { c: { x }} }}) { x }
22+
23+
24+
try {} catch ({ x }: any) { x }
25+
try {} catch ([ x ]: any) { x }
26+
27+
try {} catch ({ a: { x } }: any) { x }
28+
try {} catch ({ a: [ x ] }: any) { x }
29+
30+
try {} catch ([{ x }]: any) { x }
31+
try {} catch ([[ x ]]: any) { x }
32+
33+
try {} catch ({ a: { b: { c: { x }} }}: any) { x }
34+
35+
36+
try {} catch ({ x }: unknown) { x }
37+
~
38+
!!! error TS2339: Property 'x' does not exist on type '{}'.
39+
try {} catch ([ x ]: unknown) { x }
40+
~~~~~
41+
!!! error TS2461: Type 'unknown' is not an array type.
42+
43+
try {} catch ({ a: { x } }: unknown) { x }
44+
~
45+
!!! error TS2339: Property 'a' does not exist on type '{}'.
46+
try {} catch ({ a: [ x ] }: unknown) { x }
47+
~
48+
!!! error TS2339: Property 'a' does not exist on type '{}'.
49+
50+
try {} catch ([{ x }]: unknown) { x }
51+
~~~~~~~
52+
!!! error TS2461: Type 'unknown' is not an array type.
53+
try {} catch ([[ x ]]: unknown) { x }
54+
~~~~~~~
55+
!!! error TS2461: Type 'unknown' is not an array type.
56+
57+
try {} catch ({ a: { b: { c: { x }} }}: unknown) { x }
58+
~
59+
!!! error TS2339: Property 'a' does not exist on type '{}'.
60+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
//// [destructureCatchClause.ts]
2+
// These are okay with useUnknownInCatchVariables=false, but not okay with useUnknownInCatchVariables=true.
3+
try {} catch ({ x }) { x }
4+
try {} catch ([ x ]) { x }
5+
6+
try {} catch ({ a: { x } }) { x }
7+
try {} catch ({ a: [ x ] }) { x }
8+
9+
try {} catch ([{ x }]) { x }
10+
try {} catch ([[ x ]]) { x }
11+
12+
try {} catch ({ a: { b: { c: { x }} }}) { x }
13+
14+
15+
try {} catch ({ x }: any) { x }
16+
try {} catch ([ x ]: any) { x }
17+
18+
try {} catch ({ a: { x } }: any) { x }
19+
try {} catch ({ a: [ x ] }: any) { x }
20+
21+
try {} catch ([{ x }]: any) { x }
22+
try {} catch ([[ x ]]: any) { x }
23+
24+
try {} catch ({ a: { b: { c: { x }} }}: any) { x }
25+
26+
27+
try {} catch ({ x }: unknown) { x }
28+
try {} catch ([ x ]: unknown) { x }
29+
30+
try {} catch ({ a: { x } }: unknown) { x }
31+
try {} catch ({ a: [ x ] }: unknown) { x }
32+
33+
try {} catch ([{ x }]: unknown) { x }
34+
try {} catch ([[ x ]]: unknown) { x }
35+
36+
try {} catch ({ a: { b: { c: { x }} }}: unknown) { x }
37+
38+
39+
//// [destructureCatchClause.js]
40+
// These are okay with useUnknownInCatchVariables=false, but not okay with useUnknownInCatchVariables=true.
41+
try { }
42+
catch (_a) {
43+
var x = _a.x;
44+
x;
45+
}
46+
try { }
47+
catch (_b) {
48+
var x = _b[0];
49+
x;
50+
}
51+
try { }
52+
catch (_c) {
53+
var x = _c.a.x;
54+
x;
55+
}
56+
try { }
57+
catch (_d) {
58+
var x = _d.a[0];
59+
x;
60+
}
61+
try { }
62+
catch (_e) {
63+
var x = _e[0].x;
64+
x;
65+
}
66+
try { }
67+
catch (_f) {
68+
var x = _f[0][0];
69+
x;
70+
}
71+
try { }
72+
catch (_g) {
73+
var x = _g.a.b.c.x;
74+
x;
75+
}
76+
try { }
77+
catch (_h) {
78+
var x = _h.x;
79+
x;
80+
}
81+
try { }
82+
catch (_j) {
83+
var x = _j[0];
84+
x;
85+
}
86+
try { }
87+
catch (_k) {
88+
var x = _k.a.x;
89+
x;
90+
}
91+
try { }
92+
catch (_l) {
93+
var x = _l.a[0];
94+
x;
95+
}
96+
try { }
97+
catch (_m) {
98+
var x = _m[0].x;
99+
x;
100+
}
101+
try { }
102+
catch (_o) {
103+
var x = _o[0][0];
104+
x;
105+
}
106+
try { }
107+
catch (_p) {
108+
var x = _p.a.b.c.x;
109+
x;
110+
}
111+
try { }
112+
catch (_q) {
113+
var x = _q.x;
114+
x;
115+
}
116+
try { }
117+
catch (_r) {
118+
var x = _r[0];
119+
x;
120+
}
121+
try { }
122+
catch (_s) {
123+
var x = _s.a.x;
124+
x;
125+
}
126+
try { }
127+
catch (_t) {
128+
var x = _t.a[0];
129+
x;
130+
}
131+
try { }
132+
catch (_u) {
133+
var x = _u[0].x;
134+
x;
135+
}
136+
try { }
137+
catch (_v) {
138+
var x = _v[0][0];
139+
x;
140+
}
141+
try { }
142+
catch (_w) {
143+
var x = _w.a.b.c.x;
144+
x;
145+
}

0 commit comments

Comments
 (0)