What is the reason behind TypeScript generics suppressing type errors?

Check out this TypeScript code snippet below.

function expectInterface(args: { foo: string }): void {}

function passthrough<T>(fields: T): T {
  return fields
}

expectInterface({
  foo: 'bar',
  badKey: 'baz', // error
})

expectInterface(passthrough({
  foo: 'bar',
  badKey: 'baz', // no error
}))

What is the reason behind passthrough suppressing the type error on badKey?

Answer №1

Due to TypeScript's structural typing system, the error concerning badKey is suppressed. In this system, compatibility between types is determined based on one type containing all the fields of another type. For more specific information, refer to the type compatibility section in the TypeScript documentation.

To simplify, let's name the interfaces as HasFoo and HasFooAndBadKey.

interface HasFoo {
  foo: string;
}

interface HasFooAndBadKey {
  foo: string;
  badKey: string;
}

The interface HasFooAndBadKey includes the field foo, making it compatible with type HasFoo. Hence, the code below is valid and compiles successfully:

const withBadKey: HasFooAndBadKey = {
  foo: "bar",
  badKey: "baz"
};

const item: HasFoo = withBadKey;

However, attempting the same assignment using a literal will result in an error stating

Object literal may only specify known properties, and 'badKey' does not exist in type 'HasFoo'.

const other: HasFoo = {
  foo: "bar",
  badKey: "baz" // Error
}

Therefore, the issue is not related to generics but to specifying known properties in literals. The generic passthrough function does not trigger a compile-time error because it returns a value that is type-compatible with HasFoo. Explicitly typing passthrough to expect a HasFoo and passing the same literal containing badKey will yield the same error.

interface HasFoo {
  foo: string;
}
interface HasFooAndBadKey {
  foo: string;
  badKey: string;
}

function expectInterface(args: HasFoo): void {}

function passthrough<T>(fields: T): T {
  return fields
}

// Expecting a literal HasFoo which can only contain a foo field
expectInterface({
  foo: 'bar',
  badKey: 'baz', // error
})

const item: HasFooAndBadKey = {
  foo: 'bar',
  badKey: 'baz'
};

expectInterface(item); // No error

// Non-literal value returned by passthrough. The non-literal
// is type compatible with HasFoo, so no error occurs.
expectInterface(passthrough<HasFooAndBadKey>({
  foo: 'bar',
  badKey: 'baz', // no error
}))

// Now, passthrough displays an error because we instructed it to expect a HasFoo,
// and this HasFoo literal cannot include any keys other than 'foo'.
expectInterface(passthrough<HasFoo>({
  foo: 'bar',
  badKey: 'baz', // error
}))

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 is the correct way to integrate nested forms using Validator and Control Value Accessor?

One challenge in my application is the need for a reusable nested form component like Address. I wanted my AddressComponent to manage its own FormGroup without the need for external input. During an Angular conference (video, presentation), Kara Erikson f ...

Angular 11 Working with template-driven model within a directive

My currency directive in Angular 8.2 formats currency fields for users by using the following code: <input [(ngModel)]="currentEmployment.monthlyIncome" currency> @Directive({ selector: '[ngModel][currency]', providers: [Curr ...

Failed to retrieve values from array following the addition of a new element

Does anyone have a solution for this problem? I recently added an element to my array using the push function, but when I tried to access the element at position 3, it wasn't defined properly processInput(inputValue: any): void { this.numOfIma ...

Determine if a variable contains only one digit in Angular 6 using Typescript

I have a specific value that I want to discuss: this.value.day It gives me a numerical output ranging from 1 to 31. My requirement is to add a leading zero if the number is less than 10. Can anyone guide me on achieving this? ...

The TypeScript error message states that a value of 'undefined' cannot be assigned to a type that expects either a boolean, Connection

I've been grappling with this code snippet for a while now. It was originally written in JavaScript a few months back, but recently I decided to delve into TypeScript. However, I'm struggling to understand how data types are properly defined in T ...

Guide on importing an external JavaScript library in Node.js utilizing a TypeScript declaration file

I am working on a Node.js project using Typescript and I am facing an issue with integrating mime.js (https://github.com/broofa/node-mime). Even though I have a declaration file available (https://github.com/borisyankov/DefinitelyTyped/blob/master/mime/mim ...

What is the best method for retrieving an item from localstorage?

Seeking advice on how to retrieve an item from local storage in next.js without causing a page rerender. Here is the code snippet I am currently using: import { ThemeProvider } from "@material-ui/core"; import { FC, useEffect, useState } from "react"; i ...

Why is it considered an error when an index signature is missing in a type?

Consider the TypeScript code snippet below: type Primitive = undefined | null | boolean | number | string; // A POJO is simply meant to represent a basic object, without any complexities, where the content is unknown. interface POJO { [key: string]: ...

Changing background color during drag and drop in Angular 2: A step-by-step guide

A drag and drop container has been created using Angular 2 typescript. The goal is to alter the background color of the drag & drop container while dragging a file into it. Typescript: @HostListener('dragover', ['$event']) public onDr ...

A guide to integrating a component into another component in Angular

Currently, I am encountering an issue with importing a component into another in my Ionic 5.0.0 application. Within my application, I have two separate modules: ChatPageModule and HomePageModule. My objective is to include the Chat template within the Hom ...

tsc converts modern ES6 code into the older ES5 version

Using TypeScript 2.2, I attempted to compile my ES6 module (js file) with the tsc compiler and it successfully converted it into valid ES5 code. Previously, I relied on tools like Google's Tracur for this task. I was unsure if this capability of compi ...

"Ensuring the right data type is selected for the onChange event

In my code, I have a simple select component set up like this. import { Controller } from "react-hook-form"; import Select, { StylesConfig } from "react-select"; //.. const [universe, setUniverse] = useState<SetStateAction<TOptio ...

Caught up: TypeScript not catching errors inside Promises

Currently, I am in the process of developing a SPFx WebPart using TypeScript. Within my code, there is a function dedicated to retrieving a team based on its name (the get() method also returns a promise): public getTeamChannelByName(teamId: string, cha ...

Creating TypeScript types using GraphQL code generation: Generating types without any other code

Utilizing the typescript plugin for graphql code generator As documented This TypeScript plugin is fundamental and capable of creating typings from GraphQLSchema, which can be leveraged by other typescript plugins. It creates types for all aspects of you ...

Exploring the narrowing capabilities of TypeScript within while loops

When I write while loops, there are times when I know for sure that a certain value exists (connection in this case), but the control flow analysis is unable to narrow it down. Here's an illustration: removeVertex(vertex: string) { const c ...

What is the best way to utilize a union of interfaces in TypeScript when working with instances?

Review this TypeScript snippet: class A {public name: string = 'A';} interface B {num: number;} class Foo { get mixed(): Array<A | B> { const result = []; for (var i = 0; i < 100; i ++) { result.push(Mat ...

Adjust puppeteer window dimensions when running in non-headless mode (not viewport)

Is there a way to adjust the browser window size to match the viewport size in Chrome(ium)? When only setting the viewport, the browser can look awkward if it is not running headfully and I want to visually monitor what's happening within the browser ...

Encountering the issue: "Property '...' is not found on the type 'typeof "...."'"

Currently, I am in the process of developing a node js application using typescript. To transpile the code, I am utilizing the gulp transpiler in commonjs mode. One of the files I've written is homeController.ts, which looks like this: let homeContr ...

Inject an asynchronous callback into the constructor of a class by leveraging TypeScript and Node with Passport integration

Currently, I am utilizing the passport-http authentication library. The issue at hand is that its documentation makes use of callbacks while my implementation involves TypeScript classes with async/await functionalities, thus causing uncertainty regarding ...

Tips for resolving the issue with the 'search input field in the header' across all pages in angular 5 with typescript

I currently have a search field in the header that retrieves a list of records when you type a search term and click on one of them. The search function utilizes debounceTime to reduce API requests, but I'm encountering an issue where the search doesn ...