Skip to content

Commit 4ea6586

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

File tree

39 files changed

+954
-48
lines changed

39 files changed

+954
-48
lines changed

src/compiler/checker.ts

+14-10
Original file line numberDiff line numberDiff line change
@@ -8665,6 +8665,12 @@ namespace ts {
86658665
return undefined;
86668666
}
86678667

8668+
function getTypeForCatchClauseVariableDeclaration(declaration: VariableDeclaration) {
8669+
const typeNode = getEffectiveTypeAnnotationNode(declaration);
8670+
return typeNode ? getTypeOfNode(typeNode) :
8671+
useUnknownInCatchVariables ? unknownType : anyType;
8672+
}
8673+
86688674
function isNullOrUndefined(node: Expression) {
86698675
const expr = skipParentheses(node, /*excludeJSDocTypeAssertions*/ true);
86708676
return expr.kind === SyntaxKind.NullKeyword || expr.kind === SyntaxKind.Identifier && getResolvedSymbol(expr as Identifier) === undefinedSymbol;
@@ -8697,6 +8703,10 @@ namespace ts {
86978703
return checkRightHandSideOfForOf(forOfStatement) || anyType;
86988704
}
86998705

8706+
if (isCatchClauseVariableDeclaration(declaration)) {
8707+
return getTypeForCatchClauseVariableDeclaration(declaration as VariableDeclaration);
8708+
}
8709+
87008710
if (isBindingPattern(declaration.parent)) {
87018711
return getTypeForBindingElement(declaration as BindingElement);
87028712
}
@@ -9345,15 +9355,6 @@ namespace ts {
93459355
// Handle catch clause variables
93469356
Debug.assertIsDefined(symbol.valueDeclaration);
93479357
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-
}
93579358
// Handle export default expressions
93589359
if (isSourceFile(declaration) && isJsonSourceFile(declaration)) {
93599360
if (!declaration.statements.length) {
@@ -38315,13 +38316,16 @@ namespace ts {
3831538316
function checkTryStatement(node: TryStatement) {
3831638317
// Grammar checking
3831738318
checkGrammarStatementInAmbientContext(node);
38318-
3831938319
checkBlock(node.tryBlock);
38320+
3832038321
const catchClause = node.catchClause;
3832138322
if (catchClause) {
3832238323
// Grammar checking
3832338324
if (catchClause.variableDeclaration) {
3832438325
const declaration = catchClause.variableDeclaration;
38326+
if (isBindingPattern(declaration.name)) {
38327+
forEach(declaration.name.elements, checkSourceElement);
38328+
}
3832538329
const typeNode = getEffectiveTypeAnnotationNode(getRootDeclaration(declaration));
3832638330
if (typeNode) {
3832738331
const type = getTypeForVariableLikeDeclaration(declaration, /*includeOptionality*/ false);

tests/baselines/reference/catchClauseWithTypeAnnotation.errors.txt

+18-3
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,18 @@ tests/cases/conformance/statements/tryStatements/catchClauseWithTypeAnnotation.t
22
tests/cases/conformance/statements/tryStatements/catchClauseWithTypeAnnotation.ts(18,37): error TS2339: Property 'foo' does not exist on type 'unknown'.
33
tests/cases/conformance/statements/tryStatements/catchClauseWithTypeAnnotation.ts(19,23): error TS1196: Catch clause variable type annotation must be 'any' or 'unknown' if specified.
44
tests/cases/conformance/statements/tryStatements/catchClauseWithTypeAnnotation.ts(20,23): error TS1196: Catch clause variable type annotation must be 'any' or 'unknown' if specified.
5+
tests/cases/conformance/statements/tryStatements/catchClauseWithTypeAnnotation.ts(25,23): error TS2339: Property 'toLowerCase' does not exist on type 'number'.
56
tests/cases/conformance/statements/tryStatements/catchClauseWithTypeAnnotation.ts(29,29): error TS2492: Cannot redeclare identifier 'x' in catch clause.
67
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'.
8+
tests/cases/conformance/statements/tryStatements/catchClauseWithTypeAnnotation.ts(36,22): error TS2339: Property 'x' does not exist on type '{}'.
9+
tests/cases/conformance/statements/tryStatements/catchClauseWithTypeAnnotation.ts(37,22): error TS2339: Property 'x' does not exist on type '{}'.
10+
tests/cases/conformance/statements/tryStatements/catchClauseWithTypeAnnotation.ts(38,22): error TS2339: Property 'x' does not exist on type '{}'.
711
tests/cases/conformance/statements/tryStatements/catchClauseWithTypeAnnotation.ts(38,27): error TS1196: Catch clause variable type annotation must be 'any' or 'unknown' if specified.
12+
tests/cases/conformance/statements/tryStatements/catchClauseWithTypeAnnotation.ts(39,22): error TS2339: Property 'x' does not exist on type 'Error'.
813
tests/cases/conformance/statements/tryStatements/catchClauseWithTypeAnnotation.ts(39,27): error TS1196: Catch clause variable type annotation must be 'any' or 'unknown' if specified.
914

1015

11-
==== tests/cases/conformance/statements/tryStatements/catchClauseWithTypeAnnotation.ts (8 errors) ====
16+
==== tests/cases/conformance/statements/tryStatements/catchClauseWithTypeAnnotation.ts (13 errors) ====
1217
type any1 = any;
1318
type unknown1 = unknown;
1419

@@ -42,6 +47,8 @@ tests/cases/conformance/statements/tryStatements/catchClauseWithTypeAnnotation.t
4247
// @ts-ignore
4348
catch (e: number) { // e should not be a `number`
4449
console.log(e.toLowerCase());
50+
~~~~~~~~~~~
51+
!!! error TS2339: Property 'toLowerCase' does not exist on type 'number'.
4552
}
4653

4754
// minor bug: shows that the `catch` argument is skipped when checking scope
@@ -57,12 +64,20 @@ tests/cases/conformance/statements/tryStatements/catchClauseWithTypeAnnotation.t
5764
try { } catch ({ x }) { } // should be OK
5865
try { } catch ({ x }: any) { x.foo; } // should be OK
5966
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
67+
try { } catch ({ x }: unknown) { console.log(x); } // error in the type
68+
~
69+
!!! error TS2339: Property 'x' does not exist on type '{}'.
70+
try { } catch ({ x }: unknown1) { console.log(x); } // error in the type
71+
~
72+
!!! error TS2339: Property 'x' does not exist on type '{}'.
6273
try { } catch ({ x }: object) { } // error in the type
74+
~
75+
!!! error TS2339: Property 'x' does not exist on type '{}'.
6376
~~~~~~
6477
!!! error TS1196: Catch clause variable type annotation must be 'any' or 'unknown' if specified.
6578
try { } catch ({ x }: Error) { } // error in the type
79+
~
80+
!!! error TS2339: Property 'x' does not exist on type 'Error'.
6681
~~~~~
6782
!!! error TS1196: Catch clause variable type annotation must be 'any' or 'unknown' if specified.
6883
}

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 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

+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 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

+6-6
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,10 @@ function fn(x: boolean) {
7272
>foo : any
7373

7474
try { } catch (x: Error) { } // error in the type
75-
>x : any
75+
>x : Error
7676

7777
try { } catch (x: object) { } // error in the type
78-
>x : any
78+
>x : object
7979

8080
try { console.log(); }
8181
>console.log() : void
@@ -85,7 +85,7 @@ function fn(x: boolean) {
8585

8686
// @ts-ignore
8787
catch (e: number) { // e should not be a `number`
88-
>e : any
88+
>e : number
8989

9090
console.log(e.toLowerCase());
9191
>console.log(e.toLowerCase()) : void
@@ -94,7 +94,7 @@ function fn(x: boolean) {
9494
>log : (...data: any[]) => void
9595
>e.toLowerCase() : any
9696
>e.toLowerCase : any
97-
>e : any
97+
>e : number
9898
>toLowerCase : any
9999
}
100100

@@ -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/destructuringCatch.symbols

-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ try {
3131
>z : Symbol(z, Decl(destructuringCatch.ts, 15, 20))
3232
}
3333
catch ([{x: [y], z}]) {
34-
>x : Symbol(x)
3534
>y : Symbol(y, Decl(destructuringCatch.ts, 17, 13))
3635
>z : Symbol(z, Decl(destructuringCatch.ts, 17, 16))
3736

tests/baselines/reference/jsdocCatchClauseWithTypeAnnotation.errors.txt

+18-3
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,17 @@ tests/cases/conformance/jsdoc/foo.js(20,54): error TS2339: Property 'foo' does n
22
tests/cases/conformance/jsdoc/foo.js(21,54): error TS2339: Property 'foo' does not exist on type 'unknown'.
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.
5+
tests/cases/conformance/jsdoc/foo.js(28,25): error TS2339: Property 'toLowerCase' does not exist on type 'number'.
56
tests/cases/conformance/jsdoc/foo.js(35,7): error TS2492: Cannot redeclare identifier 'err' in catch clause.
7+
tests/cases/conformance/jsdoc/foo.js(46,45): error TS2339: Property 'x' does not exist on type '{}'.
8+
tests/cases/conformance/jsdoc/foo.js(47,45): error TS2339: Property 'x' does not exist on type '{}'.
69
tests/cases/conformance/jsdoc/foo.js(48,31): error TS1196: Catch clause variable type annotation must be 'any' or 'unknown' if specified.
10+
tests/cases/conformance/jsdoc/foo.js(48,43): error TS2339: Property 'x' does not exist on type 'Error'.
711
tests/cases/conformance/jsdoc/foo.js(49,31): error TS1196: Catch clause variable type annotation must be 'any' or 'unknown' if specified.
12+
tests/cases/conformance/jsdoc/foo.js(49,44): error TS2339: Property 'x' does not exist on type '{}'.
813

914

10-
==== tests/cases/conformance/jsdoc/foo.js (7 errors) ====
15+
==== tests/cases/conformance/jsdoc/foo.js (12 errors) ====
1116
/**
1217
* @typedef {any} Any
1318
*/
@@ -44,6 +49,8 @@ tests/cases/conformance/jsdoc/foo.js(49,31): error TS1196: Catch clause variable
4449
// @ts-ignore
4550
catch (/** @type {number} */ err) { // e should not be a `number`
4651
console.log(err.toLowerCase());
52+
~~~~~~~~~~~
53+
!!! error TS2339: Property 'toLowerCase' does not exist on type 'number'.
4754
}
4855

4956
// minor bug: shows that the `catch` argument is skipped when checking scope
@@ -63,13 +70,21 @@ tests/cases/conformance/jsdoc/foo.js(49,31): error TS1196: Catch clause variable
6370
try { } catch ({ x }) { } // should be OK
6471
try { } catch (/** @type {any} */ { x }) { x.foo; } // should be OK
6572
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
73+
try { } catch (/** @type {unknown} */ { x }) { console.log(x); } // error in the type
74+
~
75+
!!! error TS2339: Property 'x' does not exist on type '{}'.
76+
try { } catch (/** @type {Unknown} */ { x }) { console.log(x); } // error in the type
77+
~
78+
!!! error TS2339: Property 'x' does not exist on type '{}'.
6879
try { } catch (/** @type {Error} */ { x }) { } // error in the type
6980
~~~~~
7081
!!! error TS1196: Catch clause variable type annotation must be 'any' or 'unknown' if specified.
82+
~
83+
!!! error TS2339: Property 'x' does not exist on type 'Error'.
7184
try { } catch (/** @type {object} */ { x }) { } // error in the type
7285
~~~~~~
7386
!!! error TS1196: Catch clause variable type annotation must be 'any' or 'unknown' if specified.
87+
~
88+
!!! error TS2339: Property 'x' does not exist on type '{}'.
7489
}
7590

tests/baselines/reference/jsdocCatchClauseWithTypeAnnotation.js

+4-4
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

+2-2
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

+6-6
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,10 @@ function fn() {
7272
>foo : any
7373

7474
try { } catch (/** @type {Error} */ err) { } // error in the type
75-
>err : any
75+
>err : Error
7676

7777
try { } catch (/** @type {object} */ err) { } // error in the type
78-
>err : any
78+
>err : object
7979

8080
try { console.log(); }
8181
>console.log() : void
@@ -85,7 +85,7 @@ function fn() {
8585

8686
// @ts-ignore
8787
catch (/** @type {number} */ err) { // e should not be a `number`
88-
>err : any
88+
>err : number
8989

9090
console.log(err.toLowerCase());
9191
>console.log(err.toLowerCase()) : void
@@ -94,7 +94,7 @@ function fn() {
9494
>log : (...data: any[]) => void
9595
>err.toLowerCase() : any
9696
>err.toLowerCase : any
97-
>err : any
97+
>err : number
9898
>toLowerCase : any
9999
}
100100

@@ -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
@@ -1,6 +1,6 @@
11
=== tests/cases/conformance/parser/ecmascript5/CatchClauses/parserCatchClauseWithTypeAnnotation1.ts ===
22
try {
33
} catch (e: Error) {
4-
>e : any
4+
>e : Error
55
}
66

tests/baselines/reference/redeclareParameterInCatchBlock.symbols

-2
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,7 @@ try {
3131
try {
3232

3333
} catch ({ a: x, b: x }) {
34-
>a : Symbol(a)
3534
>x : Symbol(x, Decl(redeclareParameterInCatchBlock.ts, 20, 10))
36-
>b : Symbol(b)
3735
>x : Symbol(x, Decl(redeclareParameterInCatchBlock.ts, 20, 16))
3836

3937
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
tests/cases/compiler/useUnknownInCatchVariables02.ts(4,10): error TS2339: Property 'name' does not exist on type '{}'.
2+
tests/cases/compiler/useUnknownInCatchVariables02.ts(11,10): error TS2339: Property 'name' does not exist on type '{}'.
3+
4+
5+
==== tests/cases/compiler/useUnknownInCatchVariables02.ts (2 errors) ====
6+
try {
7+
// ...
8+
}
9+
catch ({ name }) {
10+
~~~~
11+
!!! error TS2339: Property 'name' does not exist on type '{}'.
12+
name;
13+
}
14+
15+
try {
16+
// ...
17+
}
18+
catch ({ name }: unknown) {
19+
~~~~
20+
!!! error TS2339: Property 'name' does not exist on type '{}'.
21+
name;
22+
}
23+

0 commit comments

Comments
 (0)