question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Consider property access a form of type guards

See original GitHub issue

This is a proposal for using property access as another form of type guards (see #900) to narrow union types. While we’re investigating expanding the power of type guards (#1007) this feature would support the natural style that JavaScript programmers have in their code today.

Using property access to narrow union types

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 && 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
    }
}

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.

Invalid property access

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 && x.length; // error, len does not exist on type string|number 

var r4 = x.length && x.len; // error, len does not exist on type string

Issues/Questions

  • 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?
  • Need to understand performance implications

I have a sample implementation and tests in a branch here: https://github.com/Microsoft/TypeScript/tree/typeGuardsViaPropertyAccess. There’re a couple bugs remaining but examples like the above all work and no existing behavior was changed.

Issue Analytics

  • State:open
  • Created 9 years ago
  • Reactions:86
  • Comments:16 (6 by maintainers)

github_iconTop GitHub Comments

7reactions
AlCalzonecommented, Jan 15, 2020

Since this is tagged “Awaiting more feedback”, I’ll just link to https://github.com/microsoft/TypeScript/issues/36194 as a potential use case.

5reactions
thorn0commented, Jan 14, 2021

Feedback for you. Prettier is a formatter that works with ASTs that come from different parsers. In the case of JS-based languages, these AST formats are all based on the same ESTree base format, but also they have differences. It’d be great to be able to use a union type for this, and it looks like it might work (simplified code for two parsers only):

import * as Babel from "@babel/types";
import { TSESTree } from "@typescript-eslint/types";
type Node = Babel.Node | TSESTree.Node;

but in practice the resulting Node type is a pain to work with.

E.g., this already quite complex condition

    node.type === "MethodDefinition" &&
    node.value &&
    node.value.type === "FunctionExpression" &&
    getFunctionParameters(node.value).length === 0 &&
    !node.value.returnType &&

needs to be rewritten this way to make TS happy:

    node.type === "MethodDefinition" &&
    node.value &&
    node.value.type === "FunctionExpression" &&
    getFunctionParameters(node.value).length === 0 &&
    (!("returnType" in node.value) || !node.value.returnType)

The added in check doesn’t make code any clearer and only bloats it. The entire code base needs adding a lot of such checks, which is why any is used almost everywhere instead of Node.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Documentation - Advanced Types - TypeScript
To get the same code working via property accessors, we'll need to use a type ... It just so happens that TypeScript has...
Read more >
How to use type guards in TypeScript - LogRocket Blog
Type guards have the unique property of assuring that the value tested is of a set type depending on the returned boolean. TypeScript...
Read more >
How To Do Anything in TypeScript With Type Guards
Type guards are conditional checks that allow types to be narrowed from general types to more specific ones. With type guards, we do ......
Read more >
How to get the types you want with TypeScript type guards
If you know that vehicle does have the property turnSteeringWheel , you can quickly solve this issue by casting vehicle as any. But...
Read more >
5 Methods to use Type Guards in TypeScript - Bits and Pieces
This technique is used to identify or narrow down the type of a variable. In case the narrowing is less familiar to you,...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found