Skip to content

Limit "type guards as assertions" behavior #10118

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Aug 3, 2016
Merged

Conversation

ahejlsberg
Copy link
Member

@ahejlsberg ahejlsberg commented Aug 3, 2016

With #8548 we introduced the notion of "type guards as assertions". This has caused a steady stream issues as users are surprised by the arguably counter-intuitive effects. With this PR we limit type guards as assertions to only affect incomplete types in control flow analysis of loops. Effectively this makes type guards as assertions an implementation detail of the control flow analyzer that isn't observable in the final types computed by the checker, and #8548 is for all practical purposes revoked.

For example, we now handle the following from #9246 correctly:

type Shape =
    { kind: "Circle"; radius: number } |
    { kind: "Rectangle"; width: number; height: number };

function area(s: Shape) {
    if (s.kind === "Circle") {
        return Math.PI * s.radius ** 2;
    }
    else if (s.kind === "Rectangle") {
        return s.width * s.height;
    }
    return assertNever(s);  // No error here
}

function assertNever(x: never): never {
    return x;
}

Likewise we produce the expected never type in this example from #9869:

let stringOrNumber: string | number = 3 > 5 ? "a" : 7;

if (typeof stringOrNumber === "number") {
    if (typeof stringOrNumber !== "number") {
        stringOrNumber;  // never
    }
}

Fixes #9246, #9254, #9260, #9869, #10087. (Yeah, it's been causing some confusion!)

// is distinguished from a regular type by a flags value of zero. Incomplete type
// objects are internal to the getFlowTypeOfRefecence function and never escape it.
export interface IncompleteType {
flags: TypeFlags; // No flags set
Copy link
Member

@weswigham weswigham Aug 3, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we have numeric literal types and enum literal types now, shouldn't this just be TypeFlags.None or 0? (Which would alleviate the need for the long explanatory comment above. And possibly allow you to remove some of the casts you have in the implementation.)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TypeFlags isn't a union enum type (because it contains non-literal initializers and is intended to be used as bit flags), so there is no TypeFlags.None type. I could use 0, but then it would be less obvious that flags is overlaid on the similarly named property in Type. Either way, the comment is still necessary.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IDK, I think 0 carries more meaning; since you're using only a sentinel value for the member, encoding the sentinel value in the type seems like a good documentation choice. And 0 should still be assignable to TypeFlags, so it should still overlay just fine, right?

@RyanCavanaugh
Copy link
Member

👍

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants