Skip to content

Strongly typed TypedArray valuesΒ #62752

@aapoalas

Description

@aapoalas

πŸ” Search Terms

"TypedArray" "TypedArray value" "indexed value" "value type"

βœ… Viability Checklist

⭐ Suggestion

Enable defining the value type of TypedArrays, similarly to how an Array's value type can be defined using T[] or Array<T>. This would probably take the form of an additional generic parameter on TypedArray types with a default of number applied to keep backwards compatibility.

While Arrays can be defined to use an arbitrary T as their value type, TypedArrays always use the plain number. This is runtime-typing-wise correct, of course, but compared to the Array-case it is possible to define an Array that at runtime contains numbers but at the type level contains a subset of numbers, eg. 1 | 2 | 4 or number & { [BRAND]: unknown }.

TypedArrays, lacking this ability, are harder to use in a code base that wants to use strongly-typed numbers through value unions or branded types. (A further feature which is not litigated here is strongly-typed indexing; the same kind of code base would quite possibly want to define an Array or TypedArray to only be indexed by a given number & { [BRAND]: unknown }.)

πŸ“ƒ Motivating Example

A TypedArray is used to store unique identifiers, such as indexes (handles) to a particular global storage array.

const GLOBAL_DATA_ARRAY: unknown[] = [];

declare const GLOBAL_DATA_HANDLE_BRAND: unique symbol;
type GlobalDataHandle = number & { [GLOBAL_DATA_HANDLE_BRAND]: unknown };

const addData = (data: unknown): GlobalDataHandle => {
  const existing = GLOBAL_DATA_ARRAY.indexOf(data);
  if (existing !== -1) {
    return existing as GlobalDataHandle;
  }
  const added = GLOBAL_DATA_ARRAY.length as GlobalDataHandle;
  GLOBAL_DATA_ARRAY.push(data);
  return added;
};

class Foo {
  // currently:
  #storage: Uint8Array | Uint16Array | Uint32Array;
  // in the future:
  // #storage: Uint8Array<ArrayBufferLike, GlobalDataHandle> | Uint16Array<ArrayBufferLike, GlobalDataHandle> | Uint32Array<ArrayBufferLike, GlobalDataHandle>;
  #storageLength: number;

  pushData(data: unknown): number {
    this.ensureStorage(1); // reallocate #storage if it is full
    const index = this.#storageLength;
    this.#storage[index] = addData(1); // okay; GlobalDataHandle is a number.
    return index;
  }

  getDataHandle(index: number): GlobalDataHandle {
    return this.#storage[index]; // error: number is not a GlobalDataHandle.
  }
}

If #storage was a plain Array, this same would work without any real issue, but if the code base wants to take advantage of the (possibly) smaller memory footprint and stronger runtime value type guarantees of TypedArrays, they must add a lot of as GlobalDataHandle assertions; these assertions are then effectively uncheckable and as a result leads to possibly missing some errors when refactoring.

πŸ’» Use Cases

  1. Strongly typing TypedArray values to eg. constrain a particular TA to contain "data handles", another to contain "relative indexes", another to contain "data bitsets", and all of these TA value types to be strongly typed so as to be unique from one another and thus not be interchangeable.
  2. Currently, TypedArray & T[] can sometimes be used but methods on the type become basically unusable, and even the value types seem to sometimes become just plain number.
  3. The "best" thing to do today is to wrap access to the TypedArray into helper functions that as assert the value type: this comes at a small runtime cost as the function does not disappear even though it is entirely trivial in effect.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Awaiting More FeedbackThis means we'd like to hear from more people who would be helped by this featureSuggestionAn idea for TypeScript

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions