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
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)
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.
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);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With