Is it logical for `string & any[]` to lead to a `never` result?

There's something peculiar going on with TypeScript. I have a type union that includes array types (string[], number[]) and non-array types (string, number). When using type inference, everything behaves as expected:

type bar = string | number | string[] | number[];
declare foo: bar;

if (Array.isArray(foo))
{
    foo // string[] | number[]
}
else
{
    foo // string | number
}

However, when attempting to constrain the type directly to array types using a type intersection, the outcome is unexpected:

declare foo: bar & any[];

// expected type: string[] | number[]

foo // (string & any[]) | (number & any[]) | (string[] & any[]) | (number[] & any[])

Why does this happen?
Shouldn't string & any[] result in never and string[] & any[] in string[]?

[link to playground]

Answer №1

It is logical to assume that when completely different types intersect, the result should be 'never', as the intersection of two non-overlapping sets is empty. This concept has been brought up previously (see ms/TS#18210) on several occasions.

Since TypeScript 2.6, there has been a partial implementation where types like "a" | "b" & "c" become 'never', while "a" & "c" do not. This was done to prevent unions and intersections from creating excessively large union types.

The decision of why this isn't always applied can be found in the pull request introducing the implementation, with insights from Anders Hejlsberg, one of the key figures behind TypeScript. One reason mentioned is not wanting to disrupt code that uses intersections for tagging primitive types.

Another reason discussed is to make it easier to identify the origin of certain types, such as when object types intersect with properties of the same name.

In conclusion, potential performance impacts are also a major consideration for implementing new features in the compiler, as aggressive checks for reducing intersections to 'never' could have a significant impact on performance.

Answer №2

When dealing with union types, the distribution of intersections becomes apparent. For example, intersecting a union A | B | C with something like D results in

A & D | B & D | C & D
.

type U = A | B | C;
type Z = U & D; // distributed as A & D | B & D | C & D

It's important to note that intersection types do not get "collapsed," as demonstrated by

type X = Array<number> & Array<any>;
. The type remains as originally declared.

This behavior is consistent regardless of whether the intersection type leads to a scenario where never is expected or not. For instance,

type X = { x: string } & { y: string };
will not be collapsed into { x: string, y: string }

The compiler steps in to restrict your intersection when you try to assign something that does not satisfy the type.

For example:

type X = Array<number> & Array<any>; // type remains unchanged
const x: X = ['some string']; // this will trigger an error
const y: X = [4]; // this is valid

To refine your type, consider using a conditional type instead of an intersection to filter the union.

type Bar = string | number | string[] | number[];

declare const foo: Exclude<Bar, Array<any>>; // results in string | number

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

Steps to fill a select dropdown with numbers ranging from 1 to 10, ensuring that each number has a distinct index or value

What is the best way to create a select dropdown containing numbers from 1 to 10, with each option having a distinct value and the default selected option being 1? HTML <select [(ngModel)]="selectNum"> <option value=""> </option& ...

Mastering React Composition: Leveraging Parent Methods

Currently, I am utilizing composition within React and am interested in invoking a method from the parent component. Most examples I have come across demonstrate the use of inheritance for this purpose. Container component - Embeds a child component inter ...

Guide on Retrieving an Array from an Observable

Hey there! I've come across a function that is supposed to return an Array. In the function below, this.cordovaFile.readAsArrayBuffer(this.cordovaFile.dataDirectory, storageId) actually returns a Promise Array. I'm converting it into an Observabl ...

Enhance the TypeScript interface by dynamically appending new fields with specific naming conventions

My interface is structured like this: interface ProjectCostData { purchasePrice: number; propertyValue: number; recentlyDamaged: boolean; } Now I am looking to dynamically create a new interface based on the one above: interface ProjectCostDataWithS ...

Utilize a type that is not made available for export

library is a module in Typescript that exports a specific function called someFunction. This function is designed to be called with a string input that must be one of a defined set of values. Within the library, the someFunction function is defined to onl ...

Displaying data from an Angular subscription in a user interface form

I am attempting to transfer these item details to a form, but I keep encountering undefined values for this.itemDetails.item1Qty, etc. My goal is to display them in the Form UI. this.wareHouseGroup = this.formBuilder.group({ id: this.formBuilder.contr ...

Is there a way to increase the level of detail in the error trace provided by tsc? This error trace is

While attempting to compile a React project using the default tsconfig.json provided by create-react-app, I encountered a baffling error that has me stumped. $ tsc error TS2488: Type 'never' must have a '[Symbol.iterator]()' method that ...

Encountering a TypeError while working with Next.js 14 and MongoDB: The error "res.status is not a function"

Currently working on a Next.js project that involves MongoDB integration. I am using the app router to test API calls with the code below, and surprisingly, I am receiving a response from the database. import { NextApiRequest, NextApiResponse, NextApiHandl ...

Create an interface that adheres to the defined mapped type

Working on a typescript project, I have defined the mapped type GlanceOsComparatorV2 interface DonutData { label: string; value: number; } interface ProjectMetric { value: number; } enum ZoneMetricId { ClickRate = 'clickRate', } enum Pa ...

Exploring two main pages, each with tabs displaying two negative behaviors

I developed an app with an ion-footer at the bottom of each root page : <ion-footer> <ipa-footer-buttons></ipa-footer-buttons> </ion-footer> The <ipa-footer-button> component is structured as follows: html: <ion-toolb ...

Update the sorting icon in the data grid using Material-UI

I'm looking for a way to replace the current icon in the Material UI data grid with a different one. Below is the code I'm using to implement the data grid component: https://i.sstatic.net/griFN.png import { DataGrid, DataGridProps, GridColDef,G ...

Utilize string generic limitations as dynamically generated key

I am looking to create a type that can accept a string value as a generic argument and use it to define a key on the type. For example: const foo: MyType<'hello'> = { hello: "Goodbye", // this key is required bar: 2 } I attempted to ...

`Can incompatible Typescript types be allowed for assignment?`

Currently, I am faced with the challenge of sharing type definitions between my server and front-end. These definitions are stored in a separate npm package that both installations utilize. The issue arises on the front-end where variables containing Objec ...

Tips for displaying a hyperlink in an Angular 6 component template

In my Angular 6 application, I am facing an issue with a component that is used to display messages on the page. Some of the messages include hyperlinks in HTML markup, but they are not being rendered properly when displayed (they appear as plain text with ...

alteration of a function through TypeScript

export const wrapCountable = (func: Function): Function => { let result: Function & { __times?: number } = () => { //result.__times = result.__times || 0 result.__times++ let value = null try { valu ...

Create a recursive array structure that contains two distinct data types and specific guidelines

I have a unique array structure where the odd index always contains elements of TypeA and the even index always contains elements of TypeB. It is guaranteed that this array will always have an even length, never odd. The data structure of this array must ...

When executing npm run build, the fonts are not displayed properly in Vite

I'm running 'npm run build' to compile my project, and then I use the 'npm run preview' command to view it. However, some images that I've set as background images in my SCSS file are not showing up. The same issue occurs with ...

Using Typescript to add an element to a specific index in an array

Currently, I am engaged in a project using Angular2 and Firebase. My goal is to consolidate all query results under a single key called this.guestPush. Within my project, there is a multiple select element with different user levels - specifically 4, 6, ...

Oops! An unexpected error occurred while parsing the JSON response

While working with my Json file, I encountered an error that has been validated on https://jsonlint.com/ @Injectable() export class LightParserService{ ITEMS_URL = "./lights.json"; constructor(private http: Http) { } getItems(): Promise<Light[ ...

Encountering issues while passing variables in Vue 3 using TypeScript

While working on Nuxt 3, I encountered a bug with the following function: My function, const checkFileSize = (file: File): boolean => { if (!file) { return true; } return props.maxSize * 1024 * 1024 >= file.size; }; This function is call ...