Is it a bug if recursive generic types are extending types in a peculiar manner?

Consider the following scenario:

interface Test {
  inner: {
    value: boolean,
  }
}

We also have a class defined as:

class ContextualData<T> {
   constructor(public data: T) {}
}

The objective is to wrap the 'value' property in 'inner' object of 'Test' interface inside a ContextualData object like this:

const original: Test = {
  inner: {
    value: true,
  },
}

// Wrap the value in a ContextualData object.
original.inner.value = new ContextualData<boolean>(original.inner.value)

To achieve this, certain types are declared:

export type Primitive = undefined | null | boolean | string | number | Function

export type Contextuable<T> = T | ContextualData<T>

export type DeepContextuable<T> =
  T extends Primitive ? Contextuable<T> : DeepContextuableObject<T>

export type DeepContextuableObject<T> = {
  [K in keyof T]: DeepContextuable<T[K]>
}

DeepContextual type is then used to transform the Test interface:

const original: DeepContextual<Test> = {
  inner: {
    value: new ContextualData<boolean>(true),
  },
}

After adding a new 'map' method to the ContextualData class:

class ContextualData<T> {
   constructor(public data: T) {}

   public map<U>(mapFn: (current: T) => U): U {
     return mapFn(this.data)
   }
}

Even without using the 'map' function, assigning 'value: ContextualData<boolean>(true)' throws a TypeScript error:

TS2322: Type 'ContextualData<boolean>' is not assignable to type 
'boolean | ContextualData<true> | ContextualData<false>'.

What could be the issue here? Is it possibly a bug?

Answer №1

You have encountered the distributive nature of conditional types. This interesting behavior states that a conditional type will spread across the various options in a union type parameter. When combined with the fact that TypeScript considers boolean as true | false, we end up with this scenario.

DeepContextuable<boolean> = DeepContextuable<true | false>  
   = DeepContextuable<true> | DeepContextuable<false>  
   = (true | Contextuable<true>) | (false | Contextuable<false>)
   = boolean | Contextuable<true> |  Contextuable<false

This behavior is specific to naked type parameters. To prevent this from happening, simply place the parameter within a tuple and everything will behave as expected.

export type Primitive = undefined | null | boolean | string | number | Function

export type Contextuable<T> = T | ContextualData<T>

export type DeepContextuable<T> =
[T] extends [Primitive] ? Contextuable<T> : DeepContextuableObject<T>

export type DeepContextuableObject<T> = {
    [K in keyof T]: DeepContextuable<T[K]>
}

interface Test {
    inner: {
        value: boolean,
    }
}

class ContextualData<T> {
    constructor(public data: T) { }

    public map<U>(mapFn: (current: T) => U): U {
        return mapFn(this.data)
    }
}

const original: DeepContextuable<Test> = {
    inner: {
        value: new ContextualData<boolean>(true),
    },
}

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

The transition to CDK version 2 resulted in a failure of our test cases

After upgrading my CDK infrastructure code from version 1 to version 2, I encountered some failed test cases. The conversion itself was successful without any issues. The only changes made were updating the references from version 1 to version 2, nothing ...

Using custom properties with RouteComponentProps from react-router-dom

In my project, I have a component named Navbar that relies on the location object from history, which is defined in RouteComponentProps. I attempted to include a custom prop in my component like this: interface IProps{ title?: string } class Navbar ex ...

TypeORM: Create case-insensitive search functionality

Creating a basic search feature where the records are as follows: AB CD A BCD ABC D ABD C If the search term is "BCD", the expected output should be: AB CD A BCD ABC D The current query looks like this: await connection.manager .createQueryBuilder(RefTra ...

Error: Angular - encountering undefined response when making HTTP request

When making a HTTP GET request to my backend, it returns the following JSON data: "{\"instID\":\"2018#10#30\",\"procID\":1161006,\"threadNum\":0,\"status\":2}", "{\"instID\":\"2018#1 ...

Exciting Update: Previously, webpack version 5 did not automatically include polyfills for node.js core modules (such as React JS, TypeScript, and JWT)!

Having trouble verifying the jwt token in React with TypeScript and encountering this error, how can I fix it? ` const [decodedToken, setDecodedToken] = useState<null | JwtPayload | string>(null); const verifyToken = (token: string) => { t ...

Another return payload failing to retrieve the return value

I'm currently facing an issue where a function that should return a value is not being passed on to another function. Below is the code snippet in question: public _getProfileToUpdate() { return { corporateId: this.storeService.setStoreData().p ...

Simplify a function by lowering its cyclomatic complexity

This particular function is designed to determine whether a specific cell on a scrabble board qualifies as a double letter bonus spot. With a cyclomatic complexity of 23, it exceeds the recommended threshold of 20. Despite this, I am unsure of an alterna ...

I have a question about TypeScript mapped types. Why is it not possible to retrieve the keys of a union of interfaces?

Below is the code snippet that I am working with: interface Data { A: { a1: string; a2: string; }; B: { b1: number; b2: string; }; } type TransformDataKey<V extends string, T extends string> = `--${V}-${T}`; type TransformDa ...

How can I restrict the return type of a generic method in TypeScript based on the argument type?

How can we constrain the return type of getStreamFor$(item: Item) based on the parameter type Item? The desired outcome is: When calling getStream$(Item.Car), the type of stream$ should be Observable<CarModel> When calling getStream$(Item.Animal), ...

Identifying unique properties with specific keys in a Typescript object

Can a specific type be used with one property when using the key in of type? Playground. type ManyProps = 'name' | 'age' | 'height' type MyObj = {[key in ManyProps]: number, name?: string} ...

Looking for assistance in setting up a straightforward TypeScript Preact application

I recently started exploring preact and I'm attempting to create a basic app using typescript in preact. I've noticed that their default and typescript templates include extras like jest and testing, which I don't necessarily require. Althou ...

What is the process for integrating an extension function into an Express response using TypeScript?

I am looking to enhance the Response object in Express by adding custom functions. Specifically, I want to introduce a function: sendError(statusCode: number, errorMessage: string) which can be called from anywhere like this: response.sendError(500, &qu ...

Dealing with Promise Wrapper Already Declared in TypeScript Return TypeMismatch

Here is a snippet of TypeScript code I am working with: type Chain = 'eth' export type ApiMethods = { getToken: { path: 'tokens/' (args: { chain: Chain tokenAddress: EthereumAddress }): string } getRank: ...

What is the best way to utilize a function prop in Typescript with the React-Selectize library?

I'm currently in the process of incorporating the MultiSelect functionality from the React-Selectize library into my project, specifically using Typescript. The properties for MultiSelectProps are outlined as follows: export interface MultiSelectProp ...

Issue transferring information between non-related components in an Angular 8 application unrelated to BehaviorSubject

Encountering an issue with passing data between unrelated components using Services and BehaviorSubject. The problem arises when the data is received, as the value of the variable Behavior arrives empty (""), despite the components having no apparent conne ...

typescript interface, mandatory if another is present

Is it possible to create an interface with multiple properties where one is required if the other is used? For example: interface MyProps { onPressAll: () => void; icon?: ImageSourcePropType; onPressIcon?: () => void; } What I'm looking ...

Unraveling the mystery of unwrapping an async or sync generator

Is there a way to extract the inner return type of an async generator or sync generator? In other words, I'm searching for something similar to Awaited which is used for promises. For promises, I would typically do: async function foo() { } let ret ...

Issues with Vite's global import feature not functioning properly in a production build

My current setup involves loading all markdown files within a directory using a glob import. The code snippet below depicts this functionality: const useGetChangelogs = () => { const [changelogs, setChangelogs] = useState<string[]>([]); useEf ...

Attaching an event listener to elements with a specified Class name

Currently facing a challenge where I am trying to implement a function that captures click events on squares. The objective is to capture the click event on every button with the square class. import { Component, OnInit } from '@angular/core&apos ...

Uncovering the secrets of incorporating axios with Vue in Typescript

I'm facing difficulties while trying to incorporate axios into my Vue project using Typescript. Below is a snippet of my main.ts file: import axios from 'axios' Vue.prototype.$axios = axios axios.defaults.baseURL = 'http://192.168.1.22 ...