Repository navigation
Lazily evaluated template literal types #43335
Description
Activity
MartinJohns commented
on Mar 22, 2021 ContributorMore actionsRelated: #41160
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.
It's not meant as a replacement. It's also not meant for writing DSL, yet people try to abuse it for that purpose.
fabiospampinato commented
on Mar 22, 2021 AuthorMore actionsIt's not meant as a replacement.
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.
- addedAwaiting More FeedbackThis means we'd like to hear from more people who would be helped by this featureThis means we'd like to hear from more people who would be helped by this featureSuggestionAn idea for TypeScriptAn idea for TypeScript
on Mar 22, 2021 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.
'itm-001'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
Expression produces a union type that is too complex to represent.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
Partcauses an error, but I can compose the smaller types together.export type TypeItem = `item-${N}${N}${N}${PartVar}${PartSpecial}`
Which, suggests there's about 30,000 entries.
By expanding the number of strings in
PartExt, my union typeitem-${N}${N}${N}${PartExt}will create compiler errors at 100,000 possible entries.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 ID = `#${ "app" | "item" }`type PseudoClass = "hover" | "before"
type WithAfter = `
${ Tag | ID }..:${ PseudoClass }..:after`..${ pattern }.. => allow for indefinite repetition and lazy evaluation
Reacted by MaxAnother 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.
type ID = { id: string; __type: 'id' }). However, we do want to ensure that things like loadObject() take in an ID obtained for somewhere else, ensuring loadObject('') is not valid.To do this, we've defined some types:
export type SUIDString = '%do not hardcode: SUID%'; export type UUIDString = `${string}-${string}-${string}-${string}-${string}`;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
object.getID() === 'a real id'from compiling).Reacted by Gabriele Tomberlimatveitolochko commented
on Jun 25, 2024 More actions+1
I encountered this issue when trying to implement hexadecimal color codes.
I could potentially implement this, given that this seems to be quite useful and wanted.MichalMarsalek commented
on Jul 5, 2024 ContributorMore actionsI'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?
Reacted by Max, Blue Falcon and Gabriele TomberliEven 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
Reacted by Alex Coleman, Matvei Tolochko and Gabriele TomberliThis 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}`
Reacted by Gabriele TomberliIt 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}`;
Reacted by Daniel Bayley, Sebastian Hädrich, Gabriele Tomberli and Joeptype HexDigit = '0'..'9' | 'A'..'F' | 'a'..'f';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.