Skip to content

Mapped types over subclassed arrays behave strangely #59260

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
Andarist opened this issue Jul 12, 2024 · 1 comment
Open

Mapped types over subclassed arrays behave strangely #59260

Andarist opened this issue Jul 12, 2024 · 1 comment
Assignees
Labels
Needs Investigation This issue needs a team member to investigate its status.

Comments

@Andarist
Copy link
Contributor

πŸ”Ž Search Terms

mapped type array subclass numeric constraint

πŸ•— Version & Regression Information

  • This is the behavior in every version I tried

⏯ Playground Link

https://www.typescriptlang.org/play/?ts=5.6.0-dev.20240712#code/MYGwhgzhAEDKCuAjAggJ1WAngHgCoD5oBTADwBciA7AExjQxwOgG8BYAKGmnAjIAV4EABZFqALmjwaRAGYBLSqOgAfaLgDcHAL4cOZTAAciaorzzFyVWtABKRMNQD2lEJnpZsYSpnyEAvCwcXADaANLQCtAA1kSYjjJqALoSuGGJmuxaGXqGxnYQ8CBk0AG4pmTYCCjoHpTwALaIRKi+GQD0bVzQAHoA-DlG0ALCJbamhWTBAEQGgkJT6RwdXX1AA

πŸ’» Code

class SubArray<T> extends Array<T> {
  lastPushed: undefined | T;
}

type Test<T extends ReadonlyArray<any>> = {
  [K in keyof T]: T[K];
};

type Result = Test<SubArray<number>>;
//   ^?
type Push = Result["push"];
//   ^?

πŸ™ Actual behavior

This mapped type doesn't have a homomorphic instantiation - its arrayness is not preserved. But yet all members are mapped to the Array's type argument

πŸ™‚ Expected behavior

I'm not entirely sure. On one hand, it feels that those members should be instantiated with the type of the original member. This wouldn't work with generic mapped types as T[K] has the numeric constraint when T has an array/tuple constraint. So the generic constraints wouldn't match what those could end up being instantiated with.

Should the result be a remapped subclassed array? That means that extra members wouldn't be mappable... but hey, regular array methods etc arent. So perhaps that's the most appropriate solution here.

cc @ahejlsberg

Additional information about the issue

No response

@ahejlsberg
Copy link
Member

ahejlsberg commented Jul 30, 2024

Hmm, yeah, the behavior here is odd, but it's not immediately clear how to fix it.

In the Test mapped type, K is given the contextual constraint number | `${number}` because T is constrained to an array or tuple type (see #48837). That in turn means that the T[K] is resolved as T[K & (number | `${number}`)]. When we instantiate with SubArray<number> for T and "push" for K, we get SubArray<number>[never], which comes out as number (you could argue it should be never, but that wouldn't really help any).

The logic error here is that we take the array constraint for T in Test to mean that T will always represent an actual array or tuple type (and not some type derived from an array type), but then in the mapped type instantiation we only map to an output array or tuple type when the input is an actual array or tuple type.

We could potentially detect that the mapped type is instantiated for a derived array type and attempt to create an instance of that type for the mapped element type. But it starts to get speculative and would only work when the derived type passes the type parameter straight through.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Needs Investigation This issue needs a team member to investigate its status.
Projects
None yet
Development

No branches or pull requests

3 participants