From aab1e004d999c3a053d5ce2d136061555e800761 Mon Sep 17 00:00:00 2001 From: benzaria Date: Tue, 20 May 2025 18:06:04 +0100 Subject: [PATCH 1/9] Add `LoosenUnion` type --- index.d.ts | 1 + readme.md | 1 + source/loosen-union.d.ts | 93 ++++++++++++++++++++++++++++++++++++++++ test-d/loosen-union.ts | 71 ++++++++++++++++++++++++++++++ 4 files changed, 166 insertions(+) create mode 100644 source/loosen-union.d.ts create mode 100644 test-d/loosen-union.ts diff --git a/index.d.ts b/index.d.ts index ee07d85a8..92e1ec72f 100644 --- a/index.d.ts +++ b/index.d.ts @@ -39,6 +39,7 @@ export type {PartialOnUndefinedDeep, PartialOnUndefinedDeepOptions} from './sour export type {UndefinedOnPartialDeep} from './source/undefined-on-partial-deep.d.ts'; export type {ReadonlyDeep} from './source/readonly-deep.d.ts'; export type {LiteralUnion} from './source/literal-union.d.ts'; +export type {LoosenUnion} from './source/loosen-union.d.ts'; export type {Promisable} from './source/promisable.d.ts'; export type {Arrayable} from './source/arrayable.d.ts'; export type {Opaque, UnwrapOpaque, Tagged, GetTagMetadata, UnwrapTagged} from './source/tagged.d.ts'; diff --git a/readme.md b/readme.md index 3f04fef88..caaffc60d 100644 --- a/readme.md +++ b/readme.md @@ -122,6 +122,7 @@ Click the type names for complete docs. - [`UndefinedOnPartialDeep`](source/undefined-on-partial-deep.d.ts) - Create a deep version of another type where all optional keys are set to also accept `undefined`. - [`ReadonlyDeep`](source/readonly-deep.d.ts) - Create a deeply immutable version of an `object`/`Map`/`Set`/`Array` type. Use [`Readonly`](https://www.typescriptlang.org/docs/handbook/utility-types.html#readonlytype) if you only need one level deep. - [`LiteralUnion`](source/literal-union.d.ts) - Create a union type by combining primitive types and literal types without sacrificing auto-completion in IDEs for the literal type part of the union. Workaround for [Microsoft/TypeScript#29729](https://github.com/Microsoft/TypeScript/issues/29729). +- [`LoosenUnion`](source/loosen-union.d.ts) - Create a union by removing all Non-Literal primitive types from a union, while retaining literal types. - [`Tagged`](source/tagged.d.ts) - Create a [tagged type](https://medium.com/@KevinBGreene/surviving-the-typescript-ecosystem-branding-and-type-tagging-6cf6e516523d) that can support [multiple tags](https://github.com/sindresorhus/type-fest/issues/665) and [per-tag metadata](https://medium.com/@ethanresnick/advanced-typescript-tagged-types-improved-with-type-level-metadata-5072fc125fcf). (This replaces the previous [`Opaque`](source/tagged.d.ts) type, which is now deprecated.) - [`UnwrapTagged`](source/tagged.d.ts) - Get the untagged portion of a tagged type created with `Tagged`. (This replaces the previous [`UnwrapOpaque`](source/tagged.d.ts) type, which is now deprecated.) - [`InvariantOf`](source/invariant-of.d.ts) - Create an [invariant type](https://basarat.gitbook.io/typescript/type-system/type-compatibility#footnote-invariance), which is a type that does not accept supertypes and subtypes. diff --git a/source/loosen-union.d.ts b/source/loosen-union.d.ts new file mode 100644 index 000000000..47ecfdc18 --- /dev/null +++ b/source/loosen-union.d.ts @@ -0,0 +1,93 @@ +import type {UnwrapTagged, TagContainer} from './tagged.js'; +import type {Primitive} from './primitive.d.ts'; +import type {IsLiteral} from './is-literal.js'; +import type {Not} from './internal/type.d.ts'; +import type {IsEqual} from './is-equal.d.ts'; +import type {ValueOf} from './value-of.d.ts'; + +/** +Create a union by removing all Non-Literal primitive types from a union, while retaining literal types. + +This utility helps you extract only the literal members from a "literal union" type +(e.g., `'foo' | 'bar' | string` becomes `'foo' | 'bar'`), saving you from defining separate types for literals and unions. + +It works with all primitive and tagged types, and supports two strictness modes: + +- **Strict mode (`Strict = true`)**: Removes any infinite signature type (e.g., `abc${string}`, `123${number}`), keeping only genuine literals. +- **Non-strict mode (`Strict = false`)**: Removes only wide primitive types (e.g., `string`, `number`). + +@default false + +@example +```ts +import type { LoosenUnion } from 'type-fest'; + +// String example: +type Pet = LiteralUnion<'dog' | 'cat' | `${string}Dog`, string>; +// ^? type Pet = 'dog' | 'cat' | `${string}Dog` | (string & {}) +type PetLiteralNonStrict = LoosenUnion; +// ^? type PetLiteralNonStrict = 'dog' | 'cat' | `${string}Dog` +type PetLiteralStrict = LoosenUnion; +// ^? type PetLiteralStrict = 'dog' | 'cat' + +// Number example: +type Nums = LiteralUnion<0 | 1 | 2, number>; +// ^? type Nums = 0 | 1 | 2 | (number & {}) +type NumsLiteral = LoosenUnion; +// ^? type NumsLiteral = 0 | 1 | 2 + +// Symbol example: +declare const sym1: unique symbol; +declare const sym2: unique symbol; +type Symbols = LiteralUnion; +// ^? type Symbols = typeof sym1 | typeof sym2 | (symbol & {}) +type SymbolsLiteral = LoosenUnion; +// ^? type SymbolsLiteral = typeof sym1 | typeof sym2 + +// BigInt example: +type Big = LiteralUnion<1n | 2n, bigint>; +// ^? type Big = 1n | 2n | (bigint & {}) +type BigLiteral = LoosenUnion; +// ^? type BigLiteral = 1n | 2n + +``` + +@author benzaria +@see LiteralUnion +@category Type +*/ +export type LoosenUnion< + LiteralUnion extends Primitive, + Strict extends boolean = false, +> = ValueOf<{ + [P in Primitive as string]: LiteralUnion extends P + ? _LoosenUnion + : never +}>; + +type _LoosenUnion = ValueOf<{ + [K in U as string]: ( + S extends true + ? Not + ? UnwrapTagged + : K + >> + : IsEqual)> + ) extends true + ? never + : K +}>; + +/** + +? Explanation: + +- LoosenUnion iterates over all primitive types (string, number, symbol, bigint, boolean). +- For each, if the input type extends that primitive, applies _LoosenUnion. +- _LoosenUnion iterates over the union members: + - In non-strict mode, removes (type & {}) (the canonical "widened" type). + - In strict mode, removes any non-literal (infinite template literal types). + - Otherwise, keeps the literal member. + +*/ diff --git a/test-d/loosen-union.ts b/test-d/loosen-union.ts new file mode 100644 index 000000000..80f90c6c7 --- /dev/null +++ b/test-d/loosen-union.ts @@ -0,0 +1,71 @@ +import {expectType} from 'tsd'; +import type {LoosenUnion, LiteralUnion, IsEqual, Tagged} from '../index.d.ts'; + +// For Strings: + +type Events = LiteralUnion<'hover' | 'click' | `click-${number}`, string>; +// ^? +type EventsLiteralNonStrict = LoosenUnion; +// ^? +type EventsLiteralStrict = LoosenUnion; +// ^? + +expectType>(true); +expectType>(true); + +expectType>(false); +expectType>(false); + +// For Numbers: + +type Nums = LiteralUnion<0 | 1 | 2, number>; +// ^? +type NumsLiteral = LoosenUnion; +// ^? + +expectType>(true); +expectType>(false); + +// For Symbols: + +declare const symbol1: unique symbol; +declare const symbol2: unique symbol; + +type Symbol1 = typeof symbol1; +type Symbol2 = typeof symbol2; + +type Symbols = LiteralUnion; +// ^? +type SymbolsLiteral = LoosenUnion; +// ^? + +expectType>(true); +expectType>(false); + +// For Bigints: + +type Big = LiteralUnion<1n | 2n, bigint>; +// ^? +type BigLiteral = LoosenUnion; +// ^? + +expectType>(true); +expectType>(false); + +// For Tagged types: + +type Animals = Tagged<'dog' | 'cat' | `${string}Dog`, 'alimals'>; +type AnimalsStrict = Exclude; + +type TaggedUnion = LiteralUnion; +// ^? +type TaggedLiteral = LoosenUnion; +// ^? +type TaggedStrict = LoosenUnion; +// ^? + +expectType>(true); +expectType>(true); + +expectType>(false); +expectType>(false); From 0dcc5c6cd148f5292c847adee88c79385a3998ea Mon Sep 17 00:00:00 2001 From: benzaria Date: Wed, 21 May 2025 10:39:35 +0100 Subject: [PATCH 2/9] Remove `LoosenUnion` type in favor of `LiteralOf` --- index.d.ts | 2 +- readme.md | 2 +- source/{loosen-union.d.ts => literal-of.d.ts} | 50 +++++++++---------- test-d/{loosen-union.ts => literal-of.ts} | 18 +++---- 4 files changed, 36 insertions(+), 36 deletions(-) rename source/{loosen-union.d.ts => literal-of.d.ts} (59%) rename test-d/{loosen-union.ts => literal-of.ts} (77%) diff --git a/index.d.ts b/index.d.ts index 92e1ec72f..fdfc74ba6 100644 --- a/index.d.ts +++ b/index.d.ts @@ -39,7 +39,7 @@ export type {PartialOnUndefinedDeep, PartialOnUndefinedDeepOptions} from './sour export type {UndefinedOnPartialDeep} from './source/undefined-on-partial-deep.d.ts'; export type {ReadonlyDeep} from './source/readonly-deep.d.ts'; export type {LiteralUnion} from './source/literal-union.d.ts'; -export type {LoosenUnion} from './source/loosen-union.d.ts'; +export type {LiteralOf} from './source/literal-of.d.ts'; export type {Promisable} from './source/promisable.d.ts'; export type {Arrayable} from './source/arrayable.d.ts'; export type {Opaque, UnwrapOpaque, Tagged, GetTagMetadata, UnwrapTagged} from './source/tagged.d.ts'; diff --git a/readme.md b/readme.md index caaffc60d..beb5777e7 100644 --- a/readme.md +++ b/readme.md @@ -122,7 +122,7 @@ Click the type names for complete docs. - [`UndefinedOnPartialDeep`](source/undefined-on-partial-deep.d.ts) - Create a deep version of another type where all optional keys are set to also accept `undefined`. - [`ReadonlyDeep`](source/readonly-deep.d.ts) - Create a deeply immutable version of an `object`/`Map`/`Set`/`Array` type. Use [`Readonly`](https://www.typescriptlang.org/docs/handbook/utility-types.html#readonlytype) if you only need one level deep. - [`LiteralUnion`](source/literal-union.d.ts) - Create a union type by combining primitive types and literal types without sacrificing auto-completion in IDEs for the literal type part of the union. Workaround for [Microsoft/TypeScript#29729](https://github.com/Microsoft/TypeScript/issues/29729). -- [`LoosenUnion`](source/loosen-union.d.ts) - Create a union by removing all Non-Literal primitive types from a union, while retaining literal types. +- [`LiteralOf`](source/literal-of.d.ts) - Creates a union of the literal members from a given union type, removing wide primitive or infinite types, and optionally specify the Strictness. - [`Tagged`](source/tagged.d.ts) - Create a [tagged type](https://medium.com/@KevinBGreene/surviving-the-typescript-ecosystem-branding-and-type-tagging-6cf6e516523d) that can support [multiple tags](https://github.com/sindresorhus/type-fest/issues/665) and [per-tag metadata](https://medium.com/@ethanresnick/advanced-typescript-tagged-types-improved-with-type-level-metadata-5072fc125fcf). (This replaces the previous [`Opaque`](source/tagged.d.ts) type, which is now deprecated.) - [`UnwrapTagged`](source/tagged.d.ts) - Get the untagged portion of a tagged type created with `Tagged`. (This replaces the previous [`UnwrapOpaque`](source/tagged.d.ts) type, which is now deprecated.) - [`InvariantOf`](source/invariant-of.d.ts) - Create an [invariant type](https://basarat.gitbook.io/typescript/type-system/type-compatibility#footnote-invariance), which is a type that does not accept supertypes and subtypes. diff --git a/source/loosen-union.d.ts b/source/literal-of.d.ts similarity index 59% rename from source/loosen-union.d.ts rename to source/literal-of.d.ts index 47ecfdc18..1e0f95205 100644 --- a/source/loosen-union.d.ts +++ b/source/literal-of.d.ts @@ -1,39 +1,39 @@ import type {UnwrapTagged, TagContainer} from './tagged.js'; -import type {Primitive} from './primitive.d.ts'; import type {IsLiteral} from './is-literal.js'; -import type {Not} from './internal/type.d.ts'; -import type {IsEqual} from './is-equal.d.ts'; -import type {ValueOf} from './value-of.d.ts'; +import type {Primitive} from './primitive.js'; +import type {Not} from './internal/type.js'; +import type {IsEqual} from './is-equal.js'; +import type {ValueOf} from './value-of.js'; /** -Create a union by removing all Non-Literal primitive types from a union, while retaining literal types. +Creates a union of the literal members from a given union type, removing wide primitive or infinite types. This utility helps you extract only the literal members from a "literal union" type (e.g., `'foo' | 'bar' | string` becomes `'foo' | 'bar'`), saving you from defining separate types for literals and unions. It works with all primitive and tagged types, and supports two strictness modes: -- **Strict mode (`Strict = true`)**: Removes any infinite signature type (e.g., `abc${string}`, `123${number}`), keeping only genuine literals. +- **Strict mode (`Strict = true`)**: Removes any infinite signature type (e.g., `abc${string}`, `123${number}`). - **Non-strict mode (`Strict = false`)**: Removes only wide primitive types (e.g., `string`, `number`). -@default false +@default true @example ```ts -import type { LoosenUnion } from 'type-fest'; +import type { LiteralOf } from 'type-fest'; // String example: type Pet = LiteralUnion<'dog' | 'cat' | `${string}Dog`, string>; // ^? type Pet = 'dog' | 'cat' | `${string}Dog` | (string & {}) -type PetLiteralNonStrict = LoosenUnion; -// ^? type PetLiteralNonStrict = 'dog' | 'cat' | `${string}Dog` -type PetLiteralStrict = LoosenUnion; -// ^? type PetLiteralStrict = 'dog' | 'cat' +type PetLiteralStrict = LiteralOf; +// ^? type PetLiteralNonStrict = 'dog' | 'cat' +type PetLiteralNonStrict = LiteralOf; +// ^? type PetLiteralStrict = 'dog' | 'cat' | `${string}Dog` // Number example: type Nums = LiteralUnion<0 | 1 | 2, number>; // ^? type Nums = 0 | 1 | 2 | (number & {}) -type NumsLiteral = LoosenUnion; +type NumsLiteral = LiteralOf; // ^? type NumsLiteral = 0 | 1 | 2 // Symbol example: @@ -41,13 +41,13 @@ declare const sym1: unique symbol; declare const sym2: unique symbol; type Symbols = LiteralUnion; // ^? type Symbols = typeof sym1 | typeof sym2 | (symbol & {}) -type SymbolsLiteral = LoosenUnion; +type SymbolsLiteral = LiteralOf; // ^? type SymbolsLiteral = typeof sym1 | typeof sym2 // BigInt example: type Big = LiteralUnion<1n | 2n, bigint>; // ^? type Big = 1n | 2n | (bigint & {}) -type BigLiteral = LoosenUnion; +type BigLiteral = LiteralOf; // ^? type BigLiteral = 1n | 2n ``` @@ -56,16 +56,16 @@ type BigLiteral = LoosenUnion; @see LiteralUnion @category Type */ -export type LoosenUnion< +export type LiteralOf< LiteralUnion extends Primitive, - Strict extends boolean = false, + Strict extends boolean = true, > = ValueOf<{ [P in Primitive as string]: LiteralUnion extends P - ? _LoosenUnion + ? _LiteralOf : never }>; -type _LoosenUnion = ValueOf<{ +type _LiteralOf = ValueOf<{ [K in U as string]: ( S extends true ? Not = ValueOf<{ ? Explanation: -- LoosenUnion iterates over all primitive types (string, number, symbol, bigint, boolean). -- For each, if the input type extends that primitive, applies _LoosenUnion. -- _LoosenUnion iterates over the union members: - - In non-strict mode, removes (type & {}) (the canonical "widened" type). - - In strict mode, removes any non-literal (infinite template literal types). - - Otherwise, keeps the literal member. +- LiteralOf iterates over all primitive types (string, number, symbol, bigint, boolean). +- For each, if the input type extends that primitive, applies _LiteralOf. +- _LiteralOf iterates over the union members: + - In non-strict mode, removes (type & {}) (the canonical "widened" type). + - In strict mode, removes any non-literal (infinite template literal types). + - Otherwise, keeps the literal member. */ diff --git a/test-d/loosen-union.ts b/test-d/literal-of.ts similarity index 77% rename from test-d/loosen-union.ts rename to test-d/literal-of.ts index 80f90c6c7..749a764b3 100644 --- a/test-d/loosen-union.ts +++ b/test-d/literal-of.ts @@ -1,13 +1,13 @@ import {expectType} from 'tsd'; -import type {LoosenUnion, LiteralUnion, IsEqual, Tagged} from '../index.d.ts'; +import type {LiteralOf, LiteralUnion, IsEqual, Tagged} from '../index.js'; // For Strings: type Events = LiteralUnion<'hover' | 'click' | `click-${number}`, string>; // ^? -type EventsLiteralNonStrict = LoosenUnion; +type EventsLiteralStrict = LiteralOf; // ^? -type EventsLiteralStrict = LoosenUnion; +type EventsLiteralNonStrict = LiteralOf; // ^? expectType>(true); @@ -20,7 +20,7 @@ expectType>(false); type Nums = LiteralUnion<0 | 1 | 2, number>; // ^? -type NumsLiteral = LoosenUnion; +type NumsLiteral = LiteralOf; // ^? expectType>(true); @@ -36,7 +36,7 @@ type Symbol2 = typeof symbol2; type Symbols = LiteralUnion; // ^? -type SymbolsLiteral = LoosenUnion; +type SymbolsLiteral = LiteralOf; // ^? expectType>(true); @@ -46,7 +46,7 @@ expectType>(false); type Big = LiteralUnion<1n | 2n, bigint>; // ^? -type BigLiteral = LoosenUnion; +type BigLiteral = LiteralOf; // ^? expectType>(true); @@ -59,12 +59,12 @@ type AnimalsStrict = Exclude; type TaggedUnion = LiteralUnion; // ^? -type TaggedLiteral = LoosenUnion; +type TaggedStrict = LiteralOf; // ^? -type TaggedStrict = LoosenUnion; +type TaggedNonStrict = LiteralOf; // ^? -expectType>(true); +expectType>(true); expectType>(true); expectType>(false); From 91b9164c2f0c953f89df037604c6b0e108b4064c Mon Sep 17 00:00:00 2001 From: benzaria Date: Thu, 22 May 2025 19:06:48 +0100 Subject: [PATCH 3/9] Add `IsPrimitive` types --- source/is-primitive.d.ts | 194 +++++++++++++++++++++++++++++++++++++++ test-d/is-primitive.ts | 113 +++++++++++++++++++++++ 2 files changed, 307 insertions(+) create mode 100644 source/is-primitive.d.ts create mode 100644 test-d/is-primitive.ts diff --git a/source/is-primitive.d.ts b/source/is-primitive.d.ts new file mode 100644 index 000000000..873ad7aac --- /dev/null +++ b/source/is-primitive.d.ts @@ -0,0 +1,194 @@ +import type {IsNotFalse, Extends} from './internal/type.js'; +import type {Primitive} from './primitive.js'; +import type {Numeric} from './numeric.js'; +import type {IsAny} from './is-any.js'; +import type {And} from './and.js'; + +/** +Returns a boolean for whether the given type `T` is the specified `PrimitiveType`. + +@example +``` +type ex = PrimitiveCheck +// ^? type ex = true + +type ex = PrimitiveCheck<1, number> +// ^? type ex = false + +type ex = PrimitiveCheck<(string & {}), string> // Because (string & {}) does not change the type string it just hide's it. See PrimitiveUnion +// ^? type ex = true +``` +*/ +type PrimitiveCheck = ( + IsAny extends false // Cannot be wider than `any` + ? And< + Extends, // Must be narrower than `PrimitiveType` + Extends // Must be wider than `PrimitiveType` + > + : false +); + +/** +Returns a boolean for whether the given type `T` is one of the specified primitive types in `PrimitiveUnionType`. + +@example +``` +type ex = PrimitiveChecks +// ^? type ex = true + +type ex = PrimitiveChecks +// ^? type ex = true + +type ex = PrimitiveChecks +// ^? type ex = false +``` +*/ +type PrimitiveChecks = ( + // Conditional type to force union distribution. + // If `T` is none of the primitive types in the union `PrimitiveUnionType`, then `PrimitiveCheck` will evaluate to `false` for the whole union. + // If `T` is one of the primitive types in the union, it will evaluate to `boolean` (i.e. `true | false`) + IsNotFalse< + PrimitiveUnionType extends Primitive + ? PrimitiveCheck + : never + > +); + +/** +Returns a boolean for whether the given type is strictly a `string` [primitive type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#the-primitives-string-number-and-boolean). + +@example +``` +import type {IsStringPrimitive} from 'type-fest'; + +type ex = IsStringPrimitive +// ^? type ex = true + +type ex = IsStringPrimitive<(string & {})> +// ^? type ex = true + +type ex = IsStringPrimitive<`on${string}`> +// ^? type ex = false + +type ex = IsStringPrimitive> +// ^? type ex = false + +``` + +@see IsStringLiteral +@category Type Guard +@category Utilities +*/ +export type IsStringPrimitive = PrimitiveCheck; + +/** +Returns a boolean for whether the given type is strictly a `number` or `bigint` [primitive type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#the-primitives-string-number-and-boolean). + +@example +``` +type ex = IsNumericPrimitive +// ^? type ex = true + +type ex = IsNumericPrimitive +// ^? type ex = true + +type ex = IsNumericPrimitive +// ^? type ex = true + +type ex = IsNumericPrimitive<1> +// ^? type ex = false + +``` + +@see IsNumericLiteral +@category Type Guard +@category Utilities +*/ +export type IsNumericPrimitive = ( + T extends Numeric + ? PrimitiveChecks + : false +); + +/** +Returns a boolean for whether the given type is strictly a `boolean` [primitive type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#the-primitives-string-number-and-boolean). + +@example +``` +import type {IsBooleanPrimitive} from 'type-fest'; + +type ex = IsBooleanPrimitive +// ^? type ex = true + +type ex = IsBooleanPrimitive +// ^? type ex = true + +type ex = IsBooleanPrimitive +// ^? type ex = false + +type ex = IsBooleanPrimitive +// ^? type ex = false + +``` + +@see IsBooleanLiteral +@category Type Guard +@category Utilities +*/ +export type IsBooleanPrimitive = PrimitiveCheck; + +/** +Returns a boolean for whether the given type is strictly a `symbol` [primitive type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#the-primitives-string-number-and-boolean). + +@example +``` +type ex = IsSymbolPrimitive +// ^? type ex = true + +type ex = IsSymbolPrimitive +// ^? type ex = false + +``` + +@see IsSymbolLiteral +@category Type Guard +@category Utilities +*/ +export type IsSymbolPrimitive = PrimitiveCheck; + +/** Helper type for `IsPrimitive`. */ +type IsPrimitiveUnion = + | IsStringPrimitive + | IsNumericPrimitive + | IsBooleanPrimitive + | IsSymbolPrimitive; + +/** +Returns a boolean for whether the given type is strictly a [primitive type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#the-primitives-string-number-and-boolean). + +@example +``` +type ex = IsPrimitive +// ^? type ex = true + +type ex = IsPrimitive +// ^? type ex = true + +type ex = IsPrimitive<'string'> +// ^? type ex = false + +type ex = IsPrimitive +// ^? type ex = false + +``` + +@author benzaria +@see IsLiteral +@category Type Guard +@category Utilities +*/ +export type IsPrimitive = ( + Extends extends true + ? IsNotFalse> + : false +); diff --git a/test-d/is-primitive.ts b/test-d/is-primitive.ts new file mode 100644 index 000000000..ffb9f79b9 --- /dev/null +++ b/test-d/is-primitive.ts @@ -0,0 +1,113 @@ +import {expectType} from 'tsd'; +import type tag from 'tagged-tag'; +import type { + IsPrimitive, + IsBooleanPrimitive, + IsNumericPrimitive, + IsStringPrimitive, + IsSymbolPrimitive, + Tagged, +} from '../index.d.ts'; +import type {Numeric} from '../source/numeric.js'; + +type stringLiteral = ''; +type numberLiteral = 1; +type bigintLiteral = 1n; +type booleanLiteral = true; +type symbolLiteral = typeof tag; +type numericLiteral = numberLiteral | bigintLiteral; + +// Primitives should be true +expectType>(true); +expectType>(true); +expectType>(true); +expectType>(true); +expectType>(true); + +// Literals should be false +expectType>(false); +expectType>(false); +expectType>(false); +expectType>(false); +expectType>(false); + +// Null, undefined, and non-primitives should fail all literal checks +expectType>(false); +expectType>(false); +expectType>(false); +expectType>(false); + +expectType>(false); +expectType>(true); + +// Strings with infinite set of possible values return `false` +expectType>>(false); +expectType>>(false); +expectType>>(false); +expectType>>(false); +expectType>>>(false); +expectType>>>(false); +expectType>(false); +expectType>(false); +expectType>(false); +expectType}`>>(false); +expectType}abc`>>(false); +expectType>(false); +expectType>(false); +expectType>>(false); +expectType | Uppercase>>(false); +expectType>(false); + +// Strings with finite set of possible values return `false` +expectType>(false); +expectType>>(false); +expectType>>(false); +expectType>>(false); +expectType>>(false); +expectType>>(false); +expectType>>(false); +expectType>(false); +expectType | 'C' | 'D'>>(false); +expectType | Capitalize<'abc'>>>(false); + +// Union of literals and non-literals return `true` +expectType | (string & {})>>(true); +expectType | (string & {})>>(true); +expectType>(true); + +// Boundary types +expectType>(false); +expectType>(false); + +expectType>(false); +expectType>(false); +expectType>(false); +expectType>(true); +expectType>(true); +expectType>(true); + +expectType>(false); +expectType>(true); +expectType>(true); + +expectType>(false); +expectType>(true); + +// Missing generic parameter +// @ts-expect-error +type A0 = IsPrimitive; +// @ts-expect-error +type A1 = IsStringPrimitive; +// @ts-expect-error +type A2 = IsNumericPrimitive; +// @ts-expect-error +type A3 = IsBooleanPrimitive; +// @ts-expect-error +type A4 = IsSymbolPrimitive; + +// Tagged types should be false +expectType>>(false); +expectType>>(false); +expectType>>(false); +expectType>>(false); +expectType>>(false); From f9a62f05f4d1d072fafe3a9e85a31ee0273b2171 Mon Sep 17 00:00:00 2001 From: benzaria Date: Thu, 22 May 2025 19:19:14 +0100 Subject: [PATCH 4/9] Improve `IsLiteral` types --- source/is-literal.d.ts | 55 ++++++++++++++++++------------ test-d/is-literal.ts | 76 +++++++++++++++++++++++------------------- 2 files changed, 75 insertions(+), 56 deletions(-) diff --git a/source/is-literal.d.ts b/source/is-literal.d.ts index 4c0ad0445..30b433a24 100644 --- a/source/is-literal.d.ts +++ b/source/is-literal.d.ts @@ -1,8 +1,8 @@ +import type {Extends, IsNotFalse, Not} from './internal/type.d.ts'; import type {Primitive} from './primitive.d.ts'; -import type {Numeric} from './numeric.d.ts'; -import type {IsNotFalse, IsPrimitive} from './internal/index.d.ts'; import type {IsNever} from './is-never.d.ts'; -import type {If} from './if.js'; +import type {Numeric} from './numeric.d.ts'; +import type {And} from './and.js'; /** Returns a boolean for whether the given type `T` is the specified `LiteralType`. @@ -24,11 +24,10 @@ LiteralCheck<1, string> type LiteralCheck = ( IsNever extends false // Must be wider than `never` ? [T] extends [LiteralType & infer U] // Remove any branding - ? [U] extends [LiteralType] // Must be narrower than `LiteralType` - ? [LiteralType] extends [U] // Cannot be wider than `LiteralType` - ? false - : true - : false + ? And< + Extends, // Must be narrower than `LiteralType` + Not> // Cannot be wider than `LiteralType` + > : false : false ); @@ -52,7 +51,8 @@ type LiteralChecks = ( // Conditional type to force union distribution. // If `T` is none of the literal types in the union `LiteralUnionType`, then `LiteralCheck` will evaluate to `false` for the whole union. // If `T` is one of the literal types in the union, it will evaluate to `boolean` (i.e. `true | false`) - IsNotFalse : never > @@ -111,17 +111,21 @@ type L2 = Length<`${number}`>; //=> number ``` +@see IsStringPrimitive @category Type Guard @category Utilities */ -export type IsStringLiteral = If, false, -// If `T` is an infinite string type (e.g., `on${string}`), `Record` produces an index signature, -// and since `{}` extends index signatures, the result becomes `false`. -T extends string - ? {} extends Record - ? false - : true - : false>; +export type IsStringLiteral = ( + // If `T` is an infinite string type (e.g., `on${string}`), `Record` produces an index signature, + // and since `{}` extends index signatures, the result becomes `false`. + IsNever extends false + ? T extends string + ? {} extends Record + ? false + : true + : false + : false +); /** Returns a boolean for whether the given type is a `number` or `bigint` [literal type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types). @@ -166,10 +170,15 @@ endsWith('abc123', end); //=> boolean ``` +@see IsNumericPrimitive @category Type Guard @category Utilities */ -export type IsNumericLiteral = LiteralChecks; +export type IsNumericLiteral = ( + T extends Numeric + ? LiteralChecks + : false +); /** Returns a boolean for whether the given type is a `true` or `false` [literal type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types). @@ -206,6 +215,7 @@ const eitherId = getId({asString: runtimeBoolean}); //=> number | string ``` +@see IsBooleanPrimitive @category Type Guard @category Utilities */ @@ -241,6 +251,7 @@ get({[symbolValue]: 1} as const, symbolValue); //=> number ``` +@see IsSymbolPrimitive @category Type Guard @category Utilities */ @@ -287,10 +298,12 @@ stripLeading(str, 'abc'); //=> string ``` +@see IsPrimitive @category Type Guard @category Utilities */ -export type IsLiteral = - IsPrimitive extends true +export type IsLiteral = ( + Extends extends true ? IsNotFalse> - : false; + : false +); diff --git a/test-d/is-literal.ts b/test-d/is-literal.ts index 0ce4850f1..6cc55ddc0 100644 --- a/test-d/is-literal.ts +++ b/test-d/is-literal.ts @@ -1,4 +1,5 @@ import {expectType} from 'tsd'; +import type tag from 'tagged-tag'; import type { IsLiteral, IsStringLiteral, @@ -7,33 +8,30 @@ import type { IsSymbolLiteral, Tagged, } from '../index.d.ts'; +import type {Numeric} from '../source/numeric.js'; -const stringLiteral = ''; -const numberLiteral = 1; -// Note: tsd warns on direct literal usage so we cast to the literal type -const bigintLiteral = BigInt(1) as 1n; -const booleanLiteral = true; -const symbolLiteral = Symbol(''); +type stringLiteral = 'aA'; +type numberLiteral = 1; +type bigintLiteral = 1n; +type booleanLiteral = true; +type symbolLiteral = typeof tag; +type numericLiteral = numberLiteral | bigintLiteral; -declare const _string: string; -declare const _number: number; -declare const _bigint: bigint; -declare const _boolean: boolean; -declare const _symbol: symbol; +declare const boolean: boolean; // Literals should be true -expectType>(true); -expectType>(true); -expectType>(true); -expectType>(true); -expectType>(true); +expectType>(true); +expectType>(true); +expectType>(true); +expectType>(true); +expectType>(true); // Primitives should be false -expectType>(false); -expectType>(false); -expectType>(false); -expectType>(false); -expectType>(false); +expectType>(false); +expectType>(false); +expectType>(false); +expectType>(false); +expectType>(false); // Null, undefined, and non-primitives should fail all literal checks expectType>(false); @@ -41,8 +39,8 @@ expectType>(false); expectType>(false); expectType>(false); -expectType>(true); -expectType>(false); +expectType>(true); +expectType>(false); // Strings with infinite set of possible values return `false` expectType>>(false); @@ -74,25 +72,31 @@ expectType>(true); expectType | 'C' | 'D'>>(true); expectType | Capitalize<'abc'>>>(true); -// Strings with union of literals and non-literals return `boolean` -expectType | 'abc'>>({} as boolean); -expectType | 'Abc'>>({} as boolean); -expectType>({} as boolean); +// Union of literals and non-literals return `boolean` +expectType | stringLiteral>>(boolean); +expectType | stringLiteral>>(boolean); + +// Union of diffrent literal types return `boolean` +expectType>(boolean); +expectType>(boolean); // Boundary types expectType>(false); expectType>(false); -expectType>(true); -expectType>(true); -expectType>(false); -expectType>(false); +expectType>(true); +expectType>(true); +expectType>(true); +expectType>(false); +expectType>(false); +expectType>(false); -expectType>(true); -expectType>(false); +expectType>(true); +expectType>(false); +expectType>(false); -expectType>(true); -expectType>(false); +expectType>(true); +expectType>(false); // Missing generic parameter // @ts-expect-error @@ -108,5 +112,7 @@ type A4 = IsSymbolLiteral; // Tagged types should be false expectType>>(false); +expectType>>(false); expectType>>(false); +expectType>>(false); expectType>>(false); From 9cc1c0881f324a8329eb3798d164bb3885fad2e3 Mon Sep 17 00:00:00 2001 From: benzaria Date: Thu, 22 May 2025 19:39:30 +0100 Subject: [PATCH 5/9] Add `Extends` type from `expect-type` --- source/internal/type.d.ts | 44 +++++++++++++++++---------------------- 1 file changed, 19 insertions(+), 25 deletions(-) diff --git a/source/internal/type.d.ts b/source/internal/type.d.ts index 4325679f7..889da1596 100644 --- a/source/internal/type.d.ts +++ b/source/internal/type.d.ts @@ -1,7 +1,8 @@ -import type {If} from '../if.js'; -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 {IsNever} from '../is-never.d.ts'; +import type {IsAny} from '../is-any.d.ts'; +import type {And} from '../and.js'; +import type {If} from '../if.js'; /** Matches any primitive, `void`, `Date`, or `RegExp` value. @@ -13,14 +14,24 @@ Matches non-recursive types. */ export type NonRecursiveType = BuiltIns | Function | (new (...arguments_: any[]) => unknown); +/** +Checks if one type extends another. Note: this is not quite the same as `Left extends Right` because: +1. If either type is `never`, the result is `true` iff the other type is also `never`. +2. Types are wrapped in a 1-tuple so that union types are not distributed - instead we consider `string | number` to _not_ extend `number`. If we used `Left extends Right` directly you would get `Extends` => `false | true` => `boolean`. +*/ +export type Extends = IsNever extends true ? IsNever : [Left] extends [Right] ? true : false; + /** Returns a boolean for whether the two given types extends the base type. */ -export type IsBothExtends = FirstType extends BaseType - ? SecondType extends BaseType - ? true - : false - : false; +export type IsBothExtends = ( + IsNotFalse< + And< + Extends, + Extends + > + > +); /** Test if the given function has multiple call signatures. @@ -44,23 +55,6 @@ Returns a boolean for whether the given `boolean` is not `false`. */ export type IsNotFalse = [T] extends [false] ? false : true; -/** -Returns a boolean for whether the given type is primitive value or primitive type. - -@example -``` -IsPrimitive<'string'> -//=> true - -IsPrimitive -//=> true - -IsPrimitive -//=> false -``` -*/ -export type IsPrimitive = [T] extends [Primitive] ? true : false; - /** Returns a boolean for whether A is false. From 2bc33bec5e84583eb3c2441197790f39b07b8381 Mon Sep 17 00:00:00 2001 From: benzaria Date: Thu, 22 May 2025 19:40:42 +0100 Subject: [PATCH 6/9] Add `ExtractLiterals` type --- ...{literal-of.d.ts => extract-literals.d.ts} | 82 ++++++++++--------- test-d/{literal-of.ts => extract-literals.ts} | 35 ++++++-- 2 files changed, 69 insertions(+), 48 deletions(-) rename source/{literal-of.d.ts => extract-literals.d.ts} (61%) rename test-d/{literal-of.ts => extract-literals.ts} (57%) diff --git a/source/literal-of.d.ts b/source/extract-literals.d.ts similarity index 61% rename from source/literal-of.d.ts rename to source/extract-literals.d.ts index 1e0f95205..8fb2d5253 100644 --- a/source/literal-of.d.ts +++ b/source/extract-literals.d.ts @@ -1,9 +1,27 @@ +import type {ApplyDefaultOptions} from './internal/object.js'; import type {UnwrapTagged, TagContainer} from './tagged.js'; +import type {IsPrimitive} from './is-primitive.js'; import type {IsLiteral} from './is-literal.js'; import type {Primitive} from './primitive.js'; import type {Not} from './internal/type.js'; -import type {IsEqual} from './is-equal.js'; -import type {ValueOf} from './value-of.js'; + +/** +ExtactLiterals options. + +@see {@link ExtactLiterals} +*/ +export type ExtractLiteralsOptions = { + /** + Whether to remove infinite signature types (e.g., `abc${string}`). + + @default true + */ + strict?: boolean; +}; + +export type DefaultExtractLiteralsOptions = { + strict: true; +}; /** Creates a union of the literal members from a given union type, removing wide primitive or infinite types. @@ -20,20 +38,20 @@ It works with all primitive and tagged types, and supports two strictness modes: @example ```ts -import type { LiteralOf } from 'type-fest'; +import type { ExtractLiterals } from 'type-fest'; // String example: type Pet = LiteralUnion<'dog' | 'cat' | `${string}Dog`, string>; // ^? type Pet = 'dog' | 'cat' | `${string}Dog` | (string & {}) -type PetLiteralStrict = LiteralOf; +type PetLiteralStrict = ExtractLiterals; // ^? type PetLiteralNonStrict = 'dog' | 'cat' -type PetLiteralNonStrict = LiteralOf; +type PetLiteralNonStrict = ExtractLiterals; // ^? type PetLiteralStrict = 'dog' | 'cat' | `${string}Dog` // Number example: type Nums = LiteralUnion<0 | 1 | 2, number>; // ^? type Nums = 0 | 1 | 2 | (number & {}) -type NumsLiteral = LiteralOf; +type NumsLiteral = ExtractLiterals; // ^? type NumsLiteral = 0 | 1 | 2 // Symbol example: @@ -41,13 +59,13 @@ declare const sym1: unique symbol; declare const sym2: unique symbol; type Symbols = LiteralUnion; // ^? type Symbols = typeof sym1 | typeof sym2 | (symbol & {}) -type SymbolsLiteral = LiteralOf; +type SymbolsLiteral = ExtractLiterals; // ^? type SymbolsLiteral = typeof sym1 | typeof sym2 // BigInt example: type Big = LiteralUnion<1n | 2n, bigint>; // ^? type Big = 1n | 2n | (bigint & {}) -type BigLiteral = LiteralOf; +type BigLiteral = ExtractLiterals; // ^? type BigLiteral = 1n | 2n ``` @@ -56,38 +74,24 @@ type BigLiteral = LiteralOf; @see LiteralUnion @category Type */ -export type LiteralOf< +export type ExtractLiterals< LiteralUnion extends Primitive, - Strict extends boolean = true, -> = ValueOf<{ - [P in Primitive as string]: LiteralUnion extends P - ? _LiteralOf + Options extends ExtractLiteralsOptions = {}, +> = LiteralUnion extends infer Member + ? (( + Member extends TagContainer + ? UnwrapTagged + : Member + ) extends infer Type + ? ApplyDefaultOptions< + ExtractLiteralsOptions, + DefaultExtractLiteralsOptions, + Options + >['strict'] extends true + ? Not> + : IsPrimitive : never -}>; - -type _LiteralOf = ValueOf<{ - [K in U as string]: ( - S extends true - ? Not - ? UnwrapTagged - : K - >> - : IsEqual)> ) extends true ? never - : K -}>; - -/** - -? Explanation: - -- LiteralOf iterates over all primitive types (string, number, symbol, bigint, boolean). -- For each, if the input type extends that primitive, applies _LiteralOf. -- _LiteralOf iterates over the union members: - - In non-strict mode, removes (type & {}) (the canonical "widened" type). - - In strict mode, removes any non-literal (infinite template literal types). - - Otherwise, keeps the literal member. - -*/ + : Member + : never; diff --git a/test-d/literal-of.ts b/test-d/extract-literals.ts similarity index 57% rename from test-d/literal-of.ts rename to test-d/extract-literals.ts index 749a764b3..b67a7d1f3 100644 --- a/test-d/literal-of.ts +++ b/test-d/extract-literals.ts @@ -1,13 +1,13 @@ import {expectType} from 'tsd'; -import type {LiteralOf, LiteralUnion, IsEqual, Tagged} from '../index.js'; +import type {ExtractLiterals, LiteralUnion, IsEqual, Tagged} from '../index.js'; // For Strings: -type Events = LiteralUnion<'hover' | 'click' | `click-${number}`, string>; +type Events = 'hover' | 'click' | `click-${number}` | (string & {}); // ^? -type EventsLiteralStrict = LiteralOf; +type EventsLiteralStrict = ExtractLiterals; // ^? -type EventsLiteralNonStrict = LiteralOf; +type EventsLiteralNonStrict = ExtractLiterals; // ^? expectType>(true); @@ -20,7 +20,7 @@ expectType>(false); type Nums = LiteralUnion<0 | 1 | 2, number>; // ^? -type NumsLiteral = LiteralOf; +type NumsLiteral = ExtractLiterals; // ^? expectType>(true); @@ -36,7 +36,7 @@ type Symbol2 = typeof symbol2; type Symbols = LiteralUnion; // ^? -type SymbolsLiteral = LiteralOf; +type SymbolsLiteral = ExtractLiterals; // ^? expectType>(true); @@ -46,7 +46,7 @@ expectType>(false); type Big = LiteralUnion<1n | 2n, bigint>; // ^? -type BigLiteral = LiteralOf; +type BigLiteral = ExtractLiterals; // ^? expectType>(true); @@ -59,9 +59,9 @@ type AnimalsStrict = Exclude; type TaggedUnion = LiteralUnion; // ^? -type TaggedStrict = LiteralOf; +type TaggedStrict = ExtractLiterals; // ^? -type TaggedNonStrict = LiteralOf; +type TaggedNonStrict = ExtractLiterals; // ^? expectType>(true); @@ -69,3 +69,20 @@ expectType>(true); expectType>(false); expectType>(false); + +type _LiteralUnion = 'dog' | 'cat' | `${string}Dog`; +type _Alimals = Tagged<_LiteralUnion, 'alimals'>; +type _AlimalsStrict = Exclude<_Alimals, `${string}Dog`>; + +type _TaggedUnion = LiteralUnion<_Alimals, string>; +// ^? +type _TaggedStrict = ExtractLiterals<_TaggedUnion>; +// ^? +type _TaggedNonStrict = ExtractLiterals<_TaggedUnion, {strict: false}>; +// ^? + +expectType>(true); +expectType>(true); + +expectType>(false); +expectType>(false); From 8578cd4ef20cd21a80733c60061a4f0e4e3df815 Mon Sep 17 00:00:00 2001 From: benzaria Date: Thu, 22 May 2025 19:41:13 +0100 Subject: [PATCH 7/9] Add `ExtractLiterals` and `IsPrimitive` types Add `Extends` internal type Improve `IsLiteral` type --- index.d.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/index.d.ts b/index.d.ts index fdfc74ba6..2d6bc96e4 100644 --- a/index.d.ts +++ b/index.d.ts @@ -39,7 +39,7 @@ export type {PartialOnUndefinedDeep, PartialOnUndefinedDeepOptions} from './sour export type {UndefinedOnPartialDeep} from './source/undefined-on-partial-deep.d.ts'; export type {ReadonlyDeep} from './source/readonly-deep.d.ts'; export type {LiteralUnion} from './source/literal-union.d.ts'; -export type {LiteralOf} from './source/literal-of.d.ts'; +export type {ExtractLiterals} from './source/extract-literals.js'; export type {Promisable} from './source/promisable.d.ts'; export type {Arrayable} from './source/arrayable.d.ts'; export type {Opaque, UnwrapOpaque, Tagged, GetTagMetadata, UnwrapTagged} from './source/tagged.d.ts'; @@ -117,12 +117,19 @@ export type {IntRange} from './source/int-range.d.ts'; export type {IntClosedRange} from './source/int-closed-range.d.ts'; export type {IsEqual} from './source/is-equal.d.ts'; export type { - IsLiteral, + IsLiteral, IsStringLiteral, IsNumericLiteral, IsBooleanLiteral, IsSymbolLiteral, } from './source/is-literal.d.ts'; +export type { + IsPrimitive, + IsStringPrimitive, + IsNumericPrimitive, + IsBooleanPrimitive, + IsSymbolPrimitive, +} from './source/is-primitive.d.ts'; export type {IsAny} from './source/is-any.d.ts'; export type {IfAny} from './source/if-any.d.ts'; export type {IsNever} from './source/is-never.d.ts'; From 96b1e17297c7e9d632f0563bcc88cd7a0bebf3db Mon Sep 17 00:00:00 2001 From: benzaria Date: Thu, 22 May 2025 21:46:09 +0100 Subject: [PATCH 8/9] Fix conflict with recent changes on is-literal --- index.d.ts | 12 ++++++------ source/is-literal.d.ts | 29 ++++++++++++++--------------- source/is-primitive.d.ts | 8 +++----- test-d/is-literal.ts | 14 +++++++------- test-d/is-primitive.ts | 6 ++++-- 5 files changed, 34 insertions(+), 35 deletions(-) diff --git a/index.d.ts b/index.d.ts index 2d6bc96e4..9af047a2f 100644 --- a/index.d.ts +++ b/index.d.ts @@ -117,18 +117,18 @@ export type {IntRange} from './source/int-range.d.ts'; export type {IntClosedRange} from './source/int-closed-range.d.ts'; export type {IsEqual} from './source/is-equal.d.ts'; export type { - IsLiteral, + IsLiteral, IsStringLiteral, IsNumericLiteral, IsBooleanLiteral, IsSymbolLiteral, } from './source/is-literal.d.ts'; export type { - IsPrimitive, - IsStringPrimitive, - IsNumericPrimitive, - IsBooleanPrimitive, - IsSymbolPrimitive, + IsPrimitive, + IsStringPrimitive, + IsNumericPrimitive, + IsBooleanPrimitive, + IsSymbolPrimitive, } from './source/is-primitive.d.ts'; export type {IsAny} from './source/is-any.d.ts'; export type {IfAny} from './source/if-any.d.ts'; diff --git a/source/is-literal.d.ts b/source/is-literal.d.ts index 1dd730ab8..e0c3c7511 100644 --- a/source/is-literal.d.ts +++ b/source/is-literal.d.ts @@ -1,11 +1,11 @@ -import type {CollapseLiterals, Extends, IsNotFalse, Not} from './internal/type.d.ts'; +import type {Extends, IsNotFalse, Not} from './internal/type.d.ts'; +import type {CollapseLiterals} from './internal/object.d.ts'; import type {TagContainer, UnwrapTagged} from './tagged.js'; import type {Primitive} from './primitive.d.ts'; import type {IsNever} from './is-never.d.ts'; import type {Numeric} from './numeric.d.ts'; import type {And} from './and.js'; - /** Returns a boolean for whether the given type `T` is the specified `LiteralType`. @@ -121,14 +121,15 @@ export type IsStringLiteral = IsNever extends false ? _IsStringLiteral ? UnwrapTagged : T>> : false; -export type _IsStringLiteral = -// If `T` is an infinite string type (e.g., `on${string}`), `Record` produces an index signature, -// and since `{}` extends index signatures, the result becomes `false`. -S extends string - ? {} extends Record - ? false - : true - : false; +export type _IsStringLiteral = ( + // If `T` is an infinite string type (e.g., `on${string}`), `Record` produces an index signature, + // and since `{}` extends index signatures, the result becomes `false`. + S extends string + ? {} extends Record + ? false + : true + : false +); /** Returns a boolean for whether the given type is a `number` or `bigint` [literal type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types). @@ -177,11 +178,9 @@ endsWith('abc123', end); @category Type Guard @category Utilities */ -export type IsNumericLiteral = ( - T extends Numeric - ? LiteralChecks - : false -); +export type IsNumericLiteral = T extends Numeric + ? LiteralChecks + : false; /** Returns a boolean for whether the given type is a `true` or `false` [literal type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types). diff --git a/source/is-primitive.d.ts b/source/is-primitive.d.ts index 873ad7aac..db99a3215 100644 --- a/source/is-primitive.d.ts +++ b/source/is-primitive.d.ts @@ -104,11 +104,9 @@ type ex = IsNumericPrimitive<1> @category Type Guard @category Utilities */ -export type IsNumericPrimitive = ( - T extends Numeric - ? PrimitiveChecks - : false -); +export type IsNumericPrimitive = T extends Numeric + ? PrimitiveChecks + : false; /** Returns a boolean for whether the given type is strictly a `boolean` [primitive type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#the-primitives-string-number-and-boolean). diff --git a/test-d/is-literal.ts b/test-d/is-literal.ts index 28544d826..b7d527a28 100644 --- a/test-d/is-literal.ts +++ b/test-d/is-literal.ts @@ -135,19 +135,19 @@ expectType>>(false); expectType, 'Tag'>>>(false); expectType>>(false); expectType>>(true); -expectType>>({} as boolean); -expectType>>({} as boolean); +expectType>>(boolean); +expectType>>(boolean); expectType | Tagged>>(false); expectType | Tagged<'bar', 'Tag'>>>(true); -expectType | Tagged>>({} as boolean); -expectType | number>>({} as boolean); +expectType | Tagged>>(boolean); +expectType | number>>(boolean); // Uncollapsed unions (e.g., `'foo' | 'bar' | (string & {})`) expectType>(false); expectType>>(false); expectType>>(false); -expectType>>({} as boolean); -expectType>>({} as boolean); +expectType>>(boolean); +expectType>>(boolean); expectType, 'Tag'>>>(false); -expectType, 'Tag'>>>({} as boolean); +expectType, 'Tag'>>>(boolean); diff --git a/test-d/is-primitive.ts b/test-d/is-primitive.ts index ffb9f79b9..99dc2aaaf 100644 --- a/test-d/is-primitive.ts +++ b/test-d/is-primitive.ts @@ -10,13 +10,15 @@ import type { } from '../index.d.ts'; import type {Numeric} from '../source/numeric.js'; -type stringLiteral = ''; +type stringLiteral = 'aA'; type numberLiteral = 1; type bigintLiteral = 1n; type booleanLiteral = true; type symbolLiteral = typeof tag; type numericLiteral = numberLiteral | bigintLiteral; +declare const boolean: boolean; + // Primitives should be true expectType>(true); expectType>(true); @@ -73,9 +75,9 @@ expectType | Capitalize<'abc'>>>(false); // Union of literals and non-literals return `true` expectType | (string & {})>>(true); expectType | (string & {})>>(true); -expectType>(true); // Boundary types +expectType>(false); expectType>(false); expectType>(false); From a7995c5917c188b5c564155bea7e02de21113b6d Mon Sep 17 00:00:00 2001 From: benzaria Date: Thu, 22 May 2025 23:01:05 +0100 Subject: [PATCH 9/9] Adding `ExtractLiterals` and `IsPrimitive` types to `docs` --- readme.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/readme.md b/readme.md index beb5777e7..11b21d2fd 100644 --- a/readme.md +++ b/readme.md @@ -122,7 +122,7 @@ Click the type names for complete docs. - [`UndefinedOnPartialDeep`](source/undefined-on-partial-deep.d.ts) - Create a deep version of another type where all optional keys are set to also accept `undefined`. - [`ReadonlyDeep`](source/readonly-deep.d.ts) - Create a deeply immutable version of an `object`/`Map`/`Set`/`Array` type. Use [`Readonly`](https://www.typescriptlang.org/docs/handbook/utility-types.html#readonlytype) if you only need one level deep. - [`LiteralUnion`](source/literal-union.d.ts) - Create a union type by combining primitive types and literal types without sacrificing auto-completion in IDEs for the literal type part of the union. Workaround for [Microsoft/TypeScript#29729](https://github.com/Microsoft/TypeScript/issues/29729). -- [`LiteralOf`](source/literal-of.d.ts) - Creates a union of the literal members from a given union type, removing wide primitive or infinite types, and optionally specify the Strictness. +- [`ExtractLiterals`](source/literal-of.d.ts) - Creates a union of the literal members from a given union type, removing wide primitive or infinite types, and optionally specify the Strictness. - [`Tagged`](source/tagged.d.ts) - Create a [tagged type](https://medium.com/@KevinBGreene/surviving-the-typescript-ecosystem-branding-and-type-tagging-6cf6e516523d) that can support [multiple tags](https://github.com/sindresorhus/type-fest/issues/665) and [per-tag metadata](https://medium.com/@ethanresnick/advanced-typescript-tagged-types-improved-with-type-level-metadata-5072fc125fcf). (This replaces the previous [`Opaque`](source/tagged.d.ts) type, which is now deprecated.) - [`UnwrapTagged`](source/tagged.d.ts) - Get the untagged portion of a tagged type created with `Tagged`. (This replaces the previous [`UnwrapOpaque`](source/tagged.d.ts) type, which is now deprecated.) - [`InvariantOf`](source/invariant-of.d.ts) - Create an [invariant type](https://basarat.gitbook.io/typescript/type-system/type-compatibility#footnote-invariance), which is a type that does not accept supertypes and subtypes. @@ -190,10 +190,15 @@ Click the type names for complete docs. - [`If`](source/if.d.ts) - An if-else-like type that resolves depending on whether the given `boolean` type is `true` or `false`. - [`IsLiteral`](source/is-literal.d.ts) - Returns a boolean for whether the given type is a [literal type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types). +- [`IsPrimitive`](source/is-primitive.d.ts) - Returns a boolean for whether the given type is strictly a [primitive type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#the-primitives-string-number-and-boolean). - [`IsStringLiteral`](source/is-literal.d.ts) - Returns a boolean for whether the given type is a `string` [literal type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types). - [`IsNumericLiteral`](source/is-literal.d.ts) - Returns a boolean for whether the given type is a `number` or `bigint` [literal type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types). - [`IsBooleanLiteral`](source/is-literal.d.ts) - Returns a boolean for whether the given type is a `true` or `false` [literal type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types). - [`IsSymbolLiteral`](source/is-literal.d.ts) - Returns a boolean for whether the given type is a `symbol` [literal type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types). +- [`IsStringPrimitive`](source/is-primitive.d.ts) - Returns a boolean for whether the given type is strictly a `string` [primitive type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#the-primitives-string-number-and-boolean). +- [`IsNumericPrimitive`](source/is-primitive.d.ts) - Returns a boolean for whether the given type is strictly a `number` or `bigint` [primitive type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#the-primitives-string-number-and-boolean). +- [`IsBooleanPrimitive`](source/is-primitive.d.ts) - Returns a boolean for whether the given type is strictly a `true` or `false` [primitive type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#the-primitives-string-number-and-boolean). +- [`IsSymbolPrimitive`](source/is-primitive.d.ts) - Returns a boolean for whether the given type is strictly a `symbol` [primitive type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#the-primitives-string-number-and-boolean). - [`IsAny`](source/is-any.d.ts) - Returns a boolean for whether the given type is `any`. - [`IsNever`](source/is-never.d.ts) - Returns a boolean for whether the given type is `never`. - [`IsUnknown`](source/is-unknown.d.ts) - Returns a boolean for whether the given type is `unknown`.