What causes type checks to be skipped when spreads are used on type-guarded types?

Query:

Why doesn't a compile-time error occur when I overlook adding nested fields to an object of type T, while constructing the object using object spread?

Illustration:

interface User {
  userId: number;
  profile: {
    username: string
  }
}

function updateUsername(user: User): User {
  return {
    ...user,
    profile: {
      // Error (expected)
    }
  }
}

function updateUsernameGeneric<T extends User>(user: T): T {
  return {
    ...user,
    profile: {
      // No error (unexpected... why?)
    }
  }
}

My personal theory regarding this issue:

I speculate that TypeScript allows subtypes to delete properties of their super types, which might explain why for some subtype T of User, the profile property could potentially have no properties. (If true, it's news to me that TypeScript permits this behavior...)


TypeScript Version 4.1.2

Playground

Answer №1

Understanding how generic types handle spread resolution in TypeScript is crucial. When dealing with non-generic types, the merged type inference may not align with the expected return type. For example:

{
    profile: {};
    userId: number;
}

This mismatch can result in compiler errors like TS 2322, indicating missing properties in the inferred type compared to the annotated return type.

On the other hand, generics behave differently, inferring an intersection of a subtype of User and a { profile: {}; } type:

T & {
    userId: string;
    profile: {};
}

This extension is accepted by the compiler since it includes all properties from both types involved.


The behavior when using generics raises debates due to scenarios where unexpected outcomes occur without triggering errors:

function updateUsernameGeneric<T extends User>(user: T): T {
  const newUser = {
    ...user,
    userId: "234",
    profile: {
      // No error (unexpected... why?)
    }
  }

  return newUser;
}

updateUsernameGeneric({ profile: { username: "John" }, userId: 123 }).userId //valid, "number"

To manage incompatible property types more effectively, omitting return type annotations allows TypeScript to deduce them as never if necessary:

function updateUsernameGenericFixed<T extends User>(user: T) {
  const newUser = {
    ...user,
    userId: "234",
    profile: {
      // No error (unexpected... why?)
    }
  }

  return newUser;
}

updateUsernameGenericFixed({ profile: { username: "John" }, userId: 123 }).userId //never

Try it out on the Playground

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

Utilizing a package from your local computer

Due to my current circumstances, I am unable to utilize the npm publish command to release a package on the internet and subsequently use npm install. I also have no intention of publishing it on a remote server like nexus. Is there a method available to ...

Angular Animation not smoothly transitioning on Internet Explorer 11 and Firefox, but functioning correctly on Chrome

Chrome seems to be handling my Angular animation attached to a div (where the height goes from 0 to '*') just fine. I've made sure to import all necessary polyfills and install web-animations-js. However, when it comes to IE and Firefox, th ...

Angular is not providing the anticipated outcome

I'm new to Angular (7) and I'm encountering an issue while trying to retrieve the status code from an HTTP request. Here's the code snippet used in a service : checkIfSymbolExists() { return this.http.get(this.url, { observe: 'res ...

Construct an outdated angular project from scratch

I'm facing an issue with an old Angular project that I'm trying to build. After pulling down the code, running npm install @angular/<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="fc9f9095bccdd2cbd2c8">[email p ...

Retrieve an object containing properties specified in the function's parameter list

I am currently working on developing a function that can dynamically create and return an object with properties based on an array of strings provided as parameters. type Foods = 'apple' | 'banana' | 'pear'; function foodObje ...

What is the most secure method to define options and retrieve their values in a type-safe manner?

I am currently utilizing a library that offers an interface with a great deal of flexibility. type Option = number | { x?: number; y?: number; z?: number; } interface Options { a?: Option; b?: Option; c?: Option; d?: Option; } function init ...

Bringing in Chai with Typescript

Currently attempting to incorporate chai into my typescript project. The javascript example for Chai is as follows: var should = require('chai').should(); I have downloaded the type definition using the command: tsd install chai After refere ...

Having trouble obtaining search parameters in page.tsx with Next.js 13

Currently, I am in the process of developing a Next.js project with the Next 13 page router. I am facing an issue where I need to access the search parameters from the server component. export default async function Home({ params, searchParams, }: { ...

Sequentially arranged are the assignments in Firebase functions

I recently came across an article on Firebase task functions published by Google, where it was mentioned that tasks should be dispatched in a first-in-first-out (FIFO) order. Despite trying different settings, the tasks are not being processed in the corre ...

What are the reasons for the inability to send form-data in Postman?

Encountering an issue when trying to send form-data in postman as Sequelize returns an error: value cannot be null However, everything works fine when sending a raw request with JSON. Have tried using body-parser and multer, but no luck. This is my inde ...

TypeScript: Defining a custom object type using an array of objects

Is there a way to dynamically generate an object based on the name values in an array of objects? interface TypeA { name: string; value: number; } interface TypeB { [key: string]: { value: any }; } // How can we create an OutputType without hard ...

Component coding in Angular 2 allows for seamless integration and customization of Material

I am looking to initiate the start.toggle() function (associated with Angular 2 material md-sidenav-layout component) when the test() method is triggered. How can I execute md-sidenav-layout's start.toggle() in the app.component.ts file? app.componen ...

The useEffect hook is triggering multiple unnecessary calls

Imagine a tree-like structure that needs to be expanded to display all checked children. Check out this piece of code below: const { data } = useGetData(); // a custom react-query hook fetching data from an endpoint Now, there's a function that fin ...

Building a PathString Tree

I encountered a similar issue like the one discussed in this post (Get a tree like structure out of path string). I attempted to implement the suggested solution but I am facing difficulties getting it to work within an Angular context. The concept involv ...

A more efficient approach to specifying types for the 'navigation' object in React Native's Stack Navigator

Struggling with modularizing prop types in React Navigation, particularly when using TypeScript. The Typescript section of the React Navigation documentation suggests defining types for each screen props in this manner: //types.ts export type RootStackPara ...

Tips for executing a function when nearing the bottom of a scroll:

I have incorporated the angular2-infinite-scroll plugin, specifically version 0.1.4. You can view my plunker here. Currently, the function onScrollDown() only runs once at the beginning when scrolling. I attempted to adjust the values for infiniteScroll ...

Using TypeScript to pass objects to an arrow function

Issue at Hand: How do I successfully transfer an object from a parent component to a child component that is derived from the same interface? I am currently facing difficulties in rendering a list of tasks by looping through a list of task objects. The ma ...

What is the method for extracting user input from a text box on a webpage?

Having trouble with retrieving the value from a text box in my search function. SearchBar Component import { Component, OnInit, Input } from '@angular/core'; @Component({ selector: 'app-search', templateUrl: './search.compon ...

Unable to choose Typescript as a programming language on the VSCode platform

Recently, I encountered an issue while using Visual Studio Code with TypeScript. Even though TypeScript is installed globally, it is not showing up in the list of file languages for syntax highlighting. Despite trying various troubleshooting methods such a ...

The data in the Angular variable is not persisting

After calling this function to retrieve an array of Articles, I noticed that the data is not being saved as expected. Take a look at my console output below. GetAll() { //return this.http.get<Array<Article>>(this.cfg.SERVER); this.http.get ...