Skip to content

Commit 9e12290

Browse files
committed
Merge pull request #9031 from Microsoft/typeGuardIntersectionTypes
Type guard intersection types
2 parents c14d711 + b1a7498 commit 9e12290

12 files changed

+922
-23
lines changed

src/compiler/checker.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -8007,7 +8007,7 @@ namespace ts {
80078007
const targetType = type.flags & TypeFlags.TypeParameter ? getApparentType(type) : type;
80088008
return isTypeAssignableTo(candidate, targetType) ? candidate :
80098009
isTypeAssignableTo(type, candidate) ? type :
8010-
neverType;
8010+
getIntersectionType([type, candidate]);
80118011
}
80128012

80138013
function narrowTypeByTypePredicate(type: Type, callExpression: CallExpression, assumeTrue: boolean): Type {

tests/baselines/reference/instanceOfAssignability.types

+2-2
Original file line numberDiff line numberDiff line change
@@ -133,8 +133,8 @@ function fn5(x: Derived1) {
133133
// 1.5: y: Derived1
134134
// Want: ???
135135
let y = x;
136-
>y : never
137-
>x : never
136+
>y : Derived1 & Derived2
137+
>x : Derived1 & Derived2
138138
}
139139
}
140140

tests/baselines/reference/stringLiteralTypesAsTags01.types

+2-2
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,6 @@ if (!hasKind(x, "B")) {
116116
}
117117
else {
118118
let d = x;
119-
>d : never
120-
>x : never
119+
>d : A & B
120+
>x : A & B
121121
}

tests/baselines/reference/stringLiteralTypesAsTags02.types

+2-2
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,6 @@ if (!hasKind(x, "B")) {
110110
}
111111
else {
112112
let d = x;
113-
>d : never
114-
>x : never
113+
>d : A & B
114+
>x : A & B
115115
}

tests/baselines/reference/stringLiteralTypesAsTags03.types

+2-2
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,6 @@ if (!hasKind(x, "B")) {
113113
}
114114
else {
115115
let d = x;
116-
>d : never
117-
>x : never
116+
>d : A & B
117+
>x : A & B
118118
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
//// [typeGuardIntersectionTypes.ts]
2+
3+
interface X {
4+
x: string;
5+
}
6+
7+
interface Y {
8+
y: string;
9+
}
10+
11+
interface Z {
12+
z: string;
13+
}
14+
15+
declare function isX(obj: any): obj is X;
16+
declare function isY(obj: any): obj is Y;
17+
declare function isZ(obj: any): obj is Z;
18+
19+
function f1(obj: Object) {
20+
if (isX(obj) || isY(obj) || isZ(obj)) {
21+
obj;
22+
}
23+
if (isX(obj) && isY(obj) && isZ(obj)) {
24+
obj;
25+
}
26+
}
27+
28+
// Repro from #8911
29+
30+
// two interfaces
31+
interface A {
32+
a: string;
33+
}
34+
35+
interface B {
36+
b: string;
37+
}
38+
39+
// a type guard for B
40+
function isB(toTest: any): toTest is B {
41+
return toTest && toTest.b;
42+
}
43+
44+
// a function that turns an A into an A & B
45+
function union(a: A): A & B | null {
46+
if (isB(a)) {
47+
return a;
48+
} else {
49+
return null;
50+
}
51+
}
52+
53+
// Repro from #9016
54+
55+
declare function log(s: string): void;
56+
57+
// Supported beast features
58+
interface Beast { wings?: boolean; legs?: number }
59+
interface Legged { legs: number; }
60+
interface Winged { wings: boolean; }
61+
62+
// Beast feature detection via user-defined type guards
63+
function hasLegs(x: Beast): x is Legged { return x && typeof x.legs === 'number'; }
64+
function hasWings(x: Beast): x is Winged { return x && !!x.wings; }
65+
66+
// Function to identify a given beast by detecting its features
67+
function identifyBeast(beast: Beast) {
68+
69+
// All beasts with legs
70+
if (hasLegs(beast)) {
71+
72+
// All winged beasts with legs
73+
if (hasWings(beast)) {
74+
if (beast.legs === 4) {
75+
log(`pegasus - 4 legs, wings`);
76+
}
77+
else if (beast.legs === 2) {
78+
log(`bird - 2 legs, wings`);
79+
}
80+
else {
81+
log(`unknown - ${beast.legs} legs, wings`);
82+
}
83+
}
84+
85+
// All non-winged beasts with legs
86+
else {
87+
log(`manbearpig - ${beast.legs} legs, no wings`);
88+
}
89+
}
90+
91+
// All beasts without legs
92+
else {
93+
if (hasWings(beast)) {
94+
log(`quetzalcoatl - no legs, wings`)
95+
}
96+
else {
97+
log(`snake - no legs, no wings`)
98+
}
99+
}
100+
}
101+
102+
function beastFoo(beast: Object) {
103+
if (hasWings(beast) && hasLegs(beast)) {
104+
beast; // Winged & Legged
105+
}
106+
else {
107+
beast;
108+
}
109+
110+
if (hasLegs(beast) && hasWings(beast)) {
111+
beast; // Legged & Winged
112+
}
113+
}
114+
115+
//// [typeGuardIntersectionTypes.js]
116+
function f1(obj) {
117+
if (isX(obj) || isY(obj) || isZ(obj)) {
118+
obj;
119+
}
120+
if (isX(obj) && isY(obj) && isZ(obj)) {
121+
obj;
122+
}
123+
}
124+
// a type guard for B
125+
function isB(toTest) {
126+
return toTest && toTest.b;
127+
}
128+
// a function that turns an A into an A & B
129+
function union(a) {
130+
if (isB(a)) {
131+
return a;
132+
}
133+
else {
134+
return null;
135+
}
136+
}
137+
// Beast feature detection via user-defined type guards
138+
function hasLegs(x) { return x && typeof x.legs === 'number'; }
139+
function hasWings(x) { return x && !!x.wings; }
140+
// Function to identify a given beast by detecting its features
141+
function identifyBeast(beast) {
142+
// All beasts with legs
143+
if (hasLegs(beast)) {
144+
// All winged beasts with legs
145+
if (hasWings(beast)) {
146+
if (beast.legs === 4) {
147+
log("pegasus - 4 legs, wings");
148+
}
149+
else if (beast.legs === 2) {
150+
log("bird - 2 legs, wings");
151+
}
152+
else {
153+
log("unknown - " + beast.legs + " legs, wings");
154+
}
155+
}
156+
else {
157+
log("manbearpig - " + beast.legs + " legs, no wings");
158+
}
159+
}
160+
else {
161+
if (hasWings(beast)) {
162+
log("quetzalcoatl - no legs, wings");
163+
}
164+
else {
165+
log("snake - no legs, no wings");
166+
}
167+
}
168+
}
169+
function beastFoo(beast) {
170+
if (hasWings(beast) && hasLegs(beast)) {
171+
beast; // Winged & Legged
172+
}
173+
else {
174+
beast;
175+
}
176+
if (hasLegs(beast) && hasWings(beast)) {
177+
beast; // Legged & Winged
178+
}
179+
}

0 commit comments

Comments
 (0)