Skip to content

IsLiteral*: Fix behavior with unions - #1482

Merged
sindresorhus merged 14 commits into
mainfrom
fix/rewrite-is-literal-types
Jul 13, 2026
Merged

sindresorhus merged 14 commits into
mainfrom
fix/rewrite-is-literal-types

Conversation

@som-sm

@som-sm som-sm commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

This PR primarily fixes the behavior of IsLiteral* types when instantiated with a union of literals and non-literals. For example, IsNumericLiteral<1 | bigint> currently returns false, instead it should return boolean.

This PR also makes a couple of other things:

  • Replaces certain complicated/contrived JSDoc examples with simpler ones.
  • Removes unclear use-cases section from JSDoc. IMO, the use-cases mentioned were not adding much value.
  • Separates the different literal types into individual files.
  • Rewrites both the implementation and tests for all literal types from scratch.
  • Fixes behavior with Tagged/LiteralUnion types.

Also, the input to these literal types is currently unconstrained, but I think that should be constrained. For example, IsStringLiteral<T extends string> = ... instead of just IsStringLiteral<T> = ....

@som-sm
som-sm force-pushed the fix/rewrite-is-literal-types branch from 0d654a8 to e62591b Compare July 4, 2026 08:38
Repository owner deleted a comment from claude Bot Jul 4, 2026
Repository owner deleted a comment from claude Bot Jul 9, 2026
Repository owner deleted a comment from claude Bot Jul 9, 2026
Repository owner deleted a comment from claude Bot Jul 9, 2026
@som-sm
som-sm force-pushed the fix/rewrite-is-literal-types branch from 85c8893 to 5ab7073 Compare July 9, 2026 10:26
Repository owner deleted a comment from claude Bot Jul 9, 2026
@som-sm
som-sm requested a review from sindresorhus July 9, 2026 10:44
@som-sm
som-sm marked this pull request as ready for review July 9, 2026 10:44
@sindresorhus

Copy link
Copy Markdown
Owner

The new union behavior makes sense, but this regresses branded broad primitives:

type Brand = {readonly __brand: unique symbol};

type A = IsNumericLiteral<number & Brand>;
//=> true, should be false

type B = IsLiteral<Opaque<number, 'NumberTag'>>;
//=> true, should be false

The old implementation explicitly stripped branding before checking whether a primitive was broad or literal, so I think the fix is to keep teh new distributive union behavior but reuse that branded-primitive check for numeric/boolean/symbol.

Repository owner deleted a comment from claude Bot Jul 10, 2026
@som-sm

som-sm commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator Author

but this regresses branded broad primitives:

Added a new internal UnwrapBrand type to handle these cases. UnwrapBrand simply extracts the base type from the specified branded type, so it's not tied to IsLiteral* types and can be used in other places as well.

type Brand = {readonly __brand: unique symbol};

type A = UnwrapBrand<string & Brand>;
//=> string

type B = UnwrapBrand<Tagged<number, 'Tag'>>;
//=> number

type C = UnwrapBrand<Opaque<bigint, 'Tag'>>;
//=> bigint

type D = UnwrapBrand<PropertyKey & Brand>;
//=> PropertyKey

type E = UnwrapBrand<(1 | 200n | 'foo' | 'bar' | Uppercase<string>) & Brand>;
//=> 1 | 200n | 'foo' | 'bar' | Uppercase<string>

Notice that UnwrapBrand also works with branded types created using Tagged/Opaque.

@sindresorhus
sindresorhus merged commit 3425142 into main Jul 13, 2026
8 checks passed
@sindresorhus
sindresorhus deleted the fix/rewrite-is-literal-types branch July 13, 2026 22:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants