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

Treat string literals as readonly char arrays while compering them #51217

Closed
5 tasks done
DeepDoge opened this issue Oct 18, 2022 · 5 comments
Closed
5 tasks done

Treat string literals as readonly char arrays while compering them #51217

DeepDoge opened this issue Oct 18, 2022 · 5 comments
Labels
Unactionable There isn't something we can do with this issue

Comments

@DeepDoge
Copy link

Suggestion

πŸ” Search Terms

string literal, template, complex, string literals as array

βœ… Viability Checklist

My suggestion meets these guidelines:

  • This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.

⭐ Suggestion

I was just experimenting with this feature #40336
As mentioned in the PR:

Beware that the cross product distribution of union types can quickly escalate into very large and costly types. Also note that union types are limited to less than 100,000 constituents, and the following will cause an error:

We can't make the string literals too complex.
If we do we get this error: Expression produces a union type that is too complex to represent.
But this error is not caused because the type we wanna check is too complex.
It's cause because IDE generates every possible literal string type.

But instead of that we can treat strings as char arrays while compering them.
So no breaking changes, everything same but we compare string literals as if they are readonly char arrays
And after achieving this string unions can become less complex.

πŸ“ƒ Motivating Example

For example i wanna check if a string is a HEX color
I can write something like this

type Split<S extends string, D extends string> =
    string extends S ? string[] :
    S extends '' ? [] :
    S extends `${infer T}${D}${infer U}` ? [T, ...Split<U, D>] :
    [S]
type ExtractGeneric<T extends readonly any[]> = T extends readonly (infer U)[] ? U : never

const hexChars = 'ABCDEFabcef0123456789' as const
type HexChars = ExtractGeneric<Split<typeof hexChars, ''>>
type HexColor = `#${HexChars}${HexChars}${HexChars}` | `#${HexChars}${HexChars}${HexChars}${HexChars}${HexChars}${HexChars}`
const color: HexColor = "#fff"

But this is gonna give us this error: Expression produces a union type that is too complex to represent.
Because it tries to generate every possible string literal

But TypeScript is quite capable of checking if a string is a hex color.

type Split<S extends string, D extends string> =
    string extends S ? string[] :
    S extends '' ? [] :
    S extends `${infer T}${D}${infer U}` ? [T, ...Split<U, D>] :
    [S]
type ExtractGeneric<T extends readonly any[]> = T extends readonly (infer U)[] ? U : never

const hexChars = 'ABCDEFabcef0123456789' as const
type HexChars = ExtractGeneric<Split<typeof hexChars, ''>>
type HexColor = ['#', HexChars, HexChars, HexChars] | ['#', HexChars, HexChars, HexChars, HexChars, HexChars, HexChars]
const color = "#fff" as const
type Test = Split<typeof address, ''> extends HexColor ? true : false // Result: true

Example above is able to check if a string is a hex color or not.

So if we were to treat strings as readonly char arrays while compering them, the first example above would have worked without any issue.

πŸ’» Use Cases

This can be use for many things.

  • Hex Colors
  • Zip Codes
  • 0x wallet addresses
  • URLs
  • etc.
@RyanCavanaugh
Copy link
Member

This misapprehends the root of the limitation; "treat as char array" is not a fix.

The root problem is that if you write something like

type X = `${"A" | "B"}`;

Then we have two options. We can flatten it:

type X = "A" | " B";

or leave it alone in its original form.

Leaving it alone in its original form works for a second, but the moment you do something more interesting with the type, the limitation immediately appears again:

type F<T> = T extends "A" ? true : false;
type G = F<X>;

Here, F needs to be evaluated for each constituent of X to see if any of them extend "A" (one does). At this point, allowing you to have a type that expands into a union with more than 10,000 elements is a recipe for performance disaster.

@RyanCavanaugh RyanCavanaugh added the Unactionable There isn't something we can do with this issue label Oct 18, 2022
@DeepDoge
Copy link
Author

I don't think you understand what I'm trying to say.

The goal here is instead of flattening the template and finding every single possible f/cking string literals and doing a check for 100,000,000 something strings, we can just check every char one by one.
So instead of doing 18^6 checks, we do 18*6 checks

So hex color example would look like this:

type HexChars = 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
type HexColor = `#${HexChars}${HexChars}${HexChars}` | `#${HexChars}${HexChars}${HexChars}${HexChars}${HexChars}${HexChars}`
const color: HexColor = "#fff"

Would work like this under the hood:

type HexChars = 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
type HexColor = ['#', HexChars, HexChars, HexChars] | ['#', HexChars, HexChars, HexChars, HexChars, HexChars, HexChars]
const color: HexColor = "#fff" // ['#', 'f', 'f', 'f']

EDIT:
I think just being able to compare ['A', 'B'] with "AB" would work too.
Makes more sense than changing how string types work tbh.

I think the problem here is, that I can do this:

type HexChars = 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
type HexColor = ['#', HexChars, HexChars, HexChars] | ['#', HexChars, HexChars, HexChars, HexChars, HexChars, HexChars]
const color: HexColor = ['#', 'f', 'f', 'f']

But can't do this:

type HexChars = 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
type HexColor = ['#', HexChars, HexChars, HexChars] | ['#', HexChars, HexChars, HexChars, HexChars, HexChars, HexChars]
const color: HexColor = "#fff"

@fatcerberus
Copy link

That works fine for simple assignability checks, but there are still cases that would require the template to be expanded into a union of all the possible strings (distributive conditional types, e.g.). And changing template strings to not behave as unions in those cases would be a huge breaking change. That’s what Ryan was trying to explain.

@RyanCavanaugh
Copy link
Member

Based on your reply, I understood the original comment correctly, and my response to that still applies.

It sounds like you're looking for RegExp types, see #41160

@DeepDoge
Copy link
Author

Cool, having RegExp types would be a lot more useful

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Unactionable There isn't something we can do with this issue
Projects
None yet
Development

No branches or pull requests

3 participants