Skip to content

Generics inference of type (static) intersection on instance shape #7934

Description

I'v build a nice library for composition of types (mixin) in a dynamic way so the bookkeeping overhead of having to write stand-in members is not needed. (the lib also has some other features and customisation for composition).

It works great but I have one last bookkeeping overhead that bothers me, The lib requires to explicitly set the Type parameters since I can't get it to work when I let typescript infer the types.

Here's an example of what i'm facing:
TypeScript Version:
1.8.9

Code
A and B are "example" classes, we will use them for demonstration.
Each have one static member and one instance member, we will use them verify the output when mixin them together.

classA{staticSTATIC_A: string='A';instanceA: string='A';constructor(){}}classB{staticSTATIC_B: string='B';instanceB: string='B';}

A "naive" approach using a simple function:

functionmergeTypes<T,Z>(type1: T,type2: Z): T&Z{// Do some work to mixin the types.return<any>{};}letAB=mergeTypes(A,B);// Inferred AB: typeof A & typeof Bletab=newAB();// Inferred ab: AAB.STATIC_A;// Compiler OK AB.STATIC_B;// Compiler OKab.instanceA;// Compiler OKab.instanceB;// Compiler ERR: Property 'instanceB' does not exist on type 'A'

Conclusion: Intersection of static types will not propagate to the instance shape.

A more explicit approach with a control unit and state.

// Define a contract to bind an instance type param (T/Z) to its static "parent" (TType/ZType)interfaceConcreteTypeOf<T>extendsFunction{new(...args): T;}// Explicitly say what we are dealing with:classMergeTypes<T,TTypeextendsConcreteTypeOf<T>,Z,ZTypeextendsConcreteTypeOf<Z>>{constructor(privatetype1: TType,privatetype2: ZType){}// We return a constructor for T & Z and intersect static members.merge(): ConcreteTypeOf<T&Z>&TType&ZType{// Do some work to mixin the types.return<any>{};}}

An implicit attempt first, let the compiler infer for us

letAB=newMergeTypes(A,B).merge();// Inferred AB: ConcreteTypeOf<{}> & typeof A & typeof Bletab=newAB();// Inferred ab: {}AB.STATIC_A;// Compiler OK AB.STATIC_B;// Compiler OKab.instanceA;// Compiler ERR: Property 'instanceA' does not exist on type '{}'ab.instanceB;// Compiler ERR: Property 'instanceB' does not exist on type '{}'

It turns out that the compiler infers ConcreteTypeOf<T & Z> to be ConcreteTypeOf<{}>, WHY?

Finally, a working version, but we need to explicitly express what we want:

letAB=newMergeTypes<A,typeofA,B,typeofB>(A,B).merge();// Inferred AB: ConcreteTypeOf<A & B> & typeof A & typeof Bletab=newAB();// Inferred ab: A & BAB.STATIC_A;// Compiler OK AB.STATIC_B;// Compiler OKab.instanceA;// Compiler OK ab.instanceB;// Compiler OK 

If i'll be able to remove the need to explicitly express the type I will be able to have an easy API for composition/mixin what ever, that can create type's on the fly and save a reference for them (compile time type reference) as if they were defined expressively.

Metadata

Metadata

Assignees

No one assigned

    Labels

    DuplicateAn existing issue was already created

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions