/** * "normalizes" types to be compared using {@link FunctionComparisonEquals} * - converts intersections of object types to normal object types * (ie. converts `{foo: number} & {bar: number}` to `{foo: number, bar: number}`). * see [this comment](https://github.com/Microsoft/TypeScript/issues/27024#issuecomment-421529650) * - removes empty object types (`{}`) from intersections (which [actually means any non-nullish * value](https://github.com/typescript-eslint/typescript-eslint/issues/2063#issuecomment-675156492)) - see * [this comment](https://github.com/Microsoft/TypeScript/issues/27024#issuecomment-778623742) */ type FunctionComparisonEqualsWrapped = T extends (T extends NonNullish ? NonNullable : infer R) ? { [P in keyof R]: R[P]; } : never; /** * compares two types using the technique described [here](https://github.com/Microsoft/TypeScript/issues/27024#issuecomment-931205995) * * # benefits * - correctly handles `any` * * # drawbacks * - doesn't work properly with object types (see {@link FunctionComparisonEqualsWrapped}) and the fixes it applies don't work recursively */ type FunctionComparisonEquals = (() => T extends FunctionComparisonEqualsWrapped ? 1 : 2) extends () => T extends FunctionComparisonEqualsWrapped ? 1 : 2 ? true : { error: 'Types are not equal'; expected: A; actual: B; }; /** * makes `T` invariant for use in conditional types * @example * type Foo = InvariantComparisonEqualsWrapped extends InvariantComparisonEqualsWrapped ? true : false //false */ interface InvariantComparisonEqualsWrapped { } /** * compares two types by creating invariant wrapper types for the `Expected` and `Actual` types, such that `extends` * in conditional types only return `true` if the types are equivalent * # benefits * - far less hacky than {@link FunctionComparisonEqualsWrapped} * - works properly with object types * * # drawbacks * - doesn't work properly with `any` (if the type itself is `any` it's handled correctly by a workaround here but not * if the type contains `any`) */ type InvariantComparisonEquals = InvariantComparisonEqualsWrapped extends InvariantComparisonEqualsWrapped ? IsAny extends true ? IsAny extends true ? true : { error: 'Types are not equal'; expected: Expected; actual: Actual; } : true : { error: 'Types are not equal'; expected: Expected; actual: Actual; }; /** * Checks if two types are equal at the type level. * * correctly checks `any` and `never` * * **WARNING:** there are several cases where this doesn't work properly, which is why i'm using two different methods to * compare the types. see [these issues](https://github.com/DetachHead/ts-helpers/labels/type%20testing) */ export type Equals = InvariantComparisonEquals extends true ? FunctionComparisonEquals : { error: 'Types are not equal'; expected: Expected; actual: Actual; }; type IsAny = FunctionComparisonEquals; /** * any value that's not `null` or `undefined` * * useful when banning the `{}` type with `@typescript-eslint/ban-types` * @see https://github.com/typescript-eslint/typescript-eslint/issues/2063#issuecomment-675156492 */ type NonNullish = {}; export {};