-
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: Infer tuple type for literal arrays passed to generic functions #22679
Comments
Actually thinking about it #16656 is just asking for a way to get tuple types without manually annotating everything, so this would basically fix that too via a function like: function tuple<T extends any[]>(array: T): T { return array }
const myTuple = tuple(["str", 10]) // myTuple would have type [string, number] The downside is that meaningless function call would actually exist at runtime but that seems pretty inconsequential. Edit: That also means that function tuple<T extends any[] & {"0": any}>(array: T): T { return array }
declare function needsTuple(arg: [string, number]): void
const regularArray = ["str", 10]
needsTuple(regularArray) // error
const myTuple = tuple(["str", 10])
needsTuple(myTuple) // no error technically works today. Playground link Maybe we could just have that behavior documented as something that will continue to work in the future? |
The proposal here would have us giving a completely wrong type to // Free version of Array#filter
declare function filter<T extends any[]>(arr: T[], cond: (arg: T) => {})
// Inferred type [number, string, number]
// Actual type [string]
const arr = filter([0, "x", 0], s => !!s); |
Your example isn't what I'm proposing. What I'm proposing would be more like type ArrayKeys = keyof any[]
type Indices<T> = Exclude<keyof T, ArrayKeys>
// notice how arr declared to have type T instead of T[]
declare function filter<T extends any[], U extends T[Indices<T>]>(arr: T, cond: (value: U ) => {}): U[];
const filtered = filter([0, "x", 0], s => !!s); but it wouldn't add any value(but also wouldn't hurt anything as far as I can tell) in that example so it'd be simpler to keep the definition something like declare function filter<T>(arr: T[], cond: (value: T) => {}): T[]; Which I'm not proposing to change at all. |
What would be the type of declare function trivialExample<T extends any[]>(arr: T): T;
const numVal = trivialExample([1, "test"]); |
In that example the type would be |
That'd be a substantial breaking change; not something we can really do. There are plenty of functions declared like this declare function reverse<T extends any[]>(arr: T): T;
const numVal = reverse([1, "test"]); // oops type is completely wrong now and we can't just say "Well you should have written the declaration differently because now it means something completely different". |
Yeah, I have seen the many discussions of the problems going from arrays to tuple and knew you don't want to generally infer tuples everywhere. I was mainly thinking about cases where you weren't going to return the array from the function and figured it wouldn't be a breaking change in those cases, and I only thought about the implications of actually returning the inferred type afterwards. I can see that it'd be a breaking change, but for the sake of argument is there any real difference today in writing declare function reverse<T extends any[]>(arr: T): T; instead of declare function reverse<T>(arr: T[]): T[]; meaning that if you hypothetically made this breaking change, would there be any cases where people who need the old behavior couldn't just write it as the Just my opinion but I think there would be such a large amount of value from providing a way to infer tuple types that it might be worth a breaking change, assuming I'm right that just converting to Edit: Also want to emphasize that I'd only suggest inferring a tuple in cases like declare function arr<T extends string>(...args: T[]): T[]
const x = arr('a', 'b') // type ('a' | 'b')[] because of T extends string but declare function arr2<T>(...args: T[]): T[]
const y = arr2('a', 'b') // type string[] because T has no constraint |
Automatically closing this issue for housekeeping purposes. The issue labels indicate that it is unactionable at the moment or has already been addressed. |
Code
Current behavior:
Compile error because typescript infers a regular array and doesn't know that
"0"
is akeyof T
.Desired behavior:
Typescript would infer a tuple type for
T
and compile without errors.Reason
Since the addition of conditional types I've come across a couple real situations where I've wanted it to work like this. Conditional types make it easy to filter out the uninteresting keys that exist on all arrays so you can just work with the indices. A simplified example of something I've actually tried to do is the following code which declares a function that takes an array of any number of objects and returns a single combined object, and would have a compile time error if you accidentally pass it the same property twice:
Hacky Workaround
By digging around in the typescript codebase I figured out that you can actually trick the compiler into making this work already. The trick is to add an intersection with
{ "0": any }
The
checker.ts
file is too long for Github to let me link to the line but the reason it works is becausecheckArrayLiteral
callscontextualTypeIsTupleLikeType
to decide if it should make the type a tuple or an array, and that ends up deciding it is a tuple type if the contextual type has a property named"0"
. That seems like an implementation detail that might stop working at any point though so I don't feel like it's something we could safely use in real projects.Drawbacks
The big drawback I can see is that this would result in a non-intuitive difference in behavior between
Typescript already has the "no excess properties on literal objects" case where it behaves differently based on passing a literal or a variable, but admittedly the reason for the difference in that case is pretty intuitive and actually helps the user while in this case the difference in behavior would be much harder to explain.
Related Issues:
#16656 is similar but is about the more general behavior of literal arrays like
["foo", 12]
being given the type(string | number)[]
instead of[string, number]
. This issue is just about the specific case of passing literal arrays directly to a function.Search Terms:
tuple, literal array, generic function, type inference, keyof array
The text was updated successfully, but these errors were encountered: