Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Typescript mind the order of instantiation in an Object literal?

I'm using the latest Typescript version :2.6.2.

I'm encountering a bizarre situation where if I do foo({a:1,b:2}) - things don't work whereas if I do : foo({b:2,a:1}) - they do work.

I have a generic class , an interface which has 2 properties and a function.

Here is the code :

class MyClass<T>  {
    value: T;
    next(value: T): void {
  }
}


export enum StateKey { backlogItems='backlogItems'}


export interface State {
    backlogItems : number[];
    [key: string]: any
}

class A {

    private subj = new MyClass<State>();

    public set<T>(name: StateKey, state: T) {
        this.subj.next({  backlogItems: [...this.subj.value.backlogItems] ,
                         [name]:state      //<--- error here
                       })        
    }    
}

I get an error :

 Argument of type '{ [name]: T; }' is not assignable to parameter of
 type 'State'.   Types of property 'backlogItems' are incompatible.
     Type 'T' is not assignable to type 'number[]'.

But If I change the order of the literals in the object :

from :

this.subj.next({ backlogItems: [...this.subj.value.backlogItems], [name]: state })

to :

 this.subj.next({ [name]:state, backlogItems: [...this.subj.value.backlogItems] })
  • Then there's no error

Question

Why does changing the order makes it compile ?

Image proof

Typescript play ground demo

like image 667
Royi Namir Avatar asked Dec 30 '17 13:12

Royi Namir


1 Answers

It compiles without errors if you change the string in StateKey enum to some other value, like this

export enum StateKey { backlogItems='somethingElse'}

(the error also goes away if you add more values to the enum)

The compiler is doing type inference and detects that [name] in your argument to next() function can be only backlogItems, which is declared as number[].

So both variants of the object literal are assigning two different values to the same property, that's why order of assignment matters - the last one wins, basically. Compiler reports the error in case when the last assigned value is incompatible with declared property type - T is not compatible with number[].

UPDATE

Also, to preserve the code and just get rid of the error, you can make the compiler forget about the type it inferred for name by assigning it to the intermediate variable with string type:

class MyClass<T>  {
    value: T;
    next(value: T): void {
  }
}


export enum StateKey { backlogItems='backlogItems'}


export interface State {
    backlogItems : number[];
    [key: string]: any
}

class A {

    private subj = new MyClass<State>();

    public set<T>(name: StateKey, state: T) {
        const key: string = name;
        this.subj.next({  backlogItems: [...this.subj.value.backlogItems] ,
                         [key]:state     
                       })        
    }    
}
like image 90
artem Avatar answered Nov 14 '22 23:11

artem