Skip to content

Generic TypedArray constructors have only one overload #61680

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
gordonmleigh opened this issue May 9, 2025 · 2 comments
Open

Generic TypedArray constructors have only one overload #61680

gordonmleigh opened this issue May 9, 2025 · 2 comments

Comments

@gordonmleigh
Copy link

πŸ”Ž Search Terms

Uint8Array

πŸ•— Version & Regression Information

  • This is the behavior in versions 5.8.3 and 5.7.3
  • I was unable to test this on prior versions because Uint8Array is not generic in earlier versions

⏯ Playground Link

https://www.typescriptlang.org/play/?target=11&ts=5.8.3#code/MYewdgzgLgBBIFcBOwCmMC8MyoO4wFUBLMKADgEEkkBDATwAoBGABgEoBuAKC9EllAAHOk0zY8hEuSq1G8ZGk69w0GELoAmMTnzFSlavQA8M+gCEEAM0uokAGSIBrVAD4G8lKiV9V6gMzaEnrShnQmoRbWtm4eitxAA

πŸ’» Code

const source = new Uint8Array(10);

const copy1 = new Uint8Array(source); // OK
const copy2 = new Uint8Array<ArrayBufferLike>(source); // TS 2345
const copy3 = new Uint8Array<ArrayBuffer>(source); // TS 2345

πŸ™ Actual behavior

Only the generic constructor overload of Uint8Array is available when using Uint8Array with a generic parameter, i.e. the one which accepts a TArrayBuffer as the first argument. It is therefore not possible to pass a Uint8Array as the first argument without a compile error, even though this is valid.

πŸ™‚ Expected behavior

It should be possible to use all the valid overloads of Uint8Array (and friends) constructor without a compiler error.

Additional information about the issue

This seems to only affect target value of ES2024 or ESNext, but I can't figure out why from looking at the types.

I assume this behaviour occurs for the same reason as given in this comment. In that case I assume the fix would be adding copies of all the constructor overloads to the Uint8ArrayConstructor interface, but with type parameters.

For example, the following works (TS Playground):

interface Uint8ArrayConstructor {
    // this is just a copy of existing overloads, with `TArrayBuffer` param added
    new <TArrayBuffer extends ArrayBufferLike = ArrayBuffer>(
      length: number,
    ): Uint8Array<TArrayBuffer>;
    new <TArrayBuffer extends ArrayBufferLike = ArrayBuffer>(
      array: ArrayLike<number>,
    ): Uint8Array<TArrayBuffer>;
    new <TArrayBuffer extends ArrayBufferLike = ArrayBuffer>(
      buffer: ArrayBuffer,
      byteOffset?: number,
      length?: number,
    ): Uint8Array<TArrayBuffer>;
    new <TArrayBuffer extends ArrayBufferLike = ArrayBuffer>(
      array: ArrayLike<number> | ArrayBuffer,
    ): Uint8Array<TArrayBuffer>;
}

const source = new Uint8Array(10);

const copy1 = new Uint8Array(source);
const copy2 = new Uint8Array<ArrayBufferLike>(source);
const copy3 = new Uint8Array<ArrayBuffer>(source);

I think this is basically the opposite problem to that which was fixed by #60934.

@RyanCavanaugh
Copy link
Member

I don't understand what you're trying to do here. In the case where you're passing a Uint8Array, there's nothing generic to do, so it's not necessary/useful/etc to pass a type argument. What's the actual code you're trying to write where this type argument would be doing something?

@RyanCavanaugh RyanCavanaugh added the Needs More Info The issue still hasn't been fully clarified label May 14, 2025
@gordonmleigh
Copy link
Author

gordonmleigh commented May 19, 2025

It's a derived class, similar to this, which I've still trimmed for brevity (playground):

type StringEncoding = "cp437" | "utf8";

export class EncodedString extends Uint8Array {
  static getEncoder(encoding: StringEncoding): TextEncoder {
    throw new Error("not implemented");
  }

  constructor(encoding: StringEncoding, value: string)
  constructor(encoding: StringEncoding, value: ArrayBufferLike, byteOffset?: number, byteLength?: number)
  constructor(encoding: StringEncoding, value: string | ArrayBufferLike, byteOffset?: number, byteLength?: number) {
    if (typeof value === 'string') {
      const encoded = EncodedString.getEncoder(encoding).encode(value)
      super(encoded);
      //    ^^^^^^^  this is a compiler error if the base class is `Uint8Array<ArrayBufferLike>`
    } else {
      super(value, byteOffset, byteLength);
      //    ^^^^^    this is a compiler error if the base class is `Uint8Array`
    }
  }
}

If I give no type parameters for Uint8Array, then the second super call has a compiler error. If I give the type parameter for Uint8Array, then the first super call has a compiler error. If I make EncodedString generic over TArrayBuffer then obviously it has the same problem as the latter.

Perhaps it is a little odd what I'm trying to do, i.e. subclassing Uint8Array, but I figure that this problem will affect anything which needs to be generic for TArrayBuffer for Uint8Array and also create an instance. Due to the calling union types caveats, the generic and non-generic Uint8Array are now effectively two different class types, each with different constructors. That surely wasn't the intention when the generic version was introduced.

@RyanCavanaugh RyanCavanaugh removed the Needs More Info The issue still hasn't been fully clarified label May 19, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants