Skip to content

Allow direct property check if (x.a) to work and narrow type if at least one type in typeof x has a #38156

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

Closed
5 tasks done
ashmind opened this issue Apr 24, 2020 · 2 comments
Closed
5 tasks done
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug

Comments

@ashmind
Copy link

ashmind commented Apr 24, 2020

Search Terms

condition property if in guard

Suggestion

I would like the following to work:

function f(x: { a: string }|{ b: string }) {
    if (x.a) {
        // x should be { a: string } here
    }
}

Use Cases

This is very common pattern in JavaScript, so when rewriting some of my JS code to TypeScript I often have to change this.

E.g.

function logError(e: Error|TaskError) {
    if (e.fromTask) {
        // TaskError, log fromTask
    }
    else {
        // other error, simple log
    }
    ...
}

I can rewrite this in two ways.

First

if ('fromTask' in e)

This has two issues:

  1. It does not check that at least one of union types has fromTask, so if I made a typo (fromTsk) it would not be found.
  2. It looks unnatural -- there is probably some JS pattern that recommends this over a shorter check, but I haven't seen it used much

Second

function logError(e: (Error & { fromTask?: undefined })|TaskError)

This works, but feels really artificial -- it does not give any information that previous signature didn't have, and the only purpose is to work around the if error.

Examples

Union

interface I1 { a: string }
interface I2 { b: string }
interface I3 { a?: string; b: string }

function f(x: I1|I2|I3) {
    if (x.a) {
        // type of x should be I1|(I3 & { a: string })
    }
}

No such property

interface I1 { a: string }
interface I2 { b: string }

function f(x: I1|I2) {
    if (x.c) { // should fail to compile as `c` is not present on either I1 or I2
    }
}

Checklist

My suggestion meets these guidelines:

  • This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.
@RyanCavanaugh RyanCavanaugh added the Working as Intended The behavior described is the intended behavior; this is not a bug label Jun 1, 2020
@RyanCavanaugh
Copy link
Member

This is the intended behavior; the proposed code isn't typesafe and we intentionally want you to opt-in to the "obvious what is going on" in operator if you really intend to do this.

See also #34590 (comment)

See also #14094

@typescript-bot
Copy link
Collaborator

This issue has been marked 'Working as Intended' and has seen no recent activity. It has been automatically closed for house-keeping purposes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug
Projects
None yet
Development

No branches or pull requests

3 participants