Ways to create a versatile function for generating TypedArrays instances

I'm working on a function that looks like this:

export function createTypedArray<T extends TypedArray>( arg : { source : T, arraySize : number } ) : T {
  if( arg.source instanceof Int32Array ) {
    return new Int32Array( arg.arraySize );
  }
  if( arg.source instanceof Float32Array ) {
    return new Float32Array( arg.arraySize );
  }
  if( arg.source instanceof Float64Array ) {
    return new Float64Array( arg.arraySize );
  }
  throw 'Unsupported typed array type!';
}

The issue I am encountering is that the TypeScript compiler is showing errors such as:

Type 'Int32Array' is not assignable to type 'T'. 'Int32Array' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'TypedArray'.ts(2322)

I discovered that I can eliminate the errors by adding a like this:

return <T> new Int32Array( arg.arraySize );

Is there a more efficient way to write this function?

Can it be written in a way similar to this:

export function createTypedArray<T extends TypedArray>( arg : { source : T, arraySize : number } ) : T {
  if( arg.source instanceof Int32Array ||
      arg.source instanceof Float32Array ||
      arg.source instanceof Float64Array
  ) {
    return new <T>( arg.arraySize );
  }
  throw 'Unsupported typed array type!';
}

Answer №1

The issue with your code is due to a misunderstanding of instanceof type guards. These guards narrow the type of the variable on the left-hand-side, in this case being arg.source. It's important to note that the type guard does not narrow the type of the type variable T.

In cases where Typescript may not infer the type correctly but you can, using type assertions becomes necessary. Therefore,

return new Int32Array( arg.arraySize ) as T

is the correct approach!


One might argue that it should narrow the type since T should be the type of arg.source, and this feedback could possibly be shared with the Typescript team.

Answer №2

This presents a challenge when it comes to implementing generic functions with union type parameters. Check out microsoft/TypeScript#24085 for detailed information on this issue. The main problem lies in the fact that while the compiler can potentially narrow the type of arg.source to, let's say, Int32Array, it cannot narrow down the generic type parameter from T extends TypedArray to something like T extends Int32Array. It is generally unsafe to narrow down T when you narrow a value of type T, even though it would be quite convenient in scenarios like yours. As of now, this functionality is not supported.

To address this limitation and ensure your existing implementation compiles, you can utilize type assertions similar to what you have already done.


If I had nothing more to add, I would have left the other answer as-is. However, I wanted to demonstrate that by manipulating your implementation logic slightly, you can achieve some level of type safety without requiring an assertion:

const typedArrayMaker = (size: number) => ({
    get Int32Array() { return new Int32Array(size); },
    get Float32Array() { return new Float32Array(size); },
    get Float64Array() { return new Float64Array(size); }
});    
export function createTypedArray<K extends keyof ReturnType<typeof typedArrayMaker>>(
    arg: { source: { [Symbol.toStringTag]: K }, arraySize: number }) {
    return typedArrayMaker(arg.arraySize)[arg.source[Symbol.toStringTag]];
}

In this revised approach, we define a function named typedArrayMaker which generates an object containing getter methods for different types of arrays. The compiler interprets the type of typedArrayMaker as:

const typedArrayMaker: (size: number) => {
    readonly Int32Array: Int32Array;
    readonly Float32Array: Float32Array;
    readonly Float64Array: Float64Array;
}

The createTypedArray() function takes arguments as before, but the generic parameter is now represented by K, corresponding to the value of the [Symbol.toStringTag] property of arg.source. Each typed array has a specific string literal value for this property, which is used for indexing into typedArrayMaker.

This transformation results in a generic indexing operation for the return type, making it comprehensible by the compiler. The outcome is a return type dependent on K, devoid of any errors. Let's put it to the test:

function test(i32: Int32Array, f32: Float32Array, f64: Float64Array, i8: Int8Array) {
    const i32New = createTypedArray({ source: i32, arraySize: 128 }); // Int32Array
    const f32New = createTypedArray({ source: f32, arraySize: 128 }); // Float32Array
    const f64New = createTypedArray({ source: f64, arraySize: 128 }); // Float64Array
    const i8New = createTypedArray({ source: i8, arraySize: 128 }); // error!
    // ----------------------------> ~~~~~~
    // Type '"Int8Array"' is not assignable to type 
    // '"Int32Array" | "Float32Array" | "Float64Array"'
}

As demonstrated, the compiler correctly identifies that i32New, f32New, and f64New have the expected types, whereas i8 triggers an error when passed to createTypedArray() due to the absence of that specific typed array type within our function.

This just goes to show one viable method of structuring the code in a manner that aligns with the compiler's understanding. In practical terms, I highly recommend resorting to a type assertion because complex higher-order functions involving getters and symbol properties can get convoluted.


I trust this explanation proves helpful; best of luck!

Link to playground for code samples

Answer №3

I am unsure of the intention behind your example

The code snippet provided is:

function createTypedArray<T extends TypedArray>(
      arg : { source : T, arraySize : number } ) : T

This suggests calling it like this:

const someExistingArray = new Float32Array(12);
const v = createTypedArray({
   source: someExistingArray,
   arraySize: 40,
});

It seems unclear why an existing array would be passed when only the type is needed.

However, the following implementation appears to work:

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

function createTypedArray<T extends TypedArray>(arg: { source: T, arraySize: number }): T {
  const {source, arraySize} = arg;
  return new (Object.getPrototypeOf(source).constructor)(arraySize);
}

TS Playground

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What is the reason for the array length view not updating when a new object is added?

I am dealing with an array of objects and the length is displayed using this code snippet: <strong *ngIf="cart">{{ cart.length }}</strong> Even though when I add items to the array, both the array and its length are showing correctly ...

How to retrieve the default type returned by a function using a custom TypeMap

I have a function that returns a promise with a type provided by generics. const api = <Model>(url: string) => Promise<Model> Currently, I always need to set the type of the returned data. const data = await api<{id: string, name: string ...

The identification of the field is not being transmitted by ng-select

Looking for help with Angular! I have an ng-select box where I can choose countries, and it's working fine when I select an option and submit - it submits the id as expected. However, when pre-populating the ng-select with data and submitting, it&apos ...

Leverage the power of gRPC with Node.js and TypeScript for seamless

I'm trying to implement GRPC in a Node.js and Typescript project, but I'm facing an issue with generating proto files on Windows 10 using npm. I need to run the file transpile-proto-ts.sh: #!/usr/bin/env bash OUT_DIR="./src" TS_OUT_DIR="./src" ...

Encountered in the Angular library: NG0203 error stating that inject() should only be invoked within an injection context like a constructor, factory function, or field initializer

Having recently updated my Angular project from version 9 to 15, I encountered the following issue. Any assistance would be greatly appreciated as I have been struggling with it for over 2 weeks. The problem lies within app-lib.module.ts in the Angular li ...

Is it possible to eliminate a parameter when the generic type 'T' is equal to 'void'?

In the code snippet below, I am attempting to specify the type of the resolve callback. Initially: Generic Approach export interface PromiseHandler<T> { resolve: (result: T) => void // <----- My query is about this line reject: (error: a ...

ES6: The attribute 'selected' is not found on the data type 'Object'

While attempting to convert the JS code from 'AMcharts4 codepen pre selecting areas' into ES6, I encountered an error. Error TS2339: Property 'selected' does not exist on type 'Object'. Here is the code snippet that I am l ...

Attempting to retrieve a file from the database with the utilization of Angular 5 and ASP.NET Core

I've been struggling with downloading a file from the database using its unique identifier. Can anyone provide guidance on what steps are necessary to achieve this successfully? The FileUpload table contains the following columns pertaining to the fi ...

Dynamic import of a SASS file in VueJS using a variable such as process.env

Is there a way to dynamically import a file using environment variables? I want to include a specific client's general theme SCSS to my app.vue (or main.ts) I'm thinking of something along the lines of: <style lang="sass"> @import"./th ...

Creating an instance of a parameterized field in a class

Could a generic field in a class be specialized to a specific type within the constructor? For instance: class customClass1 { private int num; public customClass1( int num) { this.num = num; } } class customClass2 { string na ...

Utilizing absolute path in Typescript: A comprehensive guide

I am currently working on a project written in Typescript running on NodeJS. Within the project, I have been using relative paths to import modules, but as the project grows, this approach is becoming messy. Therefore, I am looking to convert these relativ ...

What is the method to assert that two arguments are equal within a function?

When working with TypeScript, you can pass a value to a function and have that function assert the value is true for type narrowing. For example: function assertTrue(v: unknown, message: string): asserts v { if (!v) { throw new MySpecialError(message ...

Modifying a group of Components in Angular using a Service

Managing a set of Components involves changing their properties using a Service. The Components have a minimal model and are meant to remain compact. They are being rendered with *ngFor. The Service possesses a large Object and should possess the abilit ...

Disabling the submission button in the absence of any input or data

I am currently experiencing an issue where the submit button is active and displays an error message when clicked without any data. I would like to rectify this situation by disabling the submit button until there is valid data input. < ...

Issue with intrinsic attributes detected in Typescript for the component

Hey, I'm encountering an issue that says, "The type '{ data: dataProp[]; }' cannot be assigned to type 'IntrinsicAttributes & dataProp'. A property 'data' does not exist on type 'IntrinsicAttributes & dataPro ...

jester: constantly jest navigator's mock & check against userAgent/vendor

Purpose: Need to iterate through different combinations of the userAgent Simulate navigator behavior Execute the test Observation: I simulated the navigator.userAgent, simulation works as planned, first test executes as expected Second simulation is per ...

Guide to incorporating eslint with Next.js in a project that already has an eslint configuration

I recently created a new next.js project within my existing Node.js project, which already has an eslint config set up. Here's how the folder structure looks now: ...

What is the best ECMAScript version to select for implementing the TypeScript compiler in an Electron application?

In my Electron 5.0.6 project, I currently have es3 as the default target in my tsconfig.json file. However, I received an error message indicating that I need to upgrade to at least es6 to utilize getter/setter functionality in TypeScript. I am now contem ...

What is the best way to dynamically import two css frameworks in React?

Currently, I am involved in a project that requires me to incorporate a button for toggling between Bootstrap and Foundation at the request of my client. After exploring several alternatives, I have determined that utilizing hooks to manage the state of e ...

What is the purpose of running tsc --build without any project references?

Out of curiosity, I decided to experiment by executing tsc --build on a project without any project references. Surprisingly, the command ran smoothly and completed without any issues. I expected there to be a warning or error since the build flag is typic ...