Skip to content

Generic TypedArray constructors have only one overload #61680

Open

Description

🔎 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.

Activity

  1. RyanCavanaugh commented on May 14, 2025

    @RyanCavanaugh
    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?

  2. gordonmleigh commented on May 19, 2025

    @gordonmleigh
    Author

    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.

  3. RyanCavanaugh commented on Jun 5, 2025

    @RyanCavanaugh
    Member

    We can try a PR and this seems OK as long as it doesn't break something else

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Domain: lib.d.tsThe issue relates to the different libraries shipped with TypeScriptHelp WantedYou can do thisPossible ImprovementThe current behavior isn't wrong, but it's possible to see that it might be better in some cases

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions