Transforming a non-specific type into a container permits precarious assignments

I'm encountering an issue with the code snippet provided below. Despite having a specific type where Type<boolean> cannot be assigned to Type<true>, when wrapping it in an object type (BoxType as shown), suddenly BoxType<boolean> wrongly becomes assignable to BoxType<true>. I am unsure of what is causing this behavior and how it can be prevented.

type Type<T> = T extends true ? "true" : "false";

declare const z: Type<boolean>;
const w: Type<true> = z; // An error occurs here, which is expected since z could be "false"

type BoxType<T> = {
    value: Type<T>
}

const x: BoxType<boolean> = {value: z}; // The variable x contains x.value, which can either be "true" or "false"
const y: BoxType<true> = x; // Is it possible that x.value is "true"?
const w2: Type<true> = y.value; // Although similar to w, this assignment surprisingly satisfies TypeScript ???

Answer №1

It seems that TypeScript is facing a limitation in this scenario.

During the type assignment checks between X and Y, the compiler sometimes skips a complete "structural check" to optimize performance. This means it doesn't fully evaluate each member of X and compare it with those of

Y</code recursively, especially when both types are based on the same parent type.</p>
<p>Sometimes, if <code>X
and Y are derived from a common type like F<T>, the variance properties of F<T> (covariant, contravariant, bivariant, invariant) play a role in shortcutting the structural check process. For example, if F<T> is covariant in T, then checking A extends B allows the direct conclusion that F<A> extends F<B>.


However, there's an issue where incorrect variance markers are assigned by the compiler for conditional types such as

type F<T> = T extends U ? X : Y
. These conditional types end up being treated as bivariant in T, which leads to unexpected outcomes during type assignments.

In the case of Type<T>, even though it appears to have a bivariance marker, it does not exhibit bivariance behavior in T:

function foo<T, U extends T>(t: Type<T>, u: Type<U>) {
  u = t; // works
  t = u; // really?
}

This is simply a current limitation in TypeScript's handling of such scenarios.


To work around this limitation, TypeScript 4.7 has introduced optional variance annotations for type parameters. By marking types as covariant or contravariant explicitly, you can guide the compiler to make more accurate decisions:

One potential solution would be to denote Type<T> as covariant in T, but unfortunately, variance annotations are only supported in specific type aliases and not directly in type parameters.

However, you can apply variance annotations on wrapper types like BoxType<T> to achieve the desired behavior:

type BoxType<out T> = { 
  value: Type<T>
}

By doing so, you can ensure correct assignability behaviors within nested type structures.

const x: BoxType<boolean> = { value: z };
const y: BoxType<true> = x; // this will trigger an error

You can experiment further with the provided code snippet using the playground link here.

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

Exploring nested JSON objects within an array using ngFor directive

My application uses Angular 6 and Firebase. I am trying to showcase a list of all appointments. Below is my approach: service.ts getRDV() { this.rdvList = this.firebase.list('/rdv'); return this.rdvList; } Model: export class RDV { key: ...

What is the best way to ensure that a class instance only receives the necessary properties?

In my code, I have a function that takes in an object called DataObject and uses certain properties from it to create instances of a class. To determine which data object items should be assigned to which class properties, I use mappings in the form of a ...

The 'target' property is not found on the type 'KeyboardEventHandler<HTMLInputElement>'

My Visual Studio Code is giving me an error in my onKeyUp function when I try to access the input target and retrieve its value. import React from 'react'; import styles from './styles.module.scss'; export function Step3() { ...

Library for Typescript on npm

My project involves a collection of base classes in TypeScript. Within a web application built with React and written in TypeScript, I am looking to integrate a library called 'Plain Old TypeScript objects', which defines all the data types. Let& ...

Set panning value back to default in Ionic

I need assistance with resetting the panning value. Essentially, I would like the panning value to return to 0 when it reaches -130. Below is my code snippet: swipeEvent($e) { if ($e.deltaX <= -130) { document.getElementById("button").click(); ...

What is the best way to implement global error handling for NextJS API requests?

Is there a method to set up a Global error handler that captures the stack trace of errors and sends it to an external system like NewRelic without needing to modify each individual API? This would follow the DRY principle by avoiding changes to multiple ...

What are the repercussions of labeling a function, TypeScript interface, or TypeScript type with export but never actually importing it? Is this considered poor practice or is there a potential consequence?

I find myself grappling with a seemingly straightforward question that surprisingly has not been asked before by others. I am currently immersed in a TypeScript project involving Vue, and one of the developers has taken to labeling numerous interfaces and ...

What causes parameters to be undefined when making a DELETE request in my Next.js application running on version 14.1.4?

I am encountering an issue with my DELETE mapping export async function DELETE({params} : {params: {id: string}}) { try { const loanToDelete = await prisma.loan.findUnique({ where: { id: parseInt(params.id) } }) if (!loanToDelete ...

When you import objects in Typescript, they may be undefined

I have a file called mock_event that serves as a template for creating an event object used in unit testing. Below, you can find the code snippet where this object is initialized: mock_event.ts import {Event} from "../../../models/src/event"; im ...

Tips for creating a seamless horizontal scrolling effect in Angular when hovering (automatically)

One of the components I'm working on features a gallery with an X axis orientation. <div class="gallery"> <div (mouseenter)="scrollTo('left', $event)" (mouseleave)="clearIntervalRepeater()" class="left"></div> < ...

Creating unique custom 404 error pages for specific sub-directories within NextJS using the App Router Structure

Having trouble with my custom 404 error page (= not-found.tsx) files. I have two of them, one within app/(paths) and another within app/(paths)/(jobs)/jobs/(cats). The issue is that the first not-found file should render when a user visits url example myap ...

When you extend the BaseRequestOptions, the injected dependency becomes unspecified

Implementing a custom feature, I have chosen to extend BaseRequestOptions in Angular2 to incorporate headers for every request. Additionally, I have introduced a Config class that offers key/value pairs specific to the domain, which must be injected into m ...

transform an array encoded in base64 format into a JSON object

I am encountering an issue where the base64 array I'm trying to send to an API is coming up empty. Here's a breakdown of what I'm doing: Firstly, I have an array of files with images in my code: [0: File {name: '766_generated.jpg' ...

What is the best way to choose a particular radio button from a group of radio buttons using typescript?

Is there a way to automatically select a specific radio button when an item is chosen from a dropdown menu on the webpage using a TypeScript function that is triggered by the dropdown selection? ...

Having trouble retrieving data from redux toolkit using typescript

I've been diving into the world of Typescript by building a simple todo app using React, Redux-toolkit, and Typescript. One issue I encountered is when trying to access data from the store with useSelector. The retrieved object contains the desired va ...

Angular Custom Validator Error (Validation function must either return a Promise or an Observable)

I created a personalized validator for the phone field but I'm encountering an issue The validator should be returning either a Promise or an Observable. Basically, I just want to check if the phone number is less than 10 characters. HTML Cod ...

Differences between useFormState and useForm in Next.js version 14

Currently, I am intrigued by the comparison between using the react hook useForm and the react-dom useFormState. The Nextjs documentation suggests utilizing useFormState, but in practice, many developers opt for the react hook useForm. I am grappling with ...

Leverage glob patterns within TypeScript declaration files

Utilizing the file-loader webpack plugin allows for the conversion of media imports into their URLs. For example, in import src from './image.png', the variable src is treated as a string. To inform TypeScript about this behavior, one can create ...

The presence of a setupProxy file in a Create React App (CRA) project causes issues with the starting of react-scripts,

I have implemented the setupProxy file as outlined in the documentation: const proxy = require('http-proxy-middleware'); module.exports = function (app) { app.use( '/address', proxy({ target: 'http ...

Setting up ReactJS and TypeScript in a fresh MVC5 project from scratch

After extensively trying out various tutorials, I have yet to successfully run a basic MVC5 project using TypeScript and ReactJS. For reference, I have created these projects from scratch in Visual Studio 2015 with .NET 4.6.1, using the ASP.NET Web Applic ...