You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Conditional type is deferred because narrow-as-assertion on constrained type parameter has no effect #29939
typeIsString<X>=[X]extends[string] ? true : falsefunctionisString<Xextendsstring,Y>(x: X,y: Y){if(typeofx==="string"){consta: IsString<typeofx>=true;// error: conditional type is deferred}if(typeofy==="string"){constb: IsString<typeofy>=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.
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[/+]on Feb 16, 2019
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.
typeFoo<Textends{x: any}>=Textends{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:
typeBar<TextendsObject>=Textendsobject ? 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?
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.
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) 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?
Activity
[-]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[/+]jack-williams commentedon Feb 19, 2019
I understand why type parameter constraints are ignored by the restrictive instantiation. Using transitive reasoning with
anybreaks a whole load of things, as shown from the example inchecker.ts.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: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
definitelyAssignablewas a thing at one point; was that crafted with the intent to be a transitive type relation?jack-williams commentedon Aug 28, 2019
I think I know enough about this now to say that it's a design limitation.
T extends stringdoes not get narrowed toT & stringupontypeofbecause the intersection adds no information: the type parameter is already a subtype ofstring.This leads me to the conclusion that this is just an awkward composition of concepts, but one that few people seem to face.
newlukai commentedon Oct 8, 2020
Jack Williams (@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
IsStringtype. 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?