<div class="highlight highlight-source-ts notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="
// 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 &amp;&amp; typeof x.legs === 'number'; }
function hasWings(x: Beast): x is Winged { return x &amp;&amp; !!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"><pre class="notranslate"><span class="pl-c">// Supported beast features</span>
<span class="pl-k">interface</span> <span class="pl-smi">Beast</span>     <span class="pl-kos">{</span> <span class="pl-c1">wings</span>?: <span class="pl-smi">boolean</span><span class="pl-kos">;</span> <span class="pl-c1">legs</span>?: <span class="pl-smi">number</span> <span class="pl-kos">}</span>
<span class="pl-k">interface</span> <span class="pl-smi">Legged</span>    <span class="pl-kos">{</span> <span class="pl-c1">legs</span>: <span class="pl-smi">number</span><span class="pl-kos">;</span> <span class="pl-kos">}</span>
<span class="pl-k">interface</span> <span class="pl-smi">Winged</span>    <span class="pl-kos">{</span> <span class="pl-c1">wings</span>: <span class="pl-smi">boolean</span><span class="pl-kos">;</span> <span class="pl-kos">}</span>

<span class="pl-c">// Beast feature detection via user-defined type guards</span>
<span class="pl-k">function</span> <span class="pl-en">hasLegs</span><span class="pl-kos">(</span><span class="pl-s1">x</span>: <span class="pl-smi">Beast</span><span class="pl-kos">)</span>: <span class="pl-s1">x</span> is <span class="pl-smi">Legged</span> <span class="pl-kos">{</span> <span class="pl-k">return</span> <span class="pl-s1">x</span> <span class="pl-c1">&amp;&amp;</span> <span class="pl-k">typeof</span> <span class="pl-s1">x</span><span class="pl-kos">.</span><span class="pl-c1">legs</span> <span class="pl-c1">===</span> <span class="pl-s">'number'</span><span class="pl-kos">;</span> <span class="pl-kos">}</span>
<span class="pl-k">function</span> <span class="pl-en">hasWings</span><span class="pl-kos">(</span><span class="pl-s1">x</span>: <span class="pl-smi">Beast</span><span class="pl-kos">)</span>: <span class="pl-s1">x</span> is <span class="pl-smi">Winged</span> <span class="pl-kos">{</span> <span class="pl-k">return</span> <span class="pl-s1">x</span> <span class="pl-c1">&amp;&amp;</span> <span class="pl-c1">!</span><span class="pl-c1">!</span><span class="pl-s1">x</span><span class="pl-kos">.</span><span class="pl-c1">wings</span><span class="pl-kos">;</span> <span class="pl-kos">}</span>

<span class="pl-c">// Function to identify a given beast by detecting its features</span>
<span class="pl-k">function</span> <span class="pl-en">identifyBeast</span><span class="pl-kos">(</span><span class="pl-s1">beast</span>: <span class="pl-smi">Beast</span><span class="pl-kos">)</span> <span class="pl-kos">{</span>

    <span class="pl-c">// All beasts with legs</span>
    <span class="pl-k">if</span> <span class="pl-kos">(</span><span class="pl-en">hasLegs</span><span class="pl-kos">(</span><span class="pl-s1">beast</span><span class="pl-kos">)</span><span class="pl-kos">)</span> <span class="pl-kos">{</span>

        <span class="pl-c">// All winged beasts with legs</span>
        <span class="pl-k">if</span> <span class="pl-kos">(</span><span class="pl-en">hasWings</span><span class="pl-kos">(</span><span class="pl-s1">beast</span><span class="pl-kos">)</span><span class="pl-kos">)</span> <span class="pl-kos">{</span>
            <span class="pl-k">if</span> <span class="pl-kos">(</span><span class="pl-s1">beast</span><span class="pl-kos">.</span><span class="pl-c1">legs</span> <span class="pl-c1">===</span> <span class="pl-c1">4</span><span class="pl-kos">)</span> <span class="pl-kos">{</span> <span class="pl-c">// ERROR TS2339: Property 'legs' does not exist on type 'Winged'.</span>
                <span class="pl-smi">console</span><span class="pl-kos">.</span><span class="pl-en">log</span><span class="pl-kos">(</span><span class="pl-s">`pegasus - 4 legs, wings`</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
            <span class="pl-kos">}</span>
            <span class="pl-k">else</span> <span class="pl-k">if</span> <span class="pl-kos">(</span><span class="pl-s1">beast</span><span class="pl-kos">.</span><span class="pl-c1">legs</span> <span class="pl-c1">===</span> <span class="pl-c1">2</span><span class="pl-kos">)</span> <span class="pl-kos">{</span> <span class="pl-c">// ERROR TS2339...</span>
                <span class="pl-smi">console</span><span class="pl-kos">.</span><span class="pl-en">log</span><span class="pl-kos">(</span><span class="pl-s">`bird - 2 legs, wings`</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
            <span class="pl-kos">}</span>
            <span class="pl-k">else</span> <span class="pl-kos">{</span>
                <span class="pl-smi">console</span><span class="pl-kos">.</span><span class="pl-en">log</span><span class="pl-kos">(</span><span class="pl-s">`unknown - <span class="pl-s1"><span class="pl-kos">${</span><span class="pl-s1">beast</span><span class="pl-kos">.</span><span class="pl-c1">legs</span><span class="pl-kos">}</span></span> legs, wings`</span><span class="pl-kos">)</span><span class="pl-kos">;</span> <span class="pl-c">// ERROR TS2339...</span>
            <span class="pl-kos">}</span>
        <span class="pl-kos">}</span>

        <span class="pl-c">// All non-winged beasts with legs</span>
        <span class="pl-k">else</span> <span class="pl-kos">{</span>
            <span class="pl-smi">console</span><span class="pl-kos">.</span><span class="pl-en">log</span><span class="pl-kos">(</span><span class="pl-s">`manbearpig - <span class="pl-s1"><span class="pl-kos">${</span><span class="pl-s1">beast</span><span class="pl-kos">.</span><span class="pl-c1">legs</span><span class="pl-kos">}</span></span> legs, no wings`</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
        <span class="pl-kos">}</span>
    <span class="pl-kos">}</span>

    <span class="pl-c">// All beasts without legs    </span>
    <span class="pl-k">else</span> <span class="pl-kos">{</span>
        <span class="pl-k">if</span> <span class="pl-kos">(</span><span class="pl-en">hasWings</span><span class="pl-kos">(</span><span class="pl-s1">beast</span><span class="pl-kos">)</span><span class="pl-kos">)</span> <span class="pl-kos">{</span>
            <span class="pl-smi">console</span><span class="pl-kos">.</span><span class="pl-en">log</span><span class="pl-kos">(</span><span class="pl-s">`quetzalcoatl - no legs, wings`</span><span class="pl-kos">)</span>
        <span class="pl-kos">}</span>
        <span class="pl-k">else</span> <span class="pl-kos">{</span>
            <span class="pl-smi">console</span><span class="pl-kos">.</span><span class="pl-en">log</span><span class="pl-kos">(</span><span class="pl-s">`snake - no legs, no wings`</span><span class="pl-kos">)</span>
        <span class="pl-kos">}</span>
    <span class="pl-kos">}</span>
<span class="pl-kos">}</span>

<span class="pl-c">// Runtime results</span>
<span class="pl-en">identifyBeast</span><span class="pl-kos">(</span><span class="pl-kos">{</span> <span class="pl-c1">wings</span>: <span class="pl-c1">true</span> <span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">;</span>             <span class="pl-c">// quetzalcoatl - no legs, wings</span>
<span class="pl-en">identifyBeast</span><span class="pl-kos">(</span><span class="pl-kos">{</span> <span class="pl-c1">wings</span>: <span class="pl-c1">false</span> <span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">;</span>            <span class="pl-c">// snake - no legs, no wings</span>
<span class="pl-en">identifyBeast</span><span class="pl-kos">(</span><span class="pl-kos">{</span> <span class="pl-c1">legs</span>: <span class="pl-c1">2</span> <span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">;</span>                 <span class="pl-c">// manbearpig - 2 legs, no wings</span>
<span class="pl-en">identifyBeast</span><span class="pl-kos">(</span><span class="pl-kos">{</span> <span class="pl-c1">legs</span>: <span class="pl-c1">4</span> <span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">;</span>                 <span class="pl-c">// manbearpig - 4 legs, no wings</span>
<span class="pl-en">identifyBeast</span><span class="pl-kos">(</span><span class="pl-kos">{</span> <span class="pl-c1">wings</span>: <span class="pl-c1">true</span><span class="pl-kos">,</span> <span class="pl-c1">legs</span>: <span class="pl-c1">2</span> <span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">;</span>    <span class="pl-c">// bird - 2 legs, wings</span>
<span class="pl-en">identifyBeast</span><span class="pl-kos">(</span><span class="pl-kos">{</span> <span class="pl-c1">wings</span>: <span class="pl-c1">true</span><span class="pl-kos">,</span> <span class="pl-c1">legs</span>: <span class="pl-c1">4</span> <span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">;</span>    <span class="pl-c">// pegasus - 4 legs, wings</span>
<span class="pl-en">identifyBeast</span><span class="pl-kos">(</span><span class="pl-kos">{</span> <span class="pl-c1">wings</span>: <span class="pl-c1">true</span><span class="pl-kos">,</span> <span class="pl-c1">legs</span>: <span class="pl-c1">6</span> <span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">;</span>    <span class="pl-c">// unknown - 6 legs, wings</span>
<span class="pl-en">identifyBeast</span><span class="pl-kos">(</span><span class="pl-kos">{</span> <span class="pl-c1">wings</span>: <span class="pl-c1">false</span><span class="pl-kos">,</span> <span class="pl-c1">legs</span>: <span class="pl-c1">6</span> <span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">;</span>   <span class="pl-c">// manbearpig - 6 legs, no wings</span></pre></div>
<p dir="auto">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.</p>
<p dir="auto">I believe tsc is working as intended in this example. So this issue is a suggestion for <em>improving</em> type guards to support this coding pattern (sometimes called duck typing <g-emoji class="g-emoji" alias="hatched_chick" fallback-src="https://github.githubassets.com/images/icons/emoji/unicode/1f425.png">🐥</g-emoji>). The rationale is that it is a useful and common practice in JavaScript to identify and refine objects through successive feature detection.</p>
<p dir="auto">The limitation with type guards in their current form is that if we have already narrowed to <code class="notranslate">Legged</code>, and then we further narrow to <code class="notranslate">Winged</code> using a nested type guard, then tsc 'forgets' that we still have a <code class="notranslate">Legged</code>. 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.</p>
<p dir="auto">So the ideal behaviour in this case, would be that if we narrow to <code class="notranslate">Legged</code> and then further narrow to <code class="notranslate">Winged</code>, then its safe to say we must now have a <code class="notranslate">Legged &amp; Winged</code>, ie both features simultaneously. (Strong duck typing? <g-emoji class="g-emoji" alias="muscle" fallback-src="https://github.githubassets.com/images/icons/emoji/unicode/1f4aa.png">💪</g-emoji> <g-emoji class="g-emoji" alias="hatched_chick" fallback-src="https://github.githubassets.com/images/icons/emoji/unicode/1f425.png">🐥</g-emoji>)</p>
<p dir="auto">A similar issue can be seen in the following snippet:</p>
<div class="highlight highlight-source-ts notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="function beastFoo(beast: Beast) {
    if (hasWings(beast) &amp;&amp; hasLegs(beast)) {
        beast // beast is Legged
        // ideally, beast would be Winged &amp;&amp; Legged here...
    }

    if (hasLegs(beast) &amp;&amp; hasWings(beast)) {
        beast // beast is Winged
        // ideally, beast would be Legged &amp;&amp; Winged here...
    }
}"><pre class="notranslate"><span class="pl-k">function</span> <span class="pl-en">beastFoo</span><span class="pl-kos">(</span><span class="pl-s1">beast</span>: <span class="pl-smi">Beast</span><span class="pl-kos">)</span> <span class="pl-kos">{</span>
    <span class="pl-k">if</span> <span class="pl-kos">(</span><span class="pl-en">hasWings</span><span class="pl-kos">(</span><span class="pl-s1">beast</span><span class="pl-kos">)</span> <span class="pl-c1">&amp;&amp;</span> <span class="pl-en">hasLegs</span><span class="pl-kos">(</span><span class="pl-s1">beast</span><span class="pl-kos">)</span><span class="pl-kos">)</span> <span class="pl-kos">{</span>
        <span class="pl-s1">beast</span> <span class="pl-c">// beast is Legged</span>
        <span class="pl-c">// ideally, beast would be Winged &amp;&amp; Legged here...</span>
    <span class="pl-kos">}</span>

    <span class="pl-k">if</span> <span class="pl-kos">(</span><span class="pl-en">hasLegs</span><span class="pl-kos">(</span><span class="pl-s1">beast</span><span class="pl-kos">)</span> <span class="pl-c1">&amp;&amp;</span> <span class="pl-en">hasWings</span><span class="pl-kos">(</span><span class="pl-s1">beast</span><span class="pl-kos">)</span><span class="pl-kos">)</span> <span class="pl-kos">{</span>
        <span class="pl-s1">beast</span> <span class="pl-c">// beast is Winged</span>
        <span class="pl-c">// ideally, beast would be Legged &amp;&amp; Winged here...</span>
    <span class="pl-kos">}</span>
<span class="pl-kos">}</span></pre></div>
<hr>
<p dir="auto">In summary, in a block of code that is only reachable by passing multiple type guards (eg <code class="notranslate">isFoo(x) &amp;&amp; isBar(x) &amp;&amp; isBaz(x)</code>), the narrowed type inside that block would be the intersection of the narrowed types from the type guards (eg <code class="notranslate">x is Foo&amp;Bar&amp;Baz</code>).</p>
<p dir="auto"><strong>TypeScript Version:</strong></p>
<p dir="auto">1.8.10</p>
<p dir="auto"><strong>Code</strong></p>
<div class="highlight highlight-source-ts notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="// two interfaces
interface A {
  a: string;
}

interface B {
  b: string;
}

// a type guard for B
function isB(toTest: any): toTest is B {
  return toTest &amp;&amp; toTest.b;
}

// a function that turns an A into an A &amp; B
function union(a: A): A &amp; B {
  if (isB(a)) {
    return a;
  } else {
    return null;
  }
}"><pre class="notranslate"><span class="pl-c">// two interfaces</span>
<span class="pl-k">interface</span> <span class="pl-smi">A</span> <span class="pl-kos">{</span>
  <span class="pl-c1">a</span>: <span class="pl-smi">string</span><span class="pl-kos">;</span>
<span class="pl-kos">}</span>

<span class="pl-k">interface</span> <span class="pl-smi">B</span> <span class="pl-kos">{</span>
  <span class="pl-c1">b</span>: <span class="pl-smi">string</span><span class="pl-kos">;</span>
<span class="pl-kos">}</span>

<span class="pl-c">// a type guard for B</span>
<span class="pl-k">function</span> <span class="pl-en">isB</span><span class="pl-kos">(</span><span class="pl-s1">toTest</span>: <span class="pl-smi">any</span><span class="pl-kos">)</span>: <span class="pl-s1">toTest</span> is <span class="pl-smi">B</span> <span class="pl-kos">{</span>
  <span class="pl-k">return</span> <span class="pl-s1">toTest</span> <span class="pl-c1">&amp;&amp;</span> <span class="pl-s1">toTest</span><span class="pl-kos">.</span><span class="pl-c1">b</span><span class="pl-kos">;</span>
<span class="pl-kos">}</span>

<span class="pl-c">// a function that turns an A into an A &amp; B</span>
<span class="pl-k">function</span> <span class="pl-en">union</span><span class="pl-kos">(</span><span class="pl-s1">a</span>: <span class="pl-smi">A</span><span class="pl-kos">)</span>: <span class="pl-smi">A</span> <span class="pl-c1">&amp;</span> <span class="pl-smi">B</span> <span class="pl-kos">{</span>
  <span class="pl-k">if</span> <span class="pl-kos">(</span><span class="pl-en">isB</span><span class="pl-kos">(</span><span class="pl-s1">a</span><span class="pl-kos">)</span><span class="pl-kos">)</span> <span class="pl-kos">{</span>
    <span class="pl-k">return</span> <span class="pl-s1">a</span><span class="pl-kos">;</span>
  <span class="pl-kos">}</span> <span class="pl-k">else</span> <span class="pl-kos">{</span>
    <span class="pl-k">return</span> <span class="pl-c1">null</span><span class="pl-kos">;</span>
  <span class="pl-kos">}</span>
<span class="pl-kos">}</span></pre></div>
<p dir="auto"><strong>Expected behavior:</strong></p>
<p dir="auto">I would expect this to compile successfully.</p>
<p dir="auto"><strong>Actual behavior:</strong></p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="test.ts(15,12): error TS2322: Type 'A' is not assignable to type 'A &amp; B'.
  Type 'A' is not assignable to type 'B'.
    Property 'b' is missing in type 'A'."><pre class="notranslate"><code class="notranslate">test.ts(15,12): error TS2322: Type 'A' is not assignable to type 'A &amp; B'.
  Type 'A' is not assignable to type 'B'.
    Property 'b' is missing in type 'A'.
</code></pre></div>
<p dir="auto">Inside the if-block, tsc has "forgotten" about the A-ness of <code class="notranslate">a</code>. This is a useful pattern if <code class="notranslate">b</code> is an expensive property to compute and you want to do different things if <code class="notranslate">b</code> is or is not present.</p>