Skip to content

Commit ba55fcf

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

File tree

43 files changed

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

+1237
-38
lines changed

src/compiler/checker.ts

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8694,6 +8694,30 @@ namespace ts {
86948694
return undefined;
86958695
}
86968696

8697+
function getTypeForCatchClauseVariableDeclaration(declaration: VariableDeclaration | BindingElement) {
8698+
const typeNode = getEffectiveTypeAnnotationNode(getRootDeclaration(declaration));
8699+
const type = typeNode ? getTypeOfNode(typeNode) :
8700+
useUnknownInCatchVariables ? unknownType : anyType;
8701+
8702+
if (isCatchClauseVariableDeclaration(declaration)) {
8703+
if (typeNode === undefined) {
8704+
if (isBindingPattern(declaration.name)) {
8705+
return getTypeFromBindingPattern(declaration.name, /*includePatternInType*/ false, /*reportErrors*/ false);
8706+
}
8707+
return useUnknownInCatchVariables ? unknownType : anyType;
8708+
}
8709+
// an errorType will make `checkTryStatement` issue an error
8710+
return isTypeAny(type) || type === unknownType ? type : errorType;
8711+
}
8712+
8713+
if (isBindingElement(declaration) && isBindingPattern(declaration.parent)) {
8714+
if (isCatchClauseVariableDeclaration(declaration.parent.parent) && type === unknownType) {
8715+
return getBindingElementTypeFromParentType(declaration, unknownType);
8716+
}
8717+
return declaration.dotDotDotToken ? anyType : getTypeForBindingElement(declaration);
8718+
}
8719+
}
8720+
86978721
function isNullOrUndefined(node: Expression) {
86988722
const expr = skipParentheses(node, /*excludeJSDocTypeAssertions*/ true);
86998723
return expr.kind === SyntaxKind.NullKeyword || expr.kind === SyntaxKind.Identifier && getResolvedSymbol(expr as Identifier) === undefinedSymbol;
@@ -8730,6 +8754,10 @@ namespace ts {
87308754
return checkRightHandSideOfForOf(forOfStatement) || anyType;
87318755
}
87328756

8757+
if (isCatchClauseVariableDeclarationOrBindingElement(declaration)) {
8758+
return getTypeForCatchClauseVariableDeclaration(declaration as VariableDeclaration | BindingElement);
8759+
}
8760+
87338761
if (isBindingPattern(declaration.parent)) {
87348762
return getTypeForBindingElement(declaration as BindingElement);
87358763
}
@@ -9371,18 +9399,8 @@ namespace ts {
93719399
members.set("exports" as __String, result);
93729400
return createAnonymousType(symbol, members, emptyArray, emptyArray, emptyArray);
93739401
}
9374-
// Handle catch clause variables
93759402
Debug.assertIsDefined(symbol.valueDeclaration);
93769403
const declaration = symbol.valueDeclaration;
9377-
if (isCatchClauseVariableDeclarationOrBindingElement(declaration)) {
9378-
const typeNode = getEffectiveTypeAnnotationNode(declaration);
9379-
if (typeNode === undefined) {
9380-
return useUnknownInCatchVariables ? unknownType : anyType;
9381-
}
9382-
const type = getTypeOfNode(typeNode);
9383-
// an errorType will make `checkTryStatement` issue an error
9384-
return isTypeAny(type) || type === unknownType ? type : errorType;
9385-
}
93869404
// Handle export default expressions
93879405
if (isSourceFile(declaration) && isJsonSourceFile(declaration)) {
93889406
if (!declaration.statements.length) {
@@ -38493,17 +38511,20 @@ namespace ts {
3849338511
function checkTryStatement(node: TryStatement) {
3849438512
// Grammar checking
3849538513
checkGrammarStatementInAmbientContext(node);
38496-
3849738514
checkBlock(node.tryBlock);
38515+
3849838516
const catchClause = node.catchClause;
3849938517
if (catchClause) {
3850038518
// Grammar checking
3850138519
if (catchClause.variableDeclaration) {
3850238520
const declaration = catchClause.variableDeclaration;
38521+
if (isBindingPattern(declaration.name)) {
38522+
forEach(declaration.name.elements, checkSourceElement);
38523+
}
3850338524
const typeNode = getEffectiveTypeAnnotationNode(getRootDeclaration(declaration));
3850438525
if (typeNode) {
3850538526
const type = getTypeForVariableLikeDeclaration(declaration, /*includeOptionality*/ false, CheckMode.Normal);
38506-
if (type && !(type.flags & TypeFlags.AnyOrUnknown)) {
38527+
if (type && isErrorType(type)) {
3850738528
grammarErrorOnFirstToken(typeNode, Diagnostics.Catch_clause_variable_type_annotation_must_be_any_or_unknown_if_specified);
3850838529
}
3850938530
}

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)