Differentiating TypeScript classes is important in situations where they would normally be treated as equivalent

Looking to add a lightweight layer on top of Float32Array to create vec2 and vec3's. Check out the example below:

class vec3 extends Float32Array {
//    w : number;
    constructor() {
        super(3);
//        this.w = 1;
    }
    static add3(x: vec3) : vec3 {
        x[2] += 1;
        return x;
    }
};

class vec2 extends Float32Array {
    constructor() {
        super(2);
    }
    static add2(x: vec2) : vec2 {
        x[1] += 1;
        return x;
    }
};

var test1: vec3 = new vec3();
vec3.add3(test1);
console.log(test1);

var test2: vec2 = new vec2();
vec3.add3(test2);                       // Expect an error here
console.log(test2);

(Run code in TS playground here)

Typescript currently sees these classes as equivalent, hence allowing the vec3.add3(test2);

If I uncomment the line defining the w member variable, it correctly identifies them as distinct classes.

Any suggestions on how to distinguish between these two classes?

Answer №1

When it comes to Typescript, the default behavior involves structural typing. This means that if a class B is defined with public fields a and b, along with a method named bar, then any object containing fields a and b as well as a method called bar (with corresponding types) will be recognized as an instance of class B.

There is a notable exception in this framework. If a class contains a private field such as c, only instances of that specific class will be considered valid. Any other object with a field named

c</code would not match.</p>
<pre><code>class Point3D extends Float32Array {
  private __id: unknown = {};
  ...
}

class Point2D extends Float32Array {
  private __id: unknown = {};
  ...
}

Even though the structures of these two classes are identical and both have a field named __id, they are incompatible due to the private nature of the fields.

Answer №2

It seems achievable by deliberately inserting an error in the code that alters the class signature without impacting the structure size:

class vec3 extends Float32Array {
// @ts-ignore: unused member to keep the type unique
    __vec3_type : number;
    constructor() {
        super(3);
    }
    static add3(x: vec3) : vec3 {
        x[2] += 1;
        return x;
    }
};

class vec2 extends Float32Array {
// @ts-ignore: unused member to keep the type unique
    __vec2_type : number;
    constructor() {
        super(2);
    }
    static add2(x: vec2) : vec2 {
        x[1] += 1;
        return x;
    }
};

var test1: vec3 = new vec3();
vec2.add2(test1);
console.log(test1);

var test2: vec2 = new vec2();
vec3.add3(test2);                       // This should be an error
console.log(test2);

This method achieves the desired outcome without altering the object's size.

It appears to stem from TypeScript's preference for structural typing over nominal typing: https://www.typescriptlang.org/docs/handbook/type-compatibility.html

Answer №3

To create a generic class without any branding or double underscores, you can define a class that includes the number of dimensions as a property:

class Float32ArrayN<T extends number> extends Float32Array {
    constructor(public dimensions: T) {
        super(dimensions);
    }
}
const a = new Float32ArrayN(4)
a.dimensions // type: 4

const b: Float32ArrayN<3> = new Float32ArrayN(4) // error

Each class is now structurally unique because the dimensions property uses different literal number types. This allows you to extend and create subclasses like this:

class vec3 extends Float32ArrayN<3> {
    constructor() {
        super(3);
    }
    static add3(x: vec3) : vec3 {
        x[2] += 1;
        return x;
    }
};

class vec2 extends Float32ArrayN<2> {
    constructor() {
        super(2);
    }
    static add2(x: vec2) : vec2 {
        x[1] += 1;
        return x;
    }
};

This setup will throw errors if used incorrectly:

var test1: vec3 = new vec3();
vec3.add3(test1);
console.log(test1);

var test2: vec2 = new vec2();
vec3.add3(test2);             // error
console.log(test2);

var test3: vec3 = new vec2()  // error

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

Hide Angular Material menu when interacting with custom backdrop

One issue I am facing is with the menu on my website that creates a backdrop covering the entire site. While the menu can be closed by clicking anywhere outside of it, this functionality works well. The problem arises when users access the site on mobile ...

Switching the checkbox state by clicking a button in a React component

Is there a way to update checkbox values not just by clicking on the checkbox itself, but also when clicking on the entire button that contains both the input and span elements? const options = ["Option A", "Option B", "Option C"]; const [check ...

Describing the Significance of a Component Value within the Document Object

Every time I call a specific component in the DOM using its selector, I want to customize it by altering certain variables. This component will serve as a generic "Input Field" that I can define with unique values each time I use it. I specifically want t ...

What is the process of creating the /dist folder in an unreleased version of an npm package?

Currently working on implementing a pull request for this module: https://github.com/echoulen/react-pull-to-refresh ... It seems that the published module generates the /dist folder in the package.json prepublish npm script. I have my local version of the ...

Is it considered best practice to include try/catch blocks within the subscribe function in RxJs?

Imagine we have an Angular 2 application. There is a service method that returns data using post(), with a catch() statement to handle any errors. In the component, we are subscribing to the Observable's data: .subscribe( ()=> { ...

Steps to Enable a Tooltip for FontAwesomeIcon in icon prop when the Tab is Disabled in Material UI and React

While trying to override the disabled attribute in a child component, I encountered an issue while using a FontAwesomeIcon as my Tab icon. I am following the MUI documentation on this topic here, but still unable to display a tooltip on hover. Here is a s ...

Is it possible to use Eclipse for debugging AngularJS and TypeScript code?

I recently dove into the world of TypEcs and am currently working on developing a webpage using Typescript and AngularJS that I'd like to debug in Eclipse. Is it feasible to debug a TypeScript and Angular page in Eclipse? If so, could you provide m ...

Ionic 3: Struggling to Access Promise Value Outside Function

I've encountered an issue where I am unable to retrieve the value of a promise outside of my function. It keeps returning undefined. Here's what I have attempted so far: Home.ts class Home{ dd:any; constructor(public dbHelpr:DbHelperProvider ...

Unable to successfully import { next } from the 'express' module using Typescript

Having some trouble with this line of code: import {response, request, next} from 'express' The typescript compiler in vscode is giving me the following error: Module '"express"' has no exported member 'next'. Up ...

Issue with Angular 12.1: Unable to retrieve value using "$event.target.value"

I am just starting to dive into the world of Angular and have been using YouTube tutorials as my guide. However, I have encountered an error in one of the examples provided. Below is the code snippet that is causing me trouble. HTML <input type=" ...

Ways to transfer selected options from a dropdown menu to a higher-level component

I am currently in the process of configuring a modal component that showcases various data from a specific record to the user. The user is provided with a Bulma dropdown component for each field, enabling them to make changes as needed. To streamline the c ...

Ways to conceal the side menu upon logging out in Ionic 2

I have successfully implemented login and logout functionality in my project. The issue I am facing is that after logging out, the side menu is still visible. How can I hide the side menu automatically after logout and redirect to the home page? https://i ...

Converting a tagged union to a union type in Typescript can help streamline your code and

I have a tagged union interface that looks like this: interface Example { a: TypeA; b: TypeB; } My goal is to convert this tagged union into an union type as shown below: type oneOf<T> = ... In this new union type, var example: oneOf(Example); ...

Creating a non-editable form or text field upon clicking the Submit button

<form [formGroup]="calculateForm"> <div class="form-group row"> <p for="inputFrom" class="col-sm-4">Distance traveled ...

React with TypeScript presents an unusual "Unexpected token parsing error"

Recently, I encountered an issue with my helper function that was originally written in JavaScript and was functioning perfectly. However, as soon as I introduced TypeScript into the mix, strange behaviors started to surface. Here is the snippet of code fr ...

Passing a generic type as a parameter in a generic class in TypeScript

TypeScript: I have a method in the DataProvider class called getTableData: public static getTableData<T extends DataObject>(type: { new(): T}): Array<T> { ... } Everything works fine when I use it like this: let speakers = DataProvider.getT ...

The attribute 'loaded' is not found in the 'HttpSentEvent' data type

Currently, I have implemented an Angular HTTP post request with the following parameters: this.http .post<{ body: any }>(environment.api.file.upload, reqBody, { reportProgress: true, observe: 'events', }) .subscribe((ev: HttpE ...

The parameter type '(req: Request, res: Response, next: NextFunction) => void' does not match the type of 'Application<Record<string, any>>'

I'm currently working on an Express project that utilizes TypeScript. I have set up controllers, routers, and implemented a method that encapsulates my controller logic within an error handler. While working in my router.ts file, I encountered an err ...

After encountering an error, the puppeteer promptly shuts down the page

During my page testing, an error is thrown by a dependency. Although the error is not critical and does not impact my application, when testing with Puppeteer and encountering this error, it abruptly closes the tested page. How can I bypass this error to c ...

Is the naming convention for parameterized types (T, U, V, W) in Generics adhered to by Typescript?

Is TypeScript following the same naming convention for parameterized types as other languages like C++ and Java, using T,U,V,W, or is there a mixed usage of conventions? In the TS 2.8 release notes, we see examples like: type ReturnType<T> = T exten ...