When transferring type validation code to a separate function, Typescript throws an error stating "property does not exist on type"

While working on tests, I encountered a situation where my type validation code behaves differently based on its placement within the codebase. Specifically, when I have my error-throwing type validation code within the same function, Typescript is able to detect and handle it without any issues.

However, when I extract the type validation code into a separate function, Typescript starts throwing errors related to properties not existing, such as

Property 'vehicle' does not exist on type 'SomeParentType'
.

For example, the following code works as expected:

function appendToOutputString(obj: SomeParentType) {
  if (!isChildTypeA(obj)) {
    throw new Error("Must be of type ChildA!");
  }
  outputString += obj.vehicle;
}

But this alternative approach does not work:

function appendToOutputString(obj: SomeParentType) {
  expectIsChildTypeA(obj)
  outputString += obj.vehicle;
}

If you'd like to view the complete code, you can do so by following this link: https://stackblitz.com/edit/checking-type-in-function?file=index.ts

Alternatively, you can refer to the snippet provided below:

interface SomeParentType {
  title: string;
}

interface SomeChildTypeA extends SomeParentType {
  vehicle: string;
}

interface SomeChildTypeB extends SomeParentType {
  animal: string;
}

let outputString = "";

function isChildTypeA(childType: SomeParentType): childType is SomeChildTypeA {
  return "vehicle" in childType;
}

function expectIsChildTypeA(obj: any) {
  if (!isChildTypeA(obj)) {
    throw new Error("Must be of type ChildA!");
  }
}

function appendToOutputString(obj: SomeParentType) {
  // if (!isChildTypeA(obj)) {
  //   throw new Error("Must be of type ChildA!");
  // }
  expectIsChildTypeA(obj)
  outputString += obj.vehicle;  // Typescript complains!!
}

// Write TypeScript code!
const appDiv: HTMLElement = document.getElementById("app");
appDiv.innerHTML = `<h1>${outputString}</h1>`;

Answer №1

The compiler has the ability to identify when a variable is narrower in type than its annotated or inferred type in specific sections of the code by conducting a control flow-based type analysis. In the code snippet below, the compiler can determine that obj must be of type SomeChildTypeA if the control flow leads to the outputString += obj.vehicle statement, eliminating any errors:

if (!isChildTypeA(obj)) {
  throw new Error("Must be of type ChildA!");
}
outputString += obj.vehicle;  // no error

However, restructuring the code to perform checks in another function does not produce the same result. The compiler generally does not follow control flow into functions and methods during analysis, as outlined in Microsoft/TypeScript#9998 on GitHub. Due to the limitations in simulating all potential inputs and control flow paths through function calls, the compiler employs a heuristic that disregards the impact of function calls on variable types. As a result, the call to expectIsChildTypeA(obj) does not alter the type of obj, triggering an error about obj.vehicle.


Thankfully, TypeScript 3.7 introduced "assertion functions", allowing you to define functions with special return types that inform the compiler of type narrowing within the function. Although the compiler does not infer these function signatures automatically, you can manually indicate that defined() asserts a specific characteristic about its argument:

function expectIsChildTypeA(obj: any): asserts obj is SomeChildTypeA {
  if (!isChildTypeA(obj)) {
    throw new Error("Must be of type ChildA!");
  }
}

The return type of expectIsChildTypeA() is asserts obj is SomeChildTypeA, indicating that obj will be validated as SomeChildTypeA if the function completes successfully. Implementing this corrects the issue in the original example:

expectIsChildTypeA(obj)
outputString += obj.vehicle;  // no error

Great job! Hopefully this explanation is beneficial. Good luck!

Click here for the code 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

Establish a connection between a React variable and state management

In my codebase, I have an external module file named Task.ts. It contains the following: const taskList: Task[] = []; Class Task { ... } export { Task, taskList } The taskList is a list of Task objects that can be modified by the Task class. Now, i ...

Tips for preventing error TS2345 when importing TypeScript components from outside the project directory in Vue

Encountered the following TypeScript error when attempting to use a component outside of the project directory: TS2345: Argument of type '{ template: string; components: { SimpleCheckbox: typeof SimpleCheckbox; }; }' is not assignable to paramet ...

What is the best way to test the SSM getParameter function using Jasmine?

Is there a way to effectively test this? const ssmParameterData = await ssm.getParameter(params, async (error, data) => { if (error) throw error; return data; }).promise(); I have attempted mocking the method by doing: spyOn(ssm, 'getParameter& ...

Utilize string variables within TypeScript's enumeration feature

Can string variables be used in enums in TypeScript? Strings can be used in enum like so: enum AllDirections { TOP = 'top', BOTTOM = 'bottom', LEFT = 'left', RIGHT = 'right', } However, trying to use variab ...

What is the reason for the lack of functionality of the "unique" field when creating a schema?

I've created a schema where the username field should be unique, but I'm having trouble getting it to work (The "required" constraint is functioning correctly). I've tried restarting MongoDB and dropping the database. Any idea what I might b ...

Encountering issues with integrating interactjs 1.7.2 into Angular 8 renderings

Currently facing challenges with importing interactive.js 1.7.2 in Angular 8. I attempted the following installation: npm install interactjs@next I tried various ways to import it, but none seemed to work: import * as interact from 'interactjs'; ...

Creating a consistent template for typing TypeScript generics

Is it possible to modify a generic function so that it can accept an unlimited number of arguments and concatenate them with .'s? This function should be able to handle nested objects with any number of keys. The current code snippet works when manua ...

Is there a shortcut for creating interfaces that have identical sub properties?

We are seeking to streamline the interface creation process by utilizing shorthand for properties labeled from Monday through Sunday, each with identical sub-properties. interface Day { start: number end: number } interface Schedule { Monday: Day ...

Retrieve the service variable in the routing file

How do I access the service variable in my routing file? I created a UserService with a variable named user and I need to use that variable in my routing file. Here is the approach I tried, but it didn't work: In the routing file, I attempted: cons ...

Passing a service into a promise in Angular 2 using TypeScript

Is there a way to pass a service into a promise? I am currently working on a promise that will only resolve once all the http requests are complete. However, I am facing an issue where this.jiraService is undefined. Is there a method to pass it to the co ...

Assigning different data types with matching keys - "Cannot assign type '...' to type 'never'."

I have a question regarding my application, where I am utilizing values that can either be static or functions returning those values. For TypeScript, I have defined the static values along with their types in the following manner: type Static = { key1: ...

When using @testing-library/react (rtl), the 'waitFor' function achieves success even without the need for the 'await' keyword

waitFor() is causing my test to fail while waitFor() (without await) makes it pass. The official documentation states: Async methods return a Promise, so you must always use await or .then(done) when calling them. (https://testing-library.com/docs/guide ...

Ways to transfer specific properties from one object to another in TypeScript

I'm currently working on a function that selectively copies key-value pairs from one object to another in order to remove certain properties. The code snippet for this function is shown below: sanitizeData: function (sourceObject: object, ...allowedKe ...

Angular chat integration

In my application, I have a parent component called "chat" with two child components - "sidebar" (which displays the user list) and "conversation detail" (which shows the chat with each user). The functionality I am aiming for is that when a user is clicke ...

Error retrieving the latest token in Angular before the component has fully loaded

I am seeking relevant advice to address my specific need: In my Angular application, I have implemented a jwt-based authentication system. After obtaining a new token and refresh token, I have set up a setTimeout function to ensure the token is refreshed ...

Resolving Node.js Absolute Module Paths with TypeScript

Currently, I am facing an issue where the modules need to be resolved based on the baseUrl so that the output code is compatible with node.js. Here is my file path: src/server/index.ts import express = require('express'); import {port, database ...

How to check Internet upload speed in Angular without using a backend server?

I need help uploading a file to a folder within my Angular app's directory while it is running on localhost. I have been unable to find a solution that doesn't involve using backend technologies. For instance, I simply want to upload an image fi ...

Choosing a personalized component using document selector

Currently, I am working on an application using Stenciljs and have created a custom element like this: <custom-alert alertType="warning" alertId="warningMessage" hide>Be warned</custom-alert> The challenge arises when attem ...

Angular checkbox filtering for tables

I have a table populated with data that I want to filter using checkboxes. Below is the HTML code for this component: <div><mat-checkbox [(ngModel)]="pending">Pending</mat-checkbox></div> <div><mat-checkbox [(ngModel ...

Angular and Bootstrap project with an advanced dropdown menu featuring multiple levels

Looking to create a multi-level drop-down menu using TypeScript without relying on jQuery? Bootstrap CSS framework may not have exactly what you need. https://i.sstatic.net/iruev.png Wondering how to implement a multi-level dropdown in your Angular proje ...