Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Detailed differences between type annotation `variable: type` and type assertion `expression as type` in TypeScript

Tags:

typescript

Even with basarat explanations on type assertions in his great book TypeScript Deep Dive:

Basically, the assertion from type S to T succeeds if either S is a subtype of T or T is a subtype of S.

I was wondering what is the exact difference between type annotation variable: type and type assertion expression as type, more precisely when type assertion is working or not, especially regarding that TypeScript errors are sometime surprising: see them for the foo3 and foo5 variables bellow:

interface Foo {
    n?: number;
    s: string;
}

const foo1 = {} as Foo;               // ok
const foo2 = { n: 1 } as Foo;         // ok
const foo3 = { n: '' } as Foo;        // KO: "Property 's' is missing..."
const foo4 = { n: '' as any } as Foo; // ok
const foo5 = { n: 1, x: 2 } as Foo;   // KO: "Property 's' is missing..."
const foo6 = { s: '', x: 2 } as Foo;  // ok
const foo7 = { s: 1, x: 2 } as Foo;   // KO: "Types of property 's' are incompatible."

Other difference I've noticed: renaming the n property in the Foo interface in VSCode does not propagate to expressions being asserted, while it's working with type annotated variables.

like image 498
Romain Deneau Avatar asked Dec 27 '17 15:12

Romain Deneau


1 Answers

I was wondering what is the exact difference between type annotation variable: type and type assertion expression as type

Type declaration variable: type tells the compiler that the variable must always conform to the declared type. It is used by typechecker when a value is assigned to the variable (the value must be compatible with the declared type), and also whenever the variable is used (the declared type of the variable must be compatible with whatever way the variable is used in each particular place).

Type assertion overrides built-in type compatibility rules. It allows you to tell the compiler that you know that the value actually conforms to the type you give in the assertion, thus suppressing error message about type incompatibility. There are limits, however - you can't just assert that variable has any type you want (BTW there is any type just for that). As you quoted in the question, for type assertion to work,

the assertion from type S to T succeeds if either S is a subtype of T or T is a subtype of S

It works exactly in that way in each example:

const foo3 = { n: '' } as Foo; // KO: "Property 's' is missing..."

Here two types: {n?: number, s: string} and {n: string} are checked for compatibility - if any of them can be converted to another. It can't be done either way: in one way, {n: string} is missing non-optional s and n has the wrong type (must be number | undefined); in another way, {n?: number, s: string} has wrong type for n (must be string).

The complete error message is

Type '{ n: string; }' cannot be converted to type 'Foo'.
  Property 's' is missing in type '{ n: string; }'.

When reporting structural type incompatibility, the compiler chooses just one incompatible property to show in the error message - it could be any of the three incompatibilities mentioned above.


const foo4 = { n: '' as any } as Foo; // ok

Works because {n?: number, s: string} is compatible with {n: any}: the first one can be assigned to the second - any is compatible with anything, and s is just ignored (basically, a value is compatible with the type if it has all non-optional properties compatible with declared type)


const foo5 = { n: 1, x: 2 } as Foo;   // KO: "Property 's' is missing..."

{n: number, x: number} is not assignable to {n?: number, s: string} -s is missing, as compiler says:

Type '{ n: number; x: number; }' cannot be converted to type 'Foo'.
   Property 's' is missing in type '{ n: number; x: number; }'.

const foo6 = { s: '', x: 2 } as Foo;  // ok

Works because {s: string, x: number} is assignable to {n?: number, s: string}: s is OK, missing n is OK because it's declared as optional, extra x is ignored


const foo7 = { s: 1, x: 2 } as Foo;   // KO: "Types of property 's' are incompatible."

Type of s is incompatible:

Type '{ s: number; x: number; }' cannot be converted to type 'Foo'.
  Types of property 's' are incompatible.
    Type 'number' is not comparable to type 'string'.
like image 177
artem Avatar answered Oct 22 '22 07:10

artem