TypeScript icon indicating copy to clipboard operation
TypeScript copied to clipboard

Incorrect type inference when get/set accessors use different types

Open otomad opened this issue 3 years ago • 5 comments

The properties of an old library allows setting many types, but only getting one type. This is represented by Date.

interface Foo {
    get bar(): Date;
    set bar(value: Date | number);
}

When this object is get or set individually, it will work properly.

const foo = {} as Foo;
const bar = foo.bar; // typeof bar is Date, ok!
foo.bar = 123; // can be set by number, ok!

However, when I set this object literally, it will not work properly.

const foo: Foo = { bar: 123 }; // The type "number" cannot be assigned to the type "Date"
                   ^^^

Some properties are allowed to be set in many types, but can only be get in one type, which is more convenient.

ref #2521

otomad avatar Oct 19 '22 14:10 otomad

I believe this is working as intended. Object literals don't use the setter method, so whatever logic may cause the value to transform is not running.

When you try to get this property the type will still wrongly be number.

The assignment works using at type assertion (as Foo), because that way you instruct the compiler to ignore most type errors, for example the missing property (which is then undefined, not Date).

MartinJohns avatar Oct 19 '22 14:10 MartinJohns

Also, even your first example (that you claim works) is wrong:

const foo = {} as Foo;
const bar = foo.bar; // typeof bar is Date
foo.bar = 123; // can be set by number
console.log(foo.bar);  // prints a number, not a date!

The only correct way to implement the interface Foo is with an actual getter/setter pair.

fatcerberus avatar Oct 19 '22 15:10 fatcerberus

I'd also like to point out an unsoundness regarding such getter/setter: Even when you assign a valid object literal, you may end up with unsound behavior when assigning a value to the property.

interface Foo {
    get bar(): Date;
    set bar(value: Date | number);
}

const f: Foo = { bar: new Date() }
f.bar = 1;
console.log(typeof f.bar) // number

MartinJohns avatar Oct 19 '22 15:10 MartinJohns

prints a number, not a date!

I just use interface Foo as an example. I'm just writing *.d.ts for a JavaScript library and some properties works like this.

otomad avatar Oct 19 '22 16:10 otomad

and some properties works like this.

It works if the property is implemented accordingly (getter/setter). Your object literal doesn't do this.

MartinJohns avatar Oct 19 '22 16:10 MartinJohns

I understand. That library is using a function that copies attributes of the literal object to another object through Object.assign (but that platform is lower, in fact, by copying them one by one with for in). So it actually just need to get all the setter types of the interface. However, it seems cannot get the type of setter.

otomad avatar Oct 20 '22 12:10 otomad

@otomad See #43729.

fatcerberus avatar Oct 20 '22 15:10 fatcerberus