A friend of mine has recently intrigued me with a question about TypeScript.
He asked:
Why TypeScript is not consistent in the following situation?
Imagine you have a type
Foo
and typeBar
which extendsFoo
.Why, when passing instance of
Bar
into a function acceptingFoo
, TypeScript does not complain, but when we try to declare a variable of typeFoo
but with extra keys specified, it does complain.
An example:
interface Foo {
x: number;
}
interface Bar extends Foo {
y: string;
}
function acceptingFoo(arg1: Foo): void {}
const foo: Foo = {
x: 1234,
};
const bar: Bar = {
x: 1234,
y: "value",
};
// Why can you pass an instance of `Bar` into a function accepting `Foo`
// and TypeScript won't complain?
acceptingFoo(bar);
// ^ No error here.
// But you cannot declare an object of type Foo with extra keys.
const barWithFooType: Foo = {
x: 1234,
y: "value",
//^ TypeScript complains about line above:
// Object literal may only specify known properties, and 'y' does not exist in type 'Foo'.(2322)
};
After some research I found this answer on Stack Overflow.
In TypeScript, when you declare an object literal, it is initially considered “fresh”.
No matter if you construct it when calling a function or when declaring a variable using it, object literal freshness is always enforced by compiler.
To check that I’ve run TS compiler over the following code.
acceptingFoo({
x: 1234,
y: "value",
//^ TypeScript has thrown an error here:
// Object literal may only specify known properties, and 'y' does not exist in type 'Foo'.(2322)
});
And it worked as I suspected - TS complained about passing an object literal with not known property. Bingo!
It turned out the issue here was not about inconsistency between typing in declaration vs typing of a function arguments, but about object literals freshness.
The rationale behind this behaviour is that it prevents possible typing errors when declaring object literals.
For example, check the following code:
interface Options {
color?: string;
}
function acceptingOptions(arg1: Options): void {}
acceptingOptions({ colour: "black" });
// ^ Typo here, should be `color`.
// TypeScript will throw an error.
Without freshness, TypeScript would not spot this bug.
If you want to read more about freshness of object literals, check a PR that adds this feature to TypeScript compiler. You can also read a message about freshness authored by one of TS contributors.
Thanks for reading!