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
works_as_expected is correctly the type [['x','y'], [A, 1], [B, 2], [C, 3]] but doesnt_work which is basically defined the exact same way except the spreading is done inside another type alias gives a totally different and incorrect type [["x", "y"], [A, "y"], [B, 1], [C, 2]]
🙂 Expected behavior
example should compile without errors, the type of doesnt_work should be the same as works_as_expected.
I came across this while trying to do variadic tuples with a pipeline type function, playground
typeFn<P,R>=(arg:P)=>RinterfaceA{a:any}// interfaces for testinginterfaceB{b:any}interfaceC{c:any}interfaceD{d:any}// Fns<[A,B,C], [1,2,3]> == [Fn<A,1>, Fn<B,2>, Fn<C,3>]// these take a list of arguments and a list of return types and basically zips them togethertypeFns_RetDriven<Args,Rets>={[KinkeyofRets]: Fn<Args[K&keyofArgs],Rets[K]>}typeFns_ArgDriven<Args,Rets>={[KinkeyofArgs]: Fn<Args[K],Rets[K&keyofRets]>}typeFns<Args,Rets>=Fns_RetDriven<Args,Rets>&Fns_ArgDriven<Args,Rets>// the real use case is a compose/pipeline function which seems to be infering generics properly but the type is just breakingdeclarefunctionpipeline<Init,Vextendsany[],Ret>(...fns: Fns<[Init,...V],[...V,Ret]>): Fn<Init,Ret>declareconstclearly_the_expected_type: Fns<[A,B,C],[B,C,D]>pipeline(...clearly_the_expected_type)//Err Argument of type '[Fn<A, B>, Fn<B, C>, Fn<C, D>]' is not // assignable to parameter of type 'Fns<[A, B, C], [B, C, D]>'.
That pipeline function is correctly inferring generics in most cases I throw at it, if it wasn't for the correctly inferred generics producing a different and invalid type this would work great. (you can tell it is clearly invalid because the error says it isn't assignable to the type it was literally declared with) I think if this issue is fixed we may finally have the variadic compose and pipeline signature that can infer some arguments of the in-between functions.
When instantiating a mapped type that is applied to a tuple type with variadic elements, we transform M<[A, ...T, B]> into [...M<[A]>, ...M<T>, ...M<[B]>], and then rely on tuple type normalization to resolve the non-generic parts of the resulting tuple. So, say that M is a mapped type that "boxes" every element:
typeM<T>={[KinkeyofT]: {value: T[K]}};
The instantiation M<[number, ...T, string]> then turns into [{ value: number }, M<T>, { value: string }]. This is generally a desirable transformation because it ensures that a mapped type applied to a variadic tuple type is itself recognized as a tuple type, plus it reduces the genericity of the type and allows better checking of and inference from the non-generic parts. However, the transformation means that the iteration type variable (K in example above) can really only be trusted to represent a property name in the type being iterated over (T in example above), and cannot be trusted to always correlate to an index in some other tuple type or a final combined tuple type.
Here's a simpler example:
typeFoo<T>={[KinkeyofT]: K};typeBar<Aextendsany[]>=Foo<[0, ...A]>;// [0, ...Foo<A>] because of transformationtypeT0=Foo<[0,0,0,0]>;// ["0", "1", "2", "3"]typeT1=Foo<[0, ...[0,0,0]]>;// ["0", "1", "2", "3"]typeT2=Bar<[0,0,0]>;//["0", "0", "1", "2"]
Above, in the T1 example, the type [0, ...[0, 0, 0]] is normalized to [0, 0, 0, 0]beforeFoo<T> is applied to the tuple. But in the T2 example, the tuple is split into two parts and the indices now shift.
I can see how that is a bit surprising, but on the other hand, getting rid of the transformation has significant drawbacks. So, for now I'm going to say this is working as intended.
Intersting, thanks for the further information. Knowing this is how typescript is resolving the tuple spreading I can get the correct logic with this code which still doesn't quite give the auto-magical argument inference I was hoping but is functionally correct which is always good.
I suspect it still isn't working as I'd like because of the need to call Tail which uses infer on the generic it is trying to resolve meaning to figure out what V is you have to already know what V is, I'm honestly really impressed with how well typescript manages the resolution already but if you can't just zip tuples together then I don't think this use case will ever work exactly as intended.
Anyway thanks again for looking into this. Really appreciate typescript and your work to make it great! :)
typeTail<Textendsunknown[]>=Textends[any, ...infer Rest] ? Rest : never;typeFn<P,R>=(param:P)=>R;/** Borrowed from SimplyTyped */typePrev<Textendsnumber>=[-1,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62][T];typeFns_ArgDriven<Argsextendsunknown[],Retsextendsunknown[]>={[KinkeyofArgs]: Fn<Args[K],Rets[K&keyofRets]>}typeFns_RetDriven<Argsextendsunknown[],Retsextendsunknown[]>={[KinkeyofRets]: Fn<Args[K&keyofArgs],Rets[K]>}typeFns<Init,Vextendsunknown[],Ret>=Vextends[] ? [Fn<Init,Ret>]
: [Fn<Init,V[0]>, ...Fns_ArgDriven<V,[...Tail<V>,Ret]>]&[...Fns_RetDriven<[Init,...V],V>,Fn<V[Prev<V["length"]>],Ret>]declarefunctionpipeline<Init,Vextendsunknown[],Ret>(...fns: Fns<Init,V,Ret>): Fn<Init,Ret>// first test only type annotate the first one, 2nd gets argument infered, 3rd onward only gets unknown infered for parameter type// as such final return is not captured correctlyconsttest1=pipeline((a:number)=>a.toExponential(),(b)=>b.toUpperCase(),// ^?(c)=>({foo:c}))// ^?// second test, annotate first and third, now 2nd is set to unknown// gives type error that methods can't be used on unknown but return is correctconsttest2=pipeline((a:number)=>a.toExponential(),(b)=>b.toUpperCase(),// ^?(c: string)=>({foo:c}))// ^?declareconstshould_very_much_work: [Fn<1,2>,Fn<2,3>,Fn<3,4>]constyay_it_works=pipeline(...should_very_much_work)// ^?
Activity
[-]mapped tuples together [/-][+]homomorphic mapped tuple doesn't preserve parallel tuple[/+]tadhgmister commentedon Nov 9, 2021
I came across this while trying to do variadic tuples with a
pipelinetype function, playgroundThat
pipelinefunction is correctly inferring generics in most cases I throw at it, if it wasn't for the correctly inferred generics producing a different and invalid type this would work great. (you can tell it is clearly invalid because the error says it isn't assignable to the type it was literally declared with) I think if this issue is fixed we may finally have the variadiccomposeandpipelinesignature that can infer some arguments of the in-between functions.ahejlsberg commentedon Jan 12, 2022
This one is interesting.
When instantiating a mapped type that is applied to a tuple type with variadic elements, we transform
M<[A, ...T, B]>into[...M<[A]>, ...M<T>, ...M<[B]>], and then rely on tuple type normalization to resolve the non-generic parts of the resulting tuple. So, say thatMis a mapped type that "boxes" every element:The instantiation
M<[number, ...T, string]>then turns into[{ value: number }, M<T>, { value: string }]. This is generally a desirable transformation because it ensures that a mapped type applied to a variadic tuple type is itself recognized as a tuple type, plus it reduces the genericity of the type and allows better checking of and inference from the non-generic parts. However, the transformation means that the iteration type variable (Kin example above) can really only be trusted to represent a property name in the type being iterated over (Tin example above), and cannot be trusted to always correlate to an index in some other tuple type or a final combined tuple type.Here's a simpler example:
Above, in the
T1example, the type[0, ...[0, 0, 0]]is normalized to[0, 0, 0, 0]beforeFoo<T>is applied to the tuple. But in theT2example, the tuple is split into two parts and the indices now shift.I can see how that is a bit surprising, but on the other hand, getting rid of the transformation has significant drawbacks. So, for now I'm going to say this is working as intended.
tadhgmister commentedon Jan 14, 2022
Intersting, thanks for the further information. Knowing this is how typescript is resolving the tuple spreading I can get the correct logic with this code which still doesn't quite give the auto-magical argument inference I was hoping but is functionally correct which is always good.
I suspect it still isn't working as I'd like because of the need to call
Tailwhich usesinferon the generic it is trying to resolve meaning to figure out whatVis you have to already know whatVis, I'm honestly really impressed with how well typescript manages the resolution already but if you can't just zip tuples together then I don't think this use case will ever work exactly as intended.Anyway thanks again for looking into this. Really appreciate typescript and your work to make it great! :)
typescript-bot commentedon Jan 16, 2022
This issue has been marked 'Working as Intended' and has seen no recent activity. It has been automatically closed for house-keeping purposes.