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

Conditional type is deferred because narrow-as-assertion on constrained type parameter has no effect #29939

Closed
jack-williams opened this issue Feb 16, 2019 · 3 comments
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed

Comments

@jack-williams
Copy link
Collaborator

jack-williams commented Feb 16, 2019

Sorry for the gratuitous title.

Code

type IsString<X> = [X] extends [string] ? true : false

function isString<X extends string, Y>(x: X, y: Y)  {
    if (typeof x === "string") {
        const a: IsString<typeof x> = true; // error: conditional type is deferred
    }
    if (typeof y === "string") {
        const b: IsString<typeof y> = true;
    }
}

Expected behavior:

The two to behave the same; probably, they should both be OK.

Actual behavior:

The type of x does not get narrowed by assertion, presumably because the assertion adds no information. Conditional type resolution ignores parameter constraints when attempting to resolve using the most restrictive instantiation, and therefore IsString<typeof x> does not resolve.

Essentially T extends string, and T & string where T is unconstrained, do not behave the same in conditional check types. The former has its constraint ignored, while the latter gets to use its intersection proof.

Playground Link: link

Related Issues:

@jack-williams jack-williams changed the title Conditional type is deferred because narrow-as-assertion on constrained type parameter has no affect Conditional type is deferred because narrow-as-assertion on constrained type parameter has no effect Feb 16, 2019
@jack-williams
Copy link
Collaborator Author

I understand why type parameter constraints are ignored by the restrictive instantiation. Using transitive reasoning with any breaks a whole load of things, as shown from the example in checker.ts.

type Foo<T extends { x: any }> = T extends { x: string } ? string : number

A question I have is: are there other examples that do not involve any, where ignoring constraints is necessary? I can think of another one:

type Bar<T extends Object> = T extends object ? string : number

Would it be fair to say that the restrictive instantiation is currently necessary purely because conditional type resolution uses assignability, a non-transitive relation?

I know that definitelyAssignable was a thing at one point; was that crafted with the intent to be a transitive type relation?

@jack-williams
Copy link
Collaborator Author

I think I know enough about this now to say that it's a design limitation.

  • It is correct that a type parameter T extends string does not get narrowed to T & string upon typeof because the intersection adds no information: the type parameter is already a subtype of string.
  • The only other vector of change would be to consider constraints in conditional types but there are multiple reasons why this is difficult (and incorrect in some cases).

This leads me to the conclusion that this is just an awkward composition of concepts, but one that few people seem to face.

@jack-williams jack-williams added Design Limitation Constraints of the existing architecture prevent this from being fixed and removed Needs Investigation This issue needs a team member to investigate its status. labels Aug 28, 2019
@newlukai
Copy link

newlukai commented Oct 8, 2020

I think I know enough about this now to say that it's a design limitation.

* It is correct that a type parameter `T extends string` does not get narrowed to `T & string` upon `typeof` because the intersection adds no information: the type parameter is already a subtype of `string`.

* The only other vector of change would be to consider constraints in conditional types but there are multiple reasons why this is difficult (and incorrect in some cases).

This leads me to the conclusion that this is just an awkward composition of concepts, but one that few people seem to face.

@jack-williams Would be really great to learn something here. Currently I'm trying to use a conditional type as return type of a function. Inside the function I'm narrowing down on the constraints of the conditional type to return the corresponding value (similar to this SO post.

As I read your posts here I'm wondering about the square bracket notation in your IsString type. What does it do?

And you wrote considering constraints in conditional types would sometimes be incorrect. I assume this is the reason why writing a function with conditional return type is not possible. Right? And could you explain why?

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

5 participants