What sets apart TypeScript's indexed types from function signatures?

I am curious about the distinction between indexed type and function signatures in TypeScript.

type A = {
  _(num: number): void;
}['_'];

type B = {
  _(ten: 10): void;
}['_'];
let a: A = (num) => {
  console.log(num);
};

let b: B = (ten) => {
  console.log(ten);
};

a = b;
b = a;

In the code snippet above, there are no errors. However, when looking at the following code:

type A = (num: number) => void;

type B = (ten: 10) => void;

let a: A = (num) => {
  console.log(num);
};

let b: B = (ten) => {
  console.log(ten);
};

a = b;  // ❌
b = a;

An error is thrown:

Type 'B' is not assignable to type 'A'.
  Types of parameters 'ten' and 'num' are incompatible.
    Type 'number' is not assignable to type '10'.ts(2322)

What sets them apart? Why does the first block of code not generate an error?

Answer №1

The issue with the compiler arises from a concept known as contravariance, where the sub-typing relationship reverses for function inputs.


Consider a scenario where a "plastic bottle" is a subtype of "garbage". What happens when you compare bins that specifically only accept plastic bottles to those that can accommodate any type of garbage?

In this context, a "general garbage bin" acts as a subtype of a "plastic bottle bin". If you place a plastic bottle bin where people are expecting a general garbage bin, individuals may end up disposing of other types of trash that the plastic bottle bin cannot handle.

10 is considered a subtype of number. This implies that (num: number) => void is also a subtype of (ten: 10) => void

Therefore, in your second example, while b = a compiles without errors, a = b does not since B is not a subtype of A. This particular behavior was introduced in 2017 and can be enabled using the --strictFunctionTypes flag. Disabling this flag would allow both examples to compile successfully.


Why then does the first example compile when acquiring function types via indexed access?

According to the documentation regarding this flag:

During the development phase of this feature, various potentially unsafe class hierarchies, including some within the DOM, were identified. As a result, this setting exclusively applies to functions defined in function syntax and not those written in method syntax.

If needed, we can convert the method into a property featuring a function type:

type A = {
  _: (num: number) => void;
}['_'];

type B = {
  _: (ten: 10) => void;
}['_'];

Following this transformation, attempting a = b will fail to compile similar to the outcome observed in your second example.

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

What are the distinctions between generic and discriminated types?

Hi there, I've been thinking of an idea but I'm not sure how to implement it or if it's even possible. Is there a way to create a type SomeType where the first property can be any value from the set T, but the second property cannot be the ...

Unable to retrieve the request body with bodyParser

I am currently working on a NextJS React Application. Within my server/index.tsx file, I have the following code: import next from 'next'; import express from 'express'; import compression from 'compression'; import bodyParser ...

The firebase-admin module encounters compatibility issues with middleware due to the OS Module

I'm facing a major issue with my API implementation. I am working on integrating an authentication and verification middleware, but the problem arises when using firebase-admin due to its dependencies on Edge Runtime modules that are incompatible with ...

The issue is that TypeScript is indicating that the type 'string | string[]' cannot be assigned to the type 'string'

I recently upgraded to Angular 16 and encountered an issue with an @Input() property of type string | string[]. Prior to the upgrade, everything was functioning correctly, but now I am experiencing errors. I am uncertain about where I may have gone wrong i ...

What is the best way to handle alias components in next.js when using ts-jest?

When working with TypeScript, Next.js, and Jest, I wanted to simplify my imports by using aliases in my tsconfig file instead of long relative paths like "../../..". It worked fine until I introduced Jest, which caused configuration issues. This is a snip ...

How can I update the image source using Angular?

<div class="float-right"> <span class="language dashboard" data-toggle="dropdown"> <img class="current" src="us-flag.png" /> </span> <div class="dropdown dashboar ...

Dealing with null-safe operators issues has been a challenge for me, especially while working on my Mac using

Hey everyone! I'm encountering errors when using null sage operators in TypeScript. Can someone help me figure out how to solve this issue? By the way, I'm working on Visual Studio Code for Mac. https://i.stack.imgur.com/huCns.png ...

In Angular, you can easily modify and refresh an array item that is sourced from a JSON file by following these steps

Currently, I am working on implementing an edit functionality that will update the object by adding new data and deleting the old data upon updating. Specifically, I am focusing on editing and updating only the comments$. Although I am able to retrieve th ...

The React useState Props error message TS2322: Cannot assign type 'string' to type 'number'

I'm attempting to pass Props to React useState Hooks. Both of my props are required and should be numbers, but I keep receiving a Typescript error stating: Type 'string' is not assignable to type 'number'. TS2322 However, I am ...

Alter the entity type when passing it as a parameter

Trying to alter the Entity type, I am invoking a function that requires two parameters - one being the entity and the other a location. However, when trying to assign a Type for the first entity, it throws an error: Error: Argument of type 'Node<En ...

Ways to ensure TypeScript shows an error when trying to access an array index

interface ApiResponse{ data: Student[]; } interface Student { name: string; } Imagine a scenario where we receive an API response, and I am confident that it will contain the data field. However, there is a possibility that the data could be an empty ...

VSCode displaying HTML errors within a .ts file

There is a strange issue with some of my .ts files showing errors that are typically found in HTML files. For example, I am seeing "Can't bind to 'ngClass' since it isn't a known property of 'div'" appearing over an import in ...

Service consuming in Angular 2 using Stomp protocol

Why am I seeing responseBody as undefined, but I am able to see the subscribe response in msg_body? What could be causing this issue with responseBody? let stomp_subscription = this._stompService.subscribe('/topic/queue'); stomp_subscription.ma ...

Expanding the Window Object in Typescript with Next.js Version 13

Within my Next.js 13 project, I am looking to enhance the Window object by adding a property called gtag I created an index.d.ts file in the root folder with the following content: index.d.ts declare module '*.svg' { const content: any; exp ...

How to send multiple queries in one request with graphql-request while using getStaticProps?

I am currently utilizing graphCMS in combination with NextJS and have successfully implemented fetching data. However, I am facing an issue where I need to execute 2 queries on the homepage of my website - one for all posts and another for recent posts. q ...

Unlock the power of TypeScript by linking together function calls

I am looking for a way to create a type that allows me to chain functions together, but delay their execution until after the initial argument is provided. The desired functionality would be something like this: const getStringFromNumber = pipe() .then ...

The inline style in Angular 2 is not functioning as expected when set dynamically

Having a small dilemma... I'm trying to apply an inline style within a div like this: div style="background: url(..{{config.general.image}})"></div Oddly enough, it was functioning in beta 16 but ever since the RC1 upgrade, it's no longer ...

What is the best way to rid ourselves of unwanted values?

In the laravel-vue-boilerplate package, there is a User CRUD feature. I duplicated this functionality to create an Item CRUD by making some changes and adjustments. Everything is working fine except for one issue: after editing an item, when trying to add ...

shared interfaces in a complete javascript application

In the past, I have typically used different languages for front-end and back-end development. But now, I want to explore the benefits of using JavaScript/TypeScript on both sides so that I can have key data models defined in one central location for both ...

In a Typescript Next Js project, the useReducer Hook cannot be utilized

I'm completely new to Typescript and currently attempting to implement the useReducer hook with Typescript. Below is the code I've written: import { useReducer, useContext, createContext } from "react" import type { ReactNode } from &q ...