Qwertiy
Qwertiy

Reputation: 21380

How to check that types are the same?

I want to check if 2 types are the same one. I expected

export type AreSame<T, U, R> = T extends U ? U extends T ? R : never : never;

to work, but actually it fails on unions:

type A = AreSame<1, 1, Object>          // Object
type B = AreSame<1, 2, Object>          // never
type C = AreSame<1, number, Object>     // never
type D = AreSame<1, 1 | 2, Object>      // Object - why???
type E = AreSame<1 | 2, 1 | 3, Object>  // Object - why???
type F = AreSame<1 | 2, 1 | 2, Object>  // Object

How can I fix that?

Upvotes: 1

Views: 731

Answers (2)

michimo
michimo

Reputation: 317

I am just taking a guess here, but if we consider a union to be similar to a logical or, this would make sense.

Let me try to pseudo code the steps it takes for the question..

1 extends (1|2) ? (1|2) extends 1 ? Object : never : never // becomes ->
(1 extends 1 || 1 extends 2) ? (1 extends 1 || 2 extends 1) ? : Object : never : never

I would consider checking the typescript code to check how it exactly works..

EDIT: To solve this problem, I found this github issue in the typescript repository

The best solution there is this:

export type Equals<X, Y> =
    (<T>() => T extends X ? 1 : 2) extends
    (<T>() => T extends Y ? 1 : 2) ? true : false;

Maybe you can adjust that for your case? Hope that helps :)

Upvotes: 4

jcalz
jcalz

Reputation: 327944

Your conditional types are accidentally distributive. That means it ends up splitting up the unions, evaluating the type for each member, and unioning them back together. Please familiarize yourself with that documentation, because it's important. If you have a type like type G<T> = T extends U ? X : Y where T is a "naked" type parameter (so it's T extends and not SomethingWith<T> extends) then it will distribute. Sometimes this is what you want. Right now it's not.

The easiest way to turn off distributive conditional types is by "clothing" the naked type parameter: turn T extends U ? X : Y into [T] extends [U] ? X : Y. So:

export type AreSame<T, U, R> = [T] extends [U] ? [U] extends [T] ? R : never : never;

type A = AreSame<1, 1, Object>          // Object
type B = AreSame<1, 2, Object>          // never
type C = AreSame<1, number, Object>     // never
type D = AreSame<1, 1 | 2, Object>      // never
type E = AreSame<1 | 2, 1 | 3, Object>  // never
type F = AreSame<1 | 2, 1 | 2, Object>  // Object

Okay, hope that helps; good luck!

Link to code

Upvotes: 5

Related Questions