This code cannot be called as a function, Every individual in the union

My approach has been aligned with the current architecture, focusing on reducing complexity as much as possible.

I have strived for the best possible outcome, but encountered a single failed test along the way.

After three days of struggling, I'm close to giving up. However, if your expertise can provide a solution that guarantees the success of all tests, it would be truly remarkable!

Could getComponentFromEntity be the key? Perhaps some kind of magical pattern is needed to solve this issue?!

Thank you for taking the time to help out.

https://i.sstatic.net/hl25Q.png β—½playground link

//utils types
type Constructor<T> = { new(...args: any): T };
type ExtractComponentType<T> = T extends Entity<infer C> ? C : never;
type EntitiesCareMap<C extends Component> = Map<number, Entity<C>>
type ComponentType<T extends Component = Component> = Constructor<T>;
type TupleToInstances3<T extends readonly unknown[]> = {
    [K in keyof T]: T[K] extends Constructor<infer U> ? U extends {} ? U : never : never;
}

type ExtractSystemComponents4<
    S extends Rules,
    K extends RULES
> = S[K] extends ComponentType[] ? S[K] extends never[] ? UnknowComponent : TupleToInstances3<S[K]>[number] : never;

interface SystemUpdate<S extends System = System, R extends Rules = S['rules']> {
    entities: EntitiesCareMap<
        ExtractSystemComponents4<R, RULES.hasAll>
    >;

}
// issue

class Entity<
    C extends Component = Component,
> {
    declare public components: Set<C>;

    get<T extends C>(componentClass: Constructor<T>): T {
        return undefined as unknown as T;
    }
        has<T extends Component>( componentClass: Constructor<T> ): this is Entity<T> {
        return false;
    }

}
abstract class Component {
    foo() { }
}

enum RULES {
    hasAll,
}
type Rules = { readonly [K in RULES]?: ComponentType[] };
abstract class System {
    abstract rules: Rules;
    abstract onUpdate(t: SystemUpdate<System, Rules>): void;
}

export class UnknowComponent extends Component {
    #component!: never;
}

export class AComponent extends Component {
    #component!: never;
}
export class BComponent extends Component {
    #component!: never;
}
export class CComponent extends Component {
    #component!: never;
}
export class DComponent extends Component {
    #component!: never;
}

class SystemA extends System {
    public rules = {
        [RULES.hasAll]: [AComponent, BComponent],
    };

    onUpdate({entities}: SystemUpdate<SystemA>) {
        entities.forEach(( e ) => {
            e.get(BComponent)// 🟒 this should pass.
            e.get(AComponent)// 🟒 this should pass.
            e.get(CComponent)// πŸ”΄ this should error
            if (e.has(CComponent)) {
                e.get(CComponent)// 🟒 this should pass.
                e.get(DComponent)// πŸ”΄ this should error
                if (e.has(DComponent)) {
                    e.get(DComponent)// 🟒 this should pass.
                }
            }
        });
    }
}



declare const ab: Entity<BComponent> | Entity<BComponent | CComponent>;

/** Get a components from entity */
function getComponentFromEntity<E extends Entity, C extends ExtractComponentType<E>>(entity: E, component: Constructor<C>): C {
    return entity.get(component);
}

getComponentFromEntity(ab, BComponent) // 🟒 this should pass.
getComponentFromEntity(ab, AComponent) // πŸ”΄ this should error.
getComponentFromEntity(ab, CComponent) // πŸ”΄ this should error.
//^?

declare const a: Entity<BComponent | CComponent>;
a.get(BComponent)// 🟒 this should pass.
a.get(AComponent)// πŸ”΄ this should error

Answer β„–1

To achieve the desired outcome, you should use the ExtractComponentType<T> function to convert unions present in type T into intersections in the output type. For example,

ExtractComponentType<A | B>
will result in
ExtractComponentType<A> & ExtractComponentType<B>
. This means that the operation needs to be distributed over unions in T, but in a contravariant manner (refer to Difference between Variance, Covariance, Contravariance and Bivariance in TypeScript for more details on variance).

When you call getComponentFromEntity(e, c), if c is of type Entity<A | B>, it can represent either A or B</code. However, if <code>c is of type

Entity<A> | Entity<B>
, it must accept both A and B simultaneously for safety reasons.

Let's proceed with the implementation:


This approach can be taken:

type ExtractComponentType<T> =
    (T extends Entity<infer C> ? ((x: C) => void) : never) extends
    (x: infer I) => void ? I : never;

type X = ExtractComponentType<Entity<BComponent | CComponent>>;
// type X = BComponent | CComponent
type Y = ExtractComponentType<Entity<BComponent> | Entity<CComponent>>;
// type Y = BComponent & CComponent

The functionality works as intended by using a contravariance technique with conditional types, similar to what was explained in Transform union type to intersection type. By exploiting the contravariance of function types in parameter inference, we move the type inside a function parameter position before inferring from it.


We have made significant progress. Here are some final adjustments:

function getComponentFromEntity<
    E extends Entity,
    C extends Component & ExtractComponentType<E>
>(entity: E, component: Constructor<C>): C {
    return entity.get(component);
}

In this step, specifying that C will always be some kind of Component helped avoid errors during implementation. Due to TypeScript's limitations in higher-order decision-making regarding generic conditional types, even though ExtractComponentType<E> should align with Component structurally, the compiler may not recognize it. Therefore, adding Component & resolved this issue.


Testing phase commences:

declare const ab: Entity<BComponent> | Entity<BComponent | CComponent>;
getComponentFromEntity(ab, BComponent) // 🟒 success
getComponentFromEntity(ab, AComponent) // πŸ”΄ error!
getComponentFromEntity(ab, CComponent) // πŸ”΄ error!

declare const a: Entity<BComponent | CComponent>;
a.get(BComponent)// 🟒 success
a.get(AComponent)// πŸ”΄ error!

As evidenced by the outcomes, the behavior matches your expectations!

Try out the code in the 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

Minimize the count of switch cases that are not empty

How can I optimize the number of cases in my switch statement to align with SonarQube recommendations? Currently, I have 37 cases in a switch statement, but SonarQube recommends only 30. I believe that my code is functioning correctly, and the issue lies ...

Issues arising from an aging Angular project

I'm currently facing an issue with installing node and typescript for an older angular project that I have inherited. This project is using angular 2.4.10 After downloading and installing node version 6.9.5 from the executable file, I proceeded to in ...

Working with Angular to add various items to an array based on multiple conditions

Currently, I am a beginner in TypeScript and currently involved in an Angular project. As part of my work, I need to make an API call and perform various operations on the received data: public data_Config: IConfig[] = []; this.getService.Data(input).sub ...

Wildcard routes taking precedence over other defined routes

Currently, I'm developing a Node.js server utilizing Express.js and Typescript. Within my project structure, there is a folder named "routes" where I store .ts files containing route definitions. An example of a route file might appear like this: impo ...

Exploring the concept of object inheritance in Angular 5 with Typescript

I'm facing a challenge related to inheritance while building my initial angular 5 application. The error message I encounter is: Property 'message' does not exist on type 'CouponEvent', as reported by the angular-cli. export class ...

Leveraging cloud functions on Firebase for maximum efficiency

Question: Do you require a backend language when using Firebase Cloud Functions, or can TypeScript alone suffice for coding tasks like creating a matchmaking system? Response: There seems to be some uncertainty on the matter even from ChatGPT himself. Is ...

Issue encountered while using Typescript with mocha: Unable to utilize import statement outside a module

Exploring the world of unit testing with mocha and trying to create a basic test. Project Structure node_modules package.json package-lock.json testA.ts testA.spec.ts tsconfig.json tsconfig.json { "compilerOptions": { "target&qu ...

What is the equivalent of Typescript's Uint8Array and Uint16Array in Python?

new Uint8Array(new Uint16Array([64]).buffer) How can I achieve a similar data structure in pure Python? What is the equivalent of Uint8Array/Uint16Array? I am extracting a buffer from a Uint16Array type here and converting it to a Uint8Array, but I am un ...

What is the best way to utilize the next-env.d.ts file within Next.js?

In my Next.js TypeScript project, I came across a file named next-env.d.ts. This got me thinking about how I can declare enums that would be accessible across all my Next.js files. Can you guide me on how to achieve this and use the enums throughout my p ...

Obtain the ViewContainerRef object from the Component

Looking to create nested components in Angular 4? This is the Chooser Component import {InputComponent} from './input/input.component' import {BlockComponent} from './block/block.component' export const FormChooser = { Block: Block ...

Understanding how to infer the type of a function when it is passed as an argument

Looking at the images below, I am facing an issue with my function that accepts options in the form of an object where one of the arguments is a transform function. The problem is that while the type of the response argument is correctly inferred for the e ...

Creating an enum from an associative array for restructuring conditions

Hey everyone, I have a situation where my current condition is working fine, but now I need to convert it into an enum. Unfortunately, the enum doesn't seem to work with my existing condition as mentioned by the team lead. Currently, my condition loo ...

Tips on customizing the selected icon color in Material-UI's BottomNavigationAction styling

I'm facing an issue with Material-UI styled components and could use some assistance. My goal is to create a BottomNavigation bar in React using Material-UI v5 where the selected option's icon displays in red (#f00) while the unselected icons sho ...

Construct a string by combining the elements of a multi-dimensional array of children, organized into grouped

My task involves manipulating a complex, deeply nested array of nodes to create a specific query string structure. The desired format for the query string is as follows: (FULL_NAME="x" AND NOT(AGE="30" OR AGE="40" AND (ADDRESS ...

The issue with functions not executing when triggered by HammerJS

In my application, there is a component that displays information for different days as they are cycled through using the functions dayUp() and dayDown(). Here is an example of how these functions are structured: dayUp() { if (this.dayCount == 7) { ...

What are some methods for retrieving RTK Query data beyond the confines of a component?

In my React Typescript app using RTK Query, I am working on implementing custom selectors. However, I need to fetch data from another endpoint to achieve this: store.dispatch(userApiSlice.endpoints.check.initiate(undefined)) const data = userApiSlice.endpo ...

What could be causing my data to undergo alterations when transitioning from a frontend form submission to a backend API?

In my experience with Next.js 13 and Prisma, I encountered a peculiar issue. I had set up a basic form to collect user information for an api request. Oddly enough, when I printed the data right before sending it, everything seemed fine. However, upon arri ...

What is the best way to populate empty dates within an array of objects using TypeScript or JavaScript?

I am trying to populate this object with dates from today until the next 7 days. Below is my initial object: let obj = { "sessions": [{ "date": "15-05-2021" }, { "date": "16-05-2021" }, { "date": "18-05-2021" }] } The desired ...

Eliminate any unnecessary padding from elements in angular2 components

Is there a way to remove the automatic padding added to new components in angular2? I am facing this issue with the header of my project, as shown in the image below: https://i.sstatic.net/25Zpn.png I attempted to eliminate the padding by setting it to 0 ...

Ensuring the validation of JSON schemas with dynamically generated keys using Typescript

I have a directory called 'schemas' that holds various JSON files containing different schemas. For instance, /schemas/banana-schema.json { "$schema": "http://json-schema.org/draft-06/schema", "type": "object", "properties": { "banan ...