Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create compiler error on different type check (tsconfig)

Tags:

typescript

Can someone explain why this is not throwing an error?

interface A {
    id?: number;
}
const a:A = {
    id: 5
};
const testA: boolean = (a === 1); //works, but why?


interface B {
    id: number;
}
const b:B = {
    id: 5
};
const testB: boolean = (b === 1); //compiler error -> expected that also on A

Playground Link

like image 339
Seryoga Avatar asked Feb 04 '20 09:02

Seryoga


1 Answers

Let's examine this behavior:

let foo: { foo?: number } = { foo: 1 };
foo = 1; // error, no common properties
foo === 1; // okay

In TypeScript 2.4 and above, the assignment fails because the type of foo is a weak type (all properties are optional, like Partial<Something>) with no properties in common with number. By the normal rules of structural compatibility (before TS2.4), this assignment should succeed, because as long as a number doesn't have any property named foo, there's no incompatibility... at runtime (1).foo is undefined, and undefined is one of the things you expect when reading foo from {foo?: number}. But this was a potential source of bugs, so TS2.4 introduced weak type detection to catch them; that is, the language made weak type incompatibility prevent assignability.

The comparison succeeds, however, because comparability is not prevented by weak type incompatibility. The two types are still considered comparable. Why? Well, I'm not sure what the motivation is, but I can see that it's at least intentional. There was, at one point, a push to update the moldering TypeScript spec. I think they have given up on such an update (the language is changing too quickly and they don't have enough resources to constantly work on the spec, the upshot of this is that in order to understand the current language you need to keep reading release notes, GitHub issues, and even source code), but I see that one of the non-merged pull requests, microsoft/TypeScript#17215, provided details about comparability. Specifically:

whether a type is considered weak has no bearing on whether two types are comparable

So there you go. Comparability ignores weakness of the types, so you are allowed to compare {foo?: number} with number. It's intentional, although again, not sure why.

I guess you (or someone else) filed microsoft/TypeScript#36602 to ask about this. The important question to ask here if we want to imagine changing this is: what real-world TypeScript code would break if we tightened this. If it's a lot, and most of those breakages are not catching legitimate bugs, then there's a very low chance this will change. Even if it only improves things in terms of bug-finding, it might degrade compiler performance too much to be worth it. The only way to be sure is for the TS team to agree to consider a pull request for this change. And I'm not on that team so 🤷‍♂️.


Finally, let's just contrast the previous code with slight changes to make sure we understand the difference between non-overlapping weak types, non-overlapping non-weak types, and overlapping weak types:

Here's a non-overlapping non-weak type:

let bar: { bar: number } = { bar: 1 };
bar = 1; // error, not assignable
bar === 1; // error, not comparable 

Both assignment and comparison fail; the types are not structurally compatible. And here's an overlapping weak type:

let baz: { toFixed?(): string } = { toFixed: () => "" };
baz = 1; // okay
baz === 1; // okay

Both assignment and comparison succeed; since number has a toFixed() method that returns a string, (well, Number has it, and number is coerced to Number when you access its members) the type {toFixed?(): string} is a weak type that overlaps number.


Okay, hope that helps makes sense of the behavior. Good luck!

Playground link to code

like image 151
jcalz Avatar answered Oct 02 '22 10:10

jcalz