What is the reason for Parameters<Func> not being able to extend unknown[] (Array<unknown>) in TypeScript strict mode?

The main point of the title is that I have come across this particular code:

  type testNoArgsF = () => number;
  type testArgsF = (arg1: boolean, arg2: string) => number;
  type unknownArgsF = (...args: unknown[]) => number;
  type anyArgsF = (...args: any[]) => number;

  type testII = testArgsF extends anyArgsF ? true : false; // true
  type testIII = Parameters<testArgsF> extends Parameters<unknownArgsF>
    ? true
    : false; // true

  // surprising:
  type testIV = testArgsF extends unknownArgsF ? true : false; // false <- why?
  // but:
  type testV = testNoArgsF extends unknownArgsF ? true : false; // true

This piece of code is specifically in typescript (version 3.8), with strict mode enabled. What perplexes me is why a test function does not extend a function type with spread args of unknown[], even though their parameters do indeed extend unknown[]. Given that the return type always remains as number, I am unsure about what other factor might be causing the extends statement to turn out false.

Here are some additional observations:

  • The extend statement holds true only if your test function has zero arguments.
  • This unique behavior vanishes when you disable strict mode.

Answer №1

When you enable the --strictFunctionTypes compiler option, function type parameters are checked in a contravariant manner. Contravariant means that the subtype relationship of the function changes in the opposite direction compared to that of function parameters. Therefore, if A extends B, then

(x: B)=>void extends (x: A)=>void
is true rather than vice versa.

This poses a type safety issue due to the concept of "substitutability" in TypeScript, also referred to as behavioral subtyping. If A extends B is valid, you should be able to use an instance of A where a B is expected. Otherwise, the claim that A extends B is incorrect.

Disabling the --strict flag reverts the compiler to pre-TS-2.6 behavior of checking function parameters bivariantly, which although allowed for productivity reasons, is unsafe. For further details on this specific behavior, refer to the TypeScript FAQ entry on "Why are function parameters bivariant?"


In scenarios where you require a function type capable of accepting any number of unknown parameters, it's risky to restrict the usage to only a specific subtype of unknown. The following example illustrates this:

const t: testArgsF = (b, s) => (b ? s.trim() : s).length
const u: unknownArgsF = t; // error!

u(1, 2, 3); // results in runtime issues! s.trim is not defined

If testArgsF extends unknownArgsF were indeed true, the assignment of t to u above would cause runtime errors when u accepts a non-string second argument. It highlights the importance of ensuring the subtype or implementation of a function type allows arguments that are equal or wider than those anticipated by the super type or call signature, hence why --strictFunctionTypes was introduced.


...

Alright, I hope this explanation proves helpful. Best of luck!

Playground link to code

Answer №2

Let's simplify it: "extends" means "compatible" which translates to "can be used in place of."

Could you utilize

(arg1: boolean, arg2: string) => number;

in lieu of
(...args: unknown[]) => number
?

No, because the latter can work with calls without arguments, but the former may fail during runtime (for example, trying to access properties of the arguments).

For more information on function compatibility, visit this link

Answer №3

type testIV = testArgsF extends unknownArgsF ? true : false; // false 
type testIVa = unknownArgsF extends testArgsF  ? true : false; // true!

// because
type testIII = Parameters<testArgsF> extends Parameters<unknownArgsF>
    ? true
    : false; // true

Extends essentially signifies 'is assignable to'. And f(p:A)=> is assignable to f(p:B)=> if B is assignable to A. This concept is known as 'contravariant'.

// Consider a scenario where your function expects a callback with certain parameter types:

var f = (cb: ({ a, b }: { a: number, b: number }) => void) => { cb({ a:1, b: 1}); };

// you can safely call it by providing a function that accepts parameters
// which are assignable to the original parameters, but not vice versa
// this ensures that when our function f calls the callback,
// it will have sufficient data to work with
f(({ a }: { a: number }) => { })


var x = { a: 1, b: 1 };
var y = { a: 1 }

x = y; // results in error as x would lose its b property post assignment
y = x; // works fine since y has all necessary properties after assignment, it doesn't matter if it also has property b

// in simpler terms:
type F = { a: number } extends { a: number, b: number } ? true : false; // false
type T = { a: number, b: number } extends { a: number } ? true : false; // true

// interpret 'extends' as 'is assignable to'

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

Tips for incorporating a mesh into Forge Viewer v6 with Typescript

Is there a way to add meshes to Forge Viewer v6 using Type script? I've tried various methods that worked with v4, but I'm encountering issues now. private wallGeometry: THREE.BoxBufferGeometry; drawWalls() { ...

Tips for showing nested JSON data in a PrimeNG table within Angular version 7

I am struggling to display nested json data in a PrimeNG table. When I retrieve the data using an HTTP service, it appears as [object][object] when displayed directly in the table. What I want is to show the nested json data with keys and values separated ...

What is the best way to combine various express routes from separate files in a TypeScript project?

After transitioning to TypeScript, I have been facing issues with merging different routes from separate .ts files. In JavaScript, I used to combine routes like this: app.use("/users/auth", require("./routes/user/auth")); app.use("/users", require("./rou ...

Exploring the differences between Office Fabric UI's I[component]StyleProp and the I[component]Styles interface

The Office Fabric UI documentation provides two interfaces for each component, such as https://developer.microsoft.com/en-us/fabric#/components/nav includes INavStyleProps interface and INavStyles interface A component that implements INavStyleProps ...

An issue has occurred: the property 'map' cannot be read as it is undefined

Encountered this unexpected error and struggling to understand the reason behind it.. I've been attempting to showcase events on angular-calendar: Error occurred in error_handler.ts:1 - ERROR TypeError: Cannot read property 'map' of unde ...

What is the method for comparing fields within an input type in type graphql with the assistance of class validator decorators?

I am working with the following example input type: @InputType() class ExampleInputType { @Field(() => Number) @IsInt() fromAge: number @Field(() => Number) @IsInt() toAge: number } Can I validate and compare the toAge and fromAge fields in th ...

Discover the seamless transformation of a class definition into a Vue 3 component definition utilizing the dazzling 'tc39' decorators

The proposed API in the tc39/proposal-decorators repository is significantly different from the previous decorators API. Although TypeScript 5 doesn't fully support the new API yet, it's only a matter of time before the old API becomes deprecated ...

Enhancing Your React Code with Prettier, ESLint, and React

Encountering conflicting rules on TS / React imports as a beginner, while using Eslint / Prettier. I'm getting an error stating 'React' is declared but its value is never read.. However, when I remove it, another error saying 'React&apo ...

What are the steps to integrate webpack with .NET 6?

Struggling to incorporate webpack into .NET 6 razor pages. The existing documentation online only adds to my confusion. Here is a snippet from my file1.ts: export function CCC(): string { return "AAAAAA"; } And here is another snippet from ...

react Concealing the Card upon clicking a different location

Utilizing a combination of React and TypeScript, this component allows for the card to be toggled between shown and hidden states upon clicking on the specified div tag. However, there is a need to ensure that the card is hidden even if another area outs ...

The Material UI Grid is not compatible with the Chrome DevTools Device Toolbar and may not function properly

Using MUI v5 with React and Typescript has been an interesting experience for me. When I utilize the Grid system, I set options like xs sm md lg to define item width. Interestingly, setting just xs or sm works fine, but when I include md, other options su ...

Troubleshooting: Difficulty assigning a value to a nested object in Angular

I'm a beginner in Angular and TypeScript so please forgive me if this question seems silly. I am attempting to store the value of a returned object into a nested property within an object with a type of any. Unfortunately, it is not allowing me to do ...

Is React Typescript compatible with Internet Explorer 11?

As I have multiple React applications functioning in Internet Explorer 11 (with polyfills), my intention is to incorporate TypeScript into my upcoming projects. Following the same technologies and concepts from my previous apps, I built my first one using ...

After refreshing the page, RouterLinkActive in Angular 6 fails to work

Scenario In my application, there is a menu with various items. The selected item is distinguished by adding the selected class to it, which changes its appearance. https://i.sstatic.net/JEPHH.png Problem While navigating between routes works smoothly, ...

Generating a dynamic optional parameter for deduced generics

I have a specific object with the following structure: { first: (state: S, payload: boolean) => { payload: boolean, type: string }, second: (state: S) => { payload: undefined, type: string }, } My goal is to manipulate this object by removing th ...

Leverage the exported data from Highcharts Editor to create a fresh React chart

I am currently working on implementing the following workflow Create a chart using the Highcharts Editor tool Export the JSON object from the Editor that represents the chart Utilize the exported JSON to render a new chart After creating a chart through ...

What are the steps to resolving an issue in a Jest unit test?

In my ReactJs/Typescript project, I encountered an issue while running a unit test that involves a reference to a module called nock.js and using jest. Initially, the import statement was causing an error in the .cleanAll statement: import nock from &apos ...

Creating a Redis client in Typescript using the `redis.createClient()` function

I'm currently trying to integrate Redis (v4.0.1) into my Express server using TypeScript, but I've encountered a small issue. As I am still in the process of learning TypeScript, I keep getting red underline errors on the host parameter within th ...

Calling the `firstValueFrom()` method in RxJS will keep the observable alive and not

Hey there, I'm currently having issues with using firstValueFrom(), lastValueForm(), and Observable.pipe(take(1)) in my TypeScript code with Angular 14 and RxJs 7.8.0. I am working with a Firebase server that provides stored image URLs via an API wit ...

Utilizing the functionalities provided by node.js, I came across an issue and sought out a solution for the error message "TypeError: __WEBPACK_IMPORTED_MODULE_3_fs__.writeFile is not a function"

I have created a project centered around {typescript, react, electron, gaea-editor}. During an event, I utilized fs.writeFile() but encountered an error. The specific error message was: TypeError: __WEBPACK_IMPORTED_MODULE_3_fs__.writeFile is not a functi ...