-
Notifications
You must be signed in to change notification settings - Fork 12.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Suggestion: polymorphic object and tuple member type references through literal types #7730
Comments
duplicate of #6678 |
#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: function func<T extends Array<any>>(tuple: T) {
const index = 1;
return tuple[index]; // return type inferred as the polymorphic reference type T[1]
}
type MyTuple = [number, string, boolean];
let x: MyTuple = [12, "abcd", true];
let result = func(x); // type of 'result' is the type MyTuple[1] which resolves to string |
this part would be #1295. we have no way today of getting element type of an array. |
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 const x = "ABC"
function getProperty<T extends object>(obj: T, propName: string): T[propName] {
return obj[propName];
} I modified function getProperty<T extends object, S extends string>(obj: T, readonly propName: S): T[S] {
return obj[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 number
let x = getProperty({ "abcd": 42 }, "abcd"); // x receives type number I'm using the fact that in case the parameter 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. |
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. 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: function getAnyOneOfTwoStrings(): "a" | "b" {
if (Math.random() > 0.5)
return "a";
else
return "b";
}
function f<T extends object, S extends string>(obj: T, readonly str: S) {
return obj[str];
}
let MyType = { a: number, b: string, c: boolean }
let x: MyType;
// T resolves to MyType, S resolves to "a" | "b"
// MyType["a" | "b"] resolves to number | string
let result = 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. |
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: function func<T>(obj: T, propName: string): T[propName] {
...
}
let x: "a" | "b;
func<T>({a: 123, b: "ABC"}, x); 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! function func<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:
function func<T extends object, S extends string>(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 | string
func({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:
function func<T extends object, S extends string>(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 | string
func({a: 123, b: "ABC"}, { num: 123, getPropName: () => Math.random() > 0.5 ? "a" : "b"}); |
Is this related to #6080? |
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 |
The request in the OP should be handled by the new |
(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:
And with support for generic types added, this could work similarly, but resolve to an intermediate type of the form
T[S]
whereT
is an object type andS
is a string literal type:This could combine with
readonly
function parameters to provide an alternative solution for #1295 (which was actually where the idea was initially proposed):This may similarly extend to numeric literal types, for tuples:
And work with generic tuples as well:
Same can be done with symbol literal types (haven't thought about that much though):
The text was updated successfully, but these errors were encountered: