Unexpected behavior in extends clause that has two inferred rest types with constraints
Bug Report
🔎 Search Terms
- extends constraint inferred rest
- multiple inferred rest types
🕗 Version & Regression Information
Version 4.7 and higher (including nightly)
- This is the behavior in every version I tried, and I reviewed the FAQ
⏯ Playground Link
💻 Code
type Prefix<A> = A extends [...infer P extends 0[], 1] ? {p: P} : never
{ type Test = Prefix<[0,0,1]> }
// type Test = {p: [0, 0]}
{ type Test = Prefix<[0,0,1,1]> }
// type Test = never
type PrefixSuffix<A> = A extends [...infer P extends 0[], 1, ...infer S extends 0[]] ? {p: P, s: S} : never
{ type Test = PrefixSuffix<[0,0,1,0,0]> }
// type Test = {p: 0[], s: 0[]}
{ type Unexpected = PrefixSuffix<[0,0,1,1,0,0]> }
// type Unexpected = {p: 0[], s: 0[]}
🙁 Actual behavior
Similar to Prefix, which extracts a tuple of leading zeroes and works as expected, I tried to construct PrefixSuffix, to extract both the leading and trailing zeroes. When applied to an array with multiple ones, PrefixSuffix<[0,0,1,1,0,0]> evaluates to {p: 0[], s: 0[]} instead of never.
🙂 Expected behavior
I would have expected PrefixSuffix<[0,0,1,1,0,0]> to evaluate to never, as [0,0,1,1,0,0] cannot extend an array that contains only a single 1. I also would have expected PrefixSuffix<[0,0,1,0,0]> to infer tuple types {p: [0,0], s: [0,0]} rather than {p: 0[], s: 0[]}, but maybe that's just a design limitation.
The problem here is likely related to the logic in this branch. It never handled 2 variadic positions used like this - what you see in the result is not the inferred result but rather just constraints of those type params (coming from your extends clauses). Without extends this just "infers" unknown[] for both (again, constraints are assigned to them and the default constraint for those here is unknown[]).
One could argue that this is hard to infer because the lengths of those leading and trailing tuples are not known but OTOH inferring from strings using template literal types already comes with some greedy/non-greedy behaviors baked into the language and similar rules could be just used here.
The "inferred" match is equivalent to [...0[], 1, ...0[]] and that's an impossible type (A rest element cannot follow another rest element.(1265)). I would expect this to be a reason to return never here.
But note that because of what I have described above... type Test = PrefixSuffix<[0,0,1,0,0]> should also be an error as it's not that correct types are inferred there. It's just accidental that this case matches more closely your expectations.
I don't know that we've ever supported inference to two rest elements of a tuple that has one or more fixed elements between them. What ends up happening is that the inferred extends type actually ends up as (0 | 1)[], not [...0[], 1, ...0[]], because the latter form is not allowed. You can see both the error the latter type would produce, as well as the actual representation the compiler uses here: Playground Link

Since both [0, 0, 1, 0, 0] and [0, 0, 1, 1, 0, 0] are assignable to (0 | 1)[], it doesn't use the false branch of the conditional.
This is unfortunately a design limitation as we have a very narrow heuristic that provides rudimentary support for inferring to multiple rest types in a tuple. It's not clear at this time whether this is something we want to extend. While a useful minimal repro, the example shown in the issue description doesn't illustrate a need that would be sufficient motivation to reevaluate this limitation. It would be helpful to understand the specific needs this issue is trying to address.
Design limitation is fine by me. I ran into it while answering a StackOverflow question and thought it might be a bug, but I don't have an actual need for it to get addressed.
I don't have any additional feedback, shall we close the issue as a design limitation?