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
Functions with same intersection and conditional type in parameter list not assignable to each other #32442
/** * We should never be able to create a value of this type legitimately. * * `ErrorMessageT` is our error message */interfaceCompileError<ErrorMessageTextendsany[]>{/** * There should never be a value of this type */readonly__compileError : never;}typeErrorA=CompileError<["I am error A"]>;typeErrorB=CompileError<["I am error B"]>;declareconsterrorA : ErrorA;/** * Different compile errors are assignable to each other. */consterrorB : ErrorB=errorA;/** * Pretend this is `v1.0.0` of your library. */declarefunctionfoo<Nextendsnumber>(/** * This is how we use `CompileError<>` to prevent `3` from being * a parameter */n : (N&(Extract<3,N>extendsnever ?
unknown :
CompileError<[3,"is not allowed; received",N]>))) : void;/** * Argument of type '3' is not assignable to parameter of type * 'CompileError<[3, "is not allowed; received", 3]>'. */foo(3);/** * OK! */foo(5);/** * Argument of type '3 | 5' is not assignable to parameter of type * 'CompileError<[3, "is not allowed; received", 3 | 5]>'. */foo(5as3|5);/** * Argument of type 'number' is not assignable to parameter of type * 'CompileError<[3, "is not allowed; received", number]>'. */foo(5asnumber);////////////////////////////////////////////////////////////////////** * The same as `foo<>()` but with a different error message. * * Pretend this is `v1.1.0` of your library. */declarefunctionbar<Nextendsnumber>(n : (N&(Extract<3,N>extendsnever ?
unknown :
CompileError<[3,"is not allowed; received",N]>))) : void;/** * Expected: Assignable to each other * Actual: Not assignable to each other */constfooIsAssignableToBar : typeofbar=foo;constbarIsAssignableToFoo : typeoffoo=bar;
If you remove the N & part from the parameters and replace unknown with N, it works.
/** * We should never be able to create a value of this type legitimately. * * `ErrorMessageT` is our error message */interfaceCompileError<ErrorMessageTextendsany[]>{/** * There should never be a value of this type */readonly__compileError : never;}typeErrorA=CompileError<["I am error A"]>;typeErrorB=CompileError<["I am error B"]>;declareconsterrorA : ErrorA;/** * Different compile errors are assignable to each other. */consterrorB : ErrorB=errorA;/** * Pretend this is `v1.0.0` of your library. */declarefunctionfoo<Nextendsnumber>(/** * This is how we use `CompileError<>` to prevent `3` from being * a parameter */n : ((Extract<3,N>extendsnever ?
N :
CompileError<[3,"is not allowed; received",N]>))) : void;/** * Argument of type '3' is not assignable to parameter of type * 'CompileError<[3, "is not allowed; received", 3]>'. */foo(3);/** * OK! */foo(5);/** * Argument of type '3 | 5' is not assignable to parameter of type * 'CompileError<[3, "is not allowed; received", 3 | 5]>'. */foo(5as3|5);/** * Argument of type 'number' is not assignable to parameter of type * 'CompileError<[3, "is not allowed; received", number]>'. */foo(5asnumber);////////////////////////////////////////////////////////////////////** * The same as `foo<>()` but with a different error message. * * Pretend this is `v1.1.0` of your library. */declarefunctionbar<Nextendsnumber>(n : ((Extract<3,N>extendsnever ?
N :
CompileError<[3,"is not allowed; received",N]>))) : void;/** * Expected: Assignable to each other * Actual: Assignable to each other */constfooIsAssignableToBar : typeofbar=foo;constbarIsAssignableToFoo : typeoffoo=bar;
It seems like if I nest conditional types instead of introducing intersections, it works,
/** * We should never be able to create a value of this type legitimately. * * `ErrorMessageT` is our error message */interfaceCompileError<ErrorMessageTextendsany[]>{/** * There should never be a value of this type */readonly__compileError : never;}typeErrorA=CompileError<["I am error A"]>;typeErrorB=CompileError<["I am error B"]>;declareconsterrorA : ErrorA;/** * Different compile errors are assignable to each other. */consterrorB : ErrorB=errorA;typeIfExtractExtendsNever<A,B,TrueT,FalseT>=(Extract<A,B>extendsnever ? TrueT : FalseT);typeCondition0<N,TrueT>=(IfExtractExtendsNever<3,N,TrueT,CompileError<[3,"is not allowed; received",N]>>);typeCondition1<N,TrueT>=(IfExtractExtendsNever<4,N,TrueT,CompileError<[4,"is not allowed; received",N]>>);typeCondition2<N,TrueT>=(IfExtractExtendsNever<3,N,TrueT,CompileError<[3,"is not allowed; received",N,"Same as Condition0 but with different error message"]>>);typeCondition3<N,TrueT>=(IfExtractExtendsNever<4,N,TrueT,CompileError<[4,"is not allowed; received",N,"Same as Condition1 but with different error message"]>>);/** * Pretend this is `v1.0.0` of your library. */declarefunctionfoo<Nextendsnumber>(/** * This is how we use `CompileError<>` to prevent `3` from being * a parameter */n : (Condition0<N,Condition1<N,N>>)) : void;/** * Argument of type '3' is not assignable to parameter of type * 'CompileError<[3, "is not allowed; received", 3]>'. */foo(3);/** * OK! */foo(5);/** * Argument of type '3 | 5' is not assignable to parameter of type * 'CompileError<[3, "is not allowed; received", 3 | 5]>'. */foo(5as3|5);/** * Argument of type 'number' is not assignable to parameter of type * 'CompileError<[3, "is not allowed; received", number]>'. */foo(5asnumber);////////////////////////////////////////////////////////////////////** * The same as `foo<>()` but with a different error message. * * Pretend this is `v1.1.0` of your library. */declarefunctionbar<Nextendsnumber>(n : (Condition2<N,Condition3<N,N>>)) : void;/** * Expected: Assignable to each other * Actual: Assignable to each other */constfooIsAssignableToBar : typeofbar=foo;constbarIsAssignableToFoo : typeoffoo=bar;
This assignability situation is actually making life kind of hard ._.
/** * We should never be able to create a value of this type legitimately. * * `ErrorMessageT` is our error message */interfaceCompileError<ErrorMessageTextendsany[]>{/** * There should never be a value of this type */readonly__compileError : never;}typeErrorA=CompileError<["I am error A"]>;typeErrorB=CompileError<["I am error B"]>;declareconsterrorA : ErrorA;/** * Different compile errors are assignable to each other. */consterrorB : ErrorB=errorA;/** * Expected: * OK! * * Actual: * Type * '<N extends number>(n: N & (Extract<3, N> extends never ? unknown : CompileError<[3, "is not allowed; received", N]>)) => void' * is not assignable to type * '<N extends number>(n: N & (Extract<3, N> extends never ? unknown : CompileError<[3, "is not allowed; received", N]>)) => void'. * Two different types with this name exist, but they are unrelated. */constblah : (<Nextendsnumber>(n : (N&(Extract<3,N>extendsnever ?
unknown :
CompileError<[3,"is not allowed; received",N]>)))=>void)=(<Nextendsnumber>(n : (N&(Extract<3,N>extendsnever ?
unknown :
CompileError<[3,"is not allowed; received",N]>))) : void=>{})
/** * Expected: * OK! * * Actual: * Type * '<N extends number>(n: N & (3 extends N ? any : any)) => void' * is not assignable to type * '<N extends number>(n: N & (3 extends N ? any : any)) => void'. * Two different types with this name exist, but they are unrelated. */constblah : (<Nextendsnumber>(n : (N&(3extendsN ? any : any)))=>void)=(<Nextendsnumber>(n : (N&(3extendsN ? any : any))) : void=>{})
typeBLAH<N>=3extendsN ? any : any;/** * Expected: * OK! * * Actual: * Types of parameters 'n' and 'n' are incompatible. * Type 'N & BLAH<N>' is not assignable to type 'N & BLAH<N> & BLAH<N & BLAH<N>>'. * Type 'N & BLAH<N>' is not assignable to type 'BLAH<N & BLAH<N>>'. */constblah : (<Nextendsnumber>(n : (N&BLAH<N>))=>void)=(<Nextendsnumber>(n : (N&BLAH<N>)) : void=>{})
typeBLAH<N>=3extendsN ? any : any;/** * Expected: * OK! * * Actual: * Types of parameters 'n' and 'n' are incompatible. * Type 'N & BLAH<N>' is not assignable to type 'N & BLAH<N> & BLAH<N & BLAH<N>>'. * Type 'N & BLAH<N>' is not assignable to type 'BLAH<N & BLAH<N>>'. */constblah : (<Nextendsnumber>(n : (N&BLAH<N>))=>void)=(<Nextendsnumber>(n : (N&BLAH<N>)) : void=>{})as(<Nextendsnumber>(n : (N&BLAH<N>))=>void)
I have a generic function.
It has a parameter.
That parameter uses a conditional type and intersection type (at the same time).
Because of the conditional and intersection type, a different function with the exact same signature is treated as a different type... Even though they are the same type. A type assertion does not work, either.
I don't know if it is a bug, design limitation, or by design.
And I don't have an intuition for why it thinks it is assignable in some cases, but not assginable in other cases.
//Error, cannot assignconstblah : (<Nextendsnumber>(n : (N&(anyextendsN ? any : any)))=>void)=(<Nextendsnumber>(n : (N&(anyextendsN ? any : any))) : void=>{})
Activity
AnyhowStep commentedon Jul 17, 2019
If you remove the
N &part from the parameters and replaceunknownwithN, it works.Playground
AnyhowStep commentedon Jul 17, 2019
The motivation behind introducing intersection types is so that multiple compile-time requirements can be chained together,
Of course, with such a "basic" type like
number, the conditions aren't very interesting.A code snippet of something more complicated,
If all the conditions are satisfied,
AssertValidJoinTarget<QueryT, AliasedTableT>resolves toAliasedTableT.If at least one of the conditions fail, it resolves to
AliasedTableT & CompileError<>AnyhowStep commentedon Jul 17, 2019
It seems like if I nest conditional types instead of introducing intersections, it works,
Playground
AnyhowStep commentedon Jul 27, 2019
This assignability situation is actually making life kind of hard ._.
Playground
In the above, you see something like
Ais not assignable toA. And that just confuses me.My actual use-case has to do with having function properties (not methods) on a generic class.
And the function uses the
thiskeyword.AnyhowStep commentedon Jul 27, 2019
Smaller repro,
Playground
AnyhowStep commentedon Jul 27, 2019
Playground
Even a type assertion does not work,
Playground
AnyhowStep commentedon Jul 27, 2019
So, if I put the generic type param on the
RHSof theextends, it is not assignable,Playground
If I put it on the
LHSof theextends, it is assignable,Playground
And I have zero intuition for why this is the case.
RyanCavanaugh commentedon Jul 30, 2019
AnyhowStep TL;DR ?
AnyhowStep commentedon Jul 30, 2019
Umm... Kind of hard to make a TL;DR.
TL;DR
I have a generic function.
It has a parameter.
That parameter uses a conditional type and intersection type (at the same time).
Because of the conditional and intersection type, a different function with the exact same signature is treated as a different type... Even though they are the same type. A type assertion does not work, either.
I don't know if it is a bug, design limitation, or by design.
And I don't have an intuition for why it thinks it is assignable in some cases, but not assginable in other cases.
Playground
Playground showing type assertion not working
Notice that the type annotation and the type of the function have the same "type". But the compiler thinks they're different types.
But I have found that this is not always the case. In certain cases, the compiler knows they're the same type.
Like when I put
Non theLHSof theextends.Playground