Why can't "why" be assigned to all types universally?

According to the TypeScript documentation, the never type is considered a subtype of every other type and can be assigned to any type.

The reason behind this behavior is not explicitly mentioned in the documentation.

One would naturally expect the following code to fail:

const useString = (str: string) => console.log('This is definitely a string:', str)
const useNever = (not_a_string: never) => useString(not_a_string)

Surprisingly, there are no errors because a value of type never is treated as a valid string.

Could this be intentional? And if it is, what could be the rationale behind it? :)

Answer №1

In TypeScript, the never type corresponds to a concept in type theory known as the bottom type, symbolized by "⊥". This type has no values assigned to it, making it unique since it represents an empty set (). The idea is that you should never encounter a value of this type due to its lack of any values.

Understanding concepts like subtyping and assignability can help grasp how types relate to each other in TypeScript. Subtyping describes when one type contains all the values of another type, denoted by symbols such as <: or ⊆. Assignability rules are based on the principle of substitutability, allowing values from a subtype to be assigned to variables of a supertype.

The logical 'principle of explosion' states that starting with a false statement can lead to proving anything true. This principle aligns with formal logic and influences type theory in TypeScript, offering valuable insights despite its unconventional nature.

Examining the relationship between the never type and other types reveals interesting implications. Due to the nature of the empty set being a subset of every set, never is always a subtype of any other type. This means that a value of type never can be assigned to variables of any type, but not vice versa.

While delving further into topics like the top type and its counterpart unknown in TypeScript could provide additional insights, we'll conclude here to prevent turning this answer into a comprehensive textbook on type theory.

Answer №2

The useNever function is not being called in this scenario. If an attempt is made to call it with a parameter, the operation will fail because a value cannot be of type never. However, you can bypass this restriction using typeguards to deceive the compiler. For example:

const test = (val: string | number) => {
     if (typeof val === "string") {

     } else if (typeof val === "number") {

     } else {
         useNever(val); // This will work as intended since val is implicitly categorized as never
     }
 }

Answer №3

After gaining new insights, I have chosen to update this response.

In TypeScript, the never type serves as the bottom type, encompassed within all other types, contrasting with the top type unknown which encompasses all types.

Think of unknown as a collection of all types and never as a collection of no types. If we liken string | number to a set containing string and number, then adding never like string | number | never still results in a set of string and number, similar to how adding 0 to one side of an equation doesn't alter the sum on the other side: 1 + 2 + 0 still equals 3. In this analogy, never plays the role of 0.

The utility of never becomes evident when handling function arguments. While a variable typed as unknown can accept any value, specifying a type for a function argument such as f(callback: (a: T) => void) necessitates that the callback's argument a must accommodate T. Thus, for example, if T is boolean, a callback accepting boolean | string would suffice.

function f(callback: (a: boolean) => void) {}
f((a: boolean | string) => {}) // valid

Unlike a variable constrained by its type, in a callback type, T acts as a lower constraint. To permit a function with an argument of any type, utilizing the bottom type never replicates behavior akin to unknown. This approach proves useful when imposing constraints without invoking the function, where (a: never) => void signifies that the provided function can handle up to 1 argument of any type.

function brandFunction<F extends (arg: never) => void>(f: F): F & { brand: true } {
    const newF = f as (F & { brand: true })
    newF.brand = true
    return newF
}
brandFunction(() => {}) // allowed
brandFunction((a: string) => {}) // allowed
brandFunction((a: string, b: string) => {}) // not allowed

The notion of never being assignable to any type may seem peculiar, exemplified in this code snippet:

function throws(): never { throw new Error('this function does not return normally') }
const n: never = throws()
const a: string = n // permitted

This curious behavior originates from the fact that the throws() function technically doesn't return never, perhaps better termed as none. Essentially, the function ceases execution upon encountering the error, and subsequent assignments remain unrealized. The debate arises on whether never accurately conveys this concept or if a distinct unreachable type, immune to assignment, might be more precise.

To delineate unreachable code post a call to throws(), the ideal portrayal is through an unreachable type. Though TypeScript acknowledges unreachable code following a return or throw statement, incorporating an unreachable type to convey this to higher scopes remains elusive.

function f() {
  throw new Error('this function does not return normally')
  const a = 1 // TS recognises this as unreachable code
}
f()
const b = 2 // TS does NOT recognise this as unreachable code

Answer №4

How does type-checking work for assignments?

To check, take the R-value (the right side of the assignment) and determine if it can hold a value that the L-value (the left side of the assignment) cannot accommodate. If such a value exists, then the assignment is rejected. Otherwise, it is considered valid.

Let's explore some examples:

let x: number;
let y: never;

x = y; // This is allowed as any 'never' value can be assigned to a number

y = x; // However, this is not allowed because x could contain values like 1 which cannot be assigned to never

On the surface, it may seem strange since an assignment typically involves moving data into memory allocated for a variable, but in cases like these where there is no actual runtime representation, things can get tricky. In reality, while assigning from never to another type is technically permissible, it won't have any practical effect unless you manipulate TypeScript with type assertions.

Does this explanation make sense?

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Incorporating Moralis into Ionic Angular with TypeScript

I'm currently developing an app using Ionic Angular (TypeScript) that will be compatible with both Android and iOS devices. I've decided to incorporate the Moralis SDK to establish a connection with the Metamask wallet. Here's a summary of ...

Passing a variable from an observable to another function in Angular2: A step-by-step guide

The code snippet below is not functioning as expected. I'm attempting to pass a variable obtained from an rxjs observable function to another function, but I'm uncertain of the correct method to do so and haven't been able to find a suitable ...

Enhance your filter functionality in Typescript by incorporating dynamic fields

Is there a way to dynamically add an n number of fields in a filter? I am looking to pass either 1 field, 2 fields, or n fields based on what I need. myArray.filter(x => x.field1 === field1Value && x.field2 === field2Value && and so f ...

Issue with action creator documentation not displaying comments

We are exploring the possibility of integrating redux-toolkit into our application, but I am facing an issue with displaying the documentation comments for our action creators. Here is our old code snippet: const ADD_NAME = 'ADD_NAME'; /** * Se ...

Error: Missing npm install -g @angular/cli@latest package in devDependencies section

ng build is causing an issue that The error reads: Unable to Find npm install -g @angular/cli@latest in devDependencies. When I attempt to start the application using npm start, it works fine. However, while trying to build a file, I encounter this er ...

Is it possible to use null and Infinity interchangeably in JavaScript?

I've declared a default options object with a max set to Infinity: let RANGE_DEFAULT_OPTIONS: any = { min: 0, max: Infinity }; console.log(RANGE_DEFAULT_OPTIONS); // {min: 0, max: null} Surprisingly, when the RANGE_DEFAULT_OPTIONS object is logged, i ...

Is it necessary to use ReplaySubject after using location.back() in Angular 6 to avoid requesting data multiple times?

I'm currently utilizing a BehaviorSubject in my service to retrieve data from the BackEnd, which I subscribe to in my mainComponent using the async pipe. However, whenever I navigate to another subComponent and then return to my mainComponent by clic ...

Node OOM Error in Webpack Dev Server due to Material UI Typescript Integration

Currently in the process of upgrading from material-ui v0.19.1 to v1.0.0-beta.20. Initially, everything seems fine as Webpack dev server compiles successfully upon boot. However, upon making the first change, Node throws an Out of Memory error with the fol ...

Incorporate a unique CSS class using a variable in the JSX that is returned

I have developed a unique component that generates HTML markup to display content based on the provided values. Below is a simplified version of the code: interface ContainerProps { position?: string; content?: string; className?: string; } co ...

When using html2canvas in Angular, it is not possible to call an expression that does not have a call signature

I'm currently working on integrating the html2canvas library into an Angular 8 project. Despite trying to install the html2canvas types using npm install --save @types/html2canvas, I'm still facing issues with its functionality. Here's how ...

Tips for converting numerical values in a JSON object to strings within a TypeScript interface

{ "id": 13, "name": "horst", } in order to interface A { id: string; name: string; } When converting JSON data of type A to an object, I expected the conversion of id from number to string to happen automatically. However, it doesn' ...

TS will not display an error when the payload is of type Partial

Why doesn't TypeScript throw an error when making the payload Partial? It seems to only check the first value but not the second one. type UserState = { user: User | null; loading: boolean; error: Error | null } type UserAction = { type: type ...

Transfer the data for 'keys' and 'input text' from *ngFor to the .ts file

I am facing difficulty in creating a string with dynamically generated keys from *ngFor and user input text. Let me provide some code to better explain my need. <th *ngFor="let column of Filter" > <tr>{{ column.name }}: <input type="{{c ...

Understanding the expiration date of a product

I'm seeking assistance with identifying expiration dates from a list of data. For instance, if I have a list containing various dates and there is an expireDate: 2019/11/20, how can I highlight that date when it is 7 days away? Here's an example ...

Typescript defines types for parameters used in callbacks for an event bus

Encountering a TypeScript error with our custom event bus: TS2345: Argument of type 'unknown' is not assignable to parameter of type 'AccountInfo | undefined'. Type 'unknown The event bus utilizes unknown[] as an argument for ca ...

Tips for extracting the decimal portion of a floating point number in TypeScript

Is there a way to retrieve the number that comes after the decimal point in a float number using TypeScript? For example, in the number 2.3, I would like to obtain a return of 3. ...

Transferring information from ag-Grid to a different user interface

I have implemented ag-Grid to showcase some data and now I want this data to be transferred to another interface upon clicking a cell in the grid. To achieve this, I utilized the cellRendererFramework by creating a custom component called RouterLinkRendere ...

What is the best approach for retrieving an image efficiently with Angular HttpClient?

My backend currently sends back an image in response. When I access this backend through a browser, the image is displayed without any issues. The response type being received is 'image/jpeg'. Now, I am exploring different methods to fetch this ...

The ngModel for a text box does not store the accurate value when the Google Places Autocomplete API is being utilized

I am trying to implement the Google Places Autocomplete API with a textField in my project. The goal is to have city suggestions appear as I type in the textField. Currently, I have bound my textField to a variable called "searchFieldValue" using ngModel. ...

The missing properties in the TS Type are as follows:

Currently working with Angular 7.x.x and TypeScript version 3.2.4. I have defined two TypeScript interfaces where one extends the other: Main interface: export interface Result { var1: string; var2: number; var3: boolean; } The second ...