-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Spreads instantiated with array subtypes types exhibit bad behavior #26110
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
Comments
It might be difficult, but I think ideally the user would get an error message on the type argument during instantiation; something along the lines of
|
The tricky issue here is that constraints can only limit us to subtypes of arrays, but not specifically to instantiations of |
The last call should not be an error |
Here's a more obvious example of the issue: function bar<T extends any[]>(...args: T): T {
return args;
}
let a = bar(10, 20); // Ok: [10, 20]
let b = bar<CoolArray<number>>(10, 20); // Bad: CoolArray<number> We'll never infer anything but instantiations of |
@RyanCavanaugh why do you think so? |
@DanielRosenwasser consider this code, which is representative of many functional libraries: function fn<T extends any[]>(cb: (...args: T) => void, arr: T): T {
cb.call(null, arr);
return arr;
}
type MyTaggedArray = Array<number> & { "I promise it's sorted" };
const arr = [] as MyTaggedArray;
const arr2 = fn(x => x, arr); There is nothing wrong with this code! The error in Anders' example should be that the |
To recap, the problem in this issue as well as #26491 is that we have no expressible constraint that limits type parameters to only be instantiated with |
If we change the rest parameter to |
@mattmccutchen It's more like if we have a union of all tuple types, we should substitute a tuple type with all |
@ahejlsberg To emphasize the point, if we substitute interface CoolArray<E> extends Array<E> {
hello: number;
}
function bar<T extends any[]>(...args: T): T {
return args;
}
console.log(bar<CoolArray<number>>().hello); If we substitute |
On second thought, changing a generic rest parameter to something interface CoolArray<E> extends Array<E> {
hello: number;
}
function bar<T extends any[]>(...args: T): T {
return args;
}
function baz<T extends any[]>(array: T): T {
return bar(...array);
}
declare const ca: CoolArray<number>;
console.log(baz(ca).hello); The sound (but maybe annoying) approach would be to define a helper type alias type Spread<T extends any[]> = Pick<T, keyof T & number | keyof any[]>; but ideally we'd like it to always return a real array or tuple type even if |
I think I have the right solution figured out. In cases where a rest parameter type is not an In the I'll be putting up a PR with this behavior. |
@ahejlsberg Isn't it a little early to add the "Fixed" label? |
@mattmccutchen Unless we introduce a whole new kind of constraint I'm not sure we can do any better than #26676, and even then it would be a breaking change since it would be stricter than the assignable to function call<T extends unknown[], U>(f: (...args: T) => U, ...args: T) {
return f(...args);
} which means spreading a |
@ahejlsberg For completeness, I want to point out that there is another solution without introducing a new kind of constraint and even without breaking the specific example you gave. (Some other code might break but would be straightforward to fix manually; in principle, the change could be put behind a new strict flag.) Define a built-in type alias I'm guessing you're not going to want to do this, but I still wanted to point out that it would be possible. |
@mmitche FWIW a lot of us use "Open + |
Found by playing around with @mattmccutchen's example at #26013 (comment):
Expected: All of these calls produce an error and have a good error message.
Actual: The first two have bad errors, the last one is considered okay.
The text was updated successfully, but these errors were encountered: