Issue 1:
// 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) { // ERROR TS2339: Property 'legs' does not exist on type 'Winged'.
console.log(`pegasus - 4 legs, wings`);
}
else if (beast.legs === 2) { // ERROR TS2339...
console.log(`bird - 2 legs, wings`);
}
else {
console.log(`unknown - ${beast.legs} legs, wings`); // ERROR TS2339...
}
}

// All non-winged beasts with legs
else {
console.log(`manbearpig - ${beast.legs} legs, no wings`);
}
}

// All beasts without legs
else {
if (hasWings(beast)) {
console.log(`quetzalcoatl - no legs, wings`)
}
else {
console.log(`snake - no legs, no wings`)
}
}
}

// Runtime results
identifyBeast({ wings: true }); // quetzalcoatl - no legs, wings
identifyBeast({ wings: false }); // snake - no legs, no wings
identifyBeast({ legs: 2 }); // manbearpig - 2 legs, no wings
identifyBeast({ legs: 4 }); // manbearpig - 4 legs, no wings
identifyBeast({ wings: true, legs: 2 }); // bird - 2 legs, wings
identifyBeast({ wings: true, legs: 4 }); // pegasus - 4 legs, wings
identifyBeast({ wings: true, legs: 6 }); // unknown - 6 legs, wings
identifyBeast({ wings: false, legs: 6 }); // manbearpig - 6 legs, no wings

The code above produces compiler errors with tsc@next, but it does represent valid JavaScript that runs correctly and outputs the expected results as shown at the bottom of the code.

I believe tsc is working as intended in this example. So this issue is a suggestion for improving type guards to support this coding pattern (sometimes called duck typing 🐥). The rationale is that it is a useful and common practice in JavaScript to identify and refine objects through successive feature detection.

The limitation with type guards in their current form is that if we have already narrowed to Legged, and then we further narrow to Winged using a nested type guard, then tsc 'forgets' that we still have a Legged. Imagine a blindfolded person feeling the legs of a beast. If further investigation reveals that the beast has wings, the person would probably assume the legs are still there. After all, legs and wings are not mutually exclusive features, and a beast can have both.

So the ideal behaviour in this case, would be that if we narrow to Legged and then further narrow to Winged, then its safe to say we must now have a Legged & Winged, ie both features simultaneously. (Strong duck typing? 💪 🐥)

A similar issue can be seen in the following snippet:

function beastFoo(beast: Beast) {
if (hasWings(beast) && hasLegs(beast)) {
beast // beast is Legged
// ideally, beast would be Winged && Legged here...
}

if (hasLegs(beast) && hasWings(beast)) {
beast // beast is Winged
// ideally, beast would be Legged && Winged here...
}
}

In summary, in a block of code that is only reachable by passing multiple type guards (eg isFoo(x) && isBar(x) && isBaz(x)), the narrowed type inside that block would be the intersection of the narrowed types from the type guards (eg x is Foo&Bar&Baz).

Issue 2:
TypeScript Version:

1.8.10

Code

// 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 {
if (isB(a)) {
return a;
} else {
return null;
}
}

Expected behavior:

I would expect this to compile successfully.

Actual behavior:

test.ts(15,12): error TS2322: Type 'A' is not assignable to type 'A & B'.
Type 'A' is not assignable to type 'B'.
Property 'b' is missing in type 'A'.

Inside the if-block, tsc has "forgotten" about the A-ness of a. This is a useful pattern if b is an expensive property to compute and you want to do different things if b is or is not present.