Home > Software engineering >  Why 1 extends {[Key: string]: never} is false
Why 1 extends {[Key: string]: never} is false

Time:04-09

I am very confusing about this, why 1 extends {[Key: string]: never} is false. For example:

type T = 1 extends {[Key: string]: never} ? true : false;

Why T returns false.

CodePudding user response:

A type of the form {[key: string]: XXX} has a string index signature. A value of such a type may have any number (including zero) of string-keyed members (members are properties, which include methods, and include inherited properties); and any string-keyed members that it does have must be of type XXX:

let dict: { [key: string]: string };
dict = { a: "hello", b: "goodbye" }; // okay
dict = { a: "hello", b: 123 }; // error!
// ----------------> ~ 
// Type 'number' is not assignable to type 'string'.
dict = {} // okay

The never type is TypeScript's bottom type; there are no values of type never. It is to types what the empty set is to sets. No matter what value you have, it will not be of type never. You should not be able to observe a value of type never.

Putting those together, a value of type {[key: string]: never} may have any number of string-keyed members (including zero), and any string-keyed members that it does have must be of type never. But since no value is of type never, it means that a value of type {[key: string]: never} must have exactly zero string-keyed members:

let neverDict: { [key: string]: never };
neverDict = { a: "hello", b: "goodbye" }; // error!
// ---------> ~  -------> ~
// Type 'string' is not assignable to type 'never'.

dict = {} // okay

Or at least any string-keyed members that are present must be expected to be unobservable:

function error(): never { throw new Error(); }
function special() {
  dict = { a: error() } // okay
}

Here we assign a value of type {a: never} to dict, which is fine, but note that if you ever tried to run the program to look at such a value it would be unobservable because an error will be thrown.

So, in order to be assignable to {[key: string]: never}, a type must have no string-keyed members.


On the other hand we have the type 1. This is a numeric literal type which is a subtype of number. Every value of type 1 is also a value of type number. Furthermore, number is a subtype of Number, the interface for the wrapper object that number values are given when you index into them like objects.

Here's the definition of the Number interface from the TypeScript standard library:

interface Number {
    toString(radix?: number): string;
    toFixed(fractionDigits?: number): string;
    toExponential(fractionDigits?: number): string;
    toPrecision(precision?: number): string;
    valueOf(): number;
}

Do note that all primitives in JavaScript except null and undefined have such wrapper objects, and so these primitives can be assigned to interfaces and are thus "object-like" in their treatment by TypeScript.

So, 1 is a subtype of number, which is a subtype of Number, which has some known members as shown above:

let one: 1 = 1;
let prim: number = 1;
let intf: Number = prim;

(1).toFixed // (method) Number.toFixed(fractionDigits?: number | undefined): string

And so now we can see that 1 should not be assignable to {[key: string]: never}. The former has several members with keys like "toFixed" and "toExponential", and these members have values of function types. Functions are actual values which are not assignable to never. So this is an error:

neverDict = one; // error!
// Type 'number' is not assignable to type '{ [key: string]: never; }'.

And therefore 1 extends {[key: string]: never} is false, and so the conditional type you wrote takes the false branch:

type T = 1 extends { [key: string]: never } ? true : false;
// type T = false

Playground link to code

  • Related