7

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

3 Answers 3

9

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)

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks Nitzan, I will use your implementation with considering @tamas-hegedus' thought. I think typecasting is acceptable for this use case as unit testing can easily cover corner cases.
4

You could simply define a generic TypedArray in a declaration file.

1- Create a file named extras.d.ts in your app.

2- Add the following line:

type TypedArray = Int8Array | Uint8Array | Int16Array | Uint16Array | Int32Array | Uint32Array | Uint8ClampedArray | Float32Array | Float64Array;

Then the generic TypedArray type will be available throughout your project. You can use this extras.d.ts file to keep declaring more custom types to use throughout your app.

Comments

2

I grabbed @Nitzan's answer and massaged it until all the type-casts was gone, BUT unfortunately this solution suffers from the untyped constructor property issue here, and there seem to be no workaround without type-casts. I post the code anyway for future reference.

Warning: this code does not compile as of 2017/05/16.

interface GenericTypedArray<T> extends ArrayLike<number> {
    BYTES_PER_ELEMENT: number;
    set(array: ArrayLike<number>, offset?: number): void;
    slice(start?: number, end?: number): T;
    constructor: GenericTypedArrayConstructor<T>;
}

interface GenericTypedArrayConstructor<T> {
    new (): T;
    new (buffer: ArrayBuffer): T;
}

export function resize<T extends GenericTypedArray<T>>(source: T, newSize: number): T {
    if (!source.length) {
        return new source.constructor();
    }

    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);
};

class DummyArray { 
    constructor();
    constructor(buffer: ArrayBuffer);
    constructor(array: ArrayLike<number>);
    constructor(arg?) { }

    // Hack to have a typed constructor property, see https://github.com/Microsoft/TypeScript/issues/3841
    'constructor': typeof DummyArray;
    BYTES_PER_ELEMENT: number;
    length: number;
    [index: number]: number;

    set(array: ArrayLike<number>, offset?: number): void { }
    slice(start?: number, end?: number): this { return this; }

    static BYTES_PER_ELEMENT: number;
}

// How it intended to work
resize(new DummyArray([1, 2, 3]), 5);

// How it fails to typecheck
// Types of property 'constructor' are incompatible.
//   Type 'Function' is not assignable to type 'GenericTypedArrayConstructor<Uint8Array>'.
resize(new Uint8Array([1, 2, 3]), 5); 

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.