Ways to determine if two types are identically matched

My initial trial is demonstrated here: (play area link)

/** Trigger a compiler error when a value is _not_ an exact type. */
declare const exactType: <T, U extends T>(
    draft?: U,
    expected?: T
) => T extends U ? T : 1 & 0

declare let a: any[]
declare let b: [number][]

// $ExpectError
exactType(a, b)

Connected reference: https://github.com/gcanti/typelevel-ts/issues/39

Answer №1

Oh, the type-level equality operator as mentioned in microsoft/TypeScript#27024. @MattMcCutchen has introduced a solution detailed in a comment on that issue, which involves using generic conditional types to determine when two types are exactly identical rather than just assignable to each other. In an ideal type system, "mutually assignable" and "equal" would be indistinguishable, but TypeScript doesn't uphold this perfectly. Specifically, the any type can be assigned to or from any other type, resulting in situations where

string extends any ? true : false
and any extends string ? true: false both yield true, despite the fact that string and any aren't the same.

Here's an implementation of IfEquals<T, U, Y, N> which returns Y if T and U are equal, and N otherwise.

type IfEquals<T, U, Y=unknown, N=never> =
  (<G>() => G extends T ? 1 : 2) extends
  (<G>() => G extends U ? 1 : 2) ? Y : N;

Let's test it out:

type EQ = IfEquals<any[], [number][], "same", "different">; // "different"

Indeed, those are recognized as distinct types. There may be other scenarios where seemingly identical types are treated differently, and vice versa:

type EQ1 = IfEquals<
  { a: string } & { b: number },
  { a: string, b: number },
  "same", "different">; // "different"!

type EQ2 = IfEquals<
  { (): string, (x: string): number },
  { (x: string): number, (): string },
  "same", "different">; // "different", as expected, but:

type EQ3 = IfEquals<
  { (): string } & { (x: string): number },
  { (x: string): number } & { (): string },
  "same", "different">; // "same"!! however they are not equivalent.
// Function intersections are order-dependent

With this type in place, we can create a function that throws an error unless the two types are equal in this manner:

/** Trigger a compiler error when a value is _not_ an exact type. */
declare const exactType: <T, U>(
  draft: T & IfEquals<T, U>,
  expected: U & IfEquals<T, U>
) => IfEquals<T, U>

declare let a: any[]
declare let b: [number][]

// $ExpectError
exactType(a, b) // error

Each argument is combined with IfEquals<T, U> to ensure there is an error unless T and U are identical. This provides the desired outcome, I believe.

Note that optional parameters for this function have been omitted. The absence of these optional parameters could potentially weaken the check:

declare let c: string | undefined
declare let d: string
exactType(c, d) // no error if optional parameters!

You can decide whether this is significant or not.

Answer №2

If you are seeking a TypeScript solution that is free from any external libraries, the following code should meet your needs

export function validate<T extends never>() {}
type TypeCheckGuard<A,B> = Exclude<A,B> | Exclude<B,A>;

To use:

validate<TypeCheckGuard<{var1: string}, {var1:number}>>(); // output: error
validate<TypeCheckGuard<{var1: string}, {var1:string}>>(); // output: no error

Answer №3

Update: You can find the most polished version here

Presented here is the strongest solution I have discovered so far:

// prettier-ignore
type Exact<A, B> = (<T>() => T extends A ? 1 : 0) extends (<T>() => T extends B ? 1 : 0)
    ? (A extends B ? (B extends A ? unknown : never) : never)
    : never

/** Fails when `actual` and `expected` have different types. */
declare const exactType: <Actual, Expected>(
    actual: Actual & Exact<Actual, Expected>,
    expected: Expected & Exact<Actual, Expected>
) => Expected

Credit to @jcalz for guiding me in the right direction!

Answer №4

Check out my latest creation, a toolkit called tsafe, which enables you to achieve just that.

https://i.sstatic.net/tGC2u.gif

Big thanks to @jcalz for the guidance and inspiration on this project!

Answer №5

The lack of explanations in the other options was a source of annoyance for me as they simply returned false without any context.

To address this issue, I came up with a solution tailored to my specific needs which provided clear and comprehensible error messages:

type A = { size: number };
type B = { size: string };
type C = { size: boolean };

const check = <D, E extends D, F extends E>() => {}

/** This test passes successfully */
check<A, B, A>(); 

/**
 * This example demonstrates graceful failure:
 * Type 'C' does not match the constraints of 'A'.
 * The types of the 'size' property are incompatible.
 * The type 'boolean' cannot be assigned to type 'number'.
 */
check<A, C, A>();

Answer №6

An alternative implementation of the Equals function that shows promise, although not without flaws, is as follows:

type CheckEquality<A, B> = _CheckHalf<A, B> extends true ? _CheckHalf<B, A> : false;

type _CheckHalf<A, B> = (
    A extends unknown
        ? (
              B extends unknown
                  ? A extends B
                      ? B extends A
                          ? keyof A extends keyof B
                              ? keyof B extends keyof A
                                  ? A extends object
                                      ? _DeepCheck<A, B, keyof A> extends true
                                          ? 1
                                          : never
                                      : 1
                                  : never
                              : never
                          : never
                      : never
                  : unknown
          ) extends never
            ? 0
            : never
        : unknown
) extends never
    ? true
    : false;

type _DeepCheck<A, B extends A, K extends keyof A> = (
    K extends unknown ? (CheckEquality<A[K], B[K]> extends true ? never : 0) : unknown
) extends never
    ? true
    : false;

This approach encounters difficulties with scenarios like

CheckEquality<[any, number], [number, any]>
.

Discovered at: https://github.com/Microsoft/TypeScript/issues/27024#issuecomment-845655557

Answer №7

To effectively solve various issues, we must tailor our approaches accordingly. When dealing with comparing numbers to any value, utilizing typeof() can be beneficial.

Alternatively, when the situation involves comparing interfaces, consider implementing the following method:

function instanceOfA(object: any): object is A {
    return 'member' in object;
}

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

Conceal or remove disabled years in Angular Material datepicker

I previously disabled years prior to 2018, but now I would like to hide or delete them. The year selection range currently starts from 1998, but it should begin at 2018 instead. Is there a way to display only 3-4 years instead of the current 24-year rang ...

Troubleshooting History.push issue in a Typescript and React project

Currently, I'm tackling a project using React and TypeScript, but I've encountered a problem. Whenever I attempt to execute a history.push function, it throws an error that reads: Uncaught (in promise) TypeError: history.push is not a function. ...

Creating a TypeScript client using NSwag with named properties: A step-by-step guide

Our TypeScript client is created from a swagger interface using NSwag. The resulting client code typically looks like this: client.EndPointFoo(arg1, arg2, arg3, ...) However, we encounter issues when NSwag changes the order of arguments in response to mo ...

Converting an array of object values to an Interface type in Typescript

In my JSON document, I have an array named dealers that consists of various dealer objects like the examples below: "dealers" : [ { "name" : "BMW Dealer", "country" : "Belgium", "code" : "123" }, { "name" : ...

Typescript mistakenly labels express application types

Trying to configure node with typescript for the first time by following a tutorial. The code snippet below is causing the app.listen function to suggest incorrectly (refer to image). import express from 'express'; const app = express(); app.li ...

Breaking down nested arrays in typescript

After receiving a response from the service, the data included the following: "rows": [ [ "stravi/aa", "202001", "59", "51", "2558.98", "0.5358894453719162", "1.9204668112983725", "140", "2.346630 ...

What steps should I take to ensure that this test covers all possible scenarios?

I recently started learning React development and am currently exploring testing with Jest and React Testing Library (RTL). However, I'm facing challenges in achieving complete test coverage for the component code below: import { CustomCardActions, ...

Discovering ways to align specific attributes of objects or target specific components within arrays

I am trying to compare objects with specific properties or arrays with certain elements using the following code snippet: However, I encountered a compilation error. Can anyone help me troubleshoot this issue? type Pos = [number, number] type STAR = &quo ...

Can you explain the concept of being "well-typed" in TypeScript?

The website linked below discusses the compatibility of TypeScript 2.9 with well-defined JSON. What exactly does "well-typed" JSON mean? As far as I understand, JSON supports 6 valid data types: string, number, object, array, boolean, and null. Therefore, ...

Encountering an issue with the 'createObjectURL' function in URL, resulting in overload resolution failure when using npm file-saver

While working on my angular app, I encountered a situation where I needed to download user details uploaded as a Word document to my local machine using the angular app. Successfully, I was able to upload and save this data to my database, getting its byte ...

The attribute 'XXX' is not found within the type 'IntrinsicAttributes & RefAttributes<any>'

My coding hobby currently involves working on a React website using TypeScript. I recently came across a free Material UI template and decided to integrate it into my existing codebase. The only challenge is that the template was written in JavaScript, cau ...

Tips for patiently waiting for a method to be executed

I have encountered a situation where I need to ensure that the result of two methods is awaited before proceeding with the rest of the code execution. I attempted to use the async keyword before the function name and await before the GetNavigationData() me ...

Guide to mocking the 'git-simple' branchLocal function using jest.mock

Utilizing the simple-git package, I have implemented the following function: import simpleGit from 'simple-git'; /** * The function returns the ticket Id if present in the branch name * @returns ticket Id */ export const getTicketIdFromBranch ...

Creating dynamic dxi-column with different data types in dxDataGrid

Our team is currently working on an angular application that involves displaying records in a dxdatagrid. The challenge we are facing includes: Different schema each time, with data coming from various tables. The need to add/edit records. Displayi ...

Store Angular 17 control flow in a variable for easy access and manipulation

Many of us are familiar with the trick of "storing the conditional variable in a variable" using *ngIf="assertType(item) as renamedItem" to assign a type to a variable. This technique has always been quite useful for me, as shown in this example: <ng-t ...

Nesting objects within arrays using Typescript Generics

Hello, I am currently attempting to assign the correct type to an object with nested values. Here is a link to the code in the sandbox: https://codesandbox.io/s/0tftsf interface Product { name: string, id: number productList?:ProductItem[] } interf ...

In the application I'm developing, I'm utilizing React alongside TypeScript, making use of the useContext and useReducer hooks. However, I've encountered an issue where the dispatch function is not being

Currently, I have implemented a feature where two lists are displayed as cards based on one main list of objects. The goal is to toggle the 'favorite' value for each card item when the star button is clicked. This action should move the favorited ...

The React state remains stagnant and does not receive any updates

Although there have been numerous questions on this topic before, each one seems to be unique and I haven't found a close match to my specific issue. In my scenario, I have a grid containing draggable ItemComponents. When an item is selected, additio ...

Dynamic addition of script to <head> element in Angular is a common task for many developers

I have explored some examples that illustrate how to dynamically add a script with a URL in Angular However, I am interested in adding this type of content to the <head> <script> !function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(! ...

Creating an overloaded callable interface using TypeScript

The thread on implementing a callable interface provides some helpful information, but it doesn't fully address my specific query. interface lol { (a: number): (b: number) => string // (a: string): (b: string) => string // overloaded wi ...