mleko
mleko

Reputation: 12233

Empty interface allow any object?

Why empty interface doesn't require object to be empty?

interface A {};
const a: A = {a: 1};
console.log(a);

is valid code and will output { a: 1 }.

I would assume that adding optional property should work fine, but

interface A {};
interface B extends A {
    b?: any;
}
const a: B = {a: 1};
console.log(a);

ends with error Type '{ a: number; }' is not assignable to type 'B'.

Non empty interface defines both what object can and must have. Empty interface behaves like any.

Is there explanation why empty interface behaves like this? Is this intentional or just a bug?

Upvotes: 9

Views: 23014

Answers (4)

Alex
Alex

Reputation: 14493

This behavior is intentional.

The excess property check is not performed when the target is an empty object type since it is rarely the intent to only allow empty objects.


Actually, you can assign {a: 1} to B, the other answers here are mostly wrong.

You have stumbled upon another slightly confusing quirk in TypeScript, namely that you can not directly assign an object literal to a type where the object literal contains other properties than the one specified in the type.

However, you can assign any existing instance of an object to a type, as long as it fulfill the type.

For example:

interface Animal {
    LegCount: number;
}

let dog: Animal = { LegCount: 4, Fur: "Brown" }; // Nope

var myCat = { LegCount: 4, Fur: "Black" };
let theCat: Animal = myCat; // OK

This constraint is simply ignored whey you have a type that is empty.

Read more here and here.

A later answer from the Typescript team is available on GitHub.

Upvotes: 10

Michael R
Michael R

Reputation: 1607

Yes, there is an explanation in the official docs. Nope not a bug, but surprising behavior. This article microsoft/TypeScript - Why are all types assignable to empty interfaces? explains the behavior.

That documentation also adds as an aside: "In general, you should never find yourself declaring an interface with no properties."

Upvotes: 2

Bruno Grieder
Bruno Grieder

Reputation: 29814

This is the way structural typing works in Typescript. It is based on structural subtyping.

In short

An instance of B is compatible with A if B implements all the members required by A.

Since A does not require any member, all objects are compatible with A.

Full details in this documentation

Upvotes: 2

Smit
Smit

Reputation: 2138

Okay, interesting question.

Test Case 1:

interface A {};
interface B extends A {
    b?: any;
}
const a: A = {a: 1};
console.log(a);

It passes, as A is an empty interface, whatever you dumb inside is going to return back. Works like a class Object

Test Case 2:

interface A {};
interface B extends A {
    b?: any;
}
const a: A = {b: 1};
console.log(a);

Just changed the value a to b just to prove first test case. It passes.

Test Case 3:

interface A {};
interface B extends A {
    b?: any;
}
const a: B = {a: 1};
console.log(a);

It fails, because there is no prop inside interface A or B that is named a

Test Case 4:

interface A {};
interface B extends A {
    b?: any;
}
const a: B = {b: 1};
console.log(a);

It passes, as interface B has a prop named b

I hope this helps you understand.

PS: prop refers to property.

Take this as an analogy: Interface A is a empty house, dumb whatever you want. It wont utter a word. Interface B is a house in a colony, which means it needs behave specifically to size, shape and needs to stick to Interface A. Which means Interface B is not more a empty and is restricted with rules.

Upvotes: 3

Related Questions