Uh oh!
There was an error while loading. Please reload this page.
Intrinsic string types - #40580
Conversation
# Conflicts: # src/compiler/diagnosticMessages.json
Ethan Resnick (ethanresnick)
commented
Sep 16, 2020
Total bikeshedding and possibly a bad idea, but... would it make any sense to re-use I don't know declare's current semantics well enough to say whether that's a natural extension, or whether it's a confusing jumble that would overload declare with too many similar, but not quite close enough, meanings. Or maybe this already means something else. But just a thought. |
Anders Hejlsberg (ahejlsberg)
commented
Sep 16, 2020
That's certainly looks reasonable, but unlike the current |
There was a problem hiding this comment.
Can we get some tests for intrinsic being used outside of the immediate type alias context?
leta1: intrinsic;leta2: {intrinsic: intrinsic};letintrinsic: intrinsic.intrinsic;typeFoo=(intrinsic);typeFoo<intrinsic>=intrinsic;typeFoo<Textendsintrinsic>=T;typeFoo<intrinsicextendsintrinsic>=intrinsic;typeBar<intrinsicextendsintrinsic>=(intrinsic);Wesley Wigham (weswigham)
commented
Sep 16, 2020
typeintrinsic=string;leta1: intrinsic="ok";should probably also be checked to still work, right? |
Ron Buckton (rbuckton)
commented
Sep 16, 2020
As with the |
Tooling catchup concerns aren't reasonable blockers in my opinion. We could get the self-hosting blockers out of the way ahead of time if we really needed, and that's the right workflow and right way to work with the community. |
Ron Buckton (rbuckton)
commented
Sep 16, 2020
The only "tooling catchup concern" I think we're discussing is regarding tooling that out team depends on to build the product. Adding new syntax often requires a full release cycle lag before we can actually use that syntax in our declaration files, given that we use When we added compound assignment operators, we couldn't use them in our own codebase until typescript-eslint/typescript-eslint#2253 was merged, and that required us to use a nightly build until the next official release went out. At the very least, making PRs against |
Ron Buckton (rbuckton)
commented
Sep 16, 2020
The |
Trey Brisbane (treybrisbane)
commented
Sep 17, 2020
I really like this approach. Much more than the previous "magic modifiers"! 🙂 Having a relatively general solution for special-casing complex typing operations without the need for new syntax, that also gives you a free migration path if/when the language becomes expressive enough to implement them outside the compiler, seems like a win for everybody. |
| Uncapitalize | ||
| } | ||
| const intrinsicTypeKinds: ReadonlyESMap<string, IntrinsicTypeKind> = new Map(getEntries({ |
There was a problem hiding this comment.
Maybe also change ThisType into an intrinsic?
There was a problem hiding this comment.
Why? ThisType is just a marker type that doesn't have any effect on its type argument. No reason to change it.
There was a problem hiding this comment.
I thought "intrinsic" means "hey I'm compiler magic" and ThisType is a compiler magic too 👀
There was a problem hiding this comment.
There are several "magic" types already that aren't intrinsic. For example Object, Function, Array<T>, Promise<T>, Iterator<T>, and others all receive special treatment in one way or another. The particular role "intrinsic" plays is to indicate that the implementation of the type is provided by the compiler. In the case of ThisType, there really is nothing interesting about the implementation because ThisType<T> is the same as T. All we do with ThisType is to activate certain behaviors when a reference to it occurs in a contextual type, but otherwise there's nothing special about it.
Yeah, it would be great to also have In the DOM libs it is common to map from JS camelCase properties to DOM dash-case attributes (f.e. |
Joep van Liempd (joepvl)
commented
Nov 18, 2020
FWIW, if you want to play around with these types in https://www.typescriptlang.org/play @ typeUncapitalize<Sextendsstring>=`${uncapitalizeS}`;typeCapitalize<Sextendsstring>=`${capitalizeS}`;typeLowercase<Sextendsstring>=`${lowercaseS}`;typeUppercase<Sextendsstring>=`${uppercaseS}`; |
Think we can add something like Would be really sweet if we could finally type a recursive camelize function. This does it for a fixed word length: typeB='hi_mate';typeCamelize<Sextendsstring>=Sextends `${infer A}_${infer B}`
? `${A}${Capitalize<B>}`
: unknown;typeC=Camelize<B>;Is there a way to use spread to do it for many? Have managed to do it like this but it's hideous: typeZ='hi_mate_what_up_good_morning_today';typeCamelize<S>=Sextends `${infer A}_${infer B}_${infer C}_${infer D}_${infer E}_${infer F}_${infer G}_${infer H}`
? `${A}${Capitalize<B>}${Capitalize<C>}${Capitalize<D>}${Capitalize<E>}${Capitalize<F>}${Capitalize<G>}${Capitalize<H>}`
: Sextends `${infer A}_${infer B}_${infer C}_${infer D}_${infer E}_${infer F}_${infer G}`
? `${A}${Capitalize<B>}${Capitalize<C>}${Capitalize<D>}${Capitalize<E>}${Capitalize<F>}${Capitalize<G>}`
: Sextends `${infer A}_${infer B}_${infer C}_${infer D}_${infer E}_${infer F}`
? `${A}${Capitalize<B>}${Capitalize<C>}${Capitalize<D>}${Capitalize<E>}${Capitalize<F>}`
: Sextends `${infer A}_${infer B}_${infer C}_${infer D}_${infer E}`
? `${A}${Capitalize<B>}${Capitalize<C>}${Capitalize<D>}${Capitalize<E>}`
: Sextends `${infer A}_${infer B}_${infer C}_${infer D}`
? `${A}${Capitalize<B>}${Capitalize<C>}${Capitalize<D>}`
: Sextends `${infer A}_${infer B}_${infer C}`
? `${A}${Capitalize<B>}${Capitalize<C>}`
: Sextends `${infer A}_${infer B}`
? `${A}${Capitalize<B>}`
: S;typeC=Camelize<Z>;// hiMateWhatUpGoodMorningTodayfunctionupperFirst<Sextendsstring>(s: S){const[head, ...tail]=s;return[head.toUpperCase(), ...tail].join('')asCapitalize<S>;}constz=upperFirst('hello');functioncamelCase<Sextendsstring>(s: S){const[head, ...tail]=s.split('_');return`${head}${tail.map(upperFirst).join('')}`asCamelize<S>;}consty=camelCase('hello_mate');So far so good, now I just have to figure out a way of doing the typing recursively on an object. Ok, I think I nailed it: typeDeepCamelize<T>={[KinkeyofTasCamelize<K>]: DeepCamelize<T[K]>;};Would still be nice to get: Same in reverse, and maybe an implementation that uses spread. Ok, improved version using typeCapitalizeIf<Conditionextendsboolean,Textendsstring>=Conditionextendstrue ? Capitalize<T> : T;typeSplitCamel<Sextendsstring,Dextendsstring,IsTailextendsboolean=false>=stringextendsS
? string[]
: Sextends''
? []
: Sextends `${infer T}${D}${infer U}`
? [CapitalizeIf<IsTail,T>, ...SplitCamel<U,D,true>]
: [CapitalizeIf<IsTail,S>];typeCamelize<S>=Sextendsstring ? Join<SplitCamel<S,'_'>,''> : S;typeDeepCamelize<T>={[KinkeyofTasCamelize<K>]: DeepCamelize<T[K]>;}; |
Andrew Boyton (aboyton)
commented
Nov 24, 2020
For camelCase, this seems to be an example in the PR that added this feature. https://github.com/microsoft/TypeScript/pull/40336/files#diff-4c1d1a787d1d286623e4419c6b614fc45fc6f65d7ff4efde25e5d145f9e0c654R84 typeSnakeToCamelCase<Sextendsstring>=Sextends `${infer T}_${infer U}` ? `${lowercaseT}${SnakeToPascalCase<U>}` :
Sextends `${infer T}` ? `${lowercaseT}` :
SnakeToPascalCase<S>;typeSnakeToPascalCase<Sextendsstring>=stringextendsS ? string :
Sextends `${infer T}_${infer U}` ? `${capitalize `${lowercaseT}`}${SnakeToPascalCase<U>}` :
Sextends `${infer T}` ? `${capitalize `${lowercaseT}`}` :
never;typeRR0=SnakeToPascalCase<'hello_world_foo'>;// 'HelloWorldFoo'typeRR1=SnakeToPascalCase<'FOO_BAR_BAZ'>;// 'FooBarBaz'typeRR2=SnakeToCamelCase<'hello_world_foo'>;// 'helloWorldFoo'typeRR3=SnakeToCamelCase<'FOO_BAR_BAZ'>;// 'fooBarBaz'This could be useful to be shared inside TypeScript itself. |
This should work for strings, objects and arrays: typeToCamelCase<T>=Textends `${infer A}_${infer B}`
? `${Uncapitalize<A>}${Capitalize<ToCamelCase<B>>}` :
Textendsstring
? Uncapitalize<T> :
Textends(infer A)[]
? ToCamelCase<A>[] :
Textends{}
? {[KinkeyofTasToCamelCase<K>]: ToCamelCase<T[K]>;} :
T;typeToSnakeCase<T>=Textends `${infer A}${infer B}${infer C}`
? ([A,B,C]extends[Lowercase<A>,Exclude<Uppercase<B>,'_'>,C]
? `${A}_${Lowercase<B>}${ToSnakeCase<C>}`
: `${Lowercase<A>}${ToSnakeCase<`${B}${C}`>}`
) :
Textendsstring
? Lowercase<T> :
Textends(infer A)[]
? ToSnakeCase<A>[] :
Textends{}
? {[KinkeyofTasToSnakeCase<K>]: ToSnakeCase<T[K]>;} :
T; |
This PR introduces four new intrinsic string mapping types in
lib.es5.d.ts:The new
intrinsickeyword is used to indicate that the type alias references a compiler provided implementation. It is an error to specifyintrinsicanywhere but immediately following the=separator in a type alias declaration for a type namedUppercase,Lowercase,CapitalizeorUncapitalizetaking a single type parameter (but, of course, it is possible that additional intrinsic implementations will be provided in the future). There is generally no reason to ever useintrinsicin user code.The intrinsic string types behave just like ordinary generic types and are similar to distributive conditional types in that they distribute over union types. Some examples:
Note that the
Capitalize<S>andUncapitalize<S>intrinsic types could fairly easily be implemented in pure TypeScript using conditional types and template literal type inference, but it isn't practical to do so at the moment because we use ESLint which hasn't yet been updated to support template literal types (though we expect that to happen soon).The intrinsic string types replace the
uppercase,lowercase,capitalize, anduncapitalizemodifiers in template literal types (based on feedback in #40336). This PR removes those modifiers.