Skip to content

Unexpected behavior in extends clause that has two inferred rest types with constraints #50993

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

Open
Oblosys opened this issue Sep 29, 2022 · 5 comments
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript

Comments

@Oblosys
Copy link

Oblosys commented Sep 29, 2022

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

TypeScript playground

πŸ’» 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.

@RyanCavanaugh RyanCavanaugh added the Needs Investigation This issue needs a team member to investigate its status. label Sep 29, 2022
@RyanCavanaugh RyanCavanaugh added this to the TypeScript 4.9.2 milestone Sep 29, 2022
@RyanCavanaugh RyanCavanaugh added the Rescheduled This issue was previously scheduled to an earlier milestone label Feb 1, 2023
@Andarist
Copy link
Contributor

Andarist commented Mar 7, 2023

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.

@rbuckton
Copy link
Contributor

rbuckton commented May 3, 2023

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

image

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.

@rbuckton
Copy link
Contributor

rbuckton commented May 3, 2023

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.

@rbuckton rbuckton added Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature and removed Needs Investigation This issue needs a team member to investigate its status. labels May 3, 2023
@Oblosys
Copy link
Author

Oblosys commented May 4, 2023

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.

@RyanCavanaugh RyanCavanaugh removed this from the TypeScript 5.1.0 milestone May 5, 2023
@RyanCavanaugh RyanCavanaugh added Suggestion An idea for TypeScript and removed Rescheduled This issue was previously scheduled to an earlier milestone labels May 5, 2023
@Oblosys
Copy link
Author

Oblosys commented Aug 19, 2023

I don't have any additional feedback, shall we close the issue as a design limitation?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

4 participants