Skip to content
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

StringRepeat: Add support for generating longer strings & fix instantiations with unions #1046

Merged
merged 1 commit into from
Jan 20, 2025

Conversation

som-sm
Copy link
Collaborator

@som-sm som-sm commented Jan 20, 2025

Similar to #1042, StringRepeat currently can generate strings only upto length 90.

image

Why 90?

Looks like the compiler applies minor optimisations if we extract the recursive call using infer and then use the extracted type instead of the recursive call directly.

Let's consider an example: the following non-tail recursive implementation of GetChars fails at 50 characters.

type GetChars<S> = S extends `${infer Char}${infer Rest}`
  ? Char | GetChars<Rest>
  : never;

type FiftyZeroes = "00000000000000000000000000000000000000000000000000";

// Fails at 50 characters
type T1 = GetChars<FiftyZeroes>;
//        ~~~~~~~~~~~~~~~~~~~~~
//        Type instantiation is excessively deep and possibly infinite.

Playground


However, if we extract GetChars<Rest> into a separate type and then use the extracted type, the implementation starts working for upto 147 characters.

type GetChars<S> = S extends `${infer Char}${infer Rest}`
  ? GetChars<Rest> extends infer Result // Extract the result of the recursive call
    ? Char | Result // And then use it
    : never
  : never;

type FiftyZeroes = "00000000000000000000000000000000000000000000000000";

// Doesn't fail at 50 characters
type T2 = GetChars<FiftyZeroes>;

type HunderedFourtyEightZeroes =
  "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000";

// Fails at 148 characters
type T3 = GetChars<HunderedFourtyEightZeroes>;
//        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//        Type instantiation is excessively deep and possibly infinite.

Playground

A similar thing happens in our existing implementation of StringRepeat. However, the test case I've added doesn’t consider this minor improvement, so it tests for 50 characters and not 91 characters.

This PR refactors StringRepeat to leverage tail recursion, allowing it to generate strings upto 1000 characters.


Other notes:

  1. The existing implementation also doesn't work with unions, this PR fixes that as well.

     type T = StringRepeat<"foo" | "bar", 2>;
     // Actual: "foofoo" | "foobar" | "barfoo" | "barbar"
     // Expected: "foofoo" | "barbar"
  2. Removed the use of Subtract because it was severely impacting the performance when used in a tail-recursive implementation.

    demo-compressed.mov

@sindresorhus sindresorhus merged commit fbccaab into main Jan 20, 2025
12 checks passed
@sindresorhus sindresorhus deleted the perf/refactor-string-repeat-implementation branch January 20, 2025 17:06
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