Skip to content

Add ExtractLiterals and IsPrimitive types #1145

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 10 commits into
base: main
Choose a base branch
from

Conversation

benzaria
Copy link
Contributor

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

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<Pet>;
//    ^? type PetLiteralNonStrict = 'dog' | 'cat' | `${string}Dog`
type PetLiteralStrict = LoosenUnion<Pet, true>;
//    ^? type PetLiteralStrict = 'dog' | 'cat'

// Number example:
type Nums = LiteralUnion<0 | 1 | 2, number>;
//    ^? type Nums = 0 | 1 | 2 | (number & {})
type NumsLiteral = LoosenUnion<Nums>;
//    ^? type NumsLiteral = 0 | 1 | 2

// Symbol example:
declare const sym1: unique symbol;
declare const sym2: unique symbol;
type Symbols = LiteralUnion<typeof sym1 | typeof sym2, symbol>;
//    ^? type Symbols = typeof sym1 | typeof sym2 | (symbol & {})
type SymbolsLiteral = LoosenUnion<Symbols>;
//    ^? type SymbolsLiteral = typeof sym1 | typeof sym2

// BigInt example:
type Big = LiteralUnion<1n | 2n, bigint>;
//    ^? type Big = 1n | 2n | (bigint & {})
type BigLiteral = LoosenUnion<Big>;
//    ^? type BigLiteral = 1n | 2n

@benzaria benzaria force-pushed the main branch 2 times, most recently from 21a5190 to 331aa21 Compare May 20, 2025 17:59
@sindresorhus
Copy link
Owner

sindresorhus commented May 20, 2025

The name is incorrect. Should be something like ExtractLiterals. Same with the docs and descriptions.

And I think it should be strict by default.

@benzaria
Copy link
Contributor Author

ExtractLiterals

What about literalOf ? that seems good enough

@benzaria benzaria changed the title Add LoosenUnion type Add LiteralOf type May 21, 2025
@som-sm
Copy link
Collaborator

som-sm commented May 21, 2025

The name is incorrect. Should be something like ExtractLiterals. Same with the docs and descriptions.

I agree that ExtractLiterals makes more sense.

And I think it should be strict by default.

Also, agree on this.

@benzaria benzaria changed the title Add LiteralOf type Add ExtractLiterals and IsPrimitive type May 22, 2025
@benzaria benzaria changed the title Add ExtractLiterals and IsPrimitive type Add ExtractLiterals and IsPrimitive types May 22, 2025
@benzaria benzaria force-pushed the merge-conflict/main branch 3 times, most recently from 07e76cd to fbab98c Compare May 22, 2025 21:35
@benzaria benzaria force-pushed the merge-conflict/main branch from fbab98c to 96b1e17 Compare May 22, 2025 21:38
@benzaria
Copy link
Contributor Author

Hey guys👋, any updates or reviews ?

@som-sm
Copy link
Collaborator

som-sm commented May 23, 2025

@benzaria Looks like the PR does so many things now. Anyways, here are my thoughts:

  1. I don't think IsPrimitive should return false for primitive literals. After all, primitive literals are still primitives. At the very least, that shouldn’t be the default behaviour.

    type T = IsPrimitive<"foo">;
    //   ^? type T = false
  2. Maybe IsLiteral itself should accept a strict option, but I guess it's fine for now.

    To clarify, what I meant is that we should consider having a strict option not just in ExtractLiterals, but also in IsLiteral and IsStringLiteral. Ideally, the strict flag from ExtractLiterals should flow through to IsLiteral, and from there to IsStringLiteral. That way, the behaviour would remain consistent across all related types.

For now, maybe we can keep it simple and just add ExtractLiterals without any options, like this, and introduce the strict option in a follow-up PR.


Also, just curious, do you have any use cases where treating infinite string types like `on${string}`, `${number}`, or Uppercase<string> as literals (i.e. strict: false) would actually be preferred?

Copy link
Collaborator

@som-sm som-sm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please try to keep the PR focused on a single thing, bundling too many changes together makes it harder to review and understand the overall impact.

@benzaria
Copy link
Contributor Author

@som-sm

I don't think IsPrimitive should return false for primitive literals. After all, primitive literals are still primitives. At the very least, that shouldn’t be the default behaviour.

But if it does it will be just and extends like I said for that i copied the Extends type that does the same as u want,
and there is no benefit of returning true for literals or any use case. And the IsPrimitive is clearly shipped as a way to strictly detect if it0s a true wide primitive and not literal.

Also, just curious, do you have any use cases where treating infinite string types like `on${string}`, `${number}`, or Uppercase<string> as literals (i.e. strict: false) would actually be preferred?

Yes I want to introduce a IsInfinitLiteral type later on which be something like this:

type IsInfinitLiteral<T> = 
    T extends unknown
        ? And<
            Not<IsLiteral<T>>,
            Not<IsPrimitive<T>>
        >
        : never

@benzaria
Copy link
Contributor Author

@som-sm

So After thinking IsLiteral should have a strict options But also for IsPrimitive and they both will be strict = true

@benzaria benzaria marked this pull request as draft June 6, 2025 01:42
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.

3 participants