-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Variadic inference fails to infer type parameter at all #48266
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
Given
Intuitively, the first one is probably what most people want in most situations, but from the compiler's point of view, in the general case, it's ambiguous. Least-effort solution would be to pick one of the last two, and it looks like that's what the compiler is doing here. |
I'm glad you started there π
. We'd need to figure out that This definition works for inference but doesn't catch the same error cases as the original should. With a little more elbow grease you can make type Head<List, Acc> = Acc extends [] ? List : Head<SliceAllButLast<List>, SliceAllButLast<Acc>>;
type SliceAllButLast<T> = T extends [...(infer R), unknown] ? R : never;
declare function compose<A extends any[], Op1Out extends any[], C extends any[], D extends any[]>(
op1: WasmOp<A, Op1Out>,
op2: WasmOp<C, D>,
): WasmOp<A, [...Head<Op1Out, C>, ...D]>;
declare const tee: WasmOp<[i32], [i32, i32]>;
declare const add: WasmOp<[i32, i32], [i32]>;
declare const testOperator: WasmOp<[i64, i64], [externref, i32, i32]>;
// This should be inferred as Op<[i64, i64], [externref, i32]> not Op<[i64, i64], [...any[], i32]>
const op = compose(testOperator, add); A better definition than this might be possible; I won't claim this is the best. |
At this inference site it is, there are multiple intepretations yes as you point out, however only one such interpretation is consistent with
And yeah it would require branching (potentially a lot in general cases of spreading
I don't know enough about TypeScript implements this stuff internally, but couldn't it just infer Again I have no idea how this is implemented internally, but I'd imagine gathering all possible candidates for
and then narrowing it further when it hits the second |
This might be a huge can of worms, but it would be helpful if TypeScript actually produced warnings where TypeScript can't infer something fully, or takes shortcuts, or cuts branches, or otherwise. Like it would be nice if This does go well beyond this issue though, I've actually hit a decent number of the design limitations particularly with inference where a warning would be more helpful than doing the wrong thing. A lot of them feel like they should at least be detectable for warnings, especially given that based on answers for a lot of the design limitations, the actual places where TypeScript takes shortcuts or is limited do seem to be well known by implementers. |
Indeed, this is true in theory, but this would require edit: Interestingly though, swapping the order of |
The only "shortcut" that's being taken here is falling back to the constraint in the presence of zero candidates, which is sound and useful. From TypeScript's perspective, nothing wrong at all has happened -- the same mechanisms that come into play here also happen during "expected" inference situations (and indeed work has been done to make them do so per request) |
@RyanCavanaugh There are candidates for |
I don't recall how the inference rules work when there are consecutive spreads - I believe that we don't pick them up at all, even if a prior type argument has been fixed declare function fn<T extends any[], U extends any[]>(arg: readonly [...T, ...U]): { a: T, b: U }
// a: any / any
const a = fn([1, 2, 3] as const); declare function fn<T extends readonly any[], U extends readonly any[]>(a1: T, arg: readonly [...T, ...U]): { a: T, b: U }
const a = fn([1, 2] as const, [1, 2, 3] as const); |
Out of curiosity is it possible with the more recent TypeScript features to represent the type of what This is more general, and in the simplified version for just plain old unary functions, if we had a function: function compose<A, C>(
f: (a: A) => B,
g: (b: B) => C,
): (a: A) => C {
} Like is it possible to define the type?: type ComposablePair<A, C> = [/* Some dark magic here */]; Such that for example the following happens: declare const p1: [(a: A) => B, (b: B) => C];
const p2: ComposablePair<A, C> = p1; // Allowed
// Can't compose B and C so this won't be assignable
declare const p3: [(a: A) => B, (c: C) => D];
const p4: ComposablePair<A, D> = p3; // Not allowed I believe this is roughly equivalent to a dependent union (roughly dependent sum, except not requiring the disjoint union "tag"). I'm not sure just how much power TypeScript has, but I have seen a lot of fairly powerful types done with mapped types so it might be possible. |
Bug Report
π Search Terms
variadic inference
π Version & Regression Information
β― Playground Link
Playground link with relevant code
π» Code
π Actual behavior
The inferred type is incorrect, we get out
Op<[i64, i64], [...any[], i32]>
becauseB
is inferred toany[]
.My assumption is this is probably a design limitation, so an alternative way to write this type signature so that TypeScript can handle it would be helpful as well.
π Expected behavior
It should infer that
B = [externref]
and hence produce the correct return typeOp<[i64, i64], [externref, i32]>
.The text was updated successfully, but these errors were encountered: