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

  4. MartinFerret commented on Nov 6, 2026

    @MartinFerret

    I'd like to work on this.

    Before writing the PR, I checked the two possibilities for the extra generic overloads (length, ArrayLike<number>, the union case and Iterable<number>), added to Uint8ArrayConstructor through declaration merging, in TypeScript 6.0.3 and 7.0.2, against the original examples and the EncodedString extends Uint8Array<ArrayBufferLike> case.

    • Returning Uint8Array<TArrayBuffer> (as suggested in the issue) solves all of them, including the subclass. However, new Uint8Array<SharedArrayBuffer>(4) would type-check, although the constructor always allocates an ArrayBuffer.

    • Returning Uint8Array<ArrayBuffer> is sound and solves the direct calls of new Uint8Array<ArrayBufferLike>(source), but the subclassing case fails with TS2510 ("Base constructors must all have the same return type").

    In both cases, constructor calls without type arguments keep their current types. For example, new Uint8Array(10) remains Uint8Array<ArrayBuffer>.

    Only the first shape solves the subclassing case that motivated the issue. Would you accept this trade-off? If this is OK, I will update all typed array constructors (including the BigInt ones) in the lib files and add compiler tests for explicit type arguments and subclassing, as well as the existing call forms.

  5. trentmillar commented on Nov 7, 2026

    @trentmillar

    Martin Ferret (@MartinFerret) small heads up if you're putting a PR together β€” lib.es2017.typedarrays.d.ts (the 0-arg new () constructor) and lib.es2025.float16.d.ts (Float16ArrayConstructor) also have TypedArray constructors that'll need the generic overloads so super() and Float16Array<ArrayBufferLike> work too.

  6. MartinFerret commented on Nov 7, 2026

    @MartinFerret

    Thanks Trent Millar (@trentmillar), that matches what I saw! I'm definitely putting a PR together.

  7. MartinFerret commented on Nov 7, 2026

    @MartinFerret

    I hadn't seen #65376 when I wrote my last comment, it covers exactly this, so I'll leave it there.

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 TypeScriptPossible 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