Search code examples
javascriptgenericstypescripttyped-arrays

TypeScript: Generics type definition for TypedArray


I am trying to write a function that expands / shrinks TypedArray by taking an arbitrary TypedArray as an input and returns a new same-typed TypedArray with a different size and copy original elements into it.

For example, when you pass, new Uint32Array([1,2,3]) with new size of 5, it will return new Uint32Array([1,2,3,0,0]).

export const resize = <T>(
    source: ArrayLike<T>, newSize: number,
): ArrayLike<T> => {
    if (!source.length) { return new source.constructor(newSize); }
    newSize = typeof newSize === "number" ? newSize : source.length;
    if (newSize >= source.length) {
        const buf = new ArrayBuffer(newSize * source.BYTES_PER_ELEMENT);
        const arr = new source.constructor(buf);
        arr.set(source);
        return arr;
    }
    return source.slice(0, newSize);
};

While the code works as expected, TSC is complaining that 1) ArrayType does not have BYTES_PER_ELEMENT and slice, and 2) Cannot use 'new' with an expression whose type lacks a call or construct signature for the statement new source.constructor().

Is there a way to specify type interfaces for such function that TSC understands my intention?

For 1), I understand ArrayLike does not have interface defined for TypedArray but individual typed array does not seem to inherit from a common class... For instance, instead of using generics, I can use const expand = (source: <Uint32Array|Uint16Array|...>): <Uint32Array|Uint16Array|...> => {}. But it loses context of returning type being same type of the source array.

And for 2) I am clueless on how to tackle this error. It seems reasonable for TSC to complain that source's constructor is lacking type information. But if I can pass proper type for 1), I presume 2) will be disappeared too.

ref) https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray


Solution

  • This isn't beautitiful, but it works:

    type TypedArray = ArrayLike<any> & {
        BYTES_PER_ELEMENT: number;
        set(array: ArrayLike<number>, offset?: number): void;
        slice(start?: number, end?: number): TypedArray;
    };
    type TypedArrayConstructor<T> = {
        new (): T;
        new (size: number): T;
        new (buffer: ArrayBuffer): T;
        BYTES_PER_ELEMENT: number;
    }
    
    export const resize = <T extends TypedArray>(source: T, newSize: number): T => {
        if (!source.length) {
            return new (source.constructor as TypedArrayConstructor<T>)();
        }
    
        newSize = typeof newSize === "number" ? newSize : source.length;
    
        if (newSize >= source.length) {
            const buf = new ArrayBuffer(newSize * source.BYTES_PER_ELEMENT);
            const arr = new (source.constructor as TypedArrayConstructor<T>)(buf);
            arr.set(source);
            return arr;
        }
        return source.slice(0, newSize) as T;
    };
    

    (code in playground)