-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
Lazily evaluated template literal types #43335
Comments
Related: #41160
It's not meant as a replacement. It's also not meant for writing DSL, yet people try to abuse it for that purpose. |
I might be misremembering but I think I read a comment from one of the maintainers saying that template literal types got implemented because it's more clear how to compose them, while the same was not true for regex types, which are not currently supported. That doesn't mean that the current implementation is meant to be a replacement for regexes, even though there's some overlap of course. I just wanted to touch on how regex types would be able to represent vastly more complicated spaces of strings than what template literal types can currently do, especially given the way TypeScript currently implements them. |
I've run into this issue with a Typescript-based game I've been building. Users can collect rewards that are serialized in a specific format and can be composed with hyphens. 'item-001'
'item-002-sunny'
'item-002-var0'
'item-003-sunny-var0' I have a custom class to perform deserialization and serialization, although I do have a bunch of hardcoded strings scattered around. Originally I just used a string type, but it was too easy to make typos.
And rewarding this broken item to a player caused other issues in the game, resulting in a bit of manual work in player accounts. Eventually I created a shell script that would scan my item database and create an explicit type, although it's limited because it's difficult to represent the entire composability. export type TypeItem = 'item-001' | 'item-002' | 'item-003' | ...
const Item1 = 'item-001'
const Item2 = 'item-002'
const Item3 = 'item-003' While this is an improvement, I do need to do quite a bit of casting for my composing unless I really wanted to update my shell script. const prize = '${Item1}-sunny' as TypeItem Which still makes this prone to potential typos with the added compositions. My game would benefit a lot from being able to use template literal types, as I have a use-case which makes a lot of sense. However, trying to define the composition in full fails with the error type N = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
type PartExt = '-sunny' | '-rainy' | '-snowy' | '' // ...
type PartVar = '-var1' | '-var2' | '-var3' | ''
type PartSpecial = '-special' | ''
export type TypeItem = `item-${N}${N}${N}${PartExt}${PartVar}${PartSpecial}` I can represent a slightly smaller type, with a union of four types: export type TypeItem = `item-${N}${N}${N}${PartExt}` Which, when hovered over, suggests there's about 58,000 entries. Adding any other export type TypeItem = `item-${N}${N}${N}${PartVar}${PartSpecial}` Which, suggests there's about 30,000 entries. By expanding the number of strings in Perhaps trying to implement this serialized representation as a Template Literal is not the right approach, particularly as I already have a class for managing these values. At the same time, I'm not sure that my union type is too complicated that the Typescript compiler cannot process 100K items. |
Anything new on this? Just encountered the "need" for this while creating a template literal type for a string joined with colons. I know one could simply just change the implementation to taking an array, but sometimes it appears to be nice to have to be able to type an infinite sequence like in Haskell for example and have it be evaluated lazily : type Tag = "html" | "body" type PseudoClass = "hover" | "before" type WithAfter = ` ..${ pattern }.. => allow for indefinite repetition and lazy evaluation |
Another use case (similar to one mentioned in a linked issue) is typing IDs more strongly. Our IDs are stored as strings, and we don't want to wrap them in an unnecessarily heavy class or object (ex. To do this, we've defined some types:
The UUID one (while not a perfect match for UUIDs) works rather well, as any hardcoded "correctish" string will pass, and our helpers can assert they return it. The SUIDString has a more generic format (any [a-zA-Z0-9] of length 10-15) that can't be expressed currently. This means that any literals seem to fail it (preventing |
+1 |
I'd love to see this. Can this issue be put into backlog so that we can implement it or closed to indicate it won't get supported? |
Even if handling of literal types can't be modified, an alternative solution should be to have string subtypes for common string patterns. The most significant example would be hexadecimal strings. This would add string validation for common formats like RGBA color data, byte/memory representation, or UUIDs. There are several issues that have been opened asking for this kind of feature where the requestor's use case was specifically for hexadecimal validation |
This would solve (or greatly ease) nearly every use-case for fixed-length string validation, so +1 from me. I can't describe how many times I have yearned for the following type signature to work: type RGB = `#${Hex}${Hex}${Hex}${Hex}${Hex}${Hex}` |
It would be nice to support specifying ranges instead of completely listing all values as syntactic sugar. For example: type HexDigit = '0'..'9' | 'A'..'F' | 'a'..'f'; For simplicity, this can be restricted to single characters only, excluding surrogate pairs as well. I'm not sure, but maybe this could also optimize storing such a type in compiler memory without generating undecillions of all possible combinations ahead of time. For example, this could apply to these types: type HexDigitQuad = `${HexDigit}${HexDigit}${HexDigit}${HexDigit}`;
type GuidString = `${HexDigitQuad}${HexDigitQuad}-${HexDigitQuad}-${HexDigitQuad}-${HexDigitQuad}-${HexDigitQuad}${HexDigitQuad}${HexDigitQuad}`; |
or… type HexDigit = '[0-9]' | '[A-F]' | '[a-f]'; // or '[A-f]' |
Suggestion
Template literal types seem to be resolved immediately, which makes the combinatorics blow up and the whole thing much less useful than it would seem to be at first.
For example I can't even represent a type equals to 5 consecutive digits with the current system without getting a "too complex" error:
The current system may be more composable than regexes, but if it's not even able to represent something like
/\d{5}/
I'd argue that's not a replacement for it at all.The suggestion is that there's no need to resolve all the possible combinations at all, but a divide and conquer approach should be implemented where each little piece of the template literal does it's job individually and when all little pieces match than the whole thing matches.
🔍 Search Terms
✅ Viability Checklist
⭐ Suggestion
Evaluate template literal types lazily to not make the combinatorics blow up. Meaning that each little piece of the template literal should be its own little function that performs some type matching internally, so there's no need to resolve all the possible combinations at all.
📃 Motivating Example
I can't even represent the equivalent of
/\d{5}/
currently.💻 Use Cases
For example creating a type for UUID strings, which are a fairly common thing.
The text was updated successfully, but these errors were encountered: