Skip to content
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

Cannot widen type in generics while retaining knowledge of assignability #35257

Closed
UselessPickles opened this issue Nov 21, 2019 · 5 comments
Closed
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed

Comments

@UselessPickles
Copy link

Search Terms:
generics widen type

Code

type Widen<T extends number | string> = T extends number
    ? number
    : T extends string
    ? string
    : T;

class Foo<T extends number | string> {
  public constructor(private readonly validValues: readonly T[]) { }

  // UNEXPECTED ERROR: T is not assignable to Widen<T>
  public isValidValue(value: Widen<T>): value is T {
    return this.validValues.indexOf(value as any) !== -1;
  }
}

Expected behavior:
The Widen type in my example widens number and string types. For any type T, T is assignable to Widen<T>. I expect to be able to rely on this fact in generics.

Actual behavior:
The assignability relationship of T and Widen<T> is lost when T is a generic type rather than a concrete type (see // UNEXPECTED ERROR comment in example).

Playground Link:
http://www.typescriptlang.org/play/?ssl=5&ssc=7&pln=5&pc=12#code/C4TwDgpgBA6glgEwgOwDwBUoQB7BQgZymQFcBbAIwgCcoAfKA4auZAcwD4oBeKTHPMkLFyVagCgoUqAH4RlGpOkAuPllz4iTFuyVS521mz1RV6ANzjxAYwA2AQwJEAYgHtXGdYOGkFtBobsXADeSmAkFLZw1lDWrsjaJNbArtQAFGAsAG72eFDUEPYI8bYgUDlRCABq9rYkEASqBUUlZegA2gC6AJRQwVAAvlZSAPQjUACqAHIAogAaAAozAMLoMwAiUDMAStsA8ttmUHBEyK7AUI4EcGzI9pHQKbCIKBgcYRFRMSc1lb-1aQq9VU8CQaHQHG6qiB0BOalC0nyEGAJGoyCgwAAFicAHQVRD-Bo41hIbB7ABmgNq9UuRHsyBAvQAhNxeABaACMlikQwGQA

@UselessPickles
Copy link
Author

If this is "working as intended" or my expectations are otherwise infeasible, is there any other valid way to achieve what I am attempting?

For example, I want Foo<"A" | "B">.isValidValue() to accept string, but not number, I want Foo<1 | 2>.isValidValue() to accept number, but not string, and I want Foo<"A" | 2>.isValidValue() to accept number | string.

@jcalz
Copy link
Contributor

jcalz commented Nov 21, 2019

Assignability for unresolved generic conditional types is an existing sore point. (Does anyone know if there is a canonical issue for this? This has to be a duplicate or related to something, but I'm not sure. It's not exactly #26933) In cases like this I usually hit the compiler over the head with assignability using an intersection:

  public isValidValue(value: Widen<T>): value is Widen<T> & T {
    return this.validValues.indexOf(value as any) !== -1;
  }

@UselessPickles
Copy link
Author

@jcalz Thanks for the intersection trick! Seems to be working well for me as a work-around (all my dtslint tests passed).

#26933 Looks very related to me.

@RyanCavanaugh RyanCavanaugh added the Design Limitation Constraints of the existing architecture prevent this from being fixed label Nov 22, 2019
@RyanCavanaugh
Copy link
Member

For any type T, T is assignable to Widen<T>.

This is really only possible to know through second-order reasoning about all possible T and isn't something TS is capable of constructing proofs for. In fact, it's only really even true because string & number is not inhabitable; the same definition of Widen for two non-disjoint types does not have the same assignability guarantee.

@UselessPickles
Copy link
Author

@RyanCavanaugh Thanks for the explanation. I see how that would be quite complex. Do you have any ideas for:

  1. How I could most properly achieve what I am attempting ("widen" a generic type in a way that the compiler understands) with current TS capabilities? Is the intersection workaround suggested by jcalz my best option?
  2. If there's no proper way to do this, is there any reasonable way to enhance TS with the capability to "widen" a generic type in a way that it understands?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed
Projects
None yet
Development

No branches or pull requests

3 participants