Utilizing TypeScript interfaces with additional parameter object members does not result in the anticipated compilation error

Consider the different types listed below:

type Person = {
  id: string;
  name: string;
};

interface PeopleRepository {
  getPerson(query: { id: string }): Person;
}

class Repository implements PeopleRepository {
  getPerson({ id, age }: { id: string; age: number }) {
    return { id, age: age };
  }
}

const analyzeSituation = () => {
  const repository: PeopleRepository = new Repository();

  console.log(repository.getPerson({ id: "foo" }));
};

analyzeSituation();

It is important to note that the implementation method requires two properties in the input object: id and age, whereas the interface only necessitates id.

Surprisingly, this code does not generate any compilation errors!

Nevertheless, when executed, the code will encounter issues because the call to repository.getPerson fails to provide an age in its argument, resulting in it being undefined. Consequently, the line age = age will throw an error as age is undefined at runtime. This discrepancy highlights a conflict between TypeScript's assumptions and actual runtime behavior.

But why does TypeScript permit this?


Just to clarify: I am aware that this implementation contradicts the Liskov Substitution Principle (LSP). Typically, one would expect TypeScript to raise a red flag due to this violation by indicating an error during the declaration of the getPerson implementation. Specifically, warning that the method cannot adhere to the interface specifications since it mandates fields in the argument that are absent from the interface's signature.

Answer №1

The issue you are encountering is related to method parameter bivariance in TypeScript. This inconsistency in the type system allows for unsafe practices, where subclasses may require more specific arguments instead of general ones. TypeScript intentionally permits wider (safe) or narrower (unsafe) arguments, leading to unexpected behavior.

Fortunately, starting from TypeScript 2.6, a --strictFunctionTypes flag was introduced to disable function argument bivariance and only allow contravariance. However, this flag does not apply to class methods, but interfaces offer a solution by distinguishing method signatures from function-valued property signatures:

interface Foo<A, R> {
   methodSignature(arg: A): R;
   functionSignature: (arg: A) => R;
}

Both methodSignature and functionSignature represent function-like entities within a Foo, and in practice, either can be implemented with a prototype-based method or an instance-valued function property. The function-property signature complies with --strictFunctionTypes. Hence, adjusting the definition of UsersRepository as follows:

interface UsersRepository {
  getUser: (a: { id: string }) => User;
}

Will highlight the anticipated error:

class Repo implements UsersRepository {
  getUser({ id, name }: { id: string; name: string }) {
    // Error: Property 'name' is missing in type '{ id: string; }'
    // Required in type '{ id: string; name: string; }'.
    return { id, name: name.toUpperCase() };
  }
}

This lets you rectify the situation:

class FixedRepo implements UsersRepository {
  getUser({ id, name }: { id: string; name?: unknown }) {
    return {
      id,
      name: (typeof name === "string" ? name : "nobody").toUpperCase()
    };
  }
}

const illustrateProblem = () => {
  const repo: UsersRepository = new FixedRepo();
  console.log(repo.getUser({ id: "foo" }));
};

illustrateProblem(); // { id: "foo", name: "NOBODY" }

With these adjustments, hopefully, the issue will be resolved. Best of luck!

Link to code

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

Changing the name of an Angular2 import module

Deciding to improve readability, I opted to rename the @angular module to angular2 and gain a better understanding of how imports function. Prior to making this change, the systemjs.config.js appeared like so: (function(global) { var map = { ...

What is the proper way to declare static references in the Composition API with Typescript?

Currently, I am using the Composition API (with <script setup lang='ts'>) to create a ref that is utilized in my template: const searchRef = ref(null) onMounted(() => { searchRef.value.focus() }) Although it works and my code compiles w ...

Do type declaration files for NPM packages have to be in the .d.ts format?

I believe it is feasible to include type declarations in any typescript file like '.d.ts', '.ts', or '.tsx'. However, I have observed that the type declaration files for most npm packages are .d.ts files. Is this a requireme ...

The type 'typeof globalThis' does not have an index signature, therefore the element is implicitly of type 'any'. Error code: ts(7017) in TypeScript

I'm encountering an issue with my input handleChange function. Specifically, I am receiving the following error message: Element implicitly has an 'any' type because type 'typeof globalThis' has no index signature.ts(7017) when att ...

When using Typescript inheritance, the datatypes shown in IntelliSense are unexpectedly listed as "any" instead of

In my Typescript code, I have a small implementation where a class is either implementing an interface or extending another class. interface ITest { run(id: number): void } abstract class Test implements ITest { abstract run(id); } class TestEx ...

Encountering Vue linting errors related to the defineEmits function

I am encountering an issue with the linting of my Vue SPA. I am using the defineEmits function from the script setup syntactic sugar (https://v3.vuejs.org/api/sfc-script-setup.html). The error messages are perplexing, and I am seeking assistance on how to ...

Tips for utilizing interpolation for conditions instead of using *ngIf

For my application, I am using two different languages and have written them within two <option> tags. Is it possible to combine both conditions into a single <option> tag using interpolation? <option *ngIf="this.language=='en&apos ...

Dealing with enum values in Jest tests when using Prisma can be tricky. The error message "Group[] not assignable to

Here is an example of my prisma postgresql schema: model User { id Int @id @default(autoincrement()) uuid String @db.Uuid createdat DateTime @default(now()) @db.Timestamp(6) updatedat DateTime @updatedAt first ...

Guide on how to display matching options in an input box using HTML datalist based on user input at the beginning

I am a beginner in React and I am looking to create an autocomplete suggestion box for a text input field. I want the suggestions to only match the first letters of the available options, unlike the current behavior of HTML's <datalist>. Althou ...

Retrieving Vue data from parent components in a nested getter/setter context

<template> <div id="app"> {{ foo.bar }} <button @click="meaning++">click</button> <!--not reactive--> <button @click="foo.bar++">click2</button> </div> </templ ...

What is the importance of having the same data type for the searchElement in the argument for Array.prototype.includes()?

Is there an issue with my settings or is this a feature of TypeScript? Consider the code snippet below: type AllowedChars = 'x' | 'y' | 'z'; const exampleArr: AllowedChars[] = ['x', 'y', 'z']; f ...

Angular 2's ngModel feature allows for easy data binding and manipulation, particularly

I currently have an array of objects structured like this... this.survey = [ {id: 1, answer: ""}, {id: 2, answer: ""}, {id: 3, answer: ""}, {id: 4, answer: ""}, {id: 5, answer: ""}, {id: 6, answer: ""}, {id: 7, a ...

Issue encountered with Vue.js build configuration not being loaded while running on the build test server

I am working on a Vue 2 project and facing an issue with loading configuration settings from a config.json file. My router\index.ts file has the line: Vue.prototype.$config = require('/public/config.json') The config.json file contains imp ...

Is it possible to import in TypeScript using only the declaration statement?

Is there a way to use a node module in TypeScript without explicitly importing it after compilation? For example: I have a global variable declared in a file named intellisense.ts where I have: import * as fs from 'fs'; Then in another file, ...

Utilizing custom i18n blocks for Vue 3 and Vuetify 3 to enhance locale messages

Looking to localize messages separately for each component? Check out this example for Vue 2. But how can it be done for Vue 3 and Vuetify 3? Here's what I've tried: package.json "dependencies": { "@mdi/font": "6.5 ...

Recursive type analysis indicates that the instantiation of the type is excessively deep and may lead to potential infinite loops

In the process of developing a Jest utility, I have created a solution where an object mock is lazily generated as properties are accessed or called. Essentially, when a property is invoked like a method, it will utilize a jest.fn() for that specific path ...

The push() method replaces the last item in an array with another item

Two objects are available: ej= { name="", code: "", namebusinessG:"", codebusinessG:"" }; group = { name:"", code:"" } Both of these objects will be stored in two arrays: groupList:any[]=[]; ejList:any[]=[]; The program flow s ...

Change the name of the interface from the imported type

When working with Google Apps Script, I have implemented the Advanced Calendar Service, originally labeled as "Calendar". However, I have renamed it to "CalendarService". How can I incorporate this name change when utilizing the type definitions for Apps S ...

Ionic 2 faced an unresolved core-js dependency issue

Recently, I started working on a new Ionic 2 project and encountered an issue when trying to incorporate https://github.com/afrad/angular2-websocket. Upon installation, I received the warning message: UNMET PEER DEPENDENCY core-js@^2.4.1 The template pro ...

How can I change an icon and switch themes using onClick in react js?

I have successfully implemented an icon click feature to change the colorscheme of my website (in line 21 and changeTheme). However, I also want the icon to toggle between FaRegMoon and FaRegSun when clicked (switching from FaRegMoon to FaRegSun and vice v ...