What is the reason for the variance in the inferred generic type parameter between an extended interface and a type alias representing an intersection of interfaces?

Why is the generic type parameter inferred differently in the following toy experiment, depending on whether the template is instantiated with an extended type or with an intersected type? This experiment has been simplified from a real-world example.

interface Base { b: number }
interface Extra { a: string }
interface Ext1 extends Extra { b: number }
type Ext2  = Base & Extra

// f returns a function that takes a T as input
const f = <T extends Base>(inp: T & Extra): ((arg: T) => void) => {
    return (arg: T) => console.log(inp.a + arg.b) 
}

const x1: Ext1 = { a: "x1", b: 1 }
const x2: Ext2 = { a: "y1", b: 2 } 

const f1 = f(x1) // T inferred to Ext1
const f2 = f(x2) // T inferred to Base, NOT Ext2 (why?)

const inp = { b: 3 }

// error Argument of type '{ b: number; }' is not assignable to parameter of type 'Ext1'. Property 'a' is missing in type '{ b: number; }' but required in type 'Ext1'.
const out1 = f1(inp) 

// ok since inp is of type Base
const out2 = f2(inp)

Playground Link

Answer №1

The issue encountered here is not related to inference but rather stems from removing redundant intersection members. Note the presence of & Extra in the type inp. When a variable of type Ext2 is passed to function f, the type of inp becomes Base & Extra & Extra.

Due to elimination of identical types within intersections, the resulting type of inp is actually Base & Extra, causing the type parameter T to be inferred as Base since it satisfies the condition of extends Base. Removing the intersection with Extra results in correct inference:

interface Base { b: number }
interface Extra { a: string }
interface Ext1 extends Extra { b: number }
type Ext2  = Base & Extra

// f returns a function that takes a T as input
const f = <T extends Base>(inp: T): ((arg: T) => void) => {
    return (arg: T) => console.log(inp.a + arg.b)
}

const x1: Ext1 = { a: "x1", b: 1 }
const x2: Ext2 = { a: "y1", b: 2 } 

const f1 = f(x1) // T inferred to Ext1
const f2 = f(x2) // T inferred to Ext2

const inp = { b: 3 }

const out1 = f1(inp) // error
const out2 = f2(inp) // error

To clarify, extending an interface differs from intersecting two interfaces despite similar functionalities. The use of extends signifies that the left-hand side type is a subtype, while the right-hand side type is a supertype.

In contrast, intersections combine types without the concept of subtypes and supertypes. Refer to the below example to understand the distinction between extends and &:

interface A { a: string, b: boolean }
interface B { a: number, b: boolean }
interface C extends A, B {} // error, cannot extend

type a = { a:string, b: boolean }
type b = { a:number, b: boolean }
type c = a & b; // no error, but 'a' is never

Hence, when intersecting Ext1 with Extra, no changes occur due to absence of identical types for elimination, resulting in only Ext1 (subtype) and Extra (supertype).

Playground

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

"Utilize a callback function that includes the choice of an additional second

Concern I am seeking a basic function that can receive a callback with either 1 or 2 arguments. If a callback with only 1 argument is provided, the function should automatically generate the second argument internally. If a callback with 2 arguments is s ...

The data is not being displayed in the table

I am encountering an issue while attempting to populate the table with data received through props by looping over it. Unfortunately, the data is not rendering on the UI :( However, when I manually input data, it does show up. Below is my code: Code for P ...

How can you define a function type for a rest parameter in Typescript?

At this point, I have a function that takes in two parameters: param 'a' as a string and 'b' as a function that returns a string. My intention is to call it using a rest parameter and specify the types accordingly. However, on line 10 ...

Using Javascript or ES6, you can compare a nested array object with another array of elements and generate a new array based on

I am dealing with a complicated array structure as shown below sectionInfo = [{id: 1, name:'ma'}, {id: 2, name:'na'}, {id: 3, name:'ra'}, {id: 4, name:'ka'}, {id: 5, name:'pa'}]; abc = [{id:'1' ...

What is the best way to retrieve Express app configuration asynchronously?

I am utilizing an Express app developed with the Serverless Framework, which will be hosted via AWS API Gateway and AWS Lambda. The authentication process is handled by Okta, and I am considering storing the necessary secrets in SSM. Currently, I have to f ...

What is the best way to wait for the state to be set before mapping the array

I have some data stored in an array (shown in the screenshot below) that I am trying to map, but I am facing issues accessing it as it is loaded asynchronously. How can I await the data? Is there a way to achieve this within the render function? render() ...

The style fails to load correctly upon the page's initial loading

I am utilizing <a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="4d26282823603e212429283f0d7b63756378">[email protected]</a> in a <a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="026c677a76 ...

Example of real life situations where C data types are used

As a beginner in programming, I have been exploring different datatypes and have found numerous options to choose from. However, the concepts of signed and unsigned data types are still unclear to me. Can anyone provide real-life examples of when to use ...

Storing a reference globally in React and Typescript: Best practices

In my application, I have multiple instances of a specific component called <Item>. Each <Item> needs to display a dynamic tooltip when hovered over. To achieve this, I am utilizing semantic-ui-react and its Popup component. The conventional m ...

Add a hyperlink within a button element

I am looking to add a route to my reusable 'button' component in order to navigate to another page. I attempted using the <Link> </Link> tags, but it affected my CSS and caused the 'button' to appear small. The link works if ...

The mapStateToProps function in a Higher Order Component is receiving an OwnProps argument with a property that is not defined

I'm a new user of react-redux trying to connect a higher-order component to the react-redux store. In the mapStateToProps function, I'm using the ownProps argument to filter the state. However, the property I'm trying to use within OwnProps ...

Struggling to configure Sass with @snowpack/app-template-react-typescript

I'm struggling to integrate Sass with the @snowpack/app-template-react-typescript template. I attempted to follow the steps outlined in this guide, but so far I haven't been successful. I even created a new project and tried adding it, but not ...

Delete an essential attribute from an entity

I am trying to remove a required property called hash from an object, but I keep encountering TypeScript or ESLint errors. All the properties of the interface are mandatory, and I do not want to make all properties optional using Partial. Here is the inte ...

Craft a unique typings file tailored to your needs

After recently creating my first published npm package named "Foo", I encountered some difficulties while trying to consume it in a TypeScript project. The tutorials on how to declare modules with custom typings were not clear enough for me. Here are the k ...

Cannot upload the same file to both Spring and Angular twice

Having trouble uploading the same file twice. However, it works fine when uploading different files. Encountering an error under the Network tab in Chrome { timeStamp: ......, status: 417 error: 'Bad Request', message: 'Required reques ...

How can I access other properties of the createMuiTheme function within the theme.ts file in Material UI?

When including a theme in the filename.style.ts file like this: import theme from 'common/theme'; I can access various properties, such as: theme.breakpoints.down('md') I am attempting to reference the same property within the theme ...

Struggling with setting up the onChange function in a Next.js application

After successfully writing and testing the code here, I encountered an error preventing me from building it. Please review the code for any issues. I am attempting to set onChange to handle user input in a text field. Currently using onChange={onChange}, ...

Building a Vuetify Form using a custom template design

My goal is to create a form using data from a JSON object. The JSON data is stored in a settings[] object retrieved through an axios request: [ { "id" : 2, "name" : "CAR_NETWORK", "value" : 1.00 }, { "id" : 3, "name" : "SALES_FCT_SKU_MAX", "val ...

Warning: Google Map API alert for outdated Marker Class

Working on an application using the Google Maps TypeScript API, I came across a warning message when launching the app -> As of February 21, 2024, google.maps.Marker will no longer be available. Instead, developers are advised to use google.maps.marke ...

Is there a way to find the recursive key types in TypeScript?

Is there a method to ensure that code like this can compile while maintaining type safety? type ComplexObject = { primitive1: boolean; complex: { primitive2: string; primitive3: boolean; } }; interface MyReference { myKey: keyof ComplexObj ...