Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature request]type level equal operator #27024

Closed
4 tasks done
kgtkr opened this issue Sep 11, 2018 · 30 comments
Closed
4 tasks done

[Feature request]type level equal operator #27024

kgtkr opened this issue Sep 11, 2018 · 30 comments
Labels
Question

Comments

@kgtkr
Copy link

@kgtkr kgtkr commented Sep 11, 2018

Search Terms

  • Type System
  • Equal

Suggestion

T1 == T2

Use Cases

TypeScript type system is highly functional.
Type level testing is required.
However, we can not easily check type equivalence.
I want a type-level equivalence operator there.

It is difficult for users to implement any when they enter.
I implemented it, but I felt it was difficult to judge the equivalence of types including any.

Examples

type A = number == string;// false
type B = 1 == 1;// true
type C = any == 1;// false
type D = 1 | 2 == 1;// false
type E = Head<[1,2,3]> == 1;// true(see:#24897)
type F = any == never;// false
type G = [any] == [number];// false
type H = {x:1}&{y:2} == {x:1,y:2}// true
function assertType<_T extends true>(){}

assertType<Head<[1,2,3]> == 1>();
assertType<Head<[1,2,3]> == 2>();// Type Error

Checklist

My suggestion meets these guidelines:

  • This wouldn't be a breaking change in existing TypeScript / JavaScript code
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. new expression-level syntax)
@AlCalzone
Copy link

@AlCalzone AlCalzone commented Sep 11, 2018

Here's a working implementation:

/**
 * Tests if two types are equal
 */
export type Equals<T, S> =
	[T] extends [S] ? (
		[S] extends [T] ? true : false
	) : false
;

The only problem is that any is "equal to" everything, except never.

@kgtkr
Copy link
Author

@kgtkr kgtkr commented Sep 11, 2018

@AlCalzone
I know that.(https://github.com/kgtkr/typepark/blob/master/src/test.ts)
There is a problem of not being able to judge any.

example:

type X=Equals<{x:any},{x:number}>;//true

@DanielRosenwasser
Copy link
Member

@DanielRosenwasser DanielRosenwasser commented Sep 11, 2018

any is not assignable to never, so you should be able to determine whether or not either side is exclusively any.

@mattmccutchen
Copy link
Contributor

@mattmccutchen mattmccutchen commented Sep 15, 2018

Here's a solution that makes creative use of the assignability rule for conditional types, which requires that the types after extends be "identical" as that is defined by the checker:

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

This passes all the tests from the initial description that I was able to run except H, which fails because the definition of "identical" doesn't allow an intersection type to be identical to an object type with the same properties. (I wasn't able to run test E because I don't have the definition of Head.)

@RyanCavanaugh RyanCavanaugh added the Question label Sep 21, 2018
@kgtkr
Copy link
Author

@kgtkr kgtkr commented Sep 22, 2018

Thank you
There was a way

@aleclarson
Copy link

@aleclarson aleclarson commented Apr 11, 2019

The best solution I have to date: spec.ts

Examples

@jituanlin
Copy link

@jituanlin jituanlin commented Jul 9, 2019

Here's a solution that makes creative use of the assignability rule for conditional types, which requires that the types after extends be "identical" as that is defined by the checker:

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

This passes all the tests from the initial description that I was able to run except H, which fails because the definition of "identical" doesn't allow an intersection type to be identical to an object type with the same properties. (I wasn't able to run test E because I don't have the definition of Head.)

It work, but how?
Could you provide more explanation?
I try to explain it though by Typescript's bivariant behavior or something else.
But I failed, help, pls.

@fatcerberus
Copy link

@fatcerberus fatcerberus commented Jul 12, 2019

@jituanlin AFAIK it relies on conditional types being deferred when T is not known. Assignability of deferred conditional types relies on an internal isTypeIdenticalTo check, which is only true for two conditional types if:

  • Both conditional types have the same constraint
  • The true and false branches of both conditions are the same type

@weakish
Copy link

@weakish weakish commented Dec 22, 2019

@AlCalzone It seems that function overloads do not work.

type F = (x: 0, y: null) => void
type G = (x: number, y: string) => void

type EqEq<T, S> = [T] extends [S] ? ([S] extends [T] ? true : false) : false

// Function type intersection is defined as overloads in TypeScript.
type OF1 = EqEq<F & G, G & F> // true
type OF3 = EqEq<{ (x: 0, y: null): void; (x: number, y: null): void }, { (x: number, y: null): void; (x: 0, y: null): void }> // true

@mattmccutchen

Function overloads works:

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

type OF4 = EqEqEq<{ (x: 0, y: null): void; (x: number, y: null): void }, { (x: number, y: null): void; (x: 0, y: null): void }> // false

But function type intersection does not work:

type OF2 = EqEqEq<F & G, G & F> // true

@AnyhowStep
Copy link
Contributor

@AnyhowStep AnyhowStep commented Mar 10, 2020

Can we re-open this or something? Because there isn't a way =(

@AnyhowStep
Copy link
Contributor

@AnyhowStep AnyhowStep commented Jun 15, 2020

#37314 (comment)

Seems like it's fixed in master, with TS 4.0 as the milestone.

So, this doesn't need to be re-opened, I suppose. I forgot to link to that comment sooner, my bad.

@ldqUndefined
Copy link

@ldqUndefined ldqUndefined commented Sep 9, 2020

@jituanlin AFAIK it relies on conditional types being deferred when T is not known. Assignability of deferred conditional types relies on an internal isTypeIdenticalTo check, which is only true for two conditional types if:

  • Both conditional types have the same constraint
  • The true and false branches of both conditions are the same type

where can I find the infomations about the internal 'isTypeIdenticalTo' check? I can't find anything in the typescript official website....

@weakish
Copy link

@weakish weakish commented Sep 9, 2020

@ldqUndefined I do not remember it well, but you may find it in the source code (or not, since TypeScript source code changed a lot).

@gogoyqj
Copy link

@gogoyqj gogoyqj commented Feb 13, 2021

Here's a solution that makes creative use of the assignability rule for conditional types, which requires that the types after extends be "identical" as that is defined by the checker:

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

This passes all the tests from the initial description that I was able to run except H, which fails because the definition of "identical" doesn't allow an intersection type to be identical to an object type with the same properties. (I wasn't able to run test E because I don't have the definition of Head.)

not work for:

type ExampleV51 = Equals<1 | number & {}, number>; // supposed to be true, but false got

@wvanvugt-speedline
Copy link

@wvanvugt-speedline wvanvugt-speedline commented May 21, 2021

I devised a type which seems to work for all scenarios, including intersection types. While it lacks the elegance of the other solutions, it appears to perform better than either based on the tests.

type Equals<A, B> = _HalfEquals<A, B> extends true ? _HalfEquals<B, A> : false;

type _HalfEquals<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
                                      ? _DeepHalfEquals<A, B, keyof A> extends true
                                          ? 1
                                          : never
                                      : 1
                                  : never
                              : never
                          : never
                      : never
                  : unknown
          ) extends never
            ? 0
            : never
        : unknown
) extends never
    ? true
    : false;

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

Here is a TypeScript Playground link demonstrating the functionality on all the test cases. If anyone knows how to optimize this code without sacrificing functionality, I'd love to hear.

@JomoPipi
Copy link

@JomoPipi JomoPipi commented May 23, 2021

@wvanvugt-speedline
Nice, but it fails with this:

assertNotType<Equals<[any, number], [number, any]>>();

Here's mine (also fails there):

type And<X,Y> = X extends true ? Y extends true ? true : false : false
type IsAny<T> = 0 extends (1 & T) ? true : false

type _Equals<X,Y> = (X extends Y ? 1 : 2) extends (Y extends X ? 1 : 3) ? true : false
type _EqualTuple<X,Y,Z> = X extends [any] ? Y extends [any] ? Equals<X[number], Y[number]> : false : Z
type Equals<X,Y> = _EqualTuple<X,Y, And<_Equals<IsAny<X>, IsAny<Y>>, _Equals<X,Y>>>

playground

@tianzhich
Copy link

@tianzhich tianzhich commented Jun 3, 2021

@jituanlin AFAIK it relies on conditional types being deferred when T is not known. Assignability of deferred conditional types relies on an internal isTypeIdenticalTo check, which is only true for two conditional types if:

  • Both conditional types have the same constraint
  • The true and false branches of both conditions are the same type

where can I find the infomations about the internal 'isTypeIdenticalTo' check? I can't find anything in the typescript official website....

I found this in /node_modules/typescript/lib/typescript.js, by searching isTypeIdenticalTo. There are also some comments that may help someone here:

// Two conditional types 'T1 extends U1 ? X1 : Y1' and 'T2 extends U2 ? X2 : Y2' are related if
// one of T1 and T2 is related to the other, U1 and U2 are identical types, X1 is related to X2,
// and Y1 is related to Y2.

image

But I'm still not very clear what the related mean here? I can't understand the src code of isRelatedTo.

@tianzhich
Copy link

@tianzhich tianzhich commented Jun 3, 2021

I write some simple tests, seems A related to B means type A can extend from type B.

type Foo<X> = <T>() => T extends X ? 1 : 2

type Bar<Y> = <T>() => T extends Y ? number : number

type Related = Foo<number> extends Bar<number> ? true : false // true

type UnRelated = Bar<number> extends Foo<number> ? true : false // false

@MuYunyun
Copy link

@MuYunyun MuYunyun commented Aug 28, 2021

Here's a solution that makes creative use of the assignability rule for conditional types, which requires that the types after extends be "identical" as that is defined by the checker:

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

This passes all the tests from the initial description that I was able to run except H, which fails because the definition of "identical" doesn't allow an intersection type to be identical to an object type with the same properties. (I wasn't able to run test E because I don't have the definition of Head.)

It isn't correct for such an intersection case:

// expect true but get false
type result = Equal<{ x: 1 } & { y: 2 }, { x: 1, y: 2 }>

btw, it seems true if using such case to judge Equal<{ x: 1 } & { y: 2 }, { x: 1, y: 2 }>

type Equals<T, S> =
  [T] extends [S] ? (
    [S] extends [T] ? true : false
  ) : false

@Jayatubi
Copy link

@Jayatubi Jayatubi commented Aug 28, 2021

Someone gives a more clear explanation here: https://stackoverflow.com/a/68963796/2289969

birtles added a commit to birtles/superstruct that referenced this issue Sep 9, 2021
`boolean` is effectively the union type `true | false` but the current
`isMatch` implementation will treat overlapping union types as matching.

This patch introduces a more strict type equivalence check based on
microsoft/TypeScript#27024 (comment)
and uses it when checking types that extend `boolean` for equivalence to
`boolean`.

The test added in this patch fails without the corresponding code changes
here.

Fixes ianstormtaylor#754.
@yorisilo
Copy link

@yorisilo yorisilo commented Sep 16, 2021

Most people tend to write the type Equality as a #27024 (comment)

What do you think of this approach?

type Equals<X, Y> = [X, Y] extends [Y, X] ? true: false;

@DetachHead
Copy link
Contributor

@DetachHead DetachHead commented Sep 30, 2021

hey everybody, i've come up with a solution that seems to pass every test mentioned here.

it "normalizes" intersections (ie. converts {foo: number} & {bar: number} to {foo: number, bar: number}) as well as removes empty object types from intersections (#27024 (comment))

type EqualsWrapped<T> = T extends infer R & {}
    ? {
          [P in keyof R]: R[P]
      }
    : never

export type Equals<A, B> = (<T>() => T extends EqualsWrapped<A> ? 1 : 2) extends <
    T
>() => T extends EqualsWrapped<B> ? 1 : 2
    ? true
    : false

let me know if there's anything i'm missing, since it feels kinda hacky. btw i've added it to my ts-helpers package

@shitpostbotmin

This comment has been minimized.

ianstormtaylor pushed a commit to ianstormtaylor/superstruct that referenced this issue Oct 11, 2021
`boolean` is effectively the union type `true | false` but the current
`isMatch` implementation will treat overlapping union types as matching.

This patch introduces a more strict type equivalence check based on
microsoft/TypeScript#27024 (comment)
and uses it when checking types that extend `boolean` for equivalence to
`boolean`.

The test added in this patch fails without the corresponding code changes
here.

Fixes #754.
noahsilas added a commit to noahsilas/node-mongodb-native that referenced this issue Jan 25, 2022
With the removal of the `{ [key: string]: any }` extension on the
`Filter<TSchema>` type, we find some serious new shortcomings on the
handling of Recursive types: they break type safety for some
non-recursive types!

Failing Example
===============

Consider the case in the tests with the following schema:
```
interface TypedDoc {
  name: string;
  age: number;
  listOfNumbers: number[];
  tag: {
    name: string;
  };
}
```

The way we were detecting recursion was to check if, for a given `Type
extends Object` and a `Key` in that object, if `Type extends Type[Key]`.

Note that in the `TypedDoc` interface there is no recursive structure,
but that `TypedDoc extends TypedDoc['tag']` is true, because the set of
keys in `TypedDoc` is a superset of the keys in `TypedDoc['tag']`.

This meant that, for this simple schema, the `tag.name` property was
considered to be within a recursion, and so was not getting properly
type checked in filtering.

Solution Explanation
====================

I've added a new generic type helper, `isInUnion<A,B>` that checks if
`A` is a union type that includes `B`. We start by using the `Extract`
utility to find the set of types in `A` that are assignable to `B`. If
the only overlapping entry is `B` itself, then either:
- `A` and `B` are the same type, or
- `A` is a union type that includes `B`.

Of course, testing equality of Types is also not trivial; there is some
discussion at microsoft/TypeScript#27024,
with a solution by [@mattmccutchen](https://github.com/mattmccutchen).

This should be enough to differentiate when we have recursive
structures, even when the recursive part is inside a union.

Downsides
=========

There are several test cases for recursive data structures that become
more cumbersome with the improved type checking. Where the earlier type
check would allow querying with deeply nested recursive values, the
stricter type checks now added would require that the query author
annotate the key with a `@ts-expect-error` directive to opt-out of the
type checker.

This is likely to be somewhat irksome for folks who commonly write these
types of queries, but I believe that this is preferable to the loss of
type safety that used to exist (as these were implicitly matching an
`any` value).

Follow Ups
==========

I suspect that it is possible to write a much simpler version of the
`NestedPaths` helper; for example, [@jcalz](https://github.com/jcalz)
offers a much tighter implementation in
https://stackoverflow.com/questions/58434389/typescript-deep-keyof-of-a-nested-object/58436959#58436959
that uses a maximum depth to allow traversing recursive data structures
up to a given limit. This kind of implementation would improve the type
safety of recursive data structures at the cost of limiting the depth of
query nodes. I'm not in a position to understand the common usage of the
library, so I'm leaving this alone for future contributors to consider.
noahsilas added a commit to noahsilas/node-mongodb-native that referenced this issue Jan 25, 2022
With the removal of the `{ [key: string]: any }` extension on the
`Filter<TSchema>` type, we find some serious new shortcomings on the
handling of Recursive types: they break type safety for some
non-recursive types!

Failing Example
===============

Consider the case in the tests with the following schema:
```
interface TypedDoc {
  name: string;
  age: number;
  listOfNumbers: number[];
  tag: {
    name: string;
  };
}
```

The way we were detecting recursion was to check if, for a given `Type
extends Object` and a `Key` in that object, if `Type extends Type[Key]`.

Note that in the `TypedDoc` interface there is no recursive structure,
but that `TypedDoc extends TypedDoc['tag']` is true, because the set of
keys in `TypedDoc` is a superset of the keys in `TypedDoc['tag']`.

This meant that, for this simple schema, the `tag.name` property was
considered to be within a recursion, and so was not getting properly
type checked in filtering.

Solution Explanation
====================

I've added a new generic type helper, `isInUnion<A,B>` that checks if
`A` is a union type that includes `B`. We start by using the `Extract`
utility to find the set of types in `A` that are assignable to `B`. If
the only overlapping entry is `B` itself, then either:
- `A` and `B` are the same type, or
- `A` is a union type that includes `B`.

Of course, testing equality of Types is also not trivial; there is some
discussion at microsoft/TypeScript#27024,
with a solution by [@mattmccutchen](https://github.com/mattmccutchen).

This should be enough to differentiate when we have recursive
structures, even when the recursive part is inside a union.

Downsides
=========

There are several test cases for recursive data structures that become
more cumbersome with the improved type checking. Where the earlier type
check would allow querying with deeply nested recursive values, the
stricter type checks now added would require that the query author
annotate the key with a `@ts-expect-error` directive to opt-out of the
type checker.

This is likely to be somewhat irksome for folks who commonly write these
types of queries, but I believe that this is preferable to the loss of
type safety that used to exist (as these were implicitly matching an
`any` value).

Follow Ups
==========

I suspect that it is possible to write a much simpler version of the
`NestedPaths` helper; for example, [@jcalz](https://github.com/jcalz)
offers a much tighter implementation in
https://stackoverflow.com/questions/58434389/typescript-deep-keyof-of-a-nested-object/58436959#58436959
that uses a maximum depth to allow traversing recursive data structures
up to a given limit. This kind of implementation would improve the type
safety of recursive data structures at the cost of limiting the depth of
query nodes. I'm not in a position to understand the common usage of the
library, so I'm leaving this alone for future contributors to consider.
noahsilas added a commit to noahsilas/node-mongodb-native that referenced this issue Jan 25, 2022
With the removal of the `{ [key: string]: any }` extension on the
`Filter<TSchema>` type, we find some serious new shortcomings on the
handling of Recursive types: they break type safety for some
non-recursive types!

Failing Example
===============

Consider the case in the tests with the following schema:
```
interface TypedDoc {
  name: string;
  age: number;
  listOfNumbers: number[];
  tag: {
    name: string;
  };
}
```

The way we were detecting recursion was to check if, for a given `Type
extends Object` and a `Key` in that object, if `Type extends Type[Key]`.

Note that in the `TypedDoc` interface there is no recursive structure,
but that `TypedDoc extends TypedDoc['tag']` is true, because the set of
keys in `TypedDoc` is a superset of the keys in `TypedDoc['tag']`.

This meant that, for this simple schema, the `tag.name` property was
considered to be within a recursion, and so was not getting properly
type checked in filtering.

Solution Explanation
====================

I've added a new generic type helper, `isInUnion<A,B>` that checks if
`A` is a union type that includes `B`. We start by using the `Extract`
utility to find the set of types in `A` that are assignable to `B`. If
the only overlapping entry is `B` itself, then either:
- `A` and `B` are the same type, or
- `A` is a union type that includes `B`.

Of course, testing equality of Types is also not trivial; there is some
discussion at microsoft/TypeScript#27024,
with a solution by [@mattmccutchen](https://github.com/mattmccutchen).

This should be enough to differentiate when we have recursive
structures, even when the recursive part is inside a union.

Downsides
=========

There are several test cases for recursive data structures that become
more cumbersome with the improved type checking. Where the earlier type
check would allow querying with deeply nested recursive values, the
stricter type checks now added would require that the query author
annotate the key with a `@ts-expect-error` directive to opt-out of the
type checker.

This is likely to be somewhat irksome for folks who commonly write these
types of queries, but I believe that this is preferable to the loss of
type safety that used to exist (as these were implicitly matching an
`any` value).

Follow Ups
==========

I suspect that it is possible to write a much simpler version of the
`NestedPaths` helper; for example, [@jcalz](https://github.com/jcalz)
offers a much tighter implementation in
https://stackoverflow.com/questions/58434389/typescript-deep-keyof-of-a-nested-object/58436959#58436959
that uses a maximum depth to allow traversing recursive data structures
up to a given limit. This kind of implementation would improve the type
safety of recursive data structures at the cost of limiting the depth of
query nodes. I'm not in a position to understand the common usage of the
library, so I'm leaving this alone for future contributors to consider.
@IlyaKhD
Copy link

@IlyaKhD IlyaKhD commented Feb 18, 2022

@DetachHead, this seems to work wrong:

type A = { p: string | number; }
type B = { p: string } | { p: number; }

const expectedTrue: Equals<A, B> = true; // Type 'true' is not assignable to type 'false'

playground

@DetachHead
Copy link
Contributor

@DetachHead DetachHead commented Feb 19, 2022

@IlyaKhD I also realised some other issues with my approach, like how it doesn’t recurse into nested types when fixing intersections and empty types.

@kgtkr I would recommend re-opening this issue as there seems to be plenty of scenarios where that solution doesn’t work

@horita-yuya
Copy link

@horita-yuya horita-yuya commented Mar 3, 2022

@tianzhich
#27024 (comment)

This link helps me a lot.

But I'm still not very clear what the related mean here? I can't understand the src code of isRelatedTo.

I found the following code in checker.ts
I think type X is related to type Y if X is identical to Y or X is subtype of Y or X is assignable to Y or X is comparable to Y.

// L18401

        /**
         * Checks if 'source' is related to 'target' (e.g.: is a assignable to).
         * @param source The left-hand-side of the relation.
         * @param target The right-hand-side of the relation.
         * @param relation The relation considered. One of 'identityRelation', 'subtypeRelation', 'assignableRelation', or 'comparableRelation'.
         * Used as both to determine which checks are performed and as a cache of previously computed results.
         * @param errorNode The suggested node upon which all errors will be reported, if defined. This may or may not be the actual node used.
         * @param headMessage If the error chain should be prepended by a head message, then headMessage will be used.
         * @param containingMessageChain A chain of errors to prepend any new errors found.
         * @param errorOutputContainer Return the diagnostic. Do not log if 'skipLogging' is truthy.
         */
        function checkTypeRelatedTo(```

@DetachHead
Copy link
Contributor

@DetachHead DetachHead commented Mar 5, 2022

@DetachHead, this seems to work wrong:

type A = { p: string | number; }
type B = { p: string } | { p: number; }

const expectedTrue: Equals<A, B> = true; // Type 'true' is not assignable to type 'false'

playground

@IlyaKhD as @RyanCavanaugh pointed out on #48100 (comment), those two types are actually different. playground

@waitingsong
Copy link

@waitingsong waitingsong commented Mar 9, 2022

type EqualsWrapped<T> = T extends infer R & {}
    ? {
          [P in keyof R]: R[P]
      }
    : never

export type Equals<A, B> = (<T>() => T extends EqualsWrapped<A> ? 1 : 2) extends <
    T
>() => T extends EqualsWrapped<B> ? 1 : 2
    ? true
    : false

how about these:

const expectFalse: Equals<void, null> = false
const expectFalse2: Equals<undefined, null> = false
const expectFalse3: Equals<() => void, () => undefined> = false

playgrount

@motionrus
Copy link

@motionrus motionrus commented May 28, 2022

Вот решение, в котором обычно используются условные типы, которые требуют, чтобы типы extendsбыли «определены», как это определено методом проверки:

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

Это проходит все тесты из исходного описания, которые удалось выявить, за исключительным случаем H, который терпит неудачу, потому что определение «идентификации» не допускает пересечения структурных признаков с теми же свойствами. (Я не смог воспроизвести тест E, потому что у меня нет определений Head.)

Это редкость для такого массового охвата:

// expect true but get false
type result = Equal<{ x: 1 } & { y: 2 }, { x: 1, y: 2 }>

Кстати, это кажется правдой, если использовать такой случай для сужденияEqual<{ x: 1 } & { y: 2 }, { x: 1, y: 2 }>

type Equals<T, S> =
  [T] extends [S] ? (
    [S] extends [T] ? true : false
  ) : false

wrapper must be used to pass the test

// wrapper for passing the test
type Compute<T> = { [K in keyof T]: T[K] };
type test = Equals<Compute<{ x: 1 } & { y: 2 }>, { x: 1, y: 2 }> // get true

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Question
Projects
None yet
Development

No branches or pull requests