From ae1e45afc06fee28a241c3fb2e36763ff6d948d3 Mon Sep 17 00:00:00 2001 From: Som Shekhar Mukherjee <49264891+som-sm@users.noreply.github.com> Date: Fri, 19 Jun 2026 22:56:32 +0530 Subject: [PATCH 1/9] fix: refactor `IfNotAnyOrNever`'s API --- source/all-extend.d.ts | 10 ++++--- source/array-reverse.d.ts | 6 ++--- source/array-tail.d.ts | 8 +++--- source/conditional-keys.d.ts | 2 +- source/exclude-exactly.d.ts | 22 +++++++-------- source/exclude-rest-element.d.ts | 8 +++--- source/exclusify-union.d.ts | 8 +++--- source/extract-exactly.d.ts | 22 +++++++-------- source/internal/array.d.ts | 2 +- source/internal/numeric.d.ts | 10 ++++--- source/internal/type.d.ts | 28 ++++++++++++------- source/is-literal.d.ts | 8 +++--- source/object-merge.d.ts | 37 +++++++++++++++----------- source/remove-prefix.d.ts | 9 +++---- source/remove-suffix.d.ts | 9 +++---- source/require-all-or-none.d.ts | 8 +++--- source/require-at-least-one.d.ts | 8 +++--- source/require-exactly-one.d.ts | 8 +++--- source/require-one-or-none.d.ts | 8 +++--- source/schema.d.ts | 8 +++--- source/some-extend.d.ts | 10 ++++--- source/split-on-rest-element.d.ts | 9 ++++--- source/string-to-array.d.ts | 6 +++-- source/string-to-number.d.ts | 2 +- source/tuple-of.d.ts | 8 +++--- test-d/internal/if-not-any-or-never.ts | 32 +++++++++++++++------- 26 files changed, 168 insertions(+), 128 deletions(-) diff --git a/source/all-extend.d.ts b/source/all-extend.d.ts index 14bf2db3b..0e07da47f 100644 --- a/source/all-extend.d.ts +++ b/source/all-extend.d.ts @@ -103,8 +103,8 @@ type B = AllExtend<[1?, 2?, 3?], number | undefined>; export type AllExtend = _AllExtend, Type, ApplyDefaultOptions>; -type _AllExtend> = IfNotAnyOrNever> = IfNotAnyOrNever extends true ? Or, IsAny>, Not> extends true // If target `Type` is also `never`, or is `any`, or `strictNever` is disabled, recurse further. @@ -113,7 +113,9 @@ type _AllExtend : false - : true, - false, false>; + : true; + ifAny: false; + ifNever: false; +}>; export {}; diff --git a/source/array-reverse.d.ts b/source/array-reverse.d.ts index a3c219e86..347704eaf 100644 --- a/source/array-reverse.d.ts +++ b/source/array-reverse.d.ts @@ -51,12 +51,12 @@ type E = ArrayReverse<[string?, number?, ...boolean[]]>; @category Array */ -export type ArrayReverse = IfNotAnyOrNever = IfNotAnyOrNever extends infer Result ? If, Readonly, Result> : never // Should never happen - : never>; // Should never happen + : never}>; // Should never happen type _ArrayReverse< TArray extends UnknownArray, diff --git a/source/array-tail.d.ts b/source/array-tail.d.ts index 8e874ef8d..1b4294d92 100644 --- a/source/array-tail.d.ts +++ b/source/array-tail.d.ts @@ -51,13 +51,13 @@ const availableTopSciFi = curry(searchBooks)('sci-fi')(4.5)(true); @category Array */ -export type ArrayTail = IfNotAnyOrNever = IfNotAnyOrNever extends infer Result ? If, Readonly, Result> : never // Should never happen - : never ->; + : never; +}>; type _ArrayTail = TArray extends readonly [unknown?, ...infer Tail] ? keyof TArray & `${number}` extends never diff --git a/source/conditional-keys.d.ts b/source/conditional-keys.d.ts index 1acc0eb03..da9591912 100644 --- a/source/conditional-keys.d.ts +++ b/source/conditional-keys.d.ts @@ -50,7 +50,7 @@ type NumberValueIndices = ConditionalKeys<[string, number?, string?], number | u @category Object */ export type ConditionalKeys = (Base extends UnknownArray ? TupleToObject : Base) extends infer _Base // Remove non-numeric keys from arrays - ? IfNotAnyOrNever<_Base, _ConditionalKeys<_Base, Condition>, keyof _Base> + ? IfNotAnyOrNever<_Base, {ifNot: _ConditionalKeys<_Base, Condition>; ifAny: keyof _Base}> : never; type _ConditionalKeys = keyof { diff --git a/source/exclude-exactly.d.ts b/source/exclude-exactly.d.ts index 5ab98c4fa..a30d8e8b1 100644 --- a/source/exclude-exactly.d.ts +++ b/source/exclude-exactly.d.ts @@ -33,25 +33,25 @@ type TestExcludeExactly3 = ExcludeExactly<{a: string} | {a: string; b: string}, @category Improved Built-in */ export type ExcludeExactly = - IfNotAnyOrNever< - Union, - _ExcludeExactly, + IfNotAnyOrNever; // If `Union` is `any`, then if `Delete` is `any`, return `never`, else return `Union`. - If, never, Union>, + ifAny: If, never, Union>; // If `Union` is `never`, then if `Delete` is `never`, return `never`, else return `Union`. - If, never, Union> - >; + ifNever: If, never, Union>; + }>; type _ExcludeExactly = - IfNotAnyOrNever, true, never> : never] extends [never] ? Union : never - : never, + : never; // If `Delete` is `any` or `never`, then return `Union`, // because `Union` cannot be `any` or `never` here. - Union, Union - >; + ifAny: Union; + ifNever: Union; + }>; export {}; diff --git a/source/exclude-rest-element.d.ts b/source/exclude-rest-element.d.ts index 21f0341a5..1f89fea11 100644 --- a/source/exclude-rest-element.d.ts +++ b/source/exclude-rest-element.d.ts @@ -27,14 +27,14 @@ type T4 = ExcludeRestElement<[number, string]>; @see {@link SplitOnRestElement} @category Array */ -export type ExcludeRestElement = IfNotAnyOrNever extends infer Result +export type ExcludeRestElement = IfNotAnyOrNever extends infer Result ? Result extends readonly UnknownArray[] ? IsArrayReadonly extends true ? Readonly<[...Result[0], ...Result[2]]> : [...Result[0], ...Result[2]] : never - : never ->; + : never; +}>; export {}; diff --git a/source/exclusify-union.d.ts b/source/exclusify-union.d.ts index 384243062..41430545b 100644 --- a/source/exclusify-union.d.ts +++ b/source/exclusify-union.d.ts @@ -125,13 +125,13 @@ type D = ExclusifyUnion<{a?: 1; readonly b: 2} | {d: 4}>; @category Object @category Union */ -export type ExclusifyUnion = IfNotAnyOrNever, Union, +export type ExclusifyUnion = IfNotAnyOrNever, Union, Extract extends infer SkippedMembers ? SkippedMembers | _ExclusifyUnion> : never - > ->; + >; +}>; type _ExclusifyUnion = Union extends unknown // For distributing `Union` ? Simplify< diff --git a/source/extract-exactly.d.ts b/source/extract-exactly.d.ts index 4c7b16043..df5be80ca 100644 --- a/source/extract-exactly.d.ts +++ b/source/extract-exactly.d.ts @@ -32,25 +32,25 @@ type TestExtractExactly3 = ExtractExactly<{a: string} | {a: string; b: string}, @category Improved Built-in */ export type ExtractExactly = - IfNotAnyOrNever< - Union, - _ExtractExactly, + IfNotAnyOrNever; // If `Union` is `any`, then if `Match` is `any`, return `any`, else return `never`. - If, Union, never>, + ifAny: If, Union, never>; // If `Union` is `never`, return `never`, doesn't matter what `Match` is. - never - >; + ifNever: never; + }>; type _ExtractExactly = - IfNotAnyOrNever, true, never> : never] extends [never] ? never : Union - : never, + : never; // If `Match` is `any` or `never`, then return `never`, // because `Union` cannot be `any` or `never` here. - never, never - >; + ifAny: never; + ifNever: never; + }>; export {}; diff --git a/source/internal/array.d.ts b/source/internal/array.d.ts index fb713f27f..7120ce349 100644 --- a/source/internal/array.d.ts +++ b/source/internal/array.d.ts @@ -111,7 +111,7 @@ type B = CollapseRestElement<[string?, string?, ...number[]]>; //=> [string | undefined, string | undefined, number] ``` */ -export type CollapseRestElement = IfNotAnyOrNever>; +export type CollapseRestElement = IfNotAnyOrNever}>; type _CollapseRestElement< TArray extends UnknownArray, diff --git a/source/internal/numeric.d.ts b/source/internal/numeric.d.ts index e813c8351..98941009a 100644 --- a/source/internal/numeric.d.ts +++ b/source/internal/numeric.d.ts @@ -26,11 +26,13 @@ type E = IsNumberLike<'a'>; //=> false */ export type IsNumberLike = - IfNotAnyOrNever; + : false; + ifAny: boolean; + ifNever: false; + }>; /** Returns the minimum number in the given union of numbers. diff --git a/source/internal/type.d.ts b/source/internal/type.d.ts index 377c456cb..546b3243d 100644 --- a/source/internal/type.d.ts +++ b/source/internal/type.d.ts @@ -1,9 +1,7 @@ -import type {If} from '../if.d.ts'; import type {IsAny} from '../is-any.d.ts'; import type {IsNever} from '../is-never.d.ts'; import type {Primitive} from '../primitive.d.ts'; import type {UnknownArray} from '../unknown-array.d.ts'; -import type {UnionToIntersection} from '../union-to-intersection.d.ts'; /** Matches any primitive, `void`, `Date`, or `RegExp` value. @@ -91,16 +89,15 @@ An if-else-like type that resolves depending on whether the given type is `any` @example ``` -// When `T` is a NOT `any` or `never` (like `string`) => Returns `IfNotAnyOrNever` branch -type A = IfNotAnyOrNever; +type A = IfNotAnyOrNever; //=> 'VALID' // When `T` is `any` => Returns `IfAny` branch -type B = IfNotAnyOrNever; +type B = IfNotAnyOrNever; //=> 'IS_ANY' // When `T` is `never` => Returns `IfNever` branch -type C = IfNotAnyOrNever; +type C = IfNotAnyOrNever; //=> 'IS_NEVER' ``` @@ -113,7 +110,7 @@ import type {StringRepeat} from 'type-fest'; type NineHundredNinetyNineSpaces = StringRepeat<' ', 999>; // The following implementation is not tail recursive -type TrimLeft = IfNotAnyOrNever : S>; +type TrimLeft = IfNotAnyOrNever : S}>; // Hence, instantiations with long strings will fail // @ts-expect-error @@ -122,7 +119,7 @@ type T1 = TrimLeft; // Error: Type instantiation is excessively deep and possibly infinite. // To fix this, move the recursion into a helper type -type TrimLeftOptimised = IfNotAnyOrNever>; +type TrimLeftOptimised = IfNotAnyOrNever}>; type _TrimLeftOptimised = S extends ` ${infer R}` ? _TrimLeftOptimised : S; @@ -130,8 +127,19 @@ type T2 = TrimLeftOptimised; //=> '' ``` */ -export type IfNotAnyOrNever = - If, IfAny, If, IfNever, IfNotAnyOrNever>>; +export type IfNotAnyOrNever = + _IfNotAnyOrNever & ( + 'ifAny' extends keyof Cases ? unknown : {ifAny: any} + ) & ( + 'ifNever' extends keyof Cases ? unknown : {ifNever: never} + )>; + +type _IfNotAnyOrNever = + IsAny extends true + ? Cases['ifAny'] + : IsNever extends true + ? Cases['ifNever'] + : Cases['ifNot']; /** Returns a boolean for whether the given type is `any` or `never`. diff --git a/source/is-literal.d.ts b/source/is-literal.d.ts index f8e5d4856..38ad0b5cb 100644 --- a/source/is-literal.d.ts +++ b/source/is-literal.d.ts @@ -114,9 +114,11 @@ type L2 = Length<`${number}`>; @category Type Guard @category Utilities */ -export type IsStringLiteral = IfNotAnyOrNever ? UnwrapTagged : S>>, - false, false>; +export type IsStringLiteral = IfNotAnyOrNever ? UnwrapTagged : S>>; + ifAny: false; + ifNever: false; +}>; export type _IsStringLiteral = // If `T` is an infinite string type (e.g., `on${string}`), `Record` produces an index signature, diff --git a/source/object-merge.d.ts b/source/object-merge.d.ts index 1276b8d93..b5c3af035 100644 --- a/source/object-merge.d.ts +++ b/source/object-merge.d.ts @@ -90,22 +90,27 @@ Note: If you want a simple merge where properties from the second object always @category Object */ export type ObjectMerge = - IfNotAnyOrNever, - NormalizedLiteralKeys, - IsExactOptionalPropertyTypesEnabled extends true ? Required : First, - IsExactOptionalPropertyTypesEnabled extends true ? Required : Second - > - : never // Should never happen - : never>, First & Second>; // Should never happen + IfNotAnyOrNever, + NormalizedLiteralKeys, + IsExactOptionalPropertyTypesEnabled extends true ? Required : First, + IsExactOptionalPropertyTypesEnabled extends true ? Required : Second + > + : never // Should never happen + : never; // Should never happen + }>; + ifAny: First & Second; + }>; type _ObjectMerge< First extends object, diff --git a/source/remove-prefix.d.ts b/source/remove-prefix.d.ts index 8fbea61be..6b6e1fbe1 100644 --- a/source/remove-prefix.d.ts +++ b/source/remove-prefix.d.ts @@ -94,14 +94,13 @@ type D = RemovePrefix<`handle${Capitalize}`, 'handle'>; @category Template literal */ export type RemovePrefix = - IfNotAnyOrNever< - S, - If< + IfNotAnyOrNever, S, _RemovePrefix> - > - >; + >; + }>; type _RemovePrefix> = Prefix extends string // For distributing `Prefix` diff --git a/source/remove-suffix.d.ts b/source/remove-suffix.d.ts index 2814eb9a8..266a27098 100644 --- a/source/remove-suffix.d.ts +++ b/source/remove-suffix.d.ts @@ -94,14 +94,13 @@ type D = RemoveSuffix<`api/${string}/analytics`, '/analytics'>; @category Template literal */ export type RemoveSuffix = - IfNotAnyOrNever< - S, - If< + IfNotAnyOrNever, S, _RemoveSuffix> - > - >; + >; + }>; type _RemoveSuffix> = Suffix extends string // For distributing `Suffix` diff --git a/source/require-all-or-none.d.ts b/source/require-all-or-none.d.ts index c4df5ade3..2a2b1784f 100644 --- a/source/require-all-or-none.d.ts +++ b/source/require-all-or-none.d.ts @@ -40,11 +40,11 @@ const responder2: RequireAllOrNone = { @category Object */ export type RequireAllOrNone = - IfNotAnyOrNever, + IfNotAnyOrNever, ObjectType, - _RequireAllOrNone, keyof ObjectType, KeysType>> - >>; + _RequireAllOrNone, keyof ObjectType, KeysType>>>; + }>; type _RequireAllOrNone = ( | RequireAll diff --git a/source/require-at-least-one.d.ts b/source/require-at-least-one.d.ts index 6fec3c6fc..0a69a81a1 100644 --- a/source/require-at-least-one.d.ts +++ b/source/require-at-least-one.d.ts @@ -29,11 +29,11 @@ export type RequireAtLeastOne< ObjectType, KeysType extends keyof ObjectType = keyof ObjectType, > = - IfNotAnyOrNever, + IfNotAnyOrNever, never, - _RequireAtLeastOne, keyof ObjectType, KeysType>> - >>; + _RequireAtLeastOne, keyof ObjectType, KeysType>>>; + }>; type _RequireAtLeastOne< ObjectType, diff --git a/source/require-exactly-one.d.ts b/source/require-exactly-one.d.ts index ea4d983c2..b93f2dea4 100644 --- a/source/require-exactly-one.d.ts +++ b/source/require-exactly-one.d.ts @@ -33,11 +33,11 @@ const responder: RequireExactlyOne = { @category Object */ export type RequireExactlyOne = - IfNotAnyOrNever, + IfNotAnyOrNever, never, - _RequireExactlyOne, keyof ObjectType, KeysType>> - >>; + _RequireExactlyOne, keyof ObjectType, KeysType>>>; + }>; type _RequireExactlyOne = {[Key in KeysType]: ( diff --git a/source/require-one-or-none.d.ts b/source/require-one-or-none.d.ts index f33041be3..b6854be7f 100644 --- a/source/require-one-or-none.d.ts +++ b/source/require-one-or-none.d.ts @@ -35,11 +35,11 @@ const responder3: Responder = { @category Object */ export type RequireOneOrNone = - IfNotAnyOrNever, + IfNotAnyOrNever, ObjectType, - _RequireOneOrNone, keyof ObjectType, KeysType>> - >>; + _RequireOneOrNone, keyof ObjectType, KeysType>>>; + }>; type _RequireOneOrNone = ( | RequireExactlyOne diff --git a/source/schema.d.ts b/source/schema.d.ts index 6bb9e581d..7ce263c98 100644 --- a/source/schema.d.ts +++ b/source/schema.d.ts @@ -91,9 +91,11 @@ const userMaskSettings: UserMask = { @category Object */ export type Schema = - IfNotAnyOrNever>, - Value, Value>; + IfNotAnyOrNever>; + ifAny: Value; + ifNever: Value; + }>; type _Schema> = Type extends NonRecursiveType | Map | Set | ReadonlyMap | ReadonlySet diff --git a/source/some-extend.d.ts b/source/some-extend.d.ts index eeb2aef86..ecc97938a 100644 --- a/source/some-extend.d.ts +++ b/source/some-extend.d.ts @@ -97,8 +97,8 @@ type B = SomeExtend<[1?, 2?, '3'?], string | undefined>; export type SomeExtend = _SomeExtend, Type, ApplyDefaultOptions>; -type _SomeExtend> = IfNotAnyOrNever> = IfNotAnyOrNever extends true ? Or, IsAny>, Not> extends true // If target `Type` is also `never`, or is `any`, or `strictNever` is disabled, return `true`. @@ -107,7 +107,9 @@ type _SomeExtend - : false, - false, false>; + : false; + ifAny: false; + ifNever: false; +}>; export {}; diff --git a/source/split-on-rest-element.d.ts b/source/split-on-rest-element.d.ts index b8ffbb4e7..185cd3b91 100644 --- a/source/split-on-rest-element.d.ts +++ b/source/split-on-rest-element.d.ts @@ -65,10 +65,11 @@ export type SplitOnRestElement< Options extends SplitOnRestElementOptions = {}, > = Array_ extends unknown // For distributing `Array_` - ? IfNotAnyOrNever - >> extends infer Result extends UnknownArray + ? IfNotAnyOrNever + >}> extends infer Result extends UnknownArray ? If, Readonly, Result> : never // Should never happen : never; // Should never happen diff --git a/source/string-to-array.d.ts b/source/string-to-array.d.ts index d5ef44c40..7e66c8f20 100644 --- a/source/string-to-array.d.ts +++ b/source/string-to-array.d.ts @@ -77,8 +77,10 @@ type E = StringToArray<`foo${string}bar`, {mapNonLiteralsDirectly: true}>; export type StringToArray = IfNotAnyOrNever< S, - _StringToArray>, - unknown[] + { + ifNot: _StringToArray>; + ifAny: unknown[]; + } >; type _StringToArray, Accumulator extends string[] = []> = diff --git a/source/string-to-number.d.ts b/source/string-to-number.d.ts index 9f6730662..521d0875f 100644 --- a/source/string-to-number.d.ts +++ b/source/string-to-number.d.ts @@ -47,7 +47,7 @@ type NumericSeparators = StringToNumber<'12_345'>; @category Numeric @category Template literal */ -export type StringToNumber = IfNotAnyOrNever, number>; +export type StringToNumber = IfNotAnyOrNever; ifAny: number}>; type _StringToNumber = S extends `${infer N extends number}` diff --git a/source/tuple-of.d.ts b/source/tuple-of.d.ts index 7842fdcc7..7ea874424 100644 --- a/source/tuple-of.d.ts +++ b/source/tuple-of.d.ts @@ -78,9 +78,11 @@ Note: If you need a readonly tuple, simply wrap this type with `Readonly`, for e @category Array */ -export type TupleOf = IfNotAnyOrNever, 0, Length>, Fill>, - Fill[], []>; +export type TupleOf = IfNotAnyOrNever, 0, Length>, Fill>; + ifAny: Fill[]; + ifNever: []; +}>; type _TupleOf = number extends Length ? Fill[] diff --git a/test-d/internal/if-not-any-or-never.ts b/test-d/internal/if-not-any-or-never.ts index 068c8a738..c2f67ba8b 100644 --- a/test-d/internal/if-not-any-or-never.ts +++ b/test-d/internal/if-not-any-or-never.ts @@ -1,12 +1,26 @@ import {expectType} from 'tsd'; import type {IfNotAnyOrNever} from '../../source/internal/index.d.ts'; -expectType({} as IfNotAnyOrNever); -expectType({} as IfNotAnyOrNever); -expectType({} as IfNotAnyOrNever); -expectType({} as IfNotAnyOrNever); -expectType({} as IfNotAnyOrNever); -expectType({} as IfNotAnyOrNever); -expectType({} as IfNotAnyOrNever); -expectType({} as IfNotAnyOrNever); -expectType({} as IfNotAnyOrNever); +expectType({} as IfNotAnyOrNever); +expectType({} as IfNotAnyOrNever); +expectType({} as IfNotAnyOrNever); +expectType({} as IfNotAnyOrNever); +expectType({} as IfNotAnyOrNever); +expectType({} as IfNotAnyOrNever); +expectType({} as IfNotAnyOrNever); +expectType({} as IfNotAnyOrNever); +expectType({} as IfNotAnyOrNever); +expectType({} as IfNotAnyOrNever); +expectType({} as IfNotAnyOrNever); +expectType({} as IfNotAnyOrNever); + +type CrashIfAnyWrapper = IfNotAnyOrNever}>; + +type CrashIfAny = 0 extends 1 & T // Check if `T` is `any` + ? CrashIfAny + : never; + +// `CrashIfAny` errors with a recursion depth error, but `CrashIfAnyWrapper` shouldn't. +// @ts-expect-error +type T1 = CrashIfAny; +type T2 = CrashIfAnyWrapper; From 11696f485e157de116ccce593640f0aacfe5cbd0 Mon Sep 17 00:00:00 2001 From: Som Shekhar Mukherjee <49264891+som-sm@users.noreply.github.com> Date: Fri, 19 Jun 2026 22:57:01 +0530 Subject: [PATCH 2/9] lint: add `member-delimiter-style` rule --- test-d/omit-deep.ts | 2 +- xo.config.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/test-d/omit-deep.ts b/test-d/omit-deep.ts index 3467943df..18b2d2957 100644 --- a/test-d/omit-deep.ts +++ b/test-d/omit-deep.ts @@ -131,7 +131,7 @@ expectType<{array: [ {a: 1; b: 2}, // 2 {b: 2}, // 3 ...Array<{a: 1; b: 2}>, -];}>(recurseIntoArray3); +]}>(recurseIntoArray3); declare const tuple: OmitDeep<{array: BaseType['tuples']}, 'array.0'>; expectType<{array: [unknown, 'bar']}>(tuple); diff --git a/xo.config.js b/xo.config.js index 6696dc0f9..63f31e187 100644 --- a/xo.config.js +++ b/xo.config.js @@ -34,6 +34,7 @@ const xoConfig = [ '@stylistic/function-paren-newline': 'off', '@stylistic/object-curly-newline': 'off', '@stylistic/curly-newline': 'off', + '@stylistic/member-delimiter-style': ['error', {multilineDetection: 'last-member'}], '@stylistic/operator-linebreak': ['error', 'before', {overrides: {'=': 'after'}}], 'n/file-extension-in-import': 'off', 'object-curly-newline': [ From 4936d4a1a9aba1a7a44eb6dcbcfa75f7266e4f07 Mon Sep 17 00:00:00 2001 From: Som Shekhar Mukherjee <49264891+som-sm@users.noreply.github.com> Date: Sat, 20 Jun 2026 12:08:07 +0530 Subject: [PATCH 3/9] fix: formatting issues --- source/internal/type.d.ts | 8 ++++---- source/remove-prefix.d.ts | 2 +- source/split-on-rest-element.d.ts | 3 ++- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/source/internal/type.d.ts b/source/internal/type.d.ts index 546b3243d..8658fe096 100644 --- a/source/internal/type.d.ts +++ b/source/internal/type.d.ts @@ -129,10 +129,10 @@ type T2 = TrimLeftOptimised; */ export type IfNotAnyOrNever = _IfNotAnyOrNever & ( - 'ifAny' extends keyof Cases ? unknown : {ifAny: any} - ) & ( - 'ifNever' extends keyof Cases ? unknown : {ifNever: never} - )>; + 'ifAny' extends keyof Cases ? unknown : {ifAny: any} + ) & ( + 'ifNever' extends keyof Cases ? unknown : {ifNever: never} + )>; type _IfNotAnyOrNever = IsAny extends true diff --git a/source/remove-prefix.d.ts b/source/remove-prefix.d.ts index 6b6e1fbe1..f655668ba 100644 --- a/source/remove-prefix.d.ts +++ b/source/remove-prefix.d.ts @@ -94,7 +94,7 @@ type D = RemovePrefix<`handle${Capitalize}`, 'handle'>; @category Template literal */ export type RemovePrefix = - IfNotAnyOrNever, S, diff --git a/source/split-on-rest-element.d.ts b/source/split-on-rest-element.d.ts index 185cd3b91..48af321d0 100644 --- a/source/split-on-rest-element.d.ts +++ b/source/split-on-rest-element.d.ts @@ -69,7 +69,8 @@ export type SplitOnRestElement< ifNot: _SplitOnRestElement< Array_, ApplyDefaultOptions - >}> extends infer Result extends UnknownArray + >; + }> extends infer Result extends UnknownArray ? If, Readonly, Result> : never // Should never happen : never; // Should never happen From 9b5483db30e226327e44a35fa48618f1b332146a Mon Sep 17 00:00:00 2001 From: Som Shekhar Mukherjee <49264891+som-sm@users.noreply.github.com> Date: Sat, 20 Jun 2026 12:14:35 +0530 Subject: [PATCH 4/9] test: add more cases --- test-d/internal/if-not-any-or-never.ts | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/test-d/internal/if-not-any-or-never.ts b/test-d/internal/if-not-any-or-never.ts index c2f67ba8b..3332d79e4 100644 --- a/test-d/internal/if-not-any-or-never.ts +++ b/test-d/internal/if-not-any-or-never.ts @@ -24,3 +24,25 @@ type CrashIfAny = 0 extends 1 & T // Check if `T` // @ts-expect-error type T1 = CrashIfAny; type T2 = CrashIfAnyWrapper; + +type CrashIfNeverWrapper = IfNotAnyOrNever}>; + +type CrashIfNever = [never] extends [T] // Check if `T` is `never` + ? CrashIfNever + : never; + +// `CrashIfNever` errors with a recursion depth error, but `CrashIfNeverWrapper` shouldn't. +// @ts-expect-error +type T3 = CrashIfNever; +type T4 = CrashIfNeverWrapper; + +type CrashIfNotAnyWrapper = IfNotAnyOrNever}>; + +type CrashIfNotAny = 0 extends 1 & T // Check if `T` is `any` + ? never + : CrashIfNotAny; + +// `CrashIfNotAny` errors with a recursion depth error, but `CrashIfNotAnyWrapper` shouldn't. +// @ts-expect-error +type T5 = CrashIfNotAny; +type T6 = CrashIfNotAnyWrapper; From c16a51dcea34beaebc45a1fbb3e51668cb74e269 Mon Sep 17 00:00:00 2001 From: Som Shekhar Mukherjee <49264891+som-sm@users.noreply.github.com> Date: Sat, 20 Jun 2026 16:03:16 +0530 Subject: [PATCH 5/9] revert: remove lint rule addition --- source/array-reverse.d.ts | 3 ++- test-d/omit-deep.ts | 2 +- xo.config.js | 1 - 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/source/array-reverse.d.ts b/source/array-reverse.d.ts index 347704eaf..e66276f57 100644 --- a/source/array-reverse.d.ts +++ b/source/array-reverse.d.ts @@ -56,7 +56,8 @@ export type ArrayReverse = IfNotAnyOrNever extends infer Result ? If, Readonly, Result> : never // Should never happen - : never}>; // Should never happen + : never; // Should never happen +}>; type _ArrayReverse< TArray extends UnknownArray, diff --git a/test-d/omit-deep.ts b/test-d/omit-deep.ts index 18b2d2957..3467943df 100644 --- a/test-d/omit-deep.ts +++ b/test-d/omit-deep.ts @@ -131,7 +131,7 @@ expectType<{array: [ {a: 1; b: 2}, // 2 {b: 2}, // 3 ...Array<{a: 1; b: 2}>, -]}>(recurseIntoArray3); +];}>(recurseIntoArray3); declare const tuple: OmitDeep<{array: BaseType['tuples']}, 'array.0'>; expectType<{array: [unknown, 'bar']}>(tuple); diff --git a/xo.config.js b/xo.config.js index 63f31e187..6696dc0f9 100644 --- a/xo.config.js +++ b/xo.config.js @@ -34,7 +34,6 @@ const xoConfig = [ '@stylistic/function-paren-newline': 'off', '@stylistic/object-curly-newline': 'off', '@stylistic/curly-newline': 'off', - '@stylistic/member-delimiter-style': ['error', {multilineDetection: 'last-member'}], '@stylistic/operator-linebreak': ['error', 'before', {overrides: {'=': 'after'}}], 'n/file-extension-in-import': 'off', 'object-curly-newline': [ From 268e093f2398f4c7553a46431d59d9aad8d7dedd Mon Sep 17 00:00:00 2001 From: Som Shekhar Mukherjee <49264891+som-sm@users.noreply.github.com> Date: Sat, 20 Jun 2026 20:19:47 +0530 Subject: [PATCH 6/9] refactor: simplify implementation --- source/internal/type.d.ts | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/source/internal/type.d.ts b/source/internal/type.d.ts index 8658fe096..9fe102d1a 100644 --- a/source/internal/type.d.ts +++ b/source/internal/type.d.ts @@ -128,17 +128,14 @@ type T2 = TrimLeftOptimised; ``` */ export type IfNotAnyOrNever = - _IfNotAnyOrNever & ( - 'ifAny' extends keyof Cases ? unknown : {ifAny: any} - ) & ( - 'ifNever' extends keyof Cases ? unknown : {ifNever: never} - )>; - -type _IfNotAnyOrNever = IsAny extends true - ? Cases['ifAny'] + ? 'ifAny' extends keyof Cases + ? Cases['ifAny'] + : any : IsNever extends true - ? Cases['ifNever'] + ? 'ifNever' extends keyof Cases + ? Cases['ifNever'] + : never : Cases['ifNot']; /** From 9566c777840690b043340dd6e4409011d28e4541 Mon Sep 17 00:00:00 2001 From: Som Shekhar Mukherjee <49264891+som-sm@users.noreply.github.com> Date: Mon, 22 Jun 2026 12:51:22 +0530 Subject: [PATCH 7/9] test: add case for `CrashIfNotNever` --- test-d/internal/if-not-any-or-never.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test-d/internal/if-not-any-or-never.ts b/test-d/internal/if-not-any-or-never.ts index 3332d79e4..439a1f808 100644 --- a/test-d/internal/if-not-any-or-never.ts +++ b/test-d/internal/if-not-any-or-never.ts @@ -46,3 +46,14 @@ type CrashIfNotAny = 0 extends 1 & T // Check if // @ts-expect-error type T5 = CrashIfNotAny; type T6 = CrashIfNotAnyWrapper; + +type CrashIfNotNeverWrapper = IfNotAnyOrNever}>; + +type CrashIfNotNever = [T] extends [never] // Check if `T` is `never` + ? never + : CrashIfNotNever; + +// `CrashIfNotNever` errors with a recursion depth error, but `CrashIfNotNeverWrapper` shouldn't. +// @ts-expect-error +type T7 = CrashIfNotNever; +type T8 = CrashIfNotNeverWrapper; From b804cc7a6f006c576772b26b794ac8aa62ff1cb6 Mon Sep 17 00:00:00 2001 From: Som Shekhar Mukherjee <49264891+som-sm@users.noreply.github.com> Date: Mon, 22 Jun 2026 12:52:53 +0530 Subject: [PATCH 8/9] test: fix incorrect `never` check --- test-d/internal/if-not-any-or-never.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-d/internal/if-not-any-or-never.ts b/test-d/internal/if-not-any-or-never.ts index 439a1f808..7ee6b4e59 100644 --- a/test-d/internal/if-not-any-or-never.ts +++ b/test-d/internal/if-not-any-or-never.ts @@ -27,7 +27,7 @@ type T2 = CrashIfAnyWrapper; type CrashIfNeverWrapper = IfNotAnyOrNever}>; -type CrashIfNever = [never] extends [T] // Check if `T` is `never` +type CrashIfNever = [T] extends [never] // Check if `T` is `never` ? CrashIfNever : never; From d05d9a01cc7ded8d592f8b36ea8225de5e5e0de8 Mon Sep 17 00:00:00 2001 From: Som Shekhar Mukherjee <49264891+som-sm@users.noreply.github.com> Date: Mon, 22 Jun 2026 13:01:21 +0530 Subject: [PATCH 9/9] doc: improve JSDoc --- source/internal/type.d.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/source/internal/type.d.ts b/source/internal/type.d.ts index 9fe102d1a..06a5c9581 100644 --- a/source/internal/type.d.ts +++ b/source/internal/type.d.ts @@ -89,6 +89,7 @@ An if-else-like type that resolves depending on whether the given type is `any` @example ``` +// When `T` is neither `any` nor `never` (like `string`) => Returns `IfNot` branch type A = IfNotAnyOrNever; //=> 'VALID'