diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 650bc488b0e1d..ea494725fc613 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7997,7 +7997,7 @@ namespace ts { const targetType = type.flags & TypeFlags.TypeParameter ? getApparentType(type) : type; return isTypeAssignableTo(candidate, targetType) ? candidate : isTypeAssignableTo(type, candidate) ? type : - neverType; + getIntersectionType([type, candidate]); } function narrowTypeByTypePredicate(type: Type, callExpression: CallExpression, assumeTrue: boolean): Type { diff --git a/tests/baselines/reference/instanceOfAssignability.types b/tests/baselines/reference/instanceOfAssignability.types index 70d068fb0cc46..44ec45e206901 100644 --- a/tests/baselines/reference/instanceOfAssignability.types +++ b/tests/baselines/reference/instanceOfAssignability.types @@ -133,8 +133,8 @@ function fn5(x: Derived1) { // 1.5: y: Derived1 // Want: ??? let y = x; ->y : never ->x : never +>y : Derived1 & Derived2 +>x : Derived1 & Derived2 } } diff --git a/tests/baselines/reference/stringLiteralTypesAsTags01.types b/tests/baselines/reference/stringLiteralTypesAsTags01.types index 7b8ac0a8c02b8..64b099b34f2fa 100644 --- a/tests/baselines/reference/stringLiteralTypesAsTags01.types +++ b/tests/baselines/reference/stringLiteralTypesAsTags01.types @@ -116,6 +116,6 @@ if (!hasKind(x, "B")) { } else { let d = x; ->d : never ->x : never +>d : A & B +>x : A & B } diff --git a/tests/baselines/reference/stringLiteralTypesAsTags02.types b/tests/baselines/reference/stringLiteralTypesAsTags02.types index 290402cd299cb..92b294a249895 100644 --- a/tests/baselines/reference/stringLiteralTypesAsTags02.types +++ b/tests/baselines/reference/stringLiteralTypesAsTags02.types @@ -110,6 +110,6 @@ if (!hasKind(x, "B")) { } else { let d = x; ->d : never ->x : never +>d : A & B +>x : A & B } diff --git a/tests/baselines/reference/stringLiteralTypesAsTags03.types b/tests/baselines/reference/stringLiteralTypesAsTags03.types index 9d004982dda07..49ae3da4be033 100644 --- a/tests/baselines/reference/stringLiteralTypesAsTags03.types +++ b/tests/baselines/reference/stringLiteralTypesAsTags03.types @@ -113,6 +113,6 @@ if (!hasKind(x, "B")) { } else { let d = x; ->d : never ->x : never +>d : A & B +>x : A & B } diff --git a/tests/baselines/reference/typeGuardIntersectionTypes.js b/tests/baselines/reference/typeGuardIntersectionTypes.js new file mode 100644 index 0000000000000..6de7d06e3232d --- /dev/null +++ b/tests/baselines/reference/typeGuardIntersectionTypes.js @@ -0,0 +1,179 @@ +//// [typeGuardIntersectionTypes.ts] + +interface X { + x: string; +} + +interface Y { + y: string; +} + +interface Z { + z: string; +} + +declare function isX(obj: any): obj is X; +declare function isY(obj: any): obj is Y; +declare function isZ(obj: any): obj is Z; + +function f1(obj: Object) { + if (isX(obj) || isY(obj) || isZ(obj)) { + obj; + } + if (isX(obj) && isY(obj) && isZ(obj)) { + obj; + } +} + +// Repro from #8911 + +// two interfaces +interface A { + a: string; +} + +interface B { + b: string; +} + +// a type guard for B +function isB(toTest: any): toTest is B { + return toTest && toTest.b; +} + +// a function that turns an A into an A & B +function union(a: A): A & B | null { + if (isB(a)) { + return a; + } else { + return null; + } +} + +// Repro from #9016 + +declare function log(s: string): void; + +// Supported beast features +interface Beast { wings?: boolean; legs?: number } +interface Legged { legs: number; } +interface Winged { wings: boolean; } + +// Beast feature detection via user-defined type guards +function hasLegs(x: Beast): x is Legged { return x && typeof x.legs === 'number'; } +function hasWings(x: Beast): x is Winged { return x && !!x.wings; } + +// Function to identify a given beast by detecting its features +function identifyBeast(beast: Beast) { + + // All beasts with legs + if (hasLegs(beast)) { + + // All winged beasts with legs + if (hasWings(beast)) { + if (beast.legs === 4) { + log(`pegasus - 4 legs, wings`); + } + else if (beast.legs === 2) { + log(`bird - 2 legs, wings`); + } + else { + log(`unknown - ${beast.legs} legs, wings`); + } + } + + // All non-winged beasts with legs + else { + log(`manbearpig - ${beast.legs} legs, no wings`); + } + } + + // All beasts without legs + else { + if (hasWings(beast)) { + log(`quetzalcoatl - no legs, wings`) + } + else { + log(`snake - no legs, no wings`) + } + } +} + +function beastFoo(beast: Object) { + if (hasWings(beast) && hasLegs(beast)) { + beast; // Winged & Legged + } + else { + beast; + } + + if (hasLegs(beast) && hasWings(beast)) { + beast; // Legged & Winged + } +} + +//// [typeGuardIntersectionTypes.js] +function f1(obj) { + if (isX(obj) || isY(obj) || isZ(obj)) { + obj; + } + if (isX(obj) && isY(obj) && isZ(obj)) { + obj; + } +} +// a type guard for B +function isB(toTest) { + return toTest && toTest.b; +} +// a function that turns an A into an A & B +function union(a) { + if (isB(a)) { + return a; + } + else { + return null; + } +} +// Beast feature detection via user-defined type guards +function hasLegs(x) { return x && typeof x.legs === 'number'; } +function hasWings(x) { return x && !!x.wings; } +// Function to identify a given beast by detecting its features +function identifyBeast(beast) { + // All beasts with legs + if (hasLegs(beast)) { + // All winged beasts with legs + if (hasWings(beast)) { + if (beast.legs === 4) { + log("pegasus - 4 legs, wings"); + } + else if (beast.legs === 2) { + log("bird - 2 legs, wings"); + } + else { + log("unknown - " + beast.legs + " legs, wings"); + } + } + else { + log("manbearpig - " + beast.legs + " legs, no wings"); + } + } + else { + if (hasWings(beast)) { + log("quetzalcoatl - no legs, wings"); + } + else { + log("snake - no legs, no wings"); + } + } +} +function beastFoo(beast) { + if (hasWings(beast) && hasLegs(beast)) { + beast; // Winged & Legged + } + else { + beast; + } + if (hasLegs(beast) && hasWings(beast)) { + beast; // Legged & Winged + } +} diff --git a/tests/baselines/reference/typeGuardIntersectionTypes.symbols b/tests/baselines/reference/typeGuardIntersectionTypes.symbols new file mode 100644 index 0000000000000..ea8e903c76557 --- /dev/null +++ b/tests/baselines/reference/typeGuardIntersectionTypes.symbols @@ -0,0 +1,258 @@ +=== tests/cases/conformance/expressions/typeGuards/typeGuardIntersectionTypes.ts === + +interface X { +>X : Symbol(X, Decl(typeGuardIntersectionTypes.ts, 0, 0)) + + x: string; +>x : Symbol(X.x, Decl(typeGuardIntersectionTypes.ts, 1, 13)) +} + +interface Y { +>Y : Symbol(Y, Decl(typeGuardIntersectionTypes.ts, 3, 1)) + + y: string; +>y : Symbol(Y.y, Decl(typeGuardIntersectionTypes.ts, 5, 13)) +} + +interface Z { +>Z : Symbol(Z, Decl(typeGuardIntersectionTypes.ts, 7, 1)) + + z: string; +>z : Symbol(Z.z, Decl(typeGuardIntersectionTypes.ts, 9, 13)) +} + +declare function isX(obj: any): obj is X; +>isX : Symbol(isX, Decl(typeGuardIntersectionTypes.ts, 11, 1)) +>obj : Symbol(obj, Decl(typeGuardIntersectionTypes.ts, 13, 21)) +>obj : Symbol(obj, Decl(typeGuardIntersectionTypes.ts, 13, 21)) +>X : Symbol(X, Decl(typeGuardIntersectionTypes.ts, 0, 0)) + +declare function isY(obj: any): obj is Y; +>isY : Symbol(isY, Decl(typeGuardIntersectionTypes.ts, 13, 41)) +>obj : Symbol(obj, Decl(typeGuardIntersectionTypes.ts, 14, 21)) +>obj : Symbol(obj, Decl(typeGuardIntersectionTypes.ts, 14, 21)) +>Y : Symbol(Y, Decl(typeGuardIntersectionTypes.ts, 3, 1)) + +declare function isZ(obj: any): obj is Z; +>isZ : Symbol(isZ, Decl(typeGuardIntersectionTypes.ts, 14, 41)) +>obj : Symbol(obj, Decl(typeGuardIntersectionTypes.ts, 15, 21)) +>obj : Symbol(obj, Decl(typeGuardIntersectionTypes.ts, 15, 21)) +>Z : Symbol(Z, Decl(typeGuardIntersectionTypes.ts, 7, 1)) + +function f1(obj: Object) { +>f1 : Symbol(f1, Decl(typeGuardIntersectionTypes.ts, 15, 41)) +>obj : Symbol(obj, Decl(typeGuardIntersectionTypes.ts, 17, 12)) +>Object : Symbol(Object, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) + + if (isX(obj) || isY(obj) || isZ(obj)) { +>isX : Symbol(isX, Decl(typeGuardIntersectionTypes.ts, 11, 1)) +>obj : Symbol(obj, Decl(typeGuardIntersectionTypes.ts, 17, 12)) +>isY : Symbol(isY, Decl(typeGuardIntersectionTypes.ts, 13, 41)) +>obj : Symbol(obj, Decl(typeGuardIntersectionTypes.ts, 17, 12)) +>isZ : Symbol(isZ, Decl(typeGuardIntersectionTypes.ts, 14, 41)) +>obj : Symbol(obj, Decl(typeGuardIntersectionTypes.ts, 17, 12)) + + obj; +>obj : Symbol(obj, Decl(typeGuardIntersectionTypes.ts, 17, 12)) + } + if (isX(obj) && isY(obj) && isZ(obj)) { +>isX : Symbol(isX, Decl(typeGuardIntersectionTypes.ts, 11, 1)) +>obj : Symbol(obj, Decl(typeGuardIntersectionTypes.ts, 17, 12)) +>isY : Symbol(isY, Decl(typeGuardIntersectionTypes.ts, 13, 41)) +>obj : Symbol(obj, Decl(typeGuardIntersectionTypes.ts, 17, 12)) +>isZ : Symbol(isZ, Decl(typeGuardIntersectionTypes.ts, 14, 41)) +>obj : Symbol(obj, Decl(typeGuardIntersectionTypes.ts, 17, 12)) + + obj; +>obj : Symbol(obj, Decl(typeGuardIntersectionTypes.ts, 17, 12)) + } +} + +// Repro from #8911 + +// two interfaces +interface A { +>A : Symbol(A, Decl(typeGuardIntersectionTypes.ts, 24, 1)) + + a: string; +>a : Symbol(A.a, Decl(typeGuardIntersectionTypes.ts, 29, 13)) +} + +interface B { +>B : Symbol(B, Decl(typeGuardIntersectionTypes.ts, 31, 1)) + + b: string; +>b : Symbol(B.b, Decl(typeGuardIntersectionTypes.ts, 33, 13)) +} + +// a type guard for B +function isB(toTest: any): toTest is B { +>isB : Symbol(isB, Decl(typeGuardIntersectionTypes.ts, 35, 1)) +>toTest : Symbol(toTest, Decl(typeGuardIntersectionTypes.ts, 38, 13)) +>toTest : Symbol(toTest, Decl(typeGuardIntersectionTypes.ts, 38, 13)) +>B : Symbol(B, Decl(typeGuardIntersectionTypes.ts, 31, 1)) + + return toTest && toTest.b; +>toTest : Symbol(toTest, Decl(typeGuardIntersectionTypes.ts, 38, 13)) +>toTest : Symbol(toTest, Decl(typeGuardIntersectionTypes.ts, 38, 13)) +} + +// a function that turns an A into an A & B +function union(a: A): A & B | null { +>union : Symbol(union, Decl(typeGuardIntersectionTypes.ts, 40, 1)) +>a : Symbol(a, Decl(typeGuardIntersectionTypes.ts, 43, 15)) +>A : Symbol(A, Decl(typeGuardIntersectionTypes.ts, 24, 1)) +>A : Symbol(A, Decl(typeGuardIntersectionTypes.ts, 24, 1)) +>B : Symbol(B, Decl(typeGuardIntersectionTypes.ts, 31, 1)) + + if (isB(a)) { +>isB : Symbol(isB, Decl(typeGuardIntersectionTypes.ts, 35, 1)) +>a : Symbol(a, Decl(typeGuardIntersectionTypes.ts, 43, 15)) + + return a; +>a : Symbol(a, Decl(typeGuardIntersectionTypes.ts, 43, 15)) + + } else { + return null; + } +} + +// Repro from #9016 + +declare function log(s: string): void; +>log : Symbol(log, Decl(typeGuardIntersectionTypes.ts, 49, 1)) +>s : Symbol(s, Decl(typeGuardIntersectionTypes.ts, 53, 21)) + +// Supported beast features +interface Beast { wings?: boolean; legs?: number } +>Beast : Symbol(Beast, Decl(typeGuardIntersectionTypes.ts, 53, 38)) +>wings : Symbol(Beast.wings, Decl(typeGuardIntersectionTypes.ts, 56, 21)) +>legs : Symbol(Beast.legs, Decl(typeGuardIntersectionTypes.ts, 56, 38)) + +interface Legged { legs: number; } +>Legged : Symbol(Legged, Decl(typeGuardIntersectionTypes.ts, 56, 54)) +>legs : Symbol(Legged.legs, Decl(typeGuardIntersectionTypes.ts, 57, 21)) + +interface Winged { wings: boolean; } +>Winged : Symbol(Winged, Decl(typeGuardIntersectionTypes.ts, 57, 37)) +>wings : Symbol(Winged.wings, Decl(typeGuardIntersectionTypes.ts, 58, 21)) + +// Beast feature detection via user-defined type guards +function hasLegs(x: Beast): x is Legged { return x && typeof x.legs === 'number'; } +>hasLegs : Symbol(hasLegs, Decl(typeGuardIntersectionTypes.ts, 58, 39)) +>x : Symbol(x, Decl(typeGuardIntersectionTypes.ts, 61, 17)) +>Beast : Symbol(Beast, Decl(typeGuardIntersectionTypes.ts, 53, 38)) +>x : Symbol(x, Decl(typeGuardIntersectionTypes.ts, 61, 17)) +>Legged : Symbol(Legged, Decl(typeGuardIntersectionTypes.ts, 56, 54)) +>x : Symbol(x, Decl(typeGuardIntersectionTypes.ts, 61, 17)) +>x.legs : Symbol(Beast.legs, Decl(typeGuardIntersectionTypes.ts, 56, 38)) +>x : Symbol(x, Decl(typeGuardIntersectionTypes.ts, 61, 17)) +>legs : Symbol(Beast.legs, Decl(typeGuardIntersectionTypes.ts, 56, 38)) + +function hasWings(x: Beast): x is Winged { return x && !!x.wings; } +>hasWings : Symbol(hasWings, Decl(typeGuardIntersectionTypes.ts, 61, 83)) +>x : Symbol(x, Decl(typeGuardIntersectionTypes.ts, 62, 18)) +>Beast : Symbol(Beast, Decl(typeGuardIntersectionTypes.ts, 53, 38)) +>x : Symbol(x, Decl(typeGuardIntersectionTypes.ts, 62, 18)) +>Winged : Symbol(Winged, Decl(typeGuardIntersectionTypes.ts, 57, 37)) +>x : Symbol(x, Decl(typeGuardIntersectionTypes.ts, 62, 18)) +>x.wings : Symbol(Beast.wings, Decl(typeGuardIntersectionTypes.ts, 56, 21)) +>x : Symbol(x, Decl(typeGuardIntersectionTypes.ts, 62, 18)) +>wings : Symbol(Beast.wings, Decl(typeGuardIntersectionTypes.ts, 56, 21)) + +// Function to identify a given beast by detecting its features +function identifyBeast(beast: Beast) { +>identifyBeast : Symbol(identifyBeast, Decl(typeGuardIntersectionTypes.ts, 62, 67)) +>beast : Symbol(beast, Decl(typeGuardIntersectionTypes.ts, 65, 23)) +>Beast : Symbol(Beast, Decl(typeGuardIntersectionTypes.ts, 53, 38)) + + // All beasts with legs + if (hasLegs(beast)) { +>hasLegs : Symbol(hasLegs, Decl(typeGuardIntersectionTypes.ts, 58, 39)) +>beast : Symbol(beast, Decl(typeGuardIntersectionTypes.ts, 65, 23)) + + // All winged beasts with legs + if (hasWings(beast)) { +>hasWings : Symbol(hasWings, Decl(typeGuardIntersectionTypes.ts, 61, 83)) +>beast : Symbol(beast, Decl(typeGuardIntersectionTypes.ts, 65, 23)) + + if (beast.legs === 4) { +>beast.legs : Symbol(Legged.legs, Decl(typeGuardIntersectionTypes.ts, 57, 21)) +>beast : Symbol(beast, Decl(typeGuardIntersectionTypes.ts, 65, 23)) +>legs : Symbol(Legged.legs, Decl(typeGuardIntersectionTypes.ts, 57, 21)) + + log(`pegasus - 4 legs, wings`); +>log : Symbol(log, Decl(typeGuardIntersectionTypes.ts, 49, 1)) + } + else if (beast.legs === 2) { +>beast.legs : Symbol(Legged.legs, Decl(typeGuardIntersectionTypes.ts, 57, 21)) +>beast : Symbol(beast, Decl(typeGuardIntersectionTypes.ts, 65, 23)) +>legs : Symbol(Legged.legs, Decl(typeGuardIntersectionTypes.ts, 57, 21)) + + log(`bird - 2 legs, wings`); +>log : Symbol(log, Decl(typeGuardIntersectionTypes.ts, 49, 1)) + } + else { + log(`unknown - ${beast.legs} legs, wings`); +>log : Symbol(log, Decl(typeGuardIntersectionTypes.ts, 49, 1)) +>beast.legs : Symbol(Legged.legs, Decl(typeGuardIntersectionTypes.ts, 57, 21)) +>beast : Symbol(beast, Decl(typeGuardIntersectionTypes.ts, 65, 23)) +>legs : Symbol(Legged.legs, Decl(typeGuardIntersectionTypes.ts, 57, 21)) + } + } + + // All non-winged beasts with legs + else { + log(`manbearpig - ${beast.legs} legs, no wings`); +>log : Symbol(log, Decl(typeGuardIntersectionTypes.ts, 49, 1)) +>beast.legs : Symbol(Legged.legs, Decl(typeGuardIntersectionTypes.ts, 57, 21)) +>beast : Symbol(beast, Decl(typeGuardIntersectionTypes.ts, 65, 23)) +>legs : Symbol(Legged.legs, Decl(typeGuardIntersectionTypes.ts, 57, 21)) + } + } + + // All beasts without legs + else { + if (hasWings(beast)) { +>hasWings : Symbol(hasWings, Decl(typeGuardIntersectionTypes.ts, 61, 83)) +>beast : Symbol(beast, Decl(typeGuardIntersectionTypes.ts, 65, 23)) + + log(`quetzalcoatl - no legs, wings`) +>log : Symbol(log, Decl(typeGuardIntersectionTypes.ts, 49, 1)) + } + else { + log(`snake - no legs, no wings`) +>log : Symbol(log, Decl(typeGuardIntersectionTypes.ts, 49, 1)) + } + } +} + +function beastFoo(beast: Object) { +>beastFoo : Symbol(beastFoo, Decl(typeGuardIntersectionTypes.ts, 98, 1)) +>beast : Symbol(beast, Decl(typeGuardIntersectionTypes.ts, 100, 18)) +>Object : Symbol(Object, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) + + if (hasWings(beast) && hasLegs(beast)) { +>hasWings : Symbol(hasWings, Decl(typeGuardIntersectionTypes.ts, 61, 83)) +>beast : Symbol(beast, Decl(typeGuardIntersectionTypes.ts, 100, 18)) +>hasLegs : Symbol(hasLegs, Decl(typeGuardIntersectionTypes.ts, 58, 39)) +>beast : Symbol(beast, Decl(typeGuardIntersectionTypes.ts, 100, 18)) + + beast; // Winged & Legged +>beast : Symbol(beast, Decl(typeGuardIntersectionTypes.ts, 100, 18)) + } + else { + beast; +>beast : Symbol(beast, Decl(typeGuardIntersectionTypes.ts, 100, 18)) + } + + if (hasLegs(beast) && hasWings(beast)) { +>hasLegs : Symbol(hasLegs, Decl(typeGuardIntersectionTypes.ts, 58, 39)) +>beast : Symbol(beast, Decl(typeGuardIntersectionTypes.ts, 100, 18)) +>hasWings : Symbol(hasWings, Decl(typeGuardIntersectionTypes.ts, 61, 83)) +>beast : Symbol(beast, Decl(typeGuardIntersectionTypes.ts, 100, 18)) + + beast; // Legged & Winged +>beast : Symbol(beast, Decl(typeGuardIntersectionTypes.ts, 100, 18)) + } +} diff --git a/tests/baselines/reference/typeGuardIntersectionTypes.types b/tests/baselines/reference/typeGuardIntersectionTypes.types new file mode 100644 index 0000000000000..8be50453572b6 --- /dev/null +++ b/tests/baselines/reference/typeGuardIntersectionTypes.types @@ -0,0 +1,306 @@ +=== tests/cases/conformance/expressions/typeGuards/typeGuardIntersectionTypes.ts === + +interface X { +>X : X + + x: string; +>x : string +} + +interface Y { +>Y : Y + + y: string; +>y : string +} + +interface Z { +>Z : Z + + z: string; +>z : string +} + +declare function isX(obj: any): obj is X; +>isX : (obj: any) => obj is X +>obj : any +>obj : any +>X : X + +declare function isY(obj: any): obj is Y; +>isY : (obj: any) => obj is Y +>obj : any +>obj : any +>Y : Y + +declare function isZ(obj: any): obj is Z; +>isZ : (obj: any) => obj is Z +>obj : any +>obj : any +>Z : Z + +function f1(obj: Object) { +>f1 : (obj: Object) => void +>obj : Object +>Object : Object + + if (isX(obj) || isY(obj) || isZ(obj)) { +>isX(obj) || isY(obj) || isZ(obj) : boolean +>isX(obj) || isY(obj) : boolean +>isX(obj) : boolean +>isX : (obj: any) => obj is X +>obj : Object +>isY(obj) : boolean +>isY : (obj: any) => obj is Y +>obj : Object +>isZ(obj) : boolean +>isZ : (obj: any) => obj is Z +>obj : Object + + obj; +>obj : X | Y | Z + } + if (isX(obj) && isY(obj) && isZ(obj)) { +>isX(obj) && isY(obj) && isZ(obj) : boolean +>isX(obj) && isY(obj) : boolean +>isX(obj) : boolean +>isX : (obj: any) => obj is X +>obj : Object +>isY(obj) : boolean +>isY : (obj: any) => obj is Y +>obj : X +>isZ(obj) : boolean +>isZ : (obj: any) => obj is Z +>obj : X & Y + + obj; +>obj : X & Y & Z + } +} + +// Repro from #8911 + +// two interfaces +interface A { +>A : A + + a: string; +>a : string +} + +interface B { +>B : B + + b: string; +>b : string +} + +// a type guard for B +function isB(toTest: any): toTest is B { +>isB : (toTest: any) => toTest is B +>toTest : any +>toTest : any +>B : B + + return toTest && toTest.b; +>toTest && toTest.b : any +>toTest : any +>toTest.b : any +>toTest : any +>b : any +} + +// a function that turns an A into an A & B +function union(a: A): A & B | null { +>union : (a: A) => (A & B) | null +>a : A +>A : A +>A : A +>B : B +>null : null + + if (isB(a)) { +>isB(a) : boolean +>isB : (toTest: any) => toTest is B +>a : A + + return a; +>a : A & B + + } else { + return null; +>null : null + } +} + +// Repro from #9016 + +declare function log(s: string): void; +>log : (s: string) => void +>s : string + +// Supported beast features +interface Beast { wings?: boolean; legs?: number } +>Beast : Beast +>wings : boolean | undefined +>legs : number | undefined + +interface Legged { legs: number; } +>Legged : Legged +>legs : number + +interface Winged { wings: boolean; } +>Winged : Winged +>wings : boolean + +// Beast feature detection via user-defined type guards +function hasLegs(x: Beast): x is Legged { return x && typeof x.legs === 'number'; } +>hasLegs : (x: Beast) => x is Legged +>x : Beast +>Beast : Beast +>x : any +>Legged : Legged +>x && typeof x.legs === 'number' : boolean +>x : Beast +>typeof x.legs === 'number' : boolean +>typeof x.legs : string +>x.legs : number | undefined +>x : Beast +>legs : number | undefined +>'number' : string + +function hasWings(x: Beast): x is Winged { return x && !!x.wings; } +>hasWings : (x: Beast) => x is Winged +>x : Beast +>Beast : Beast +>x : any +>Winged : Winged +>x && !!x.wings : boolean +>x : Beast +>!!x.wings : boolean +>!x.wings : boolean +>x.wings : boolean | undefined +>x : Beast +>wings : boolean | undefined + +// Function to identify a given beast by detecting its features +function identifyBeast(beast: Beast) { +>identifyBeast : (beast: Beast) => void +>beast : Beast +>Beast : Beast + + // All beasts with legs + if (hasLegs(beast)) { +>hasLegs(beast) : boolean +>hasLegs : (x: Beast) => x is Legged +>beast : Beast + + // All winged beasts with legs + if (hasWings(beast)) { +>hasWings(beast) : boolean +>hasWings : (x: Beast) => x is Winged +>beast : Legged + + if (beast.legs === 4) { +>beast.legs === 4 : boolean +>beast.legs : number +>beast : Legged & Winged +>legs : number +>4 : number + + log(`pegasus - 4 legs, wings`); +>log(`pegasus - 4 legs, wings`) : void +>log : (s: string) => void +>`pegasus - 4 legs, wings` : string + } + else if (beast.legs === 2) { +>beast.legs === 2 : boolean +>beast.legs : number +>beast : Legged & Winged +>legs : number +>2 : number + + log(`bird - 2 legs, wings`); +>log(`bird - 2 legs, wings`) : void +>log : (s: string) => void +>`bird - 2 legs, wings` : string + } + else { + log(`unknown - ${beast.legs} legs, wings`); +>log(`unknown - ${beast.legs} legs, wings`) : void +>log : (s: string) => void +>`unknown - ${beast.legs} legs, wings` : string +>beast.legs : number +>beast : Legged & Winged +>legs : number + } + } + + // All non-winged beasts with legs + else { + log(`manbearpig - ${beast.legs} legs, no wings`); +>log(`manbearpig - ${beast.legs} legs, no wings`) : void +>log : (s: string) => void +>`manbearpig - ${beast.legs} legs, no wings` : string +>beast.legs : number +>beast : Legged +>legs : number + } + } + + // All beasts without legs + else { + if (hasWings(beast)) { +>hasWings(beast) : boolean +>hasWings : (x: Beast) => x is Winged +>beast : Beast + + log(`quetzalcoatl - no legs, wings`) +>log(`quetzalcoatl - no legs, wings`) : void +>log : (s: string) => void +>`quetzalcoatl - no legs, wings` : string + } + else { + log(`snake - no legs, no wings`) +>log(`snake - no legs, no wings`) : void +>log : (s: string) => void +>`snake - no legs, no wings` : string + } + } +} + +function beastFoo(beast: Object) { +>beastFoo : (beast: Object) => void +>beast : Object +>Object : Object + + if (hasWings(beast) && hasLegs(beast)) { +>hasWings(beast) && hasLegs(beast) : boolean +>hasWings(beast) : boolean +>hasWings : (x: Beast) => x is Winged +>beast : Object +>hasLegs(beast) : boolean +>hasLegs : (x: Beast) => x is Legged +>beast : Winged + + beast; // Winged & Legged +>beast : Winged & Legged + } + else { + beast; +>beast : Object + } + + if (hasLegs(beast) && hasWings(beast)) { +>hasLegs(beast) && hasWings(beast) : boolean +>hasLegs(beast) : boolean +>hasLegs : (x: Beast) => x is Legged +>beast : Object +>hasWings(beast) : boolean +>hasWings : (x: Beast) => x is Winged +>beast : Legged + + beast; // Legged & Winged +>beast : Legged & Winged + } +} diff --git a/tests/baselines/reference/typeGuardsWithInstanceOf.errors.txt b/tests/baselines/reference/typeGuardsWithInstanceOf.errors.txt deleted file mode 100644 index dfcb8a598dae5..0000000000000 --- a/tests/baselines/reference/typeGuardsWithInstanceOf.errors.txt +++ /dev/null @@ -1,14 +0,0 @@ -tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOf.ts(7,20): error TS2339: Property 'global' does not exist on type 'never'. - - -==== tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOf.ts (1 errors) ==== - interface I { global: string; } - var result: I; - var result2: I; - - if (!(result instanceof RegExp)) { - result = result2; - } else if (!result.global) { - ~~~~~~ -!!! error TS2339: Property 'global' does not exist on type 'never'. - } \ No newline at end of file diff --git a/tests/baselines/reference/typeGuardsWithInstanceOf.symbols b/tests/baselines/reference/typeGuardsWithInstanceOf.symbols new file mode 100644 index 0000000000000..6d1667a651995 --- /dev/null +++ b/tests/baselines/reference/typeGuardsWithInstanceOf.symbols @@ -0,0 +1,26 @@ +=== tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOf.ts === +interface I { global: string; } +>I : Symbol(I, Decl(typeGuardsWithInstanceOf.ts, 0, 0)) +>global : Symbol(I.global, Decl(typeGuardsWithInstanceOf.ts, 0, 13)) + +var result: I; +>result : Symbol(result, Decl(typeGuardsWithInstanceOf.ts, 1, 3)) +>I : Symbol(I, Decl(typeGuardsWithInstanceOf.ts, 0, 0)) + +var result2: I; +>result2 : Symbol(result2, Decl(typeGuardsWithInstanceOf.ts, 2, 3)) +>I : Symbol(I, Decl(typeGuardsWithInstanceOf.ts, 0, 0)) + +if (!(result instanceof RegExp)) { +>result : Symbol(result, Decl(typeGuardsWithInstanceOf.ts, 1, 3)) +>RegExp : Symbol(RegExp, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) + + result = result2; +>result : Symbol(result, Decl(typeGuardsWithInstanceOf.ts, 1, 3)) +>result2 : Symbol(result2, Decl(typeGuardsWithInstanceOf.ts, 2, 3)) + +} else if (!result.global) { +>result.global : Symbol(global, Decl(typeGuardsWithInstanceOf.ts, 0, 13), Decl(lib.d.ts, --, --)) +>result : Symbol(result, Decl(typeGuardsWithInstanceOf.ts, 1, 3)) +>global : Symbol(global, Decl(typeGuardsWithInstanceOf.ts, 0, 13), Decl(lib.d.ts, --, --)) +} diff --git a/tests/baselines/reference/typeGuardsWithInstanceOf.types b/tests/baselines/reference/typeGuardsWithInstanceOf.types new file mode 100644 index 0000000000000..f54498f93c875 --- /dev/null +++ b/tests/baselines/reference/typeGuardsWithInstanceOf.types @@ -0,0 +1,31 @@ +=== tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOf.ts === +interface I { global: string; } +>I : I +>global : string + +var result: I; +>result : I +>I : I + +var result2: I; +>result2 : I +>I : I + +if (!(result instanceof RegExp)) { +>!(result instanceof RegExp) : boolean +>(result instanceof RegExp) : boolean +>result instanceof RegExp : boolean +>result : I +>RegExp : RegExpConstructor + + result = result2; +>result = result2 : I +>result : I +>result2 : I + +} else if (!result.global) { +>!result.global : boolean +>result.global : string & boolean +>result : I & RegExp +>global : string & boolean +} diff --git a/tests/cases/conformance/expressions/typeGuards/typeGuardIntersectionTypes.ts b/tests/cases/conformance/expressions/typeGuards/typeGuardIntersectionTypes.ts new file mode 100644 index 0000000000000..85c003787fd38 --- /dev/null +++ b/tests/cases/conformance/expressions/typeGuards/typeGuardIntersectionTypes.ts @@ -0,0 +1,113 @@ +// @strictNullChecks: true + +interface X { + x: string; +} + +interface Y { + y: string; +} + +interface Z { + z: string; +} + +declare function isX(obj: any): obj is X; +declare function isY(obj: any): obj is Y; +declare function isZ(obj: any): obj is Z; + +function f1(obj: Object) { + if (isX(obj) || isY(obj) || isZ(obj)) { + obj; + } + if (isX(obj) && isY(obj) && isZ(obj)) { + obj; + } +} + +// Repro from #8911 + +// two interfaces +interface A { + a: string; +} + +interface B { + b: string; +} + +// a type guard for B +function isB(toTest: any): toTest is B { + return toTest && toTest.b; +} + +// a function that turns an A into an A & B +function union(a: A): A & B | null { + if (isB(a)) { + return a; + } else { + return null; + } +} + +// Repro from #9016 + +declare function log(s: string): void; + +// Supported beast features +interface Beast { wings?: boolean; legs?: number } +interface Legged { legs: number; } +interface Winged { wings: boolean; } + +// Beast feature detection via user-defined type guards +function hasLegs(x: Beast): x is Legged { return x && typeof x.legs === 'number'; } +function hasWings(x: Beast): x is Winged { return x && !!x.wings; } + +// Function to identify a given beast by detecting its features +function identifyBeast(beast: Beast) { + + // All beasts with legs + if (hasLegs(beast)) { + + // All winged beasts with legs + if (hasWings(beast)) { + if (beast.legs === 4) { + log(`pegasus - 4 legs, wings`); + } + else if (beast.legs === 2) { + log(`bird - 2 legs, wings`); + } + else { + log(`unknown - ${beast.legs} legs, wings`); + } + } + + // All non-winged beasts with legs + else { + log(`manbearpig - ${beast.legs} legs, no wings`); + } + } + + // All beasts without legs + else { + if (hasWings(beast)) { + log(`quetzalcoatl - no legs, wings`) + } + else { + log(`snake - no legs, no wings`) + } + } +} + +function beastFoo(beast: Object) { + if (hasWings(beast) && hasLegs(beast)) { + beast; // Winged & Legged + } + else { + beast; + } + + if (hasLegs(beast) && hasWings(beast)) { + beast; // Legged & Winged + } +} \ No newline at end of file