Skip to content

Commit ef0f6c8

Browse files
committed
Merge pull request #7235 from weswigham/narrow-all-types
Fix #7224, #7441 - Replace TypeFlags.Narrowable
2 parents 9f087cb + 9a620bf commit ef0f6c8

14 files changed

+409
-2
lines changed

src/compiler/checker.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -7662,7 +7662,7 @@ namespace ts {
76627662

76637663
function getFlowTypeOfReference(reference: Node, declaredType: Type, assumeInitialized: boolean, includeOuterFunctions: boolean) {
76647664
let key: string;
7665-
if (!reference.flowNode || assumeInitialized && !(declaredType.flags & TypeFlags.Narrowable)) {
7665+
if (!reference.flowNode || assumeInitialized && (declaredType.flags & TypeFlags.NotNarrowable)) {
76667666
return declaredType;
76677667
}
76687668
const initialType = assumeInitialized ? declaredType : addNullableKind(declaredType, TypeFlags.Undefined);

src/compiler/types.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -2218,7 +2218,13 @@ namespace ts {
22182218
ObjectType = Class | Interface | Reference | Tuple | Anonymous,
22192219
UnionOrIntersection = Union | Intersection,
22202220
StructuredType = ObjectType | Union | Intersection,
2221-
Narrowable = Any | ObjectType | Union | TypeParameter,
2221+
2222+
// 'NotNarrowable' types are types where narrowing reverts to the original type, rather than actually narrow.
2223+
// This is never really correct - you can _always_ narrow to an intersection with that type, _but_ we keep
2224+
// Void as the only non-narrowable type, since it's a non-value type construct (representing a lack of a value)
2225+
// and, generally speaking, narrowing `void` should fail in some way, as it is nonsensical. (`void` narrowing
2226+
// to `void & T`, in a structural sense, is just narrowing to T, which we wouldn't allow under normal rules)
2227+
NotNarrowable = Void,
22222228
/* @internal */
22232229
RequiresWidening = ContainsUndefinedOrNull | ContainsObjectLiteral,
22242230
/* @internal */
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
//// [typeGuardNarrowsPrimitiveIntersection.ts]
2+
type Tag = {__tag: any};
3+
declare function isNonBlank(value: string) : value is (string & Tag);
4+
declare function doThis(value: string & Tag): void;
5+
declare function doThat(value: string) : void;
6+
let value: string;
7+
if (isNonBlank(value)) {
8+
doThis(value);
9+
} else {
10+
doThat(value);
11+
}
12+
13+
14+
const enum Tag2 {}
15+
declare function isNonBlank2(value: string) : value is (string & Tag2);
16+
declare function doThis2(value: string & Tag2): void;
17+
declare function doThat2(value: string) : void;
18+
if (isNonBlank2(value)) {
19+
doThis2(value);
20+
} else {
21+
doThat2(value);
22+
}
23+
24+
25+
//// [typeGuardNarrowsPrimitiveIntersection.js]
26+
var value;
27+
if (isNonBlank(value)) {
28+
doThis(value);
29+
}
30+
else {
31+
doThat(value);
32+
}
33+
if (isNonBlank2(value)) {
34+
doThis2(value);
35+
}
36+
else {
37+
doThat2(value);
38+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
=== tests/cases/conformance/expressions/typeGuards/typeGuardNarrowsPrimitiveIntersection.ts ===
2+
type Tag = {__tag: any};
3+
>Tag : Symbol(Tag, Decl(typeGuardNarrowsPrimitiveIntersection.ts, 0, 0))
4+
>__tag : Symbol(__tag, Decl(typeGuardNarrowsPrimitiveIntersection.ts, 0, 12))
5+
6+
declare function isNonBlank(value: string) : value is (string & Tag);
7+
>isNonBlank : Symbol(isNonBlank, Decl(typeGuardNarrowsPrimitiveIntersection.ts, 0, 24))
8+
>value : Symbol(value, Decl(typeGuardNarrowsPrimitiveIntersection.ts, 1, 28))
9+
>value : Symbol(value, Decl(typeGuardNarrowsPrimitiveIntersection.ts, 1, 28))
10+
>Tag : Symbol(Tag, Decl(typeGuardNarrowsPrimitiveIntersection.ts, 0, 0))
11+
12+
declare function doThis(value: string & Tag): void;
13+
>doThis : Symbol(doThis, Decl(typeGuardNarrowsPrimitiveIntersection.ts, 1, 69))
14+
>value : Symbol(value, Decl(typeGuardNarrowsPrimitiveIntersection.ts, 2, 24))
15+
>Tag : Symbol(Tag, Decl(typeGuardNarrowsPrimitiveIntersection.ts, 0, 0))
16+
17+
declare function doThat(value: string) : void;
18+
>doThat : Symbol(doThat, Decl(typeGuardNarrowsPrimitiveIntersection.ts, 2, 51))
19+
>value : Symbol(value, Decl(typeGuardNarrowsPrimitiveIntersection.ts, 3, 24))
20+
21+
let value: string;
22+
>value : Symbol(value, Decl(typeGuardNarrowsPrimitiveIntersection.ts, 4, 3))
23+
24+
if (isNonBlank(value)) {
25+
>isNonBlank : Symbol(isNonBlank, Decl(typeGuardNarrowsPrimitiveIntersection.ts, 0, 24))
26+
>value : Symbol(value, Decl(typeGuardNarrowsPrimitiveIntersection.ts, 4, 3))
27+
28+
doThis(value);
29+
>doThis : Symbol(doThis, Decl(typeGuardNarrowsPrimitiveIntersection.ts, 1, 69))
30+
>value : Symbol(value, Decl(typeGuardNarrowsPrimitiveIntersection.ts, 4, 3))
31+
32+
} else {
33+
doThat(value);
34+
>doThat : Symbol(doThat, Decl(typeGuardNarrowsPrimitiveIntersection.ts, 2, 51))
35+
>value : Symbol(value, Decl(typeGuardNarrowsPrimitiveIntersection.ts, 4, 3))
36+
}
37+
38+
39+
const enum Tag2 {}
40+
>Tag2 : Symbol(Tag2, Decl(typeGuardNarrowsPrimitiveIntersection.ts, 9, 1))
41+
42+
declare function isNonBlank2(value: string) : value is (string & Tag2);
43+
>isNonBlank2 : Symbol(isNonBlank2, Decl(typeGuardNarrowsPrimitiveIntersection.ts, 12, 18))
44+
>value : Symbol(value, Decl(typeGuardNarrowsPrimitiveIntersection.ts, 13, 29))
45+
>value : Symbol(value, Decl(typeGuardNarrowsPrimitiveIntersection.ts, 13, 29))
46+
>Tag2 : Symbol(Tag2, Decl(typeGuardNarrowsPrimitiveIntersection.ts, 9, 1))
47+
48+
declare function doThis2(value: string & Tag2): void;
49+
>doThis2 : Symbol(doThis2, Decl(typeGuardNarrowsPrimitiveIntersection.ts, 13, 71))
50+
>value : Symbol(value, Decl(typeGuardNarrowsPrimitiveIntersection.ts, 14, 25))
51+
>Tag2 : Symbol(Tag2, Decl(typeGuardNarrowsPrimitiveIntersection.ts, 9, 1))
52+
53+
declare function doThat2(value: string) : void;
54+
>doThat2 : Symbol(doThat2, Decl(typeGuardNarrowsPrimitiveIntersection.ts, 14, 53))
55+
>value : Symbol(value, Decl(typeGuardNarrowsPrimitiveIntersection.ts, 15, 25))
56+
57+
if (isNonBlank2(value)) {
58+
>isNonBlank2 : Symbol(isNonBlank2, Decl(typeGuardNarrowsPrimitiveIntersection.ts, 12, 18))
59+
>value : Symbol(value, Decl(typeGuardNarrowsPrimitiveIntersection.ts, 4, 3))
60+
61+
doThis2(value);
62+
>doThis2 : Symbol(doThis2, Decl(typeGuardNarrowsPrimitiveIntersection.ts, 13, 71))
63+
>value : Symbol(value, Decl(typeGuardNarrowsPrimitiveIntersection.ts, 4, 3))
64+
65+
} else {
66+
doThat2(value);
67+
>doThat2 : Symbol(doThat2, Decl(typeGuardNarrowsPrimitiveIntersection.ts, 14, 53))
68+
>value : Symbol(value, Decl(typeGuardNarrowsPrimitiveIntersection.ts, 4, 3))
69+
}
70+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
=== tests/cases/conformance/expressions/typeGuards/typeGuardNarrowsPrimitiveIntersection.ts ===
2+
type Tag = {__tag: any};
3+
>Tag : { __tag: any; }
4+
>__tag : any
5+
6+
declare function isNonBlank(value: string) : value is (string & Tag);
7+
>isNonBlank : (value: string) => value is string & { __tag: any; }
8+
>value : string
9+
>value : any
10+
>Tag : { __tag: any; }
11+
12+
declare function doThis(value: string & Tag): void;
13+
>doThis : (value: string & { __tag: any; }) => void
14+
>value : string & { __tag: any; }
15+
>Tag : { __tag: any; }
16+
17+
declare function doThat(value: string) : void;
18+
>doThat : (value: string) => void
19+
>value : string
20+
21+
let value: string;
22+
>value : string
23+
24+
if (isNonBlank(value)) {
25+
>isNonBlank(value) : boolean
26+
>isNonBlank : (value: string) => value is string & { __tag: any; }
27+
>value : string
28+
29+
doThis(value);
30+
>doThis(value) : void
31+
>doThis : (value: string & { __tag: any; }) => void
32+
>value : string & { __tag: any; }
33+
34+
} else {
35+
doThat(value);
36+
>doThat(value) : void
37+
>doThat : (value: string) => void
38+
>value : string
39+
}
40+
41+
42+
const enum Tag2 {}
43+
>Tag2 : Tag2
44+
45+
declare function isNonBlank2(value: string) : value is (string & Tag2);
46+
>isNonBlank2 : (value: string) => value is string & Tag2
47+
>value : string
48+
>value : any
49+
>Tag2 : Tag2
50+
51+
declare function doThis2(value: string & Tag2): void;
52+
>doThis2 : (value: string & Tag2) => void
53+
>value : string & Tag2
54+
>Tag2 : Tag2
55+
56+
declare function doThat2(value: string) : void;
57+
>doThat2 : (value: string) => void
58+
>value : string
59+
60+
if (isNonBlank2(value)) {
61+
>isNonBlank2(value) : boolean
62+
>isNonBlank2 : (value: string) => value is string & Tag2
63+
>value : string
64+
65+
doThis2(value);
66+
>doThis2(value) : void
67+
>doThis2 : (value: string & Tag2) => void
68+
>value : string & Tag2
69+
70+
} else {
71+
doThat2(value);
72+
>doThat2(value) : void
73+
>doThat2 : (value: string) => void
74+
>value : string
75+
}
76+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//// [typeGuardNarrowsToLiteralType.ts]
2+
declare function isFoo(value: string) : value is "foo";
3+
declare function doThis(value: "foo"): void;
4+
declare function doThat(value: string) : void;
5+
let value: string;
6+
if (isFoo(value)) {
7+
doThis(value);
8+
} else {
9+
doThat(value);
10+
}
11+
12+
13+
14+
//// [typeGuardNarrowsToLiteralType.js]
15+
var value;
16+
if (isFoo(value)) {
17+
doThis(value);
18+
}
19+
else {
20+
doThat(value);
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
=== tests/cases/conformance/expressions/typeGuards/typeGuardNarrowsToLiteralType.ts ===
2+
declare function isFoo(value: string) : value is "foo";
3+
>isFoo : Symbol(isFoo, Decl(typeGuardNarrowsToLiteralType.ts, 0, 0))
4+
>value : Symbol(value, Decl(typeGuardNarrowsToLiteralType.ts, 0, 23))
5+
>value : Symbol(value, Decl(typeGuardNarrowsToLiteralType.ts, 0, 23))
6+
7+
declare function doThis(value: "foo"): void;
8+
>doThis : Symbol(doThis, Decl(typeGuardNarrowsToLiteralType.ts, 0, 55))
9+
>value : Symbol(value, Decl(typeGuardNarrowsToLiteralType.ts, 1, 24))
10+
11+
declare function doThat(value: string) : void;
12+
>doThat : Symbol(doThat, Decl(typeGuardNarrowsToLiteralType.ts, 1, 44))
13+
>value : Symbol(value, Decl(typeGuardNarrowsToLiteralType.ts, 2, 24))
14+
15+
let value: string;
16+
>value : Symbol(value, Decl(typeGuardNarrowsToLiteralType.ts, 3, 3))
17+
18+
if (isFoo(value)) {
19+
>isFoo : Symbol(isFoo, Decl(typeGuardNarrowsToLiteralType.ts, 0, 0))
20+
>value : Symbol(value, Decl(typeGuardNarrowsToLiteralType.ts, 3, 3))
21+
22+
doThis(value);
23+
>doThis : Symbol(doThis, Decl(typeGuardNarrowsToLiteralType.ts, 0, 55))
24+
>value : Symbol(value, Decl(typeGuardNarrowsToLiteralType.ts, 3, 3))
25+
26+
} else {
27+
doThat(value);
28+
>doThat : Symbol(doThat, Decl(typeGuardNarrowsToLiteralType.ts, 1, 44))
29+
>value : Symbol(value, Decl(typeGuardNarrowsToLiteralType.ts, 3, 3))
30+
}
31+
32+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
=== tests/cases/conformance/expressions/typeGuards/typeGuardNarrowsToLiteralType.ts ===
2+
declare function isFoo(value: string) : value is "foo";
3+
>isFoo : (value: string) => value is "foo"
4+
>value : string
5+
>value : any
6+
7+
declare function doThis(value: "foo"): void;
8+
>doThis : (value: "foo") => void
9+
>value : "foo"
10+
11+
declare function doThat(value: string) : void;
12+
>doThat : (value: string) => void
13+
>value : string
14+
15+
let value: string;
16+
>value : string
17+
18+
if (isFoo(value)) {
19+
>isFoo(value) : boolean
20+
>isFoo : (value: string) => value is "foo"
21+
>value : string
22+
23+
doThis(value);
24+
>doThis(value) : void
25+
>doThis : (value: "foo") => void
26+
>value : "foo"
27+
28+
} else {
29+
doThat(value);
30+
>doThat(value) : void
31+
>doThat : (value: string) => void
32+
>value : string
33+
}
34+
35+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//// [typeGuardNarrowsToLiteralTypeUnion.ts]
2+
declare function isFoo(value: string) : value is ("foo" | "bar");
3+
declare function doThis(value: "foo" | "bar"): void;
4+
declare function doThat(value: string) : void;
5+
let value: string;
6+
if (isFoo(value)) {
7+
doThis(value);
8+
} else {
9+
doThat(value);
10+
}
11+
12+
13+
14+
//// [typeGuardNarrowsToLiteralTypeUnion.js]
15+
var value;
16+
if (isFoo(value)) {
17+
doThis(value);
18+
}
19+
else {
20+
doThat(value);
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
=== tests/cases/conformance/expressions/typeGuards/typeGuardNarrowsToLiteralTypeUnion.ts ===
2+
declare function isFoo(value: string) : value is ("foo" | "bar");
3+
>isFoo : Symbol(isFoo, Decl(typeGuardNarrowsToLiteralTypeUnion.ts, 0, 0))
4+
>value : Symbol(value, Decl(typeGuardNarrowsToLiteralTypeUnion.ts, 0, 23))
5+
>value : Symbol(value, Decl(typeGuardNarrowsToLiteralTypeUnion.ts, 0, 23))
6+
7+
declare function doThis(value: "foo" | "bar"): void;
8+
>doThis : Symbol(doThis, Decl(typeGuardNarrowsToLiteralTypeUnion.ts, 0, 65))
9+
>value : Symbol(value, Decl(typeGuardNarrowsToLiteralTypeUnion.ts, 1, 24))
10+
11+
declare function doThat(value: string) : void;
12+
>doThat : Symbol(doThat, Decl(typeGuardNarrowsToLiteralTypeUnion.ts, 1, 52))
13+
>value : Symbol(value, Decl(typeGuardNarrowsToLiteralTypeUnion.ts, 2, 24))
14+
15+
let value: string;
16+
>value : Symbol(value, Decl(typeGuardNarrowsToLiteralTypeUnion.ts, 3, 3))
17+
18+
if (isFoo(value)) {
19+
>isFoo : Symbol(isFoo, Decl(typeGuardNarrowsToLiteralTypeUnion.ts, 0, 0))
20+
>value : Symbol(value, Decl(typeGuardNarrowsToLiteralTypeUnion.ts, 3, 3))
21+
22+
doThis(value);
23+
>doThis : Symbol(doThis, Decl(typeGuardNarrowsToLiteralTypeUnion.ts, 0, 65))
24+
>value : Symbol(value, Decl(typeGuardNarrowsToLiteralTypeUnion.ts, 3, 3))
25+
26+
} else {
27+
doThat(value);
28+
>doThat : Symbol(doThat, Decl(typeGuardNarrowsToLiteralTypeUnion.ts, 1, 52))
29+
>value : Symbol(value, Decl(typeGuardNarrowsToLiteralTypeUnion.ts, 3, 3))
30+
}
31+
32+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
=== tests/cases/conformance/expressions/typeGuards/typeGuardNarrowsToLiteralTypeUnion.ts ===
2+
declare function isFoo(value: string) : value is ("foo" | "bar");
3+
>isFoo : (value: string) => value is "foo" | "bar"
4+
>value : string
5+
>value : any
6+
7+
declare function doThis(value: "foo" | "bar"): void;
8+
>doThis : (value: "foo" | "bar") => void
9+
>value : "foo" | "bar"
10+
11+
declare function doThat(value: string) : void;
12+
>doThat : (value: string) => void
13+
>value : string
14+
15+
let value: string;
16+
>value : string
17+
18+
if (isFoo(value)) {
19+
>isFoo(value) : boolean
20+
>isFoo : (value: string) => value is "foo" | "bar"
21+
>value : string
22+
23+
doThis(value);
24+
>doThis(value) : void
25+
>doThis : (value: "foo" | "bar") => void
26+
>value : "foo" | "bar"
27+
28+
} else {
29+
doThat(value);
30+
>doThat(value) : void
31+
>doThat : (value: string) => void
32+
>value : string
33+
}
34+
35+

0 commit comments

Comments
 (0)