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
Suggestion: polymorphic object and tuple member type references through literal types #7730
(This seems to have some things in common with #6080 and is strongly related to #1295 as it can be used to provide an alternative solution to it but l opened this separately since I did not feel it was appropriate to continue discussing it there and wanted to approach it in a more generalized way)
This would provide improved type safety for cases where an interface property is referenced through a string having a value that's known at compile time:
typeMyType={"ABC": number};letx: MyType={ABC: 42};constpropName="ABC";// 'propName' received the literal type "ABC" (not string)lety=x[propName];// 'y' received the type MyType["ABC"] which resolved to 'number'
And with support for generic types added, this could work similarly, but resolve to an intermediate type of the form T[S] where T is an object type and S is a string literal type:
functionfunc<Textendsobject>(){letx: T;constpropName="ABC";// 'propName' received the literal type "ABC"lety=x[propName];// 'y' would receive the type T["ABC"]}
functiongetProperty<Textendsobject,Sextendsstring>(obj: T,readonlypropName: S): T[S]{returnobj[propName];}// Here T resolves to { ABC: number }, S resolves to the literal type "ABC", // and T[S] resolves to numberletx=getProperty({ABC: 42},"ABC");// Type of 'x' is number// Here T resolves to { ABC: number }, S resolves to the literal type "CBA" // and T[S] resolves to anyletx=getProperty({ABC: 42},"CBA");// Type of 'x' is any// Here T resolves to { ABC: number }, S resolves to string// and T[S] resolves to anyletx=getProperty({ABC: 42},getRandomString());// Type of 'x' is any
typeMyTupleType=[number,string,boolean];letx: MyTupleType;constindex=1;// 'index' received the literal type 1;lety=x[index];// 'y' received the type MyTupleType[1] which resolved to 'string'
#6678 does seem to cover some of the tuple aspects (which are only a small part of this), but this one also covers generic tuples as well:
functionfunc<TextendsArray<any>>(tuple: T){constindex=1;returntuple[index];// return type inferred as the polymorphic reference type T[1]}typeMyTuple=[number,string,boolean];letx: MyTuple=[12,"abcd",true];letresult=func(x);// type of 'result' is the type MyTuple[1] which resolves to string
Please read this comment in #1295. This was initially proposed as an alternative solution the one originally proposed there but is fundamentally different from it. The main difference is that the property specifier here is a type rather than a run-time entity:
The basic idea of a literal type is that it essentially captures the awareness of the compiler of a concrete value that is known to be held by a run-time entity. Since in
constx="ABC"
x receives the literal type"ABC" (e.g: x: "ABC"), I have reformulated the proposed solution for #1295 to reference a literal type instead of a parameter, e.g. instead of
I modifiedT[propName] to T[S] where S is a type that extends string:
functiongetProperty<Textendsobject,Sextendsstring>(obj: T,readonlypropName: S): T[S]{returnobj[propName];}// T is inferred as { abcd: number }, S is inferred as the string literal _type_ "abcd"// T[S] resolves to the type { abcd: number }["abcd"] which then resolves to numberletx=getProperty({"abcd": 42},"abcd");// x receives type number
I'm using the fact that in case the parameter propName would receive a value that is known at compile time and the parameter propName is immutable. The type S would be inferred to be a literal string type and can be referenced and eventually resolved in the expression T[S].
I have also generalized this to any position in the code (not only the particular case where it is used return types) and for numeric literals as well (numeric references are not covered by #1295.), and as a pattern that could be used to provide improved type inference in general.
There was a very good reason this was formulated this way and I believe this approach would give a wider benefit to more aspects of the language. I believe this captures the original intention and semantics better (as well as having no mixing of type and run-time entities at type positions) and may also turn out to be easier to implement than the original approach proposed at #1295.
This issue is not a "duplicate" as it describes a pattern that hasn't been mentioned yet (to my best knowledge). It is about discussing and developing a wider understanding of these forms of polymorphic references through literal types. I wanted this to be considered and discussed by the TS team and I did not feel it was appropriate or effective to continue posting about it there (I'm not sure if TS members even follow that discussion).
Edits: significantly expanded and clarified compared to what appeared in the e-mail notification.
Seems like the whole discussion has been diverted to arguing whether this is a duplicate or not, and the actual content ignored. I'm sorry but this feels like a complete waste of time. I'll close it myself.
I did not intend to wast any one's time. sorry if i did. i believe i understand the proposal, and it seems to be there are two parts, 1. using the values of const initializes to get better type information, which seems to be tracked by #6678, and 2. the ability to talk about type of a property of a type. the proposal here seems to be a slight variation from #1295. as mentioned in #1295 (comment), we are open to accepting PRs for the general proposal, and we would be open to consider your proposed implementation approach as well.
one thing to note, there are no literal types for numbers, or symbols at the moment. so your proposal only works for string literal types.
That's OK, I did not mean you were wasting my time, more like "it turned out it wasn't that much of a great idea to open a new issue and wasn't positively received, never mind". Sometimes in online conversations there could be some misunderstandings, so it could be one.
Anyway, the modification I proposed for #1295 could be seen as "minor" in the sense that it is still the same syntax and the general approach in relation to the problem presented is very similar (though it does require type argument inference to be truly effective), but is different (perhaps not "fundamentally" different - that was probably an exaggeration) in the sense that the two cannot work together - either run-time entities are used as specifiers (e.g. T[propName]) or types (e.g. T[S]), having them both would create problems with name collisions etc. and would have somewhat ambiguous semantics.
The features this approach relies on weren't available or even proposed when #1295 was conceived. Things like string literal types and how they are applied to immutable run-time entities and the possibility of read-only function parameters (though I'm not sure if that has been decided on at this point).
There are also some interesting extensions that can be done with this, seemingly without significant effort as it relies on conventional inference mechanisms of the language. For example, as a complement to #6080 (or maybe as an application for #7722) it could support more complex scenarios like:
functiongetAnyOneOfTwoStrings(): "a"|"b"{if(Math.random()>0.5)return"a";elsereturn"b";}functionf<Textendsobject,Sextendsstring>(obj: T,readonlystr: S){returnobj[str];}letMyType={a: number,b: string,c: boolean}letx: MyType;// T resolves to MyType, S resolves to "a" | "b"// MyType["a" | "b"] resolves to number | stringletresult=f(x,getAnyOneOfTwoStrings());// type of result is number | string
This may be very difficult or even impossible to implement with the original proposal, but may come essentially "for free" with this slight modification.
As mentioned in #1295 (comment) we would love to look at an implementation proposal for this feature, regardless of the approach. i think both approach has merits, and looking at an implementation proposal can illuminate additional information about the correct way to go.
The core team has no plans to tackle this feature in the short term, so PRs are welcomed.
I don't think the examples I gave so far were convincing enough to truly demonstrate the strength of this approach.
The previous example could still somehow (though not very elegantly) use type inference to "pull out" the possible list of literals even with the previous approach:
I mean, even if this looks like a real 'stretch' or somewhat semantically inaccurate.
However there are some cases where there is no possible way to get any type information, as there isn't even a particular parameter to reference!
functionfunc<T>(obj: T,getPropName: ()=>string): T[???];// What would T reference here?
But when using the type as a specifier, this isn't really a problem, the existing machinery of type argument inference would easily infer it:
// No problem here:functionfunc<Textendsobject,Sextendsstring>(obj: T,getPropName: ()=>S): T[S]// T resolves to {a: number, b: string}, S resolves to "a" | "b"// return type is {a: number, b: string}["a" | "b"] which resolves to number | stringfunc({a: 123,b: "ABC"},()=>Math.random()>0.5 ? "a" : "b");
This can be extended to arbitrarily complex expressions, as complex as type argument inference could support:
// No problem here as well:functionfunc<Textendsobject,Sextendsstring>(obj: T,complexObject: {num: number,getPropName: ()=>S}): T[S]// T resolves to {a: number, b: string}, S resolves to "a" | "b"// return type is {a: number, b: string}["a" | "b"] which resolves to number | stringfunc({a: 123,b: "ABC"},{num: 123,getPropName: ()=>Math.random()>0.5 ? "a" : "b"});
Yes, I have mentioned it may provide a solution to it but it goes much further than that. It also includes intermediate polymorphic expressions like T["a" | "b"], or even T[S] where both T and S are generic parameters, and T must extend object and S must extend string. Also it describes similar functionality of for tuples, i.e. T[N] where T must extend Array<any> and N must extend number.
Activity
mhegazy commentedon Mar 29, 2016
duplicate of #6678
malibuzios commentedon Mar 29, 2016
Mohamed Hegazy (@mhegazy)
#6678 does seem to cover some of the tuple aspects (which are only a small part of this), but this one also covers generic tuples as well:
mhegazy commentedon Mar 29, 2016
this part would be #1295.
we have no way today of getting element type of an array.
malibuzios commentedon Mar 30, 2016
Mohamed Hegazy (@mhegazy)
Please read this comment in #1295. This was initially proposed as an alternative solution the one originally proposed there but is fundamentally different from it. The main difference is that the property specifier here is a type rather than a run-time entity:
The basic idea of a literal type is that it essentially captures the awareness of the compiler of a concrete value that is known to be held by a run-time entity. Since in
xreceives the literal type"ABC"(e.g:x: "ABC"), I have reformulated the proposed solution for #1295 to reference a literal type instead of a parameter, e.g. instead ofI modified
T[propName]toT[S]whereSis a type that extendsstring:I'm using the fact that in case the parameter
propNamewould receive a value that is known at compile time and the parameterpropNameis immutable. The typeSwould be inferred to be a literal string type and can be referenced and eventually resolved in the expressionT[S].I have also generalized this to any position in the code (not only the particular case where it is used return types) and for numeric literals as well (numeric references are not covered by #1295.), and as a pattern that could be used to provide improved type inference in general.
There was a very good reason this was formulated this way and I believe this approach would give a wider benefit to more aspects of the language. I believe this captures the original intention and semantics better (as well as having no mixing of type and run-time entities at type positions) and may also turn out to be easier to implement than the original approach proposed at #1295.
This issue is not a "duplicate" as it describes a pattern that hasn't been mentioned yet (to my best knowledge). It is about discussing and developing a wider understanding of these forms of polymorphic references through literal types. I wanted this to be considered and discussed by the TS team and I did not feel it was appropriate or effective to continue posting about it there (I'm not sure if TS members even follow that discussion).
Edits: significantly expanded and clarified compared to what appeared in the e-mail notification.
malibuzios commentedon Mar 30, 2016
Seems like the whole discussion has been diverted to arguing whether this is a duplicate or not, and the actual content ignored. I'm sorry but this feels like a complete waste of time. I'll close it myself.
mhegazy commentedon Mar 30, 2016
I did not intend to wast any one's time. sorry if i did. i believe i understand the proposal, and it seems to be there are two parts, 1. using the values of const initializes to get better type information, which seems to be tracked by #6678, and 2. the ability to talk about type of a property of a type. the proposal here seems to be a slight variation from #1295. as mentioned in #1295 (comment), we are open to accepting PRs for the general proposal, and we would be open to consider your proposed implementation approach as well.
one thing to note, there are no literal types for numbers, or symbols at the moment. so your proposal only works for string literal types.
malibuzios commentedon Mar 30, 2016
Mohamed Hegazy (@mhegazy)
That's OK, I did not mean you were wasting my time, more like "it turned out it wasn't that much of a great idea to open a new issue and wasn't positively received, never mind". Sometimes in online conversations there could be some misunderstandings, so it could be one.
Anyway, the modification I proposed for #1295 could be seen as "minor" in the sense that it is still the same syntax and the general approach in relation to the problem presented is very similar (though it does require type argument inference to be truly effective), but is different (perhaps not "fundamentally" different - that was probably an exaggeration) in the sense that the two cannot work together - either run-time entities are used as specifiers (e.g.
T[propName]) or types (e.g.T[S]), having them both would create problems with name collisions etc. and would have somewhat ambiguous semantics.The features this approach relies on weren't available or even proposed when #1295 was conceived. Things like string literal types and how they are applied to immutable run-time entities and the possibility of read-only function parameters (though I'm not sure if that has been decided on at this point).
There are also some interesting extensions that can be done with this, seemingly without significant effort as it relies on conventional inference mechanisms of the language. For example, as a complement to #6080 (or maybe as an application for #7722) it could support more complex scenarios like:
This may be very difficult or even impossible to implement with the original proposal, but may come essentially "for free" with this slight modification.
mhegazy commentedon Mar 30, 2016
As mentioned in #1295 (comment) we would love to look at an implementation proposal for this feature, regardless of the approach. i think both approach has merits, and looking at an implementation proposal can illuminate additional information about the correct way to go.
The core team has no plans to tackle this feature in the short term, so PRs are welcomed.
malibuzios commentedon Apr 1, 2016
I don't think the examples I gave so far were convincing enough to truly demonstrate the strength of this approach.
The previous example could still somehow (though not very elegantly) use type inference to "pull out" the possible list of literals even with the previous approach:
I mean, even if this looks like a real 'stretch' or somewhat semantically inaccurate.
However there are some cases where there is no possible way to get any type information, as there isn't even a particular parameter to reference!
But when using the type as a specifier, this isn't really a problem, the existing machinery of type argument inference would easily infer it:
This can be extended to arbitrarily complex expressions, as complex as type argument inference could support:
DanielRosenwasser commentedon Apr 1, 2016
Is this related to #6080?
malibuzios commentedon Apr 1, 2016
Daniel Rosenwasser (@DanielRosenwasser)
Yes, I have mentioned it may provide a solution to it but it goes much further than that. It also includes intermediate polymorphic expressions like
T["a" | "b"], or evenT[S]where bothTandSare generic parameters, andTmust extendobjectandSmust extendstring. Also it describes similar functionality of for tuples, i.e.T[N]whereTmust extendArray<any>andNmust extendnumber.mhegazy commentedon Nov 2, 2016
The request in the OP should be handled by the new
keyofandT[K]operators introduced by #11929