简体   繁体   中英

Conditional type is inferred differently with generic type

Typescript infers the conditional type (with disabled distribution) differently when a generic type is passed.

type Box<T> = { a: T };

// Conditional type with disabled distribution using tuple.
type CondType<T> = [T] extends [string | number]
    ? Box<T> : false;

class Test<T extends string | number> {
    public constructor(private value: T) {}

    // CondType<T> is not inferred as Box<string | number>
    public one(param: CondType<T>): void {
        param; // type is false | Box<T>
    }

    // This works as expected
    public two(param: CondType<string | number>): void {
        param; // type is Box<string | number>
    }
}

Why is this? and is there a way around it?

This is currently a missing feature in TypeScript. See microsoft/TypeScript#23132 for the relevant feature request. Its status is "awaiting more feedback" so it might be a good idea to go there, give it a 👍, and describe your use case for why you need this, if you think it's especially compelling (it might need to be a little more motivated than the example here, though).

The issue is that the compiler does not consult generic constraints when evaluating conditional types that depend upon as-yet-unspecified generic type parameters. Instead, the compiler just defers the evaluation entirely. So even though T is constrained to string | number string | number , the compiler does not use this information to eagerly take the true branch of CondType<T> and result in Box<T> . It stays CondType<T> which is not known to be assignable to Box<T> for unspecified T .


Possible workarounds until and unless ms/TS#23132 is addressed:

Well, since in fact CondType<T> will definitely evaluate to Box<T> , you could just use that type instead:

  public three(param: Box<T>): void {
    param; // Box<T> of course
  }

although I'd imagine that this might not work for your actual use case (and this is why I'd say any comment in ms/TS#23132 should feature a situation that can't be trivially worked around like this).

The general purpose workaround for situations where you know more about the type of some expression than the compiler does is to use a type assertion . Essentially you are taking the job of verifying type safety away from the compiler and taking the responsibility onto yourself:

  public four(param: CondType<T>): void {
    const paramAsserted = param as Box<T>; // just assert it
  }

This will let you move ahead as desired, although the danger is that you might have accidentally lied to the compiler; let's say you later change class Test<T extends string | number> {/*...*/} class Test<T extends string | number> {/*...*/} to class Test<T extends string | number | boolean> {/*...*/} class Test<T extends string | number | boolean> {/*...*/} class Test<T extends string | number | boolean> {/*...*/} . That type assertion will no longer be true, but the compiler will not report an error. After all, the compiler cannot tell the difference between when CondType<T> is assignable to Box<T> for generic T and when it's not assignable. So you have to be careful with type assertions, and double check that what you're asserting is true.

Playground link to code

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM