Skip to content

Commit 78cea2a

Browse files
Merge pull request #10334 from yortus/narrow-from-any
Narrow from 'any' in most situations
2 parents 10d1e02 + 837688f commit 78cea2a

13 files changed

+458
-30
lines changed

src/compiler/checker.ts

+12-5
Original file line numberDiff line numberDiff line change
@@ -8548,10 +8548,6 @@ namespace ts {
85488548
}
85498549
return type;
85508550
}
8551-
// We never narrow type any in an instanceof guard
8552-
if (isTypeAny(type)) {
8553-
return type;
8554-
}
85558551

85568552
// Check that right operand is a function type with a prototype property
85578553
const rightType = checkExpression(expr.right);
@@ -8569,6 +8565,11 @@ namespace ts {
85698565
}
85708566
}
85718567

8568+
// Don't narrow from 'any' if the target type is exactly 'Object' or 'Function'
8569+
if (isTypeAny(type) && (targetType === globalObjectType || targetType === globalFunctionType)) {
8570+
return type;
8571+
}
8572+
85728573
if (!targetType) {
85738574
// Target type is type of construct signature
85748575
let constructSignatures: Signature[];
@@ -8615,14 +8616,20 @@ namespace ts {
86158616
}
86168617

86178618
function narrowTypeByTypePredicate(type: Type, callExpression: CallExpression, assumeTrue: boolean): Type {
8618-
if (type.flags & TypeFlags.Any || !hasMatchingArgument(callExpression, reference)) {
8619+
if (!hasMatchingArgument(callExpression, reference)) {
86198620
return type;
86208621
}
86218622
const signature = getResolvedSignature(callExpression);
86228623
const predicate = signature.typePredicate;
86238624
if (!predicate) {
86248625
return type;
86258626
}
8627+
8628+
// Don't narrow from 'any' if the predicate type is exactly 'Object' or 'Function'
8629+
if (isTypeAny(type) && (predicate.type === globalObjectType || predicate.type === globalFunctionType)) {
8630+
return type;
8631+
}
8632+
86268633
if (isIdentifierTypePredicate(predicate)) {
86278634
const predicateArgument = callExpression.arguments[predicate.parameterIndex];
86288635
if (predicateArgument) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
tests/cases/conformance/types/any/narrowExceptionVariableInCatchClause.ts(11,17): error TS2339: Property 'doPanic' does not exist on type '{ type: "foo"; dontPanic(): any; }'.
2+
tests/cases/conformance/types/any/narrowExceptionVariableInCatchClause.ts(16,17): error TS2339: Property 'massage' does not exist on type 'Error'.
3+
4+
5+
==== tests/cases/conformance/types/any/narrowExceptionVariableInCatchClause.ts (2 errors) ====
6+
declare function isFooError(x: any): x is { type: 'foo'; dontPanic(); };
7+
8+
function tryCatch() {
9+
try {
10+
// do stuff...
11+
}
12+
catch (err) { // err is implicitly 'any' and cannot be annotated
13+
14+
if (isFooError(err)) {
15+
err.dontPanic(); // OK
16+
err.doPanic(); // ERROR: Property 'doPanic' does not exist on type '{...}'
17+
~~~~~~~
18+
!!! error TS2339: Property 'doPanic' does not exist on type '{ type: "foo"; dontPanic(): any; }'.
19+
}
20+
21+
else if (err instanceof Error) {
22+
err.message;
23+
err.massage; // ERROR: Property 'massage' does not exist on type 'Error'
24+
~~~~~~~
25+
!!! error TS2339: Property 'massage' does not exist on type 'Error'.
26+
}
27+
28+
else {
29+
throw err;
30+
}
31+
}
32+
}
33+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
//// [narrowExceptionVariableInCatchClause.ts]
2+
declare function isFooError(x: any): x is { type: 'foo'; dontPanic(); };
3+
4+
function tryCatch() {
5+
try {
6+
// do stuff...
7+
}
8+
catch (err) { // err is implicitly 'any' and cannot be annotated
9+
10+
if (isFooError(err)) {
11+
err.dontPanic(); // OK
12+
err.doPanic(); // ERROR: Property 'doPanic' does not exist on type '{...}'
13+
}
14+
15+
else if (err instanceof Error) {
16+
err.message;
17+
err.massage; // ERROR: Property 'massage' does not exist on type 'Error'
18+
}
19+
20+
else {
21+
throw err;
22+
}
23+
}
24+
}
25+
26+
27+
//// [narrowExceptionVariableInCatchClause.js]
28+
function tryCatch() {
29+
try {
30+
}
31+
catch (err) {
32+
if (isFooError(err)) {
33+
err.dontPanic(); // OK
34+
err.doPanic(); // ERROR: Property 'doPanic' does not exist on type '{...}'
35+
}
36+
else if (err instanceof Error) {
37+
err.message;
38+
err.massage; // ERROR: Property 'massage' does not exist on type 'Error'
39+
}
40+
else {
41+
throw err;
42+
}
43+
}
44+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
tests/cases/conformance/types/any/narrowFromAnyWithInstanceof.ts(17,7): error TS2339: Property 'mesage' does not exist on type 'Error'.
2+
tests/cases/conformance/types/any/narrowFromAnyWithInstanceof.ts(22,7): error TS2339: Property 'getHuors' does not exist on type 'Date'.
3+
4+
5+
==== tests/cases/conformance/types/any/narrowFromAnyWithInstanceof.ts (2 errors) ====
6+
declare var x: any;
7+
8+
if (x instanceof Function) { // 'any' is not narrowed when target type is 'Function'
9+
x();
10+
x(1, 2, 3);
11+
x("hello!");
12+
x.prop;
13+
}
14+
15+
if (x instanceof Object) { // 'any' is not narrowed when target type is 'Object'
16+
x.method();
17+
x();
18+
}
19+
20+
if (x instanceof Error) { // 'any' is narrowed to types other than 'Function'/'Object'
21+
x.message;
22+
x.mesage;
23+
~~~~~~
24+
!!! error TS2339: Property 'mesage' does not exist on type 'Error'.
25+
}
26+
27+
if (x instanceof Date) {
28+
x.getDate();
29+
x.getHuors();
30+
~~~~~~~~
31+
!!! error TS2339: Property 'getHuors' does not exist on type 'Date'.
32+
}
33+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
//// [narrowFromAnyWithInstanceof.ts]
2+
declare var x: any;
3+
4+
if (x instanceof Function) { // 'any' is not narrowed when target type is 'Function'
5+
x();
6+
x(1, 2, 3);
7+
x("hello!");
8+
x.prop;
9+
}
10+
11+
if (x instanceof Object) { // 'any' is not narrowed when target type is 'Object'
12+
x.method();
13+
x();
14+
}
15+
16+
if (x instanceof Error) { // 'any' is narrowed to types other than 'Function'/'Object'
17+
x.message;
18+
x.mesage;
19+
}
20+
21+
if (x instanceof Date) {
22+
x.getDate();
23+
x.getHuors();
24+
}
25+
26+
27+
//// [narrowFromAnyWithInstanceof.js]
28+
if (x instanceof Function) {
29+
x();
30+
x(1, 2, 3);
31+
x("hello!");
32+
x.prop;
33+
}
34+
if (x instanceof Object) {
35+
x.method();
36+
x();
37+
}
38+
if (x instanceof Error) {
39+
x.message;
40+
x.mesage;
41+
}
42+
if (x instanceof Date) {
43+
x.getDate();
44+
x.getHuors();
45+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
tests/cases/conformance/types/any/narrowFromAnyWithTypePredicate.ts(22,7): error TS2339: Property 'method' does not exist on type '{}'.
2+
tests/cases/conformance/types/any/narrowFromAnyWithTypePredicate.ts(23,5): error TS2349: Cannot invoke an expression whose type lacks a call signature.
3+
tests/cases/conformance/types/any/narrowFromAnyWithTypePredicate.ts(28,7): error TS2339: Property 'mesage' does not exist on type 'Error'.
4+
tests/cases/conformance/types/any/narrowFromAnyWithTypePredicate.ts(33,7): error TS2339: Property 'getHuors' does not exist on type 'Date'.
5+
6+
7+
==== tests/cases/conformance/types/any/narrowFromAnyWithTypePredicate.ts (4 errors) ====
8+
declare var x: any;
9+
declare function isFunction(x): x is Function;
10+
declare function isObject(x): x is Object;
11+
declare function isAnything(x): x is {};
12+
declare function isError(x): x is Error;
13+
declare function isDate(x): x is Date;
14+
15+
16+
if (isFunction(x)) { // 'any' is not narrowed when target type is 'Function'
17+
x();
18+
x(1, 2, 3);
19+
x("hello!");
20+
x.prop;
21+
}
22+
23+
if (isObject(x)) { // 'any' is not narrowed when target type is 'Object'
24+
x.method();
25+
x();
26+
}
27+
28+
if (isAnything(x)) { // 'any' is narrowed to types other than 'Function'/'Object' (including {})
29+
x.method();
30+
~~~~~~
31+
!!! error TS2339: Property 'method' does not exist on type '{}'.
32+
x();
33+
~~~
34+
!!! error TS2349: Cannot invoke an expression whose type lacks a call signature.
35+
}
36+
37+
if (isError(x)) {
38+
x.message;
39+
x.mesage;
40+
~~~~~~
41+
!!! error TS2339: Property 'mesage' does not exist on type 'Error'.
42+
}
43+
44+
if (isDate(x)) {
45+
x.getDate();
46+
x.getHuors();
47+
~~~~~~~~
48+
!!! error TS2339: Property 'getHuors' does not exist on type 'Date'.
49+
}
50+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
//// [narrowFromAnyWithTypePredicate.ts]
2+
declare var x: any;
3+
declare function isFunction(x): x is Function;
4+
declare function isObject(x): x is Object;
5+
declare function isAnything(x): x is {};
6+
declare function isError(x): x is Error;
7+
declare function isDate(x): x is Date;
8+
9+
10+
if (isFunction(x)) { // 'any' is not narrowed when target type is 'Function'
11+
x();
12+
x(1, 2, 3);
13+
x("hello!");
14+
x.prop;
15+
}
16+
17+
if (isObject(x)) { // 'any' is not narrowed when target type is 'Object'
18+
x.method();
19+
x();
20+
}
21+
22+
if (isAnything(x)) { // 'any' is narrowed to types other than 'Function'/'Object' (including {})
23+
x.method();
24+
x();
25+
}
26+
27+
if (isError(x)) {
28+
x.message;
29+
x.mesage;
30+
}
31+
32+
if (isDate(x)) {
33+
x.getDate();
34+
x.getHuors();
35+
}
36+
37+
38+
//// [narrowFromAnyWithTypePredicate.js]
39+
if (isFunction(x)) {
40+
x();
41+
x(1, 2, 3);
42+
x("hello!");
43+
x.prop;
44+
}
45+
if (isObject(x)) {
46+
x.method();
47+
x();
48+
}
49+
if (isAnything(x)) {
50+
x.method();
51+
x();
52+
}
53+
if (isError(x)) {
54+
x.message;
55+
x.mesage;
56+
}
57+
if (isDate(x)) {
58+
x.getDate();
59+
x.getHuors();
60+
}

0 commit comments

Comments
 (0)