Why is an arbitrary value considered compatible with a class instance for an empty class?

I’m not sure why the following snippet of code works…

class GroupLeader { /* snip */ };
function foo(leader: GroupLeader): void { /* snip: do stuff */ }

const isLeader = false;
const groupLeader = isLeader && new GroupLeader();

foo(groupLeader);

In the REPL, I can see groupLeader ends up being a boolean type, but there is no error generated by TypeScript compiler (version 4.4.3) when invoking foo(groupLeader).

Why does this work?

Playground

3

This is the expected behavior in TypeScript.

TypeScript’s type system is largely structural as opposed to nominal. So it’s the shape or structure of a type that matters, and not its name or declaration. So the TypeScript compiler can decide that type A is a subtype of B (or “A is assignable to B” or “A extends B“) whether or not you mention A anywhere in the declaration of B. It only matters if the apparent properties of B have compatible properties in A:

interface A {
    x: string;
    y: number;
}

interface B {
    x: string;
}

const a: A = { x: "", y: 0 };
const b: B = a; // okay, A extends B

The last line is not an error like it would be in a nominally typed language.


Note that “apparent property” means that even some primitives like number, string, and boolean can be considered structurally compatible with object types, because JavaScript automatically wraps some primitives in wrapper objects when you index into them. So the type {length: number} is a subtype of string:

interface L { length: number };
const l: L = "hello"; // okay

because string values have an apparent length property of type number.


In TypeScript, class declarations are also treated structurally in general (although there are some cases where nominal-like typing happens, such as using instanceof to distinguish between two classes or when you add private or protected members). So if you have an empty class:

class GroupLeader { }

This class is structurally identical to the empty object type {}, an object type with no members. And so any value which can be indexed into like an object will be seen as assignable to that class:

function foo(leader: GroupLeader): void { /* snip: do stuff */ }

foo(true); // okay
foo(123); // okay
foo({}); // okay
foo(() => 3); // okay
foo(new Date()); // okay
foo(Symbol("oops")); // okay

foo(null); // error
foo(undefined); // error

Only null and undefined are not assignable to GroupLeader, because null and undefined throw runtime errors if you index into them like an object.


So that’s why it’s happening. Generally you want to prevent such behavior, so empty classes and empty object types are best avoided, even in example code. The (somewhat outdated) TypeScript FAQ has a bunch of entries around this, like Why are all types assignable to empty interfaces? and Why do these empty classes behave strangely?. If you want the compiler to treat two types as distinct, you should make sure they have incompatible shapes.

Adding any property that boolean doesn’t share to GroupLeader will change things:

class GroupLeader { unsnip = 0 };

function foo(leader: GroupLeader): void { /* snip: do stuff */ }
foo(true); // error
foo(123); // error
foo({}); // error
foo(() => 3); // error
foo(new Date()); // error
foo(Symbol("oops")); // error

foo(new GroupLeader()); // okay

Of course the possibility still exists that something structurally compatible will be passed in:

foo({ unsnip: 123 }); // okay

You can try to prevent this if you want (a private property will do it) but the path of least resistance is to just write code that only cares about structural compatibility. Whatever foo()‘s implementation is, it should only care about the structure of leader and not the declaration.

Playground link to code

0

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật