Changing the type of a `let` variable will revert back to its original type when it is passed to a closure

Can you explain why foobar reverts to type Foobar when referenced from the function passed to filter in the code snippet below?

type Foo = { type: "foo", foo: number };
type Bar = { type: "bar", bar: number };
type Foobar = Foo | Bar;

const foobars: Foobar[] = [
  { type: "foo", foo: 42 },
  { type: "bar", bar: 43 },
];

const numbers = [40, 41, 42, 43, 44];

function logFoo(foo: Foo) {
  console.log(foo.foo);
}

for (let foobar of foobars) {
  if (foobar.type === "foo") {
    console.log(foobar.foo); // foobar is Foo
    logFoo(foobar); // OK
    console.log(numbers.filter(x => x < foobar.foo)); // Property 'foo' does not exist on type 'Foobar'
  }
}

If you modify let foobar of foobars to const foobar of foobars, the type error disappears.

Answer №1

When you're looking for a quick solution, the answer is simple: use const.

for (const foobar of foobars) {
  if (foobar.type === "foo") {
    console.log(foobar.foo); // fb is Foo
    logFoo(foobar); // OK
    console.log(numbers.filter(x => x < foobar.foo)); // Property 'foo' does not exist on type 'Foobar'
  }
}

A const variable, also known as 'final' in Java, cannot be reassigned. This provides strong guarantees. Within the if statement, we can be absolutely certain that the value of foobar is of type Foo. Since it will never change, this information holds true within any closures inside the if statement, giving us the expected behavior.

Now let's talk about let.

for (let foobar of foobars) {
  if (foobar.type === "foo") {
    console.log(foobar.foo); // fb is Foo
    logFoo(foobar); // OK
    console.log(numbers.filter(x => x < foobar.foo)); // Property 'foo' does not exist on type 'Foobar'
  }
}

A let-bound variable is mutable. TypeScript understands that within the if statement, the value hasn't changed. However, when passed to a closure, there is a risk of it being modified elsewhere in the codebase. The closure makes the safest assumption possible by preserving the original type of the let-bound variable.

In the specific example mentioned, TypeScript could potentially infer that our foobar remains unchanged given its limited scope within a for loop without reassignments. But instead of delving deep into these specifics, TypeScript chooses caution.

If you do anticipate modifying the variable later but wish to capture its current value rather than the variable itself, you have the option to create a new const-bound variable for that purpose.

for (let foobar of foobars) {
  if (foobar.type === "foo") {
    const newFoobar = foobar;
    console.log(numbers.filter(x => x < newFoobar.foo)); // Property 'foo' does not exist on type 'Foobar'
  }
}

At the point of creating newFoobar, it retains the type Foo that foobar has. As a const, newFoobar always remains as type Foo. Security is assured when capturing newFoobar (as type Foo) within a closure.

Answer №2

Silvio provided an accurate answer, however, it does not address the issue in your code where you may need to utilize Bar within your loop (as noted, using const will cause foobar to be a Foo). Therefore, I suggest utilizing a forEach (or a for in with let) on foobars like this:

foobars.forEach((foobar: Foobar) => {
    if (foobar.type === "foo") {
      const myFoo: Foo = <Foo> foobar;
      logFoo(myFoo); // OK
      console.log(numbers.filter(x => x < myFoo.foo));
    } else if (foobar.type === "bar") {
      const myBar: Bar = <Bar> foobar;
      console.log(numbers.filter(x => x > myBar.bar));
    }
});

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

Error encountered during Typescript compilation: Type 'void' cannot be assigned to type 'Item[]'

Below are my typescript functions. When I edit in vscode, the second function does not show any error message. However, upon compilation, an error is displayed for the second function: error TS2322: Type 'Promise<void>' is not assignable t ...

There is no index signature that includes a parameter of type 'string' in the specified type

I have a background in mobile app development and am looking to learn more about TypeScript. How can I declare a map object with the form [string:any]? The error is occurring at line: map[key] = value; Element implicitly has an 'any' type becaus ...

Faulty deduction can occur when implementing the return statement in an identity function, or when incorporating an optional parameter

Encountering strange behavior while working on identity functions in a wizard system schema. Using a constrained identity function for inference is causing issues with one property that cannot be inferred when using the following: When the return value fr ...

Cannot display data in template

After successfully retrieving JSON data, I am facing trouble displaying the value in my template. It seems that something went wrong with the way I am trying to output it compared to others. My function looks like this, getUserInfo() { var service ...

Output Scalable Vector Graphics (SVG) content on a webpage

I need to include an SVG element in my Angular 2+ code. My goal is to provide users with the option to print the SVG element as it appears on the screen. <div class="floor-plan" id="printSectionId2" (drop)="onDrop($event)" (dragover)="onDragOver ...

The element Component is not recognized even after importing the module and applying the CUSTOM_ELEMENTS_SCHEMA schema

Recently, I integrated PinchZoom into my Angular 6 project as a node module called ngx-pinch-zoom. It's important to mention that my project is also based on Ionic 4. Within my app.module.ts file, I imported the PinchZoomModule and included CUSTOM_EL ...

Error: Cookie cannot be set due to headers already being sent

Here lies my code snippet import { Request, Response } from "express"; import { database } from "firebase-admin"; async function updateAccessToken( req: Request, res: Response, db: database.Database ) { try { await db ...

Why is my RxJS timer not waiting for the specified time?

I'm diving into the world of RxJS and trying to grasp its concepts. During some testing, I encountered a puzzling issue that has me stumped. Below is the snippet in question : let item = { id: 1, name: 'chair' }; const asyncItem = timer(20 ...

There was an issue with matching the call for formatting the start date to "dd MMMM yy" in the function

Hey there, while deploying my project, I encountered this error: https://i.sstatic.net/kiXLA.png Error: No overload matches this call. Overload 1 of 4, '(value: string | number | Date): Date', resulted in the following error: Argument with ...

Why is NestJs having trouble resolving dependencies?

Recently delving into NestJs, I followed the configuration instructions outlined in https://docs.nestjs.com/techniques/database, but I am struggling to identify the issue within my code. Error: Nest cannot resolve dependencies of the AdminRepository ...

Is it possible to generate a new union type by extracting values from an existing union type?

type Cartoon = { kind: 'cat', name: 'Tom'} | { kind: 'mouse', name: 'Jerry' } type Animal = 'cat' | 'mouse' // I am trying to figure out how to create the type Animal based on the values of kin ...

The interface IJobDetails cannot be assigned to type null

https://i.sstatic.net/cVVSs.png In the code snippet below, I have created an interface called ClientState1. Now, I am attempting to define a constant named descriptionJobDetails with the type ClientState1, specifically for IJobDetails. However, I am encou ...

The input field cannot accommodate the lengthy value in the Mat Select option

When a user selects a value in my mat select, it doesn't display well in the selection box. The text wraps when the selection is opened, but once a choice is made, it gets cut off without proper spacing between the ellipses and the dropdown arrow. Th ...

How to Maintain Default Styling in Next.js with Material UI When Disabling Accordion Feature

I am currently working on a project using Next.js and incorporating Material UI for the user interface elements. One particular challenge I am facing is with an Accordion component that needs to be disabled under specific conditions, but still appear witho ...

Web performance issues noticed with Angular 8 and Webpack 3.7 rendering speed

My web application is currently taking 35 seconds to render or at least 1.15 seconds from the initial Webpack start. I've made efforts to optimize Webpack, which has improved the launch speed, but the majority of time is consumed after loading main.j ...

Having trouble with Angular's ActivatedRoute and paramMap.get('id')?

Currently, I am attempting to retrieve information from my server using the object's ID. The ID can be found in the URL as well: http://xyz/detail/5ee8cb8398e9a44d0df65455 In order to achieve this, I have implemented the following code in xyz.compo ...

How do I implement a dynamic input field in Angular 9 to retrieve data from a list or array?

I'm looking to extract all the strings from the Assignes array, which is a property of the Atm object, and populate dynamic input fields with each string. Users should be able to update or delete these strings individually. What approach can I take us ...

Use ng2-select2 directive to connect a reactive form formControlName

For managing forms in my Angular 5 project, I have implemented Reactive Form. Within the form, I integrated ng2-select2 to create a dropdown. However, when attempting to bind formControlName to the <select2></select2> directive, an error is thr ...

Using Dropbox for seamless navigation

My navigation using Dropbox is not redirecting to the selected page as expected. Below, I have provided code and a demo for your reference. App Routing Module import { NgModule } from '@angular/core'; import { CommonModule } from '@angular ...

Clicking on the <Link to=URL> in a React application built with Typescript and Redux triggers the disappearance of the component

Issue Background The application was created using npx create-react-app rrts --typescript, which sets up React, Redux, and Typescript. Problem Visualization (Content is the component with sentences) View Problem Image Here Problem Description Clicking o ...