Welcome toVigges Developer Community-Open, Learning,Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
1.5k views
in Technique[技术] by (71.8m points)

typescript - How to test if two types are exactly the same

Here is my first attempt: (playground 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)

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

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

Ah, the type-level equality operator. @MattMcCutchen has come up with a solution involving generic conditional types which does a decent job of detecting when two types are exactly equal, as opposed to just mutually assignable. In a perfectly sound type system, "mutually assignable" and "equal" would probably be the same thing, but TypeScript isn't perfectly sound. In particular, the any type is both assignable to and assignable from any other type, meaning that string extends any ? true : false and any extends string ? true: false both evaluate to true, despite the fact that string and any are not the same type.

Here's an IfEquals<T, U, Y, N> type which evaluates to 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 see it work:

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

Okay, those are recognized as different types. There are probably some other edge cases where two types that you think are the same are seen as different, 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"!! but they are not the same, 
// intersections of functions are order-dependent

Anyway, given this type we can make a function that generates an error unless the two types are equal in this way:

/** 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 has a type T or U (for type inference of the generic parameter) intersected with IfEquals<T, U> so that there will be an error unless T and U are equal. This gives the behavior you want, I think.

Note that the arguments of this function are not optional. I don't really know why you wanted them to be optional, but (at least with --strictNullChecks turned on) it weakens the check to do so:

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

It's up to you if that matters.

Anyway hope that helps. Good luck!


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to Vigges Developer Community for programmer and developer-Open, Learning and Share
...