-
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
Template literal types and mapped type 'as' clauses #40336
Conversation
Looks incredible! Nice work. |
very cool, thanks for doing this! It will be tricky to use this well without exceeding the "50 steps" limit with libraries like mongoose, but still will enable a lot of great things |
This looks really good! Will there be a way to add more modifiers? The times I’ve wanted this feature it’s been to convert from ALL_CAPS to camelCase. |
@bschlenk It seems like that should be implementable by users since split/join can be implemented as shown in the PR description |
Can we get a playground for this PR? Not sure if this will work since I'm not a part of the TS team: @typescript-bot pack this |
@typescript-bot pack this |
Hey @orta, I've packed this into an installable tgz. You can install it for testing by referencing it in your
and then running There is also a playground for this build. |
Is it possible to split a string literal type into a tuple of its characters? If you could split |
@danvk converting between |
Thanks @rickbutton! I'm wondering if it's possible to go the other way, though: |
For the mapped type type DoubleProp<T> = { [P in keyof T & string as `${P}1` | `${P}2`]: T[P] }
type T70 = DoubleProp<{ a: string, b: number }>
type Keys = keyof T70 // ==> 'a' | 'b' Is this intended? Why isn't |
|
Dear devs... I see this an scream a little, allow me to explain. You have done a titanic job, that doesn't mean that I agree with everthing. Could you give a real life example of someone taking leverage of this "Template Literal Types" for solving a given problem? I Mean, what where the motivations to complex the language like this in my proyect? What is the benefit?... Looks like you are mixing types and values in a chaotic way. I have never seen something like So let's try this... you may convince me... A thing like this 1️⃣
Could be written like this... 2️⃣
And the second choise is more "familiar" that the first one. I'm all eyes! |
@vituchon the linked issue in the PR, #12754, shows dozens of examples of people needing to do string manipulation in the type system, and had hundreds of upvotes (which at the time would have made it one of the top 10 or so most-upvoted suggestion at the time). There are also dozens of inbound duplicate links to #12574 outlining other use cases. |
Ok thanks @RyanCavanaugh for pointing out some material. It will take some time to analize and understand, probably later (without rush) I can answer properly. For others that shows an 👎 allow me to teel that gesture doesn't show/tell/say me nothing. Allow me to explain the feeling: I do not need desapprobal neither approbal, I need to comunicate and understand things. I just wrote an example, in the meantime while I study the new material, I'm open to see what is wrong with that. |
My 👎 was intended to convey that this issue really isn't a proper forum for the kind of discussion you're trying to have, without necessarily adding to the noise by leaving my own comment. I guess I failed at both pieces of that. I'd suggest you read the TypeScript Handbook sections on literal types and template literal types and the release notes for template literal types and then, if you still want to discuss this, go to an appropriate forum like the TypeScript Community Discord server. |
Jcalz, as far I know, the tab title says "conversation", and that is what we are having. Thanks for the material. If you don't want to recibe my feedback, you may ignore it. Recall if you don't express with words (or just use emogis) and don't try to talk to another person (of a different country with different culture) you barely (and unlike) will reach goals of getting understand |
Jumping into a nearly three-year-old feature and aggressively questioning the motivation of why it was added to the language is very unlikely to result in a useful dialog, and is indeed not the intended use of this forum. Getting a 👎 reaction for that is frankly the absolute minimum amount of pushback you should expect. Picking fights with other users (or the maintainers for that matter) is not acceptable behavior and you can consider yourself warned on that count. |
Well, I have no intention to be aggresive... at all. In any case, if stating "I see this an(d) scream a little" makes you fell bad, you may forgive me. Leaving that aside I guess I was polite... I really don't see any trail of aggression. You may highlight my aggresive statements and I will take that into account. And allow to me repeat... My intention was to see someone to answer my code with motivations, I mean ¿why I should use a (complex) type if a generic function already solves the problem? You may show me what I'm missing, if that is no too much to ask. |
Well, it doesn't. Your function in value space doesn't do anything to address string transforms in type space. |
Thanks for the answer. I don't get it too well. I'm missing some key points and don't want to bother anymore. Until then! Greetings Víctor. |
To use your example to explain why it exists, try to add the specific return type to your function (not just string) |
Thanks @Alpvax... I appreaciate your intention, you mean adding a Formal type parameter in the return type, ¿like this? , well that is something I have never thought about (but I do recall playing in Java with covariant return types). I'm trying to get it, I do need more time and further reading. TL;DR; I have faced complexity a lot of times and I know I will keep facing it so many more. At the end of the day I do feel that the "battle" is reduced to find the right set of abstractions (for solving a given problem) and the language in which you code will help more or will help less writing effectively those abstractions (by abstraction I mean types: classes, interfaces, ADT or any other declaration or definition of data structure) . In typescript I do have my technical debts, for example: writing a generic abstraction for dealing with persistence of a given data Type that acts as an Entity ("Entity" in DDD terms), like a base abstract class that servers for writing a template DAO. I try to understand a scenario where this "new" types (at least, for me) cames to be in handy, I also know that I need to read more. Working everyday gives me little to read, but I will continue struggeling until get some XP (experience points) off this. |
Yes, but that type is not correct. If the input is |
Thanks @Alpvax! You provide me valuable insight. I see now that the thing is about values that follow a pattern.... In the chronological continuum (I mean, from here to some time in the future) I hope to see and chat with all or anyone of you in the TypeScript Community Discord server. I understand that is the place to continue "long conversations". Thanks again. |
This PR implements two new features:
as
clauses, which provide the ability to transform property names in mapped types.Template literal types
Template literal types are the type space equivalent of template literal expressions. Similar to template literal expressions, template literal types are enclosed in backtick delimiters and can contain placeholders of the form
${T}
, whereT
is a type that is assignable tostring
,number
,boolean
, orbigint
. Template literal types provide the ability to concatenate literal strings, convert literals of non-string primitive types to their string representation, and change the capitalization or casing of string literals. Furthermore, through type inference, template literal types provide a simple form of string pattern matching and decomposition.Template literal types are resolved as follows:
`[${A|B|C}]`
resolves to`[${A}]` | `[${B}]` | `[${C}]`
. Union types in multiple placeholders resolve to the cross product. For example`[${A|B},${C|D}]`
resolves to`[${A},${C}]` | `[${A},${D}]` | `[${B},${C}]` | `[${B},${D}]`
.`[${'abc'}]`
resolves to`[abc]`
and`[${42}]`
resolves to`[42]`
.any
,string
,number
,boolean
, orbigint
in a placeholder causes the template literal to resolve to typestring
.never
type in a placeholder causes the template literal to resolve tonever
.Some examples:
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:
A template literal placeholder may optionally specify anuppercase
,lowercase
,capitalize
, oruncapitalize
modifier before the type. This modifier changes the casing of the entire replacement string or the first character of the replacement string. For example:EDIT: Based on feedback, the casing modifiers have been replaced by intrinsic string types in #40580.
Template literal types are all assignable to and subtypes of
string
. Furthermore, a template literal type`${T}`
is assignable to and a subtype of a template literal type`${C}`
, whereC
is a string literal type constraint ofT
. For example:Type inference supports inferring from a string literal type to a template literal type. For inference to succeed the starting and ending literal character spans (if any) of the target must exactly match the starting and ending spans of the source. Inference proceeds by matching each placeholder to a substring in the source from left to right: A placeholder followed by a literal character span is matched by inferring zero or more characters from the source until the first occurrence of that literal character span in the source. A placeholder immediately followed by another placeholder is matched by inferring a single character from the source.
Some examples:
Template literal types can be combined with recursive conditional types to write
Join
andSplit
types that iterate over repeated patterns.The recursive inference capabilities can for example be used to strongly type functions that access properties using "dotted paths", and pattern that is sometimes used in JavaScript frameworks.
Mapped type
as
clausesWith this PR, mapped types support an optional
as
clause through which a transformation of the generated property names can be specified:where
N
must be a type that is assignable tostring | number | symbol
. Typically,N
is a type that transformsP
, such as a template literal type that usesP
in a placeholder. For example:Above, a
keyof T & string
intersection is required becausekeyof T
could containsymbol
types that cannot be transformed using template literal types.When the type specified in an
as
clause resolves tonever
, no property is generated for that key. Thus, anas
clause can be used as a filter:When the type specified in an
as
clause resolves to a union of literal types, multiple properties with the same type are generated:Fixes #12754.
Playground: https://www.typescriptlang.org/play?ts=4.1.0-pr-40336-88