From d71242aee4f6a285ed2b20da8dba03111650d2bd Mon Sep 17 00:00:00 2001 From: benz Date: Thu, 5 Jun 2025 17:50:59 +0100 Subject: [PATCH] Add `ExtendsStrict` type (#1165) Co-authored-by: Som Shekhar Mukherjee --- index.d.ts | 1 + readme.md | 1 + source/extends-strict.d.ts | 42 ++++++++++++++++++++ test-d/extends-strict.ts | 78 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 122 insertions(+) create mode 100644 source/extends-strict.d.ts create mode 100644 test-d/extends-strict.ts diff --git a/index.d.ts b/index.d.ts index d6e46694..f909a864 100644 --- a/index.d.ts +++ b/index.d.ts @@ -186,5 +186,6 @@ export type {PackageJson} from './source/package-json.d.ts'; export type {TsConfigJson} from './source/tsconfig-json.d.ts'; // Improved built-in +export type {ExtendsStrict} from './source/extends-strict.d.ts'; export type {ExtractStrict} from './source/extract-strict.d.ts'; export type {ExcludeStrict} from './source/exclude-strict.d.ts'; diff --git a/readme.md b/readme.md index 89adb0c0..128957b5 100644 --- a/readme.md +++ b/readme.md @@ -296,6 +296,7 @@ Click the type names for complete docs. ### Improved built-in +- [`ExtendsStrict`](source/extends-strict.d.ts) - A stricter, non-distributive version of `extends` for checking whether one type is assignable to another. - [`ExtractStrict`](source/extract-strict.d.ts) - A stricter version of `Extract` that ensures every member of `U` can successfully extract something from `T`. - [`ExcludeStrict`](source/exclude-strict.d.ts) - A stricter version of `Exclude` that ensures every member of `U` can successfully exclude something from `T`. diff --git a/source/extends-strict.d.ts b/source/extends-strict.d.ts new file mode 100644 index 00000000..92bc9470 --- /dev/null +++ b/source/extends-strict.d.ts @@ -0,0 +1,42 @@ +import type {IsNever} from './is-never.d.ts'; +import type {IsAny} from './is-any.d.ts'; + +/** +A stricter, non-distributive version of `extends` for checking whether one type is assignable to another. + +Unlike the built-in `extends` keyword, `ExtendsStrict`: + +1. Prevents distribution over union types by wrapping both types in tuples. For example, `ExtendsStrict` returns `false`, whereas `string | number extends number` would result in `boolean`. + +2. Treats `never` as a special case: `never` doesn't extend every other type, it only extends itself (or `any`). For example, `ExtendsStrict` returns `false` whereas `never extends number` would result in `true`. + +@example +``` +import type {ExtendsStrict} from 'type-fest'; + +type T1 = ExtendsStrict; +//=> false + +type T2 = ExtendsStrict; +//=> false + +type T3 = ExtendsStrict; +//=> true + +type T4 = ExtendsStrict; +//=> true + +type T5 = ExtendsStrict; +//=> true +``` + +@category Improved Built-in +*/ +export type ExtendsStrict = + IsAny extends true + ? true + : IsNever extends true + ? IsNever + : [Left] extends [Right] + ? true + : false; diff --git a/test-d/extends-strict.ts b/test-d/extends-strict.ts new file mode 100644 index 00000000..8d93bc0d --- /dev/null +++ b/test-d/extends-strict.ts @@ -0,0 +1,78 @@ +import {expectType} from 'tsd'; +import type {Tagged} from '../source/tagged.d.ts'; +import type {ExtendsStrict} from '../source/extends-strict.d.ts'; + +// Basic +expectType>(true); +expectType>(true); +expectType>(true); +expectType>(false); +expectType>(true); +expectType>(false); + +// Union behavior +expectType>(false); +expectType>(true); +expectType>(false); +expectType>(true); + +// Never handling +expectType>(true); +expectType>(false); +expectType>(false); + +// Any and unknown +expectType>(true); +expectType>(true); +expectType>(true); +expectType>(true); +expectType>(true); // `any` is assignable to `unknown` +expectType>(true); // `unknown` is assignable to `any` +expectType>(true); +expectType>(true); +expectType>(false); + +// Tuples +expectType>(true); +expectType>(false); +expectType>(true); + +// Objects +expectType>(true); +expectType>(false); +expectType>(false); +expectType>(true); + +// Functions +expectType void, Function>>(true); +expectType void>>(false); +expectType void, () => void>>(true); +expectType unknown, Function>>(true); + +// Intersections +expectType>(true); +expectType>(false); + +// Literal vs primitive +expectType>(true); +expectType>(false); + +// Arrays +expectType>(true); +expectType>(true); // Tuple is assignable to array +expectType>(false); // Array not assignable to fixed tuple + +// Branded types +type UserId = Tagged; + +expectType>(true); +expectType>(false); +expectType>(true); + +// Edge meta-types +expectType>(true); +expectType>(true); +expectType>(false); +expectType>(false); +expectType>(true); +expectType>(true);