<div class="highlight highlight-source-js notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="var htmlElement:HTMLElement = getElement()

// 
var offsetParent = htmlElement.offsetParent;
if(offsetParent instanceof HTMLElement){
    htmlElement = offsetParent; // OK!
}

// alternatively this will cause an error:
if(htmlElement.offsetParent instanceof HTMLElement){
    htmlElement = htmlElement.offsetParent; // ERROR: Assigned expression type Element is not assignable to type HTMLElement 
}"><pre class="notranslate"><span class="pl-k">var</span> <span class="pl-s1">htmlElement</span>:<span class="pl-v">HTMLElement</span> <span class="pl-c1">=</span> <span class="pl-en">getElement</span><span class="pl-kos">(</span><span class="pl-kos">)</span>

<span class="pl-c">// </span>
<span class="pl-k">var</span> <span class="pl-s1">offsetParent</span> <span class="pl-c1">=</span> <span class="pl-s1">htmlElement</span><span class="pl-kos">.</span><span class="pl-c1">offsetParent</span><span class="pl-kos">;</span>
<span class="pl-k">if</span><span class="pl-kos">(</span><span class="pl-s1">offsetParent</span> <span class="pl-k">instanceof</span> <span class="pl-v">HTMLElement</span><span class="pl-kos">)</span><span class="pl-kos">{</span>
    <span class="pl-s1">htmlElement</span> <span class="pl-c1">=</span> <span class="pl-s1">offsetParent</span><span class="pl-kos">;</span> <span class="pl-c">// OK!</span>
<span class="pl-kos">}</span>

<span class="pl-c">// alternatively this will cause an error:</span>
<span class="pl-k">if</span><span class="pl-kos">(</span><span class="pl-s1">htmlElement</span><span class="pl-kos">.</span><span class="pl-c1">offsetParent</span> <span class="pl-k">instanceof</span> <span class="pl-v">HTMLElement</span><span class="pl-kos">)</span><span class="pl-kos">{</span>
    <span class="pl-s1">htmlElement</span> <span class="pl-c1">=</span> <span class="pl-s1">htmlElement</span><span class="pl-kos">.</span><span class="pl-c1">offsetParent</span><span class="pl-kos">;</span> <span class="pl-c">// ERROR: Assigned expression type Element is not assignable to type HTMLElement </span>
<span class="pl-kos">}</span></pre></div>
<p dir="auto">This is a proposal for using property access as another form of type guards (see <a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="45918355" data-permission-text="Title is private" data-url="https://github.com/microsoft/TypeScript/issues/900" data-hovercard-type="pull_request" data-hovercard-url="/microsoft/TypeScript/pull/900/hovercard" href="https://github.com/microsoft/TypeScript/pull/900">#900</a>) to narrow union types. While we're investigating expanding the power of type guards (<a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="47327525" data-permission-text="Title is private" data-url="https://github.com/microsoft/TypeScript/issues/1007" data-hovercard-type="issue" data-hovercard-url="/microsoft/TypeScript/issues/1007/hovercard" href="https://github.com/microsoft/TypeScript/issues/1007">#1007</a>) this feature would support the natural style that JavaScript programmers have in their code today.</p>
<h3 dir="auto">Using property access to narrow union types</h3>
<div class="highlight highlight-source-ts notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="var x: string|number;
if (x.length) { // no error even though number lacks a length property
    var r = x.charAt(3); // x is of type string inside this block
}

var r2 = x.length || x * x; // no error, x narrowed to number on right hand side, r3 is number

var y: string[]|number[]|string;
if (y.length &amp;&amp; y.push) {
    // y is string[]|number[] now
    var first = y[0]; // first is string|number
    if (first.length) {
        // first is string in here
    } else {
        // first is number in here
    }
}"><pre class="notranslate"><span class="pl-k">var</span> <span class="pl-s1">x</span>: <span class="pl-smi">string</span><span class="pl-c1">|</span><span class="pl-smi">number</span><span class="pl-kos">;</span>
<span class="pl-k">if</span> <span class="pl-kos">(</span><span class="pl-s1">x</span><span class="pl-kos">.</span><span class="pl-c1">length</span><span class="pl-kos">)</span> <span class="pl-kos">{</span> <span class="pl-c">// no error even though number lacks a length property</span>
    <span class="pl-k">var</span> <span class="pl-s1">r</span> <span class="pl-c1">=</span> <span class="pl-s1">x</span><span class="pl-kos">.</span><span class="pl-en">charAt</span><span class="pl-kos">(</span><span class="pl-c1">3</span><span class="pl-kos">)</span><span class="pl-kos">;</span> <span class="pl-c">// x is of type string inside this block</span>
<span class="pl-kos">}</span>

<span class="pl-k">var</span> <span class="pl-s1">r2</span> <span class="pl-c1">=</span> <span class="pl-s1">x</span><span class="pl-kos">.</span><span class="pl-c1">length</span> <span class="pl-c1">||</span> <span class="pl-s1">x</span> <span class="pl-c1">*</span> <span class="pl-s1">x</span><span class="pl-kos">;</span> <span class="pl-c">// no error, x narrowed to number on right hand side, r3 is number</span>

<span class="pl-k">var</span> <span class="pl-s1">y</span>: <span class="pl-smi">string</span><span class="pl-kos">[</span><span class="pl-kos">]</span><span class="pl-c1">|</span><span class="pl-smi">number</span><span class="pl-kos">[</span><span class="pl-kos">]</span><span class="pl-c1">|</span><span class="pl-smi">string</span><span class="pl-kos">;</span>
<span class="pl-k">if</span> <span class="pl-kos">(</span><span class="pl-s1">y</span><span class="pl-kos">.</span><span class="pl-c1">length</span> <span class="pl-c1">&amp;&amp;</span> <span class="pl-s1">y</span><span class="pl-kos">.</span><span class="pl-c1">push</span><span class="pl-kos">)</span> <span class="pl-kos">{</span>
    <span class="pl-c">// y is string[]|number[] now</span>
    <span class="pl-k">var</span> <span class="pl-s1">first</span> <span class="pl-c1">=</span> <span class="pl-s1">y</span><span class="pl-kos">[</span><span class="pl-c1">0</span><span class="pl-kos">]</span><span class="pl-kos">;</span> <span class="pl-c">// first is string|number</span>
    <span class="pl-k">if</span> <span class="pl-kos">(</span><span class="pl-s1">first</span><span class="pl-kos">.</span><span class="pl-c1">length</span><span class="pl-kos">)</span> <span class="pl-kos">{</span>
        <span class="pl-c">// first is string in here</span>
    <span class="pl-kos">}</span> <span class="pl-k">else</span> <span class="pl-kos">{</span>
        <span class="pl-c">// first is number in here</span>
    <span class="pl-kos">}</span>
<span class="pl-kos">}</span></pre></div>
<p dir="auto">We do not expand the situations in which types are narrowed, but we do expand the known type guard patterns to include basic property access. In these narrowing contexts it would be an error to access a property that does not exist in at least one of the constituent types of a union type (as it is today). However, it would now be valid to access a property that exists in at least one, but not all, constituent types. Any such property access will then narrow the type of the operand to only those constituent types in the union which do contain the accessed property. In any other context property access is unchanged.</p>
<h3 dir="auto">Invalid property access</h3>
<div class="highlight highlight-source-ts notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="var x: string|number;

var r = x.length; // error, not a type guard, normal property access rules apply

if (x.len) { } // error, len does not exist on type string|number 

var r3 = !x.len &amp;&amp; x.length; // error, len does not exist on type string|number 

var r4 = x.length &amp;&amp; x.len; // error, len does not exist on type string"><pre class="notranslate"><span class="pl-k">var</span> <span class="pl-s1">x</span>: <span class="pl-smi">string</span><span class="pl-c1">|</span><span class="pl-smi">number</span><span class="pl-kos">;</span>

<span class="pl-k">var</span> <span class="pl-s1">r</span> <span class="pl-c1">=</span> <span class="pl-s1">x</span><span class="pl-kos">.</span><span class="pl-c1">length</span><span class="pl-kos">;</span> <span class="pl-c">// error, not a type guard, normal property access rules apply</span>

<span class="pl-k">if</span> <span class="pl-kos">(</span><span class="pl-s1">x</span><span class="pl-kos">.</span><span class="pl-c1">len</span><span class="pl-kos">)</span> <span class="pl-kos">{</span> <span class="pl-kos">}</span> <span class="pl-c">// error, len does not exist on type string|number </span>

<span class="pl-k">var</span> <span class="pl-s1">r3</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">len</span> <span class="pl-c1">&amp;&amp;</span> <span class="pl-s1">x</span><span class="pl-kos">.</span><span class="pl-c1">length</span><span class="pl-kos">;</span> <span class="pl-c">// error, len does not exist on type string|number </span>

<span class="pl-k">var</span> <span class="pl-s1">r4</span> <span class="pl-c1">=</span> <span class="pl-s1">x</span><span class="pl-kos">.</span><span class="pl-c1">length</span> <span class="pl-c1">&amp;&amp;</span> <span class="pl-s1">x</span><span class="pl-kos">.</span><span class="pl-c1">len</span><span class="pl-kos">;</span> <span class="pl-c">// error, len does not exist on type string</span></pre></div>
<h3 dir="auto">Issues/Questions</h3>
<ul dir="auto">
<li>Should the language service behave differently in these type guard contexts? Should dotting off a union type in a type guard list all members on all types rather than only those that exist on all of them?</li>
<li>Need to understand performance implications</li>
</ul>
<p dir="auto">I have a sample implementation and tests in a branch here: <a href="https://github.com/Microsoft/TypeScript/tree/typeGuardsViaPropertyAccess">https://github.com/Microsoft/TypeScript/tree/typeGuardsViaPropertyAccess</a>. There're a couple bugs remaining but examples like the above all work and no existing behavior was changed.</p>