Skip to content

Commit fbce4f6

Browse files
authored
Intrinsic string types (#40580)
* Introduce Uppercase<T> and Lowercase<T> intrinsic types * Accept new API baselines * Add Uppercase/Lowercase/Capitalize/Uncapitalize to lib.d.ts * Update fourslash * Add an 'intrinsic' keyword * Update template literal type tests * Accept new API baselines * Minor fixes * Switch Capitalize<T> and Uncapitalize<T> to intrinsic types * Add tests * Accept new baselines * Accept new baselines * Remove template literal type casing modifiers * Update tests * Accept new baselines * Add more tests * Normalize nested template literal types * Add normalization tests * Accept new baselines * Update tests
1 parent ce3dbef commit fbce4f6

30 files changed

+1742
-853
lines changed

src/compiler/checker.ts

+139-53
Large diffs are not rendered by default.

src/compiler/diagnosticMessages.json

+4
Original file line numberDiff line numberDiff line change
@@ -3043,6 +3043,10 @@
30433043
"category": "Error",
30443044
"code": 2794
30453045
},
3046+
"The 'intrinsic' keyword can only be used to declare compiler provided intrinsic types.": {
3047+
"category": "Error",
3048+
"code": 2795
3049+
},
30463050

30473051
"Import declaration '{0}' is using private name '{1}'.": {
30483052
"category": "Error",

src/compiler/emitter.ts

-9
Original file line numberDiff line numberDiff line change
@@ -2023,15 +2023,6 @@ namespace ts {
20232023
}
20242024

20252025
function emitTemplateTypeSpan(node: TemplateLiteralTypeSpan) {
2026-
const keyword = node.casing === TemplateCasing.Uppercase ? "uppercase" :
2027-
node.casing === TemplateCasing.Lowercase ? "lowercase" :
2028-
node.casing === TemplateCasing.Capitalize ? "capitalize" :
2029-
node.casing === TemplateCasing.Uncapitalize ? "uncapitalize" :
2030-
undefined;
2031-
if (keyword) {
2032-
writeKeyword(keyword);
2033-
writeSpace();
2034-
}
20352026
emit(node.type);
20362027
emit(node.literal);
20372028
}

src/compiler/factory/nodeFactory.ts

+4-6
Original file line numberDiff line numberDiff line change
@@ -1605,21 +1605,19 @@ namespace ts {
16051605
}
16061606

16071607
// @api
1608-
function createTemplateLiteralTypeSpan(casing: TemplateCasing, type: TypeNode, literal: TemplateMiddle | TemplateTail) {
1608+
function createTemplateLiteralTypeSpan(type: TypeNode, literal: TemplateMiddle | TemplateTail) {
16091609
const node = createBaseNode<TemplateLiteralTypeSpan>(SyntaxKind.TemplateLiteralTypeSpan);
1610-
node.casing = casing;
16111610
node.type = type;
16121611
node.literal = literal;
16131612
node.transformFlags = TransformFlags.ContainsTypeScript;
16141613
return node;
16151614
}
16161615

16171616
// @api
1618-
function updateTemplateLiteralTypeSpan(casing: TemplateCasing, node: TemplateLiteralTypeSpan, type: TypeNode, literal: TemplateMiddle | TemplateTail) {
1619-
return node.casing !== casing
1620-
|| node.type !== type
1617+
function updateTemplateLiteralTypeSpan(node: TemplateLiteralTypeSpan, type: TypeNode, literal: TemplateMiddle | TemplateTail) {
1618+
return node.type !== type
16211619
|| node.literal !== literal
1622-
? update(createTemplateLiteralTypeSpan(casing, type, literal), node)
1620+
? update(createTemplateLiteralTypeSpan(type, literal), node)
16231621
: node;
16241622
}
16251623

src/compiler/parser.ts

+1-10
Original file line numberDiff line numberDiff line change
@@ -2618,22 +2618,13 @@ namespace ts {
26182618
const pos = getNodePos();
26192619
return finishNode(
26202620
factory.createTemplateLiteralTypeSpan(
2621-
parseTemplateCasing(),
26222621
parseType(),
26232622
parseLiteralOfTemplateSpan(/*isTaggedTemplate*/ false)
26242623
),
26252624
pos
26262625
);
26272626
}
26282627

2629-
function parseTemplateCasing(): TemplateCasing {
2630-
return parseOptional(SyntaxKind.UppercaseKeyword) ? TemplateCasing.Uppercase :
2631-
parseOptional(SyntaxKind.LowercaseKeyword) ? TemplateCasing.Lowercase :
2632-
parseOptional(SyntaxKind.CapitalizeKeyword) ? TemplateCasing.Capitalize :
2633-
parseOptional(SyntaxKind.UncapitalizeKeyword) ? TemplateCasing.Uncapitalize :
2634-
TemplateCasing.None;
2635-
}
2636-
26372628
function parseLiteralOfTemplateSpan(isTaggedTemplate: boolean) {
26382629
if (token() === SyntaxKind.CloseBraceToken) {
26392630
reScanTemplateToken(isTaggedTemplate);
@@ -6751,7 +6742,7 @@ namespace ts {
67516742
const name = parseIdentifier();
67526743
const typeParameters = parseTypeParameters();
67536744
parseExpected(SyntaxKind.EqualsToken);
6754-
const type = parseType();
6745+
const type = token() === SyntaxKind.IntrinsicKeyword && tryParse(parseKeywordAndNoDot) || parseType();
67556746
parseSemicolon();
67566747
const node = factory.createTypeAliasDeclaration(decorators, modifiers, name, typeParameters, type);
67576748
return withJSDoc(finishNode(node, pos), hasJSDoc);

src/compiler/scanner.ts

+1-4
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ namespace ts {
111111
infer: SyntaxKind.InferKeyword,
112112
instanceof: SyntaxKind.InstanceOfKeyword,
113113
interface: SyntaxKind.InterfaceKeyword,
114+
intrinsic: SyntaxKind.IntrinsicKeyword,
114115
is: SyntaxKind.IsKeyword,
115116
keyof: SyntaxKind.KeyOfKeyword,
116117
let: SyntaxKind.LetKeyword,
@@ -151,10 +152,6 @@ namespace ts {
151152
yield: SyntaxKind.YieldKeyword,
152153
async: SyntaxKind.AsyncKeyword,
153154
await: SyntaxKind.AwaitKeyword,
154-
uppercase: SyntaxKind.UppercaseKeyword,
155-
lowercase: SyntaxKind.LowercaseKeyword,
156-
capitalize: SyntaxKind.CapitalizeKeyword,
157-
uncapitalize: SyntaxKind.UncapitalizeKeyword,
158155
of: SyntaxKind.OfKeyword,
159156
};
160157

src/compiler/types.ts

+15-24
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ namespace ts {
167167
DeclareKeyword,
168168
GetKeyword,
169169
InferKeyword,
170+
IntrinsicKeyword,
170171
IsKeyword,
171172
KeyOfKeyword,
172173
ModuleKeyword,
@@ -186,10 +187,6 @@ namespace ts {
186187
FromKeyword,
187188
GlobalKeyword,
188189
BigIntKeyword,
189-
UppercaseKeyword,
190-
LowercaseKeyword,
191-
CapitalizeKeyword,
192-
UncapitalizeKeyword,
193190
OfKeyword, // LastKeyword and LastToken and LastContextualKeyword
194191

195192
// Parse tree nodes
@@ -544,7 +541,6 @@ namespace ts {
544541
| SyntaxKind.BigIntKeyword
545542
| SyntaxKind.BooleanKeyword
546543
| SyntaxKind.BreakKeyword
547-
| SyntaxKind.CapitalizeKeyword
548544
| SyntaxKind.CaseKeyword
549545
| SyntaxKind.CatchKeyword
550546
| SyntaxKind.ClassKeyword
@@ -574,10 +570,10 @@ namespace ts {
574570
| SyntaxKind.InKeyword
575571
| SyntaxKind.InstanceOfKeyword
576572
| SyntaxKind.InterfaceKeyword
573+
| SyntaxKind.IntrinsicKeyword
577574
| SyntaxKind.IsKeyword
578575
| SyntaxKind.KeyOfKeyword
579576
| SyntaxKind.LetKeyword
580-
| SyntaxKind.LowercaseKeyword
581577
| SyntaxKind.ModuleKeyword
582578
| SyntaxKind.NamespaceKeyword
583579
| SyntaxKind.NeverKeyword
@@ -605,11 +601,9 @@ namespace ts {
605601
| SyntaxKind.TryKeyword
606602
| SyntaxKind.TypeKeyword
607603
| SyntaxKind.TypeOfKeyword
608-
| SyntaxKind.UncapitalizeKeyword
609604
| SyntaxKind.UndefinedKeyword
610605
| SyntaxKind.UniqueKeyword
611606
| SyntaxKind.UnknownKeyword
612-
| SyntaxKind.UppercaseKeyword
613607
| SyntaxKind.VarKeyword
614608
| SyntaxKind.VoidKeyword
615609
| SyntaxKind.WhileKeyword
@@ -635,6 +629,7 @@ namespace ts {
635629
| SyntaxKind.AnyKeyword
636630
| SyntaxKind.BigIntKeyword
637631
| SyntaxKind.BooleanKeyword
632+
| SyntaxKind.IntrinsicKeyword
638633
| SyntaxKind.NeverKeyword
639634
| SyntaxKind.NumberKeyword
640635
| SyntaxKind.ObjectKeyword
@@ -1665,19 +1660,10 @@ namespace ts {
16651660
export interface TemplateLiteralTypeSpan extends TypeNode {
16661661
readonly kind: SyntaxKind.TemplateLiteralTypeSpan,
16671662
readonly parent: TemplateLiteralTypeNode;
1668-
readonly casing: TemplateCasing;
16691663
readonly type: TypeNode;
16701664
readonly literal: TemplateMiddle | TemplateTail;
16711665
}
16721666

1673-
export const enum TemplateCasing {
1674-
None,
1675-
Uppercase,
1676-
Lowercase,
1677-
Capitalize,
1678-
Uncapitalize,
1679-
}
1680-
16811667
// Note: 'brands' in our syntax nodes serve to give us a small amount of nominal typing.
16821668
// Consider 'Expression'. Without the brand, 'Expression' is actually no different
16831669
// (structurally) than 'Node'. Because of this you can pass any Node to a function that
@@ -4892,6 +4878,7 @@ namespace ts {
48924878
Substitution = 1 << 25, // Type parameter substitution
48934879
NonPrimitive = 1 << 26, // intrinsic object type
48944880
TemplateLiteral = 1 << 27, // Template literal type
4881+
StringMapping = 1 << 28, // Uppercase/Lowercase type
48954882

48964883
/* @internal */
48974884
AnyOrUnknown = Any | Unknown,
@@ -4909,7 +4896,7 @@ namespace ts {
49094896
Intrinsic = Any | Unknown | String | Number | BigInt | Boolean | BooleanLiteral | ESSymbol | Void | Undefined | Null | Never | NonPrimitive,
49104897
/* @internal */
49114898
Primitive = String | Number | BigInt | Boolean | Enum | EnumLiteral | ESSymbol | Void | Undefined | Null | Literal | UniqueESSymbol,
4912-
StringLike = String | StringLiteral | TemplateLiteral,
4899+
StringLike = String | StringLiteral | TemplateLiteral | StringMapping,
49134900
NumberLike = Number | NumberLiteral | Enum,
49144901
BigIntLike = BigInt | BigIntLiteral,
49154902
BooleanLike = Boolean | BooleanLiteral,
@@ -4922,15 +4909,15 @@ namespace ts {
49224909
StructuredType = Object | Union | Intersection,
49234910
TypeVariable = TypeParameter | IndexedAccess,
49244911
InstantiableNonPrimitive = TypeVariable | Conditional | Substitution,
4925-
InstantiablePrimitive = Index | TemplateLiteral,
4912+
InstantiablePrimitive = Index | TemplateLiteral | StringMapping,
49264913
Instantiable = InstantiableNonPrimitive | InstantiablePrimitive,
49274914
StructuredOrInstantiable = StructuredType | Instantiable,
49284915
/* @internal */
49294916
ObjectFlagsType = Any | Nullable | Never | Object | Union | Intersection,
49304917
/* @internal */
49314918
Simplifiable = IndexedAccess | Conditional,
49324919
/* @internal */
4933-
Substructure = Object | Union | Intersection | Index | IndexedAccess | Conditional | Substitution | TemplateLiteral,
4920+
Substructure = Object | Union | Intersection | Index | IndexedAccess | Conditional | Substitution | TemplateLiteral | StringMapping,
49344921
// 'Narrowable' types are types where narrowing actually narrows.
49354922
// This *should* be every type other than null, undefined, void, and never
49364923
Narrowable = Any | Unknown | StructuredOrInstantiable | StringLike | NumberLike | BigIntLike | BooleanLike | ESSymbol | UniqueESSymbol | NonPrimitive,
@@ -5379,11 +5366,15 @@ namespace ts {
53795366
}
53805367

53815368
export interface TemplateLiteralType extends InstantiableType {
5382-
texts: readonly string[]; // Always one element longer than casings/types
5383-
casings: readonly TemplateCasing[]; // Always at least one element
5369+
texts: readonly string[]; // Always one element longer than types
53845370
types: readonly Type[]; // Always at least one element
53855371
}
53865372

5373+
export interface StringMappingType extends InstantiableType {
5374+
symbol: Symbol;
5375+
type: Type;
5376+
}
5377+
53875378
// Type parameter substitution (TypeFlags.Substitution)
53885379
// Substitution types are created for type parameters or indexed access types that occur in the
53895380
// true branch of a conditional type. For example, in 'T extends string ? Foo<T> : Bar<T>', the
@@ -6774,8 +6765,8 @@ namespace ts {
67746765
createIndexSignature(decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, parameters: readonly ParameterDeclaration[], type: TypeNode): IndexSignatureDeclaration;
67756766
/* @internal */ createIndexSignature(decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, parameters: readonly ParameterDeclaration[], type: TypeNode | undefined): IndexSignatureDeclaration; // eslint-disable-line @typescript-eslint/unified-signatures
67766767
updateIndexSignature(node: IndexSignatureDeclaration, decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, parameters: readonly ParameterDeclaration[], type: TypeNode): IndexSignatureDeclaration;
6777-
createTemplateLiteralTypeSpan(casing: TemplateCasing, type: TypeNode, literal: TemplateMiddle | TemplateTail): TemplateLiteralTypeSpan;
6778-
updateTemplateLiteralTypeSpan(casing: TemplateCasing, node: TemplateLiteralTypeSpan, type: TypeNode, literal: TemplateMiddle | TemplateTail): TemplateLiteralTypeSpan;
6768+
createTemplateLiteralTypeSpan(type: TypeNode, literal: TemplateMiddle | TemplateTail): TemplateLiteralTypeSpan;
6769+
updateTemplateLiteralTypeSpan(node: TemplateLiteralTypeSpan, type: TypeNode, literal: TemplateMiddle | TemplateTail): TemplateLiteralTypeSpan;
67796770

67806771
//
67816772
// Types

src/harness/fourslashInterfaceImpl.ts

+4
Original file line numberDiff line numberDiff line change
@@ -1065,6 +1065,10 @@ namespace FourSlashInterface {
10651065
typeEntry("ConstructorParameters"),
10661066
typeEntry("ReturnType"),
10671067
typeEntry("InstanceType"),
1068+
typeEntry("Uppercase"),
1069+
typeEntry("Lowercase"),
1070+
typeEntry("Capitalize"),
1071+
typeEntry("Uncapitalize"),
10681072
interfaceEntry("ThisType"),
10691073
varEntry("ArrayBuffer"),
10701074
interfaceEntry("ArrayBufferTypes"),

src/lib/es5.d.ts

+20
Original file line numberDiff line numberDiff line change
@@ -1508,6 +1508,26 @@ type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => i
15081508
*/
15091509
type InstanceType<T extends new (...args: any) => any> = T extends new (...args: any) => infer R ? R : any;
15101510

1511+
/**
1512+
* Convert string literal type to uppercase
1513+
*/
1514+
type Uppercase<S extends string> = intrinsic;
1515+
1516+
/**
1517+
* Convert string literal type to lowercase
1518+
*/
1519+
type Lowercase<S extends string> = intrinsic;
1520+
1521+
/**
1522+
* Convert first character of string literal type to uppercase
1523+
*/
1524+
type Capitalize<S extends string> = intrinsic;
1525+
1526+
/**
1527+
* Convert first character of string literal type to lowercase
1528+
*/
1529+
type Uncapitalize<S extends string> = intrinsic;
1530+
15111531
/**
15121532
* Marker for contextual 'this' type
15131533
*/

0 commit comments

Comments
 (0)