Uh oh!
There was an error while loading. Please reload this page.
Rest type - #13470
Conversation
Igor Oleinikov (Igorbek)
commented
Jan 13, 2017
Suggestion:
|
Doesn't this break some design consistency? All value argument lists are now delimited with braces This PR adds a Since, I regard |
Previously, rest types were only allowed to be identifiers by mistake. Also remove unneeded parsing support for rest types that got checked in by mistake.
1. Object rest checking allows nested generics now. 2. Use getLiteralTypeFromText instead of createLiteral in order to intern literals correctly.
Igor Oleinikov (@Igorbek) I didn't call it out sufficiently in the proposal text above, but the second argument of [1] The reason that the second argument is not generic is that it's impossible to produce a generic object rest — you have to hard-code the names of properties that will be removed in the object destructuring/binding. Tingan Ho (@tinganho) I don't have a strong design opinion one way or another on the syntax. The only constraint is that we expect rest and spread types to be used rarely, especially rest types, so I don't want to use (for example) How about we gauge the community's preference by using 👍 to your comment to indicate |
Patrick Lienau (rozzzly)
commented
Jan 14, 2017
interfaceFooBar{foo: string;bar: string;}interfaceFoo{foo: string;}interfaceBar{bar: string;}would |
Tingan Ho (tinganho)
commented
Jan 14, 2017
Nathan Shively-Sanders (@sandersn) thanks for your comment. Sorry, that I didn't discovered your initial proposal earlier(I just read the spread part). I think, I understand why you use lowercase |
Igor Oleinikov (Igorbek)
commented
Jan 14, 2017
Nathan Shively-Sanders (@sandersn) I see what you mean. But isn't that the case for computed properties? constb="b";letc={[b]: b,a: 10};// c is { [x: string]: string | number, a: number; }let{[b]: b1,a: a1}=c;// b1 is any; a1 is numberso should this be restricted then? constb="b";letrest={a: 10};letc={[b]: b, ...rest};let{[b]: b1, ...rest1}=c;// BTW, this is allowed by the spec and Babel supports itand generic case: functiondestructure<T,UextendskeyofT>(src: T,name: U): {value: T[U];rest: rest(T,U);}{const{[name]: value, ...rest}=src;return{value,rest};}just thinking out loud |
Patrick Lienau (@rozzzly) Yes. Tingan Ho (@tinganho) I would much prefer a general solution too; I tried to make something work with mapped types already. Maybe you can use these problems that I ran into to come up with something:
|
Aluan Haddad (aluanhaddad)
commented
Jan 15, 2017
Although I don't love the proposed rest syntax I don't really care for the generic type syntax either in this case. The advantage of not introducing new syntax is just that it would allow a better syntax to be introduced, preferably one that covered more type operators, in the future without causing confusion because it would be a shorthand or sugar as opposed to an alternate manifest form. |
Tingan Ho (tinganho)
commented
Jan 15, 2017
Nathan Shively-Sanders (@sandersn) So I submitted my idea to the issue tracker, though it is very rough. Though my initial idea was to solve typings for ORMs, but it seems it can apply to typeRest<T, ...Sextendsstring[]>=>{typeType;for(KTinT){typeIsInS=false;for(KSofS){if(S[KT]!=undefined){IsInS=true;}}if(!IsInS){Type[KT]=T[KT]}}returnType;} |
functionconcrete(tricky: keyof(typeofc)){returndestructure(c,tricky)}This might remove
The typescript checker should be compared to flow, which doesn't support the generic case either and defaults to returning I will add an error instead. I don't like silently failing to remove properties that people asked to remove. Aluan Haddad (@aluanhaddad) I hoped that introducing new, bad syntax would have nearly the same benefits as not introducing new syntax at all, with the added benefit that the syntax for generic types stays consistent. |
PyroVortex
commented
Jan 18, 2017
So, does the rest operator finally allow the strict typing of applying declarefunctionmap<AextendsArray<T>,T,U>(input: A|[void],operator: (t: T)=>U): Array<U>&{[Pinkeyofrest(A,keyofArray<T>)]: U};constx=map([1,2,3],(x: number)=>x.toString());// x should have type [string, string, string], or at least one that has all the same semanticsAlternatively, if you want to generate invariant tuples: functionasInvariantTuple<AextendsArray<any>>(input: A|[void]): rest(A,keyof[])&{readonlylength: A['length']}{returninput;}consty=asInvariantTuple([1,2,3]);// y has type { 0: number, 1: number, 2: number, length: number } |
It is an error with --noImplicitAny, unless the source type is also any.
Also update baselines
Ah, yes, you're right. This rules are breaking basic identities.
As an example of generic HOC could be: constwithProps=<P>(props: P)=><T/* missing constraint that P extends T */>(Component: Component<T>)=>(innerProps: rest(T,keyofP))=><Component{...{innerProps, ...props}}/>constSample=(props: {a: string;b: string;})=><div>{a+ b}</div>;<Samplea='hello, 'b='world'/>constHelloSample=withProps({a: 'hello, '})(Sample);<HelloSampleb='world'/>An alternative approach could be using something like Mapped conditional types#12424 where properties could be filtered out or replaced with some other type (like, make some members optional). |
And one more alternative: constwithProps=<P>(props: P)=><T>(Component: Component<{...T, ...P}>)=>(innerProps: T)=><Component{...{innerProps, ...props}}/>This is perfect typing, but the issue here is that type |
Nathan Shively-Sanders (@sandersn) Maybe I should have not included |
Kirill Agalakov (raveclassic)
commented
Mar 10, 2017
Is there any way of seeing this merged at least without support of generics? |
How about using |
Perhaps this could be solved with a syntax closer matching JS (avoids syntax clash, keeps learning curve low) with the type-level destructuring explored in #10727: Maybe then one might grab them from objects and/or unions using a spread as well, hopefully allowing for straight-forward support for generics: // destructure objecttypeObj={a: 1,c: 3};type{ ...Obj, ...Rest}={a: 1,b: 2};// Rest: { b: 2 }Note
Alt.: destructure unioned keys typeObj={a: 1,c: 3};typeKeys=keyofObj;// 'a' | 'c'type{ ...Keys, ...Rest}={a: 1,b: 2};// Rest: { b: 2 }Notes:
Nathan Shively-Sanders (@sandersn):
Not just |
Tried for a bit more to do a union difference operation using today's syntax. Unlike the current approach, this would handle the generics use-case as well. // define some helpers...typeObj<T>={[k: string]: T};typeSafeObj<Oextends{[k: string]: any},Nameextendsstring,Paramextendsstring>=O&Obj<{[KinName]: Param}>;typeSwitchObj<Paramextendsstring,Nameextendsstring,OextendsObj<any>>=SafeObj<O,Name,Param>[Param];typeNot<Textendsstring>=SwitchObj<T,'InvalidNotParam',{'1': '0';'0': '1';}>;typeUnion2Obj<Keysextendsstring>={[KinKeys]: K};typeUnion2Keys<Textendsstring>=Union2Obj<T>&{[k: string]: undefined};typeUnionHas<Unionextendsstring,Kextendsstring>=({[SinUnion]: '1'}&{[k: string]: '0'})[K];// okay, let's try this...typeUnionDiff<Bigextendsstring,Smallextendsstring>={[KinBig]: {1: Union2Keys<Big>[K],0: undefined}[Not<UnionHas<Small,K>>]}[Big]/*!*/;typetest1=UnionDiff<'a'|'b'|'c','b'|'c'|'d'>;// 'a'|'b'|'c' instead of 'a' :(This yields Second attempt, separate the steps... // hm, let's separate the steps...typeUnionDiff2<Bigextendsstring,Smallextendsstring>={[KinBig]: {1: Union2Keys<Big>[K],0: undefined}[Not<UnionHas<Small,K>>]}//[Big] /*!*/;typeA=UnionDiff2<'a'|'b'|'c','b'|'c'|'d'>;// now manually do that second step...typeBig='a'|'b'|'c';typeB=A[Big];// "a" (yay!), or `"a" | undefined` if `strictNullChecks` are enabled.This returns either The Edit: included needed helpers. |
jaen (jaen)
commented
Jun 9, 2017
How about trying typeUnionDiff2<Bigextendsstring,Smallextendsstring>={[KinBig]: {1: Union2Keys<Big>[K],0: never}[Not<UnionHas<Small,K>>]}//[Big] /*!*/;typeA=UnionDiff2<'a'|'b'|'c','b'|'c'|'d'>;// now manually do that second step...typeBig='a'|'b'|'c';typeB=A[Big];lettestYes: B="a";// typecheckslettestNo: B="b";// failsPS nice type level magic. Kind of sad this works only for string literal types, not arbitrary types due to index signature limitation though. |
kiara (KiaraGrouwstra)
commented
Jun 9, 2017
jaen (@jaen): progress! thanks for that! 😄 I'll concede the limitation is unfortunate, yeah. Out of curiosity though, what's your use-case?
I couldn't think of much either, hence I'm all the more curious. :) |
Depends on use case of what you're asking about. If we're talking about subtracting of arbitrary types from an union (which is what led me here) it's fairly simple, our codebase uses tsmonad with strict null checks leading to some annoying issues, for example: constmaybe: <T>(value: T)=>Maybe<T>=(value)=>!!value
? Just<T>(value)
: Nothing<T>();maybe(possiblyNull).fmap(x=>x+2);// complains about `x` being possibly null, even though// `fmap` will by definition be only called with inhabited// `Just` values.valueOr(10)which could be alleviated with something like either of (or preferably both): constbetterMaybe: <T>(value: T)=>Maybe<T-null|undefined>=…;// alternatively by lifting non-null assertion to type levelconstbetterMaybe: <T>(value: T)=>Maybe<T!>=…;Is that a satisfactory answer? |
jaen (@jaen): hmm. what if you'd write say Edit: oh, I know this isn't exposed on the type level either, but have you tried doing something like in the type guards and type assertions example? It looks like if you use a type guard that way that may suffice to have TS recognize the |
Yeah, I am aware of the non-null assertion As for your suggestion — this will work I assume, but will require me forking tsmonad and modifying the source, which I wanted to avoid. Being able to do this on the type level has the added benefit on being able to do it in a local typings file to shadow the library's type. So that's my use case, I guess — this can be done otherwise, by writing down a non-null assertion at each use site or forking or modifying the library to work with strict null checks, but both feel fairly inelegant to me. |
kiara (KiaraGrouwstra)
commented
Jun 11, 2017
jaen (@jaen): From the TS team's perspective, I suppose a general solution was considered fairly involved, in the sense it'd require introducing negation types ('a type that is not |
jaen (jaen)
commented
Jun 11, 2017
Hm, I guess I could consider a PR. I thought the library was dead (had improvements over last version in the repo, but no officially released package), but now that I checked it's not, so it might make sense to change that upstream, I guess. Well, I'd probably prefer a more generic solution, since while lifting the Do you have any link explaining why the negation type would be needed? I would assume you can remove from a union by equality (which types already have) so I'm not sure why some not- |
kiara (KiaraGrouwstra)
commented
Jun 11, 2017
jaen (@jaen): that was this idea. There may well be ways without it. I personally just hope |
kiara (KiaraGrouwstra)
commented
Jun 11, 2017
Putting things together based on nir (@nirendy)'s solution to work around the glitch by cutting things into steps with generics defaults: typeObj<T>={[k: string]: T};typeSafeObj<Oextends{[k: string]: any},Nameextendsstring,Paramextendsstring>=O&Obj<{[KinName]: Param}>;typeSwitchObj<Paramextendsstring,Nameextendsstring,OextendsObj<any>>=SafeObj<O,Name,Param>[Param];typeNot<Textendsstring>=SwitchObj<T,'InvalidNotParam',{'1': '0';'0': '1';}>;typeUnion2Obj<Keysextendsstring>={[KinKeys]: K};typeUnion2Keys<Textendsstring>=Union2Obj<T>&{[k: string]: undefined};typeUnionHasKey<Unionextendsstring,Kextendsstring>=({[SinUnion]: '1'}&{[k: string]: '0'})[K];exporttypeUnionDiff_<Bigextendsstring,Smallextendsstring>={[KinBig]: {1: Union2Keys<Big>[K],0: never}[Not<UnionHasKey<Small,K>>]}//[Big];exporttypeUnionDiff<Bigextendsstring,Smallextendsstring,StepextendsUnionDiff_<Big,Small>=UnionDiff_<Big,Small>>=Step[Big];typeTestUnionDiff=UnionDiff<'a'|'b'|'c','b'|'c'|'d'>;// ^ 'a' |
Mihail Malo (qm3ster)
commented
Jun 12, 2018
Should this be closed? |
Ryan Cavanaugh (RyanCavanaugh)
commented
Sep 5, 2018
On hold for now |
Add rest types. They implement the semantics of object rest as detailed in the second part of the proposal #10727. I have copied and updated that proposal here.
Notes:
Rest types
The rest type is the opposite of the spread type. It types the TC39 stage 3 object-rest destructuring operator. The rest type
rest(T, 'a' | 'b' | 'c')represents the typeTafter the propertiesa,bandchave been removed, as well as call signatures and construct signatures.A short example illustrates the way this type is used:
Syntax
The syntax is
rest(T, U)whereTis any type andUisstringor a union of string literals (includingneverand single string literals).Type Relationships
rest(A, never)is not equivalent toAbecause it is missing call and construct signatures.rest(A | B, 'a')is equivalent torest(A, 'a') | rest(B, 'a').rest(A, string)is equivalent to{}.rest(rest(A, 'a'), 'b')is equivalent torest(A, 'a' | 'b')and therefore also torest(rest(A, 'b'), 'a').Assignment compatibility
rest(T, 'x')is not assignable toT.Tis assignable torest(T, 'x')becauseThas more properties and signatures.rest(T, V)is assignable torest(U, W)ifTis assignable toUandVis assignable toW.Properties and index signatures
The type
rest(A, P)removesPfromAif it exists. Otherwise, it does nothing.Aretains its index signatures unlessPisstring.Call and Construct signatures
rest(A)does not have call or construct signatures.Precedence
Rest types are just below the keyof operator in precedence.