Skip to content
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

Readonly<[...unknown[], infer L]> and readonly [...unknown[], infer L] exhibit inconsistent behavior. #56888

Closed
13OnTheCode opened this issue Dec 27, 2023 · 7 comments Β· Fixed by #57031
Assignees
Labels
Bug A bug in TypeScript Fix Available A PR has been opened for this issue

Comments

@13OnTheCode
Copy link

πŸ”Ž Search Terms

Readonly<[...unknown[], infer L]> and readonly [...unknown[], infer L] exhibit inconsistent behavior.

πŸ•— Version & Regression Information

⏯ Playground Link

https://www.typescriptlang.org/play?ts=5.3.2#code/C4TwDgpgBAglC8UBKECGATA9gOwDYgB4BtAOjIFdsBrbTAd2yIF0AaKSm+7JgPgFgAUFCgB6EVAB6AfkGDQkKACEEUAE5oseEFFIVqtBszYcD3QcLGSZAueGgAVAIwqiAclSu2rgEaeorgGNXJigIAA9gCGx0AGc1DRx8HQBLbAAzCFUoAAk2MhITLmYQqRyoAC4obAgAN0zzUXFpWQF5BwAmF3c-Hx6gkPDI6LiUDETCIlSMrNyofMLDJl4oUuyKqtr6oUarFraoewBmLo8vXy9+0Iio2PixrR15-SLWKCnMqAAZEq-16rrVA1LM0bK07AcACwnHrnfyXQY3EYJLTEJ6cRZsd5Zb48Fa-Sr-LYWJrWYmwALAcioXCVBbYIHiACiYUgFIg6EqgVcgiAA

πŸ’» Code

type A = Readonly<[...unknown[], unknown]>
  // ^? type A = readonly [...unknown[], unknown]

type B = readonly [...unknown[], unknown]
  // ^? type B = readonly [...unknown[], unknown]

type T1 = ['a', 'b', 'c'] extends readonly [infer H, ...unknown[]] ? H : never
  // ^? type T1 = 'a'

type T2 = ['a', 'b', 'c'] extends Readonly<[infer H, ...unknown[]]> ? H : never
  // ^? type T2 = 'a'

type T3 = ['a', 'b', 'c'] extends readonly [...unknown[], infer L] ? L : never
  // ^? type T3 = 'c'

type T4 = ['a', 'b', 'c'] extends Readonly<[...unknown[], infer L]> ? L : never
  // ^? type T4 = unknown
  // Actual: unknown
  // Expected: 'c'

πŸ™ Actual behavior

πŸ™‚ Expected behavior

Infer the correct type.

Additional information about the issue

No response

@Andarist
Copy link
Contributor

Nice find. I find it hard to believe that some problems like this were not already reported but I can't find any issues related to this.

The problem can be shown in a much simpler example that doesn't involve any infer type parameters:

type Result = Readonly<[...string[], number]>
//      ^? type Result = readonly [...(string | number)[], string | number]

Since Readonly is a homomorphic mapped type and this goes through the homomorphic type instantiation, I believe that type arguments of this tuple should be iterated "as is". "Indexing" an element past the fixed element count shouldn't combine it with preceding rest etc

@fatcerberus
Copy link

That homomorphic behavior makes me think this is related to #56885

@Andarist
Copy link
Contributor

The underlying mechanism that is responsible for both is currently the same. I think I've read some comments very recently about this other case being the limitation of the current design - it could potentially be improved but it's a known behavior of the current design and it's considered to be the correct behavior per the current implementation.

The issue here feels different because the user doesn't interact explicitly with property access like this - it's just coincidental that when the mapping is done internally it goes through getPropertyTypeForIndexType "as is" and thus it leads to this issue.

@Andarist
Copy link
Contributor

I re-read the @craigphicks’s comment there and there is definitely something off there. I distinctly remember reading a comment about this behavior with variadic+trailing required elements to be by design today but i might be misremembering what i’ve read or maybe it was an outdated comment or smth

@fatcerberus
Copy link

Not to derail things too much, but...

I distinctly remember reading a comment about this behavior with variadic+trailing required elements to be by design

The relevant comment was recently linked by @jcalz and is from Anders's original PR that implemented leading/middle rest elements: #41544

Also note that indexing a tuple type beyond its starting fixed elements (if any) yields a union type of all possible element types (in this case string | number).

I can't find anything that suggests this behavior has (or should have) changed. Craig points to #49138 being fixed in 5.0.4 but that issue is about assignability, not indexing.

@craigphicks
Copy link

craigphicks commented Dec 28, 2023

@fatcerberus

The fix there was introducing bi-directional matching - from both end towards the middle.
As you say

that issue is about assignability, not indexing
so it is different code. Obviously it would be "nice" if the behaviors of assignment and indexing corresponded with each other.

#56885 started out as a bug report #56883, but that was closed and reopened as a feature request. Currently it doesn't look like a feature request, and I have recommended to @13OnTheCode that it be rewritten as a proper feature request.

Hopefully that same indexing feature improvement would eventually be reflected here as well.

@RyanCavanaugh RyanCavanaugh added the Needs Investigation This issue needs a team member to investigate its status. label Jan 3, 2024
@RyanCavanaugh RyanCavanaugh added this to the Backlog milestone Jan 3, 2024
@ahejlsberg ahejlsberg added Bug A bug in TypeScript and removed Needs Investigation This issue needs a team member to investigate its status. labels Jan 10, 2024
@ahejlsberg ahejlsberg modified the milestones: Backlog, TypeScript 5.4.0 Jan 10, 2024
@ahejlsberg
Copy link
Member

Simpler repro:

type R1<T> = readonly [...unknown[], T];  // readonly [...unknown[], T]
type R2<T> = Readonly<[...unknown[], T]>;  // readonly [...unknown[], unknown]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug A bug in TypeScript Fix Available A PR has been opened for this issue
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants