What is the rationale behind permitting interface method implementations to have varying intersection type arguments?

The interface and implementation presented here are quite straightforward:

class Transform {
  X: number = 0
  Y: number = 0
}
class RenderData {
  Model: object | null = null
}

interface System {
  Update(e: Transform & RenderData): void
}

class RenderSystem implements System {
  Update(e: RenderData): void {
    // Why is this allowed?
  }
}

Playground link

I would have expected an error to occur in this scenario, similar to if Update(e: RenderData) had been replaced with something like Update(e: number). In that case, the interface would not be fully implemented, prompting an error from TSC. So, why does an incomplete intersection type allow this?

Answer №1

The behavior described is the desired outcome and it maintains type safety measures. To elaborate on this concept, let's modify the types by using function-valued properties (e.g., foo: ()=>void) instead of methods (e.g., foo(): void). Additionally, we will apply the --strictFunctionTypes compiler flag:

interface System {
  Update: (e: Transform & RenderData) => void
}

class RenderSystem implements System {
  Update = (e: RenderData) => { } // valid
}

class BadRenderSystem implements System {
  Update = (e: number) => { } // error!
  // Type 'Transform & RenderData' cannot be assigned to type 'number'
}

In terms of type safety, we are primarily concerned with substitutability. In simple terms, if a value of type X can seamlessly replace a value of type Y, then X is considered a subtype of

Y</code.</p>
<p>This principle requires that function types exhibit <a href="https://www.stephanboyer.com/post/132/what-are-covariance-and-contravariance" rel="nofollow noreferrer"><em>contravariant</em></a> behavior in their parameter types. Essentially, the subtype relationship between function types operates inversely to the relationship between their parameters.</p>
<p>TypeScript enforces this type safety rule when <code>--strictFunctionTyes
is enabled for non-method types.


Therefore, in the given context, the implementation of RenderSystem is appropriate as its Update method accepts input of type RenderData, which is a supertype of Transform & RenderData.

You can verify this substitutability scenario by pretending I request a System instance and you provide an object named system of type RenderSystem. If I treat it as a System and pass an object e of type Transform & RenderData calling system.Update(e), it works because anything the RenderSystem.Update method does with

e</code remains within the bounds of both <code>Transform
and RenderData.

In contrast, consider the case where I receive a value from BadRenderSystem. When I attempt to execute system.Update(e), the BadRenderSystem's Update method treats e as a number, which typically leads to errors since Transform & RenderData cannot function as a number. Hence, BadRenderSystem fails the substitutability test for System.

In summary, everything adheres to the expected standards in this scenario.

This concludes the response to the initial query, although you might question why the type definitions were altered earlier.


Interestingly, TypeScript permits unsafe practices with method implementations. By disabling --strictFunctionTypes or sticking to method syntax, TypeScript allows covariance in method types. Covariance implies that method parameter inputs align either as supertypes or subtypes with the extended or implemented methods.

Bivariant checking of method parameters isn't entirely safe but offers convenience. You can delve into the rationale behind this approach in the FAQ section or through the provided link on "bivariantly".

While not directly pertinent to the displayed code snippet, switching back to methods reveals that UnsafeRenderSystem is now acceptable due to bivariant parameter checks, unlike BadRenderSystem which still violates type compatibility conventions.

This distinction may be worth remembering for future practical scenarios.

Access the Playground link here

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

Pass information captured from Mat Dialog up to the main component

Looking for a way to pass true/false boolean data from a dialog box into the parent component without just console logging the result? You want to store it in a variable in the parent component for further use. Any suggestions on how to achieve this? This ...

Managing enum types with json2typescript

After receiving a JSON response from the back-end that includes an Enum type, I need to deserialize it. The JSON looks like this: { ..., pst:['SMS','EMAIL'], ... } In Typescript, I have defined my enum class as follows: export enum Pos ...

Having trouble importing d3.js and typescript in IntelliJ?

Encountering errors in browsers' console when utilizing d3.select() in typescript code. Despite trying alternative methods like d3-timer.now(), the issue persists. As a newcomer to typescript, I am utilizing intelliJ Ultimate 2019.1. Through npm, I h ...

Encountering a problem with the mock object in Angular 11 unit testing when converting a JSON object to a TypeScript interface

Within my Angular 11 application, I am working with a JSON response and have defined an interface to match the structure of this JSON object. The JSON object looks like this: { "division": { "uid": "f5a10d90-60d6-4937-b917- ...

Can a subclass or interface delete an inherited field or method from its parent class?

Here's a scenario: interface A { a: number; x: any; } interface B extends A { b: number; } interface C { a: number; b: number; } Can we make B equal to C (excluding the x field but still extending A)? If it is possible, then how can we a ...

employing a parameterized type to accommodate a combination of two diverse items as input

I am facing a challenge with a simple use case and have been unable to find an example that covers it adequately. The situation is this: I have a function that should accept two different objects that are very similar. Therefore, I want to use a generic t ...

Issue with the proper functionality of the this.formGroup.updateValueAndValidity() method in Angular 6

Currently, I am facing an issue where I need to add or remove validators in a formGroup's controls based on certain conditions. When I try to update the validators using `formGroup.updateValueAndValidity()` for the entire form, it does not seem to wor ...

Discover the power of TypeScript's dynamic type inference in functions

Below is a function that selects a random item from an array: const randomFromArray = (array: unknown[]) => { return array[randomNumberFromRange(0, array.length - 1)]; }; My query pertains to dynamically typing this input instead of resorting to u ...

Ng-Zorro nz-range-picker resizing issue on smaller mobile screens

Currently using ng-zorro-antd 7.0.0 rc3 alongside angular 7.2.4. Encountering an issue where horizontal scrolling is not possible on mobile browsers when using the nz-range-picker. It appears that the element is too large for the screen, even though the p ...

How can I modify the join() function of an Array<MyType> in Typescript to instead return MyType instead of a string?

I am working with a specialized string type called MyType = string & { __brand: 'mytype' }. Is there a way to define an override for the Array.join method specifically for arrays of type Array<MyType> so that it returns MyType instead of s ...

What is the reason behind the file not found error encountered when running Deno with the command "echo hello"?

Exploring Deno's standard library, I encountered an issue with Deno.run - a function designed to create a new subprocess. Here is the example provided in the documentation: const p = Deno.run({ cmd: ["echo", "hello"], }); When I attempt to run ...

Adding a QR code on top of an image in a PDF using TypeScript

Incorporating TypeScript and PdfMakeWrapper library, I am creating PDFs on a website integrated with svg images and QR codes. Below is a snippet of the code in question: async generatePDF(ID_PRODUCT: string) { PdfMakeWrapper.setFonts(pdfFonts); ...

Transforming "larger" items into "smaller" items using Typescript

I am experiencing challenges when trying to assign larger objects into smaller ones. To illustrate this issue, I will provide a simple example: Let's say I have defined the Typescript interface: export interface CrewMember { name: string; orga ...

Unlocking the capabilities of Chrome extensions using Angular 2 and TypeScript

Currently attempting to develop a chrome extension using angular2 and typescript, I have hit a roadblock in trying to access the chrome API (in this case, chrome.bookmarks). I have successfully gained access to the chrome object by following guidance from ...

Utilizing Angular's FormGroup within a FormArray for a novel control structure

In my Angular application, I am working with a reactive form that contains a formArray of formGroups named sections: sectionForm = new FormGroup({ title: new FormControl<string>('New Section', {nonNullable: true, validators: ...

Angular: extracting value from forkJoin nested within another observable's pipe

Here is the scenario that needs to be implemented: An API call is made which returns a response containing an array of objects. The objects are then mapped to another array of objects. For each item in this new array, another API call needs to be made. Th ...

Is there a way for Ionic to remember the last page for a few seconds before session expiry?

I have set the token for my application to expire after 30 minutes, and I have configured the 401/403 error handling as follows: // Handling 401 or 403 error async unauthorisedError() { const alert = await this.alertController.create({ header: 'Ses ...

Unable to leverage vscode workspace path for the next js 13 project

I am facing an issue with TypeScript despite having the latest versions installed in my project (TypeScript 5.2.2 and @types/react 18.2.21): Next 13 — client and async server component combined: 'Promise<Element>' is not a valid JSX elem ...

Encountering issues in Angular 2 when attempting to pass data into root component through ng-content and binding. Objective: Creating a reusable form component

I currently have a .NET MVC application and I'm looking to integrate Angular 2 into it. The structure of my page is as follows: <html> <head>css imports and jquery imports</head> <body> <div> a bunch of table ...

Issue: Property is not found within the parameters of the Generic Type

Currently, I am delving into the world of Typescript and have been exploring various exercises that I stumbled upon online. However, I have encountered some trouble with the feedback on incorrect solutions. Specifically, I am facing an issue with the follo ...