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.

[Proposal] Type assertion statement (type cast) at block-scope level

See original GitHub issue

This is a proposal in order to simplify the way we have to deal with type guards in TypeScript in order to enforce the type inference.

The use case is the following. Let us assume we have dozens (and dozens) of interfaces as the following:

Code

interface AARect {
    x: number; // top left corner
    y: number; // top left corner
    width: number;
    height: number;
}

interface AABox {
    x: number; // center
    y: number; // center
    halfDimX: number;
    halfDimY: number;
}

interface Circle {
    x: number; // center
    y: number; // center
    radius: number;
}

// And much more...

And we have a union type like this one:

type Geometry = AARect | AABox | Circle | // ... And much more

It is quite easy to discriminate a type from another with hasOwnProperty or the in keyword:

function processGeometry(obj: Geometry): void {
    if ("width" in obj) {
        let width = (obj as AARect).width;
        // ...
    }
    if ("halfDimX" in obj) {
        let halfDimX = (obj as AABox).halfDimX;
        // ...
    }
    else if ("radius" in obj) {
        let radius = (obj as Circle).radius;
        // ...
    }
    // And much more...
}

But, as we can see, this is quite burdensome when we need to manipulate obj inside each if block, since we need to type cast each time we use obj.

A first way to mitigate this issue would be to create an helper variable like this:

    if ("width" in obj) {
        let helpObj = obj as AARect;
        let width = helpObj.width;
        // ...
    }

But this is not really satisfying since it creates an artefact we will find in the emitted JavaScript file, which is here just for the sake of the type inference.

So another solution could be to use user-defined type guard functions:

function isAARect(obj: Geometry): obj is AARect {
    return "width" in obj;
}

function isAABox(obj: Geometry): obj is AABox {
    return "halfDimX" in obj;
}

function isCircle(obj: Geometry): obj is Circle {
    return "radius" in obj;
}

// And much more...

function processGeometry(obj: Geometry): void {
    if (isAARect(obj)) {
        let width = obj.width;
        // ...
    }
    if (isAABox(obj)) {
        let halfDimX = obj.halfDimX;
        // ...
    }
    else if (isCircle(obj)) {
        let radius = obj.radius;
        // ...
    }
    // And much more...
}

But again, I find this solution not really satisfying since it still creates persistent helpers functions just for the sake of the type inference and can be overkill for situations when we do not often need to perform type guards.

So, my proposal is to introduce a new syntax in order to force the type of an identifier at a block-scope level.

function processGeometry(obj: Geometry): void {
    if ("width" in obj) {
        assume obj is AARect;
        let width = obj.width;
        // ...
    }
    if ("halfDimX" in obj) {
        assume obj is AABox;
        let halfDimX = obj.halfDimX;
        // ...
    }
    else if ("radius" in obj) {
        assume obj is Circle;
        let radius = obj.radius;
        // ...
    }
    // And much more...
}

Above, the syntax assume <identifier> is <type> gives the information to the type inference that inside the block, following this annotation, <identifier> has to be considered as <type>. No need to type cast any more. Such a way has the advantage over the previous techniques not to generate any code in the emitted JavaScript. And in my opinion, it is less tedious than creating dedicated helper functions.

This syntax can be simplified or changed. For instance we could just have : <identifier> is <obj> without a new keyword assume, but I am unsure this would be compliant with the current grammar and design goals of the TypeScript team. Nevertheless, whatever any welcomed optimization, I think the general idea is relevant for making TypeScript clearer, less verbose in the source code and in the final JavaScript, and less tedious to write when we have to deal with union types.

Issue Analytics

  • State:open
  • Created 7 years ago
  • Reactions:104
  • Comments:76 (31 by maintainers)

github_iconTop GitHub Comments

14reactions
DanielRosenwassercommented, Aug 18, 2016

Technically we could just consider in as a form of type guards.

But I can still imagine something that says “assume the type of this entity is so-and-so for this block”.

13reactions
pm-nsimiccommented, Nov 23, 2018

how about adding the typeguard after the if condition itself by re-using the existing typeguard syntax:

if ("width" in obj): obj is AABox {
    // compiler assumes the obj is `AABox` within this scope
}
Read more comments on GitHub >

github_iconTop Results From Across the Web

Can I do a type assertion for a variable in Typescript that ...
I believe that new variable is acceptable solution, but your other option is an user-defined type guard. Contrary to new variable with cast, ......
Read more >
BlockScope: Detecting and Investigating Propagated ... - arXiv
To facilitate this study, we propose BlockScope, a novel tool that can effectively and efficiently detect multiple types of cloned ...
Read more >
V6055. Expression inside assert statement can change object's state.
The analyzer has detected a problem where an ′assert′ statement contains a method modifying an object′s state. How exactly such methods are called...
Read more >
Restricted Pointers in C (Draft 2), X3J11.1 93-006 - Lysator
This document incorporates the change proposed in X3J11.1/93-040 to Draft 1 of the ... A restrict qualifier in a type definition makes an...
Read more >
SystemVerilog 3.1a Language Reference Manual - Index of /
“SystemVerilog 3.x” is Verilog-2001 plus an extensive set of high-level ... A cast can be used to convert literal real values to the...
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