Unpredictable actions arise when utilizing `never` to render a field as non-optional if another is provided

Can you help me understand why TypeScript behaves differently when defining entities using types directly versus returning them from functions?

type Entity =
  | { text: string; color?: never; icon?: never }
  | { text: string; color: string; icon?: string }

const entities: Entity[] = [
  // ok
  { text: 'foo' },
  { text: 'foo', color: 'red' },
  { text: 'foo', color: 'red', icon: 'apple' },

  // error ~ 'color' is missing
  { text: 'foo',  icon: 'apple' },
]
type EntityFn = 
  | (() => { text: string; color?: never; icon?: never })
  | (() => { text: string; color: string; icon?: string })

const entityFns: EntityFn[] = [
  // ok
  () => ({ text: 'foo' }),
  () => ({ text: 'foo', color: 'red' }),
  () => ({ text: 'foo', color: 'red', icon: 'apple' }),

  // Unexpectedly no error thrown here
  () => ({ text: 'foo', icon: 'apple' }),
]

Appending an unassignable function to entityFns, makes the compiler start complaining about a different entity. Can you explain this behavior?

Playground link for reference: playground link

Answer №1

It appears to be a linting issue, possibly related to an incorrect "priority" on type checks.

If you modify your code by removing the first rule of the custom type, you should see the expected error:

type EntityFn = (() => { text: string; color: string; icon?: string });

const entityFns: EntityFn[] = [
    // causing an error in this situation
    () => ({ text: 'foo' }),

    // valid
    () => ({ text: 'foo', color: 'red' }),
    () => ({ text: 'foo', color: 'red', icon: 'apple' }),

    // expected error
    () => ({ text: 'foo', icon: 'apple' }),
]

It seems that the stricter rule (alone) functions correctly as the return type in a function. However, when combined with the additional rule

{ text: string; color?: never; icon?: never }
, the linter halts at the optional (?) rule.

This behavior is logical because when using

{ text: string; color?: never; icon?: never }
, the fields color and icon are not simultaneously excluded; they are each optional individually.

A potential solution is to utilize satisfies on function outcomes:

const entityFns: EntityFn[] = [
    // valid
    () => ({ text: 'foo' } satisfies Entity),
    () => ({ text: 'foo', color: 'red' } satisfies Entity),
    () => ({ text: 'foo', color: 'red', icon: 'apple' } satisfies Entity),

    // error
    () => ({ text: 'foo', icon: 'apple' } satisfies Entity),
]

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

A Typescript object that matches types and eventually returns a string when called upon

In the process of overengineering a type that can match either a string or an object whose valueOf() function, when evaluated recursively, ultimately returns a string. type Stringable = string | StringableObject; interface StringableObject { valueOf() ...

Develop a Typescript interface containing all the necessary properties for a specific JSX component

My goal is to create a ControlledInputComponent that consists of a label and an input inside a div. I aim to make it highly customizable as I plan on using it in various ways. At the moment, I have included all the necessary elements, but I would like to e ...

Define the expected argument type of a function as an arrow function

In TypeScript, is there any way to enforce the use of arrow functions as function arguments? For instance, when using a publish-subscriber model and passing a listener function to a 'server' object, the server calls this function whenever a publi ...

Utilizing Optional Generics in TypeScript

I have created a wrapper for making API calls to a Strapi server. export const api = { post: async<T extends unknown, K>(url: string, body: Partial<T>, jwt?: string): Promise<K> => { try { const result = await ...

The error message "The function XXX.isSupported is not defined" is displayed

Integrating a JavaScript library into Typescript When I use this function in JavaScript, it works perfectly: _clickHandler() { TouchID.isSupported() .then(authenticate) .catch(error => { AlertIOS.alert('TouchID not supported'); }); ...

TypeScript Tutorial: How to retrieve the data type of a deeply nested object

I have a question about extracting the type of a nested object with similar structures. The object in question contains multiple sub-objects that all follow the same pattern. const VALUES = { currentStreak: { display: "Current streak", ...

What is the method to access a component from a module that has been imported into the app module?

We are currently working on a project that has the following hierarchy. app/ ├── app.component.html ├── app.component.ts ├── app.module.ts <--moduleA and moduleB is imported here ├── app-routing.module.ts ├── moduleA/ ...

What causes the index to display [object Object] rather than an integer in React?

It has been a long time since I last worked with React, and now I'm facing an issue. Whenever I use console.log to display the index within the map function, my console output looks like this: https://i.stack.imgur.com/VbGmE.png However, the result ...

Error in Template Syntax for External Pug Templates: Component template must have a root element, not just plain text

I've been struggling to make Pug templates work with Vue class-based components using a separate file for the pug template. The documentation suggests that adding this code should do the trick: // webpack.config.js -> module.rules { test: /&bsol ...

What are the steps to enable full functionality of the strict option in TypeScript?

Despite enforcing strict options, TypeScript is not flagging the absence of defined types for port, req, and res in this code snippet. I am using Vscode and wondering how to fully enforce type checking. import express from 'express'; const app ...

Attempting to categorize JSON object elements into separate arrays dynamically depending on their values

Here's the JSON data I'm currently working with: ?$where=camis%20=%2230112340%22 I plan to dynamically generate queries using different datasets, so the information will vary. My main objective is to categorize elements within this array into ...

Mozilla struggles to interpret JSON data

When using Angular2 (RC4), I utilize this code snippet to retrieve data from my WebApi: getAppointment(id: number): Observable<Event> { return this._http.get(this._serviceUrl + 'get/' + id) .map(this.extractData) .catch ...

The variable 'minSum' is being referenced before having a value set to it

const arrSort: number[] = arr.sort(); let minSum: number = 0; arrSort.forEach((a, b) => { if(b > 0){ minSum += minSum + a; console.log(b) } }) console.log(minSum); Even though 'minSum' is defined at the top, TypeScript still throws ...

Why is the getElement().getProperty("value") function not functioning properly?

I am facing an issue with reading a property in my web component. I am puzzled as to why it is not working correctly. I created a simple example, and after clicking the button, I expect to retrieve the value of the property, but it returns null. I am unsur ...

There are no matching overloads in React for this call

Below is an error message in the code. It seems to be related to the usage of <IHistorical[]> in useQuery, but unfortunately, I haven't found a solution for it yet. Overload 1 of 2, '(props: Props | Readonly<Props>): ReactApexChart& ...

Angular-template static functions refer to functions that do not require an

Our project utilizes the linting-config provided by AirBnB. There is a rule that stipulates class methods must utilize this or be declared as static. While this rule theoretically makes sense, it seems to present challenges within an angular context. Consi ...

Create a generalized function that retrieves offspring sharing a common interface

Struggling to create a single generic function that can return child interfaces inheriting the same parent interface. Specifically, looking to implement the getById method: interface Car { brand: string } interface Ford extends Car { someUniqueAttribute: ...

Unlimited Webpack watching cycle - tips on disregarding modifications in .js files, and more

There seems to be an issue with my webpack -w command when using ts-loader, as it is stuck in an endless loop. It appears that the problem arises because webpack -w detects changes in .js files, resulting in a continuous cycle: webpack -w => ts trans ...

Type definition for Vuex store functionality

Working on creating a versatile type to provide typing hints for mutations in Vuex. After reading an inspiring article on Vuex + TypeScript, I decided to develop something more generic. Here is what I came up with: export type MutationType<S, P, K exten ...

Calculate the difference between two arrays containing objects using ES6 or TypeScript

I am faced with a challenge involving two arrays: arr1 = [{ id: 1, name: 'Diego', age: 23 }, { id: 2, name: 'Brian', age: 18 }] arr2 = [{ id: 1, name: 'Diego', ag ...