Skip to content

Commit aaacfbe

Browse files
committed
fix(47383): throw an error on catch variable with unknown type
1 parent 935c05c commit aaacfbe

File tree

43 files changed

+1240
-38
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+1240
-38
lines changed

src/compiler/checker.ts

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8665,6 +8665,33 @@ namespace ts {
86658665
return undefined;
86668666
}
86678667

8668+
function getTypeForCatchClauseVariableDeclaration(declaration: VariableDeclaration | BindingElement) {
8669+
const typeNode = getEffectiveTypeAnnotationNode(getRootDeclaration(declaration));
8670+
const type = typeNode ? getTypeOfNode(typeNode) :
8671+
useUnknownInCatchVariables ? unknownType : anyType;
8672+
8673+
if (isCatchClauseVariableDeclaration(declaration)) {
8674+
if (typeNode === undefined) {
8675+
if (isBindingPattern(declaration.name)) {
8676+
return getTypeFromBindingPattern(declaration.name, /*includePatternInType*/ false, /*reportErrors*/ false);
8677+
}
8678+
return useUnknownInCatchVariables ? unknownType : anyType;
8679+
}
8680+
// an errorType will make `checkTryStatement` issue an error
8681+
return isTypeAny(type) || type === unknownType ? type : errorType;
8682+
}
8683+
8684+
if (isBindingElement(declaration) && isBindingPattern(declaration.parent)) {
8685+
if (isCatchClauseVariableDeclaration(declaration.parent.parent) && type === unknownType) {
8686+
return getBindingElementTypeFromParentType(declaration, unknownType);
8687+
}
8688+
if (declaration.dotDotDotToken) {
8689+
return anyType;
8690+
}
8691+
return getTypeForBindingElement(declaration);
8692+
}
8693+
}
8694+
86688695
function isNullOrUndefined(node: Expression) {
86698696
const expr = skipParentheses(node, /*excludeJSDocTypeAssertions*/ true);
86708697
return expr.kind === SyntaxKind.NullKeyword || expr.kind === SyntaxKind.Identifier && getResolvedSymbol(expr as Identifier) === undefinedSymbol;
@@ -8697,6 +8724,10 @@ namespace ts {
86978724
return checkRightHandSideOfForOf(forOfStatement) || anyType;
86988725
}
86998726

8727+
if (isCatchClauseVariableDeclarationOrBindingElement(declaration)) {
8728+
return getTypeForCatchClauseVariableDeclaration(declaration as VariableDeclaration | BindingElement);
8729+
}
8730+
87008731
if (isBindingPattern(declaration.parent)) {
87018732
return getTypeForBindingElement(declaration as BindingElement);
87028733
}
@@ -9342,18 +9373,8 @@ namespace ts {
93429373
members.set("exports" as __String, result);
93439374
return createAnonymousType(symbol, members, emptyArray, emptyArray, emptyArray);
93449375
}
9345-
// Handle catch clause variables
93469376
Debug.assertIsDefined(symbol.valueDeclaration);
93479377
const declaration = symbol.valueDeclaration;
9348-
if (isCatchClauseVariableDeclarationOrBindingElement(declaration)) {
9349-
const typeNode = getEffectiveTypeAnnotationNode(declaration);
9350-
if (typeNode === undefined) {
9351-
return useUnknownInCatchVariables ? unknownType : anyType;
9352-
}
9353-
const type = getTypeOfNode(typeNode);
9354-
// an errorType will make `checkTryStatement` issue an error
9355-
return isTypeAny(type) || type === unknownType ? type : errorType;
9356-
}
93579378
// Handle export default expressions
93589379
if (isSourceFile(declaration) && isJsonSourceFile(declaration)) {
93599380
if (!declaration.statements.length) {
@@ -38315,17 +38336,20 @@ namespace ts {
3831538336
function checkTryStatement(node: TryStatement) {
3831638337
// Grammar checking
3831738338
checkGrammarStatementInAmbientContext(node);
38318-
3831938339
checkBlock(node.tryBlock);
38340+
3832038341
const catchClause = node.catchClause;
3832138342
if (catchClause) {
3832238343
// Grammar checking
3832338344
if (catchClause.variableDeclaration) {
3832438345
const declaration = catchClause.variableDeclaration;
38346+
if (isBindingPattern(declaration.name)) {
38347+
forEach(declaration.name.elements, checkSourceElement);
38348+
}
3832538349
const typeNode = getEffectiveTypeAnnotationNode(getRootDeclaration(declaration));
3832638350
if (typeNode) {
3832738351
const type = getTypeForVariableLikeDeclaration(declaration, /*includeOptionality*/ false);
38328-
if (type && !(type.flags & TypeFlags.AnyOrUnknown)) {
38352+
if (type && isErrorType(type)) {
3832938353
grammarErrorOnFirstToken(typeNode, Diagnostics.Catch_clause_variable_type_annotation_must_be_any_or_unknown_if_specified);
3833038354
}
3833138355
}

tests/baselines/reference/catchClauseWithTypeAnnotation.errors.txt

Lines changed: 9 additions & 3 deletions
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 type
63+
~
64+
!!! error TS2339: Property 'x' does not exist on type '{}'.
65+
try { } catch ({ x }: unknown1) { console.log(x); } // error in the type
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.

tests/baselines/reference/catchClauseWithTypeAnnotation.js

Lines changed: 4 additions & 4 deletions
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 type
38+
try { } catch ({ x }: unknown1) { console.log(x); } // error in the type
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 type
128128
try { }
129129
catch (_e) {
130130
var x_6 = _e.x;
131131
console.log(x_6);
132-
} // should be OK
132+
} // error in the type
133133
try { }
134134
catch (_f) {
135135
var x_7 = _f.x;

tests/baselines/reference/catchClauseWithTypeAnnotation.symbols

Lines changed: 2 additions & 2 deletions
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 type
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 type
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, --, --))

tests/baselines/reference/catchClauseWithTypeAnnotation.types

Lines changed: 2 additions & 2 deletions
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 type
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 type
138138
>x : any
139139
>console.log(x) : void
140140
>console.log : (...data: any[]) => void

tests/baselines/reference/jsdocCatchClauseWithTypeAnnotation.errors.txt

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ tests/cases/conformance/jsdoc/foo.js(21,54): error TS2339: Property 'foo' does n
33
tests/cases/conformance/jsdoc/foo.js(22,31): error TS1196: Catch clause variable type annotation must be 'any' or 'unknown' if specified.
44
tests/cases/conformance/jsdoc/foo.js(23,31): error TS1196: Catch clause variable type annotation must be 'any' or 'unknown' if specified.
55
tests/cases/conformance/jsdoc/foo.js(35,7): error TS2492: Cannot redeclare identifier 'err' in catch clause.
6+
tests/cases/conformance/jsdoc/foo.js(46,45): error TS2339: Property 'x' does not exist on type '{}'.
7+
tests/cases/conformance/jsdoc/foo.js(47,45): error TS2339: Property 'x' does not exist on type '{}'.
68
tests/cases/conformance/jsdoc/foo.js(48,31): error TS1196: Catch clause variable type annotation must be 'any' or 'unknown' if specified.
79
tests/cases/conformance/jsdoc/foo.js(49,31): error TS1196: Catch clause variable type annotation must be 'any' or 'unknown' if specified.
810

911

10-
==== tests/cases/conformance/jsdoc/foo.js (7 errors) ====
12+
==== tests/cases/conformance/jsdoc/foo.js (9 errors) ====
1113
/**
1214
* @typedef {any} Any
1315
*/
@@ -63,8 +65,12 @@ tests/cases/conformance/jsdoc/foo.js(49,31): error TS1196: Catch clause variable
6365
try { } catch ({ x }) { } // should be OK
6466
try { } catch (/** @type {any} */ { x }) { x.foo; } // should be OK
6567
try { } catch (/** @type {Any} */ { x }) { x.foo;} // should be OK
66-
try { } catch (/** @type {unknown} */ { x }) { console.log(x); } // should be OK
67-
try { } catch (/** @type {Unknown} */ { x }) { console.log(x); } // should be OK
68+
try { } catch (/** @type {unknown} */ { x }) { console.log(x); } // error in the type
69+
~
70+
!!! error TS2339: Property 'x' does not exist on type '{}'.
71+
try { } catch (/** @type {Unknown} */ { x }) { console.log(x); } // error in the type
72+
~
73+
!!! error TS2339: Property 'x' does not exist on type '{}'.
6874
try { } catch (/** @type {Error} */ { x }) { } // error in the type
6975
~~~~~
7076
!!! error TS1196: Catch clause variable type annotation must be 'any' or 'unknown' if specified.

tests/baselines/reference/jsdocCatchClauseWithTypeAnnotation.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ function fn() {
4444
try { } catch ({ x }) { } // should be OK
4545
try { } catch (/** @type {any} */ { x }) { x.foo; } // should be OK
4646
try { } catch (/** @type {Any} */ { x }) { x.foo;} // should be OK
47-
try { } catch (/** @type {unknown} */ { x }) { console.log(x); } // should be OK
48-
try { } catch (/** @type {Unknown} */ { x }) { console.log(x); } // should be OK
47+
try { } catch (/** @type {unknown} */ { x }) { console.log(x); } // error in the type
48+
try { } catch (/** @type {Unknown} */ { x }) { console.log(x); } // error in the type
4949
try { } catch (/** @type {Error} */ { x }) { } // error in the type
5050
try { } catch (/** @type {object} */ { x }) { } // error in the type
5151
}
@@ -132,11 +132,11 @@ function fn() {
132132
try { }
133133
catch ( /** @type {unknown} */{ x }) {
134134
console.log(x);
135-
} // should be OK
135+
} // error in the type
136136
try { }
137137
catch ( /** @type {Unknown} */{ x }) {
138138
console.log(x);
139-
} // should be OK
139+
} // error in the type
140140
try { }
141141
catch ( /** @type {Error} */{ x }) { } // error in the type
142142
try { }

tests/baselines/reference/jsdocCatchClauseWithTypeAnnotation.symbols

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,14 +110,14 @@ function fn() {
110110
>x : Symbol(x, Decl(foo.js, 44, 39))
111111
>x : Symbol(x, Decl(foo.js, 44, 39))
112112

113-
try { } catch (/** @type {unknown} */ { x }) { console.log(x); } // should be OK
113+
try { } catch (/** @type {unknown} */ { x }) { console.log(x); } // error in the type
114114
>x : Symbol(x, Decl(foo.js, 45, 43))
115115
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
116116
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
117117
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
118118
>x : Symbol(x, Decl(foo.js, 45, 43))
119119

120-
try { } catch (/** @type {Unknown} */ { x }) { console.log(x); } // should be OK
120+
try { } catch (/** @type {Unknown} */ { x }) { console.log(x); } // error in the type
121121
>x : Symbol(x, Decl(foo.js, 46, 43))
122122
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
123123
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))

tests/baselines/reference/jsdocCatchClauseWithTypeAnnotation.types

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,15 +131,15 @@ function fn() {
131131
>x : any
132132
>foo : any
133133

134-
try { } catch (/** @type {unknown} */ { x }) { console.log(x); } // should be OK
134+
try { } catch (/** @type {unknown} */ { x }) { console.log(x); } // error in the type
135135
>x : any
136136
>console.log(x) : void
137137
>console.log : (...data: any[]) => void
138138
>console : Console
139139
>log : (...data: any[]) => void
140140
>x : any
141141

142-
try { } catch (/** @type {Unknown} */ { x }) { console.log(x); } // should be OK
142+
try { } catch (/** @type {Unknown} */ { x }) { console.log(x); } // error in the type
143143
>x : any
144144
>console.log(x) : void
145145
>console.log : (...data: any[]) => void
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
//// [useUnknownInCatchVariables02.ts]
2+
try {}
3+
catch ({ name }) { name; }
4+
5+
try {}
6+
catch ({ name }) {}
7+
8+
try {}
9+
catch ({ a: { b } }) { b; }
10+
11+
try {}
12+
catch ({ a: { b } }) {}
13+
14+
try {}
15+
catch ([a, { b }]) { a; }
16+
17+
try {}
18+
catch ([a, { b }]) {}
19+
20+
21+
//// [useUnknownInCatchVariables02.js]
22+
try { }
23+
catch (_a) {
24+
var name = _a.name;
25+
name;
26+
}
27+
try { }
28+
catch (_b) {
29+
var name = _b.name;
30+
}
31+
try { }
32+
catch (_c) {
33+
var b = _c.a.b;
34+
b;
35+
}
36+
try { }
37+
catch (_d) {
38+
var b = _d.a.b;
39+
}
40+
try { }
41+
catch (_e) {
42+
var a = _e[0], b = _e[1].b;
43+
a;
44+
}
45+
try { }
46+
catch (_f) {
47+
var a = _f[0], b = _f[1].b;
48+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
=== tests/cases/compiler/useUnknownInCatchVariables02.ts ===
2+
try {}
3+
catch ({ name }) { name; }
4+
>name : Symbol(name, Decl(useUnknownInCatchVariables02.ts, 1, 8))
5+
>name : Symbol(name, Decl(useUnknownInCatchVariables02.ts, 1, 8))
6+
7+
try {}
8+
catch ({ name }) {}
9+
>name : Symbol(name, Decl(useUnknownInCatchVariables02.ts, 4, 8))
10+
11+
try {}
12+
catch ({ a: { b } }) { b; }
13+
>a : Symbol(a)
14+
>b : Symbol(b, Decl(useUnknownInCatchVariables02.ts, 7, 13))
15+
>b : Symbol(b, Decl(useUnknownInCatchVariables02.ts, 7, 13))
16+
17+
try {}
18+
catch ({ a: { b } }) {}
19+
>a : Symbol(a)
20+
>b : Symbol(b, Decl(useUnknownInCatchVariables02.ts, 10, 13))
21+
22+
try {}
23+
catch ([a, { b }]) { a; }
24+
>a : Symbol(a, Decl(useUnknownInCatchVariables02.ts, 13, 8))
25+
>b : Symbol(b, Decl(useUnknownInCatchVariables02.ts, 13, 12))
26+
>a : Symbol(a, Decl(useUnknownInCatchVariables02.ts, 13, 8))
27+
28+
try {}
29+
catch ([a, { b }]) {}
30+
>a : Symbol(a, Decl(useUnknownInCatchVariables02.ts, 16, 8))
31+
>b : Symbol(b, Decl(useUnknownInCatchVariables02.ts, 16, 12))
32+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
=== tests/cases/compiler/useUnknownInCatchVariables02.ts ===
2+
try {}
3+
catch ({ name }) { name; }
4+
>name : any
5+
>name : any
6+
7+
try {}
8+
catch ({ name }) {}
9+
>name : any
10+
11+
try {}
12+
catch ({ a: { b } }) { b; }
13+
>a : any
14+
>b : any
15+
>b : any
16+
17+
try {}
18+
catch ({ a: { b } }) {}
19+
>a : any
20+
>b : any
21+
22+
try {}
23+
catch ([a, { b }]) { a; }
24+
>a : any
25+
>b : any
26+
>a : any
27+
28+
try {}
29+
catch ([a, { b }]) {}
30+
>a : any
31+
>b : any
32+

0 commit comments

Comments
 (0)