Skip to content

Commit 6f0c91c

Browse files
ahejlsbergegamma
andauthored
Template literal types and mapped type 'as' clauses (microsoft#40336)
* Initial implementation of string template types * Accept new API baselines * Accept new baselines * Unified checking for large cross product union types * Accept new baselines * Ensure errors from union type resolution are reported * Accept new baselines * Compute constraints for string template types * Support `as T` clause in mapped types * Accept new API baselines * Add missing semicolon * Add checking of `as T` clauses * Support casing modifiers in string template types * Accept new baselines * Bump keyword maximum length * fix anders * Revert "fix anders" This reverts commit b3178d4. * Properly handle 'as T' clause with keyof for mapped type * Fix lint error * Single character inferences and anchored end span matching * Fewer array copy operations in template literal type resolution * Handle cases where 'as T' maps multiple properties onto one * Fix lint error * Store key type instead of type mapper in MappedSymbol * No constraint on `in T` type when `as N` clause present * Rename from TemplateType to TemplateLiteralType * Accept new API baselines * Add tests * Accept new baselines * Address CR feedback * Accept new API baselines Co-authored-by: Erich Gamma <[email protected]>
1 parent 96b0832 commit 6f0c91c

40 files changed

+3474
-583
lines changed

src/compiler/checker.ts

Lines changed: 261 additions & 53 deletions
Large diffs are not rendered by default.

src/compiler/diagnosticMessages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3028,6 +3028,10 @@
30283028
"category": "Error",
30293029
"code": 2792
30303030
},
3031+
"Template literal type argument '{0}' is not literal type or a generic type.": {
3032+
"category": "Error",
3033+
"code": 2793
3034+
},
30313035

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

src/compiler/emitter.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1313,6 +1313,8 @@ namespace ts {
13131313
return emitConstructSignature(<ConstructSignatureDeclaration>node);
13141314
case SyntaxKind.IndexSignature:
13151315
return emitIndexSignature(<IndexSignatureDeclaration>node);
1316+
case SyntaxKind.TemplateLiteralTypeSpan:
1317+
return emitTemplateTypeSpan(<TemplateLiteralTypeSpan>node);
13161318

13171319
// Types
13181320
case SyntaxKind.TypePredicate:
@@ -1357,6 +1359,8 @@ namespace ts {
13571359
return emitMappedType(<MappedTypeNode>node);
13581360
case SyntaxKind.LiteralType:
13591361
return emitLiteralType(<LiteralTypeNode>node);
1362+
case SyntaxKind.TemplateLiteralType:
1363+
return emitTemplateType(<TemplateLiteralTypeNode>node);
13601364
case SyntaxKind.ImportType:
13611365
return emitImportTypeNode(<ImportTypeNode>node);
13621366
case SyntaxKind.JSDocAllType:
@@ -2010,6 +2014,20 @@ namespace ts {
20102014
writeTrailingSemicolon();
20112015
}
20122016

2017+
function emitTemplateTypeSpan(node: TemplateLiteralTypeSpan) {
2018+
const keyword = node.casing === TemplateCasing.Uppercase ? "uppercase" :
2019+
node.casing === TemplateCasing.Lowercase ? "lowercase" :
2020+
node.casing === TemplateCasing.Capitalize ? "capitalize" :
2021+
node.casing === TemplateCasing.Uncapitalize ? "uncapitalize" :
2022+
undefined;
2023+
if (keyword) {
2024+
writeKeyword(keyword);
2025+
writeSpace();
2026+
}
2027+
emit(node.type);
2028+
emit(node.literal);
2029+
}
2030+
20132031
function emitSemicolonClassElement() {
20142032
writeTrailingSemicolon();
20152033
}
@@ -2202,6 +2220,12 @@ namespace ts {
22022220
writePunctuation("[");
22032221

22042222
pipelineEmit(EmitHint.MappedTypeParameter, node.typeParameter);
2223+
if (node.nameType) {
2224+
writeSpace();
2225+
writeKeyword("as");
2226+
writeSpace();
2227+
emit(node.nameType);
2228+
}
22052229

22062230
writePunctuation("]");
22072231
if (node.questionToken) {
@@ -2228,6 +2252,11 @@ namespace ts {
22282252
emitExpression(node.literal);
22292253
}
22302254

2255+
function emitTemplateType(node: TemplateLiteralTypeNode) {
2256+
emit(node.head);
2257+
emitList(node, node.templateSpans, ListFormat.TemplateExpressionSpans);
2258+
}
2259+
22312260
function emitImportTypeNode(node: ImportTypeNode) {
22322261
if (node.isTypeOf) {
22332262
writeKeyword("typeof");

src/compiler/factory/nodeFactory.ts

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@ namespace ts {
9494
updateConstructSignature,
9595
createIndexSignature,
9696
updateIndexSignature,
97+
createTemplateLiteralTypeSpan,
98+
updateTemplateLiteralTypeSpan,
9799
createKeywordTypeNode,
98100
createTypePredicateNode,
99101
updateTypePredicateNode,
@@ -138,6 +140,8 @@ namespace ts {
138140
updateMappedTypeNode,
139141
createLiteralTypeNode,
140142
updateLiteralTypeNode,
143+
createTemplateLiteralType,
144+
updateTemplateLiteralType,
141145
createObjectBindingPattern,
142146
updateObjectBindingPattern,
143147
createArrayBindingPattern,
@@ -1600,6 +1604,25 @@ namespace ts {
16001604
: node;
16011605
}
16021606

1607+
// @api
1608+
function createTemplateLiteralTypeSpan(casing: TemplateCasing, type: TypeNode, literal: TemplateMiddle | TemplateTail) {
1609+
const node = createBaseNode<TemplateLiteralTypeSpan>(SyntaxKind.TemplateLiteralTypeSpan);
1610+
node.casing = casing;
1611+
node.type = type;
1612+
node.literal = literal;
1613+
node.transformFlags = TransformFlags.ContainsTypeScript;
1614+
return node;
1615+
}
1616+
1617+
// @api
1618+
function updateTemplateLiteralTypeSpan(casing: TemplateCasing, node: TemplateLiteralTypeSpan, type: TypeNode, literal: TemplateMiddle | TemplateTail) {
1619+
return node.casing !== casing
1620+
|| node.type !== type
1621+
|| node.literal !== literal
1622+
? update(createTemplateLiteralTypeSpan(casing, type, literal), node)
1623+
: node;
1624+
}
1625+
16031626
//
16041627
// Types
16051628
//
@@ -1891,6 +1914,23 @@ namespace ts {
18911914
: node;
18921915
}
18931916

1917+
// @api
1918+
function createTemplateLiteralType(head: TemplateHead, templateSpans: readonly TemplateLiteralTypeSpan[]) {
1919+
const node = createBaseNode<TemplateLiteralTypeNode>(SyntaxKind.TemplateLiteralType);
1920+
node.head = head;
1921+
node.templateSpans = createNodeArray(templateSpans);
1922+
node.transformFlags = TransformFlags.ContainsTypeScript;
1923+
return node;
1924+
}
1925+
1926+
// @api
1927+
function updateTemplateLiteralType(node: TemplateLiteralTypeNode, head: TemplateHead, templateSpans: readonly TemplateLiteralTypeSpan[]) {
1928+
return node.head !== head
1929+
|| node.templateSpans !== templateSpans
1930+
? update(createTemplateLiteralType(head, templateSpans), node)
1931+
: node;
1932+
}
1933+
18941934
// @api
18951935
function createImportTypeNode(argument: TypeNode, qualifier?: EntityName, typeArguments?: readonly TypeNode[], isTypeOf = false) {
18961936
const node = createBaseNode<ImportTypeNode>(SyntaxKind.ImportType);
@@ -1968,23 +2008,25 @@ namespace ts {
19682008
}
19692009

19702010
// @api
1971-
function createMappedTypeNode(readonlyToken: ReadonlyKeyword | PlusToken | MinusToken | undefined, typeParameter: TypeParameterDeclaration, questionToken: QuestionToken | PlusToken | MinusToken | undefined, type: TypeNode | undefined): MappedTypeNode {
2011+
function createMappedTypeNode(readonlyToken: ReadonlyKeyword | PlusToken | MinusToken | undefined, typeParameter: TypeParameterDeclaration, nameType: TypeNode | undefined, questionToken: QuestionToken | PlusToken | MinusToken | undefined, type: TypeNode | undefined): MappedTypeNode {
19722012
const node = createBaseNode<MappedTypeNode>(SyntaxKind.MappedType);
19732013
node.readonlyToken = readonlyToken;
19742014
node.typeParameter = typeParameter;
2015+
node.nameType = nameType;
19752016
node.questionToken = questionToken;
19762017
node.type = type;
19772018
node.transformFlags = TransformFlags.ContainsTypeScript;
19782019
return node;
19792020
}
19802021

19812022
// @api
1982-
function updateMappedTypeNode(node: MappedTypeNode, readonlyToken: ReadonlyKeyword | PlusToken | MinusToken | undefined, typeParameter: TypeParameterDeclaration, questionToken: QuestionToken | PlusToken | MinusToken | undefined, type: TypeNode | undefined): MappedTypeNode {
2023+
function updateMappedTypeNode(node: MappedTypeNode, readonlyToken: ReadonlyKeyword | PlusToken | MinusToken | undefined, typeParameter: TypeParameterDeclaration, nameType: TypeNode | undefined, questionToken: QuestionToken | PlusToken | MinusToken | undefined, type: TypeNode | undefined): MappedTypeNode {
19832024
return node.readonlyToken !== readonlyToken
19842025
|| node.typeParameter !== typeParameter
2026+
|| node.nameType !== nameType
19852027
|| node.questionToken !== questionToken
19862028
|| node.type !== type
1987-
? update(createMappedTypeNode(readonlyToken, typeParameter, questionToken, type), node)
2029+
? update(createMappedTypeNode(readonlyToken, typeParameter, nameType, questionToken, type), node)
19882030
: node;
19892031
}
19902032

src/compiler/parser.ts

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,7 @@ namespace ts {
206206
case SyntaxKind.MappedType:
207207
return visitNode(cbNode, (<MappedTypeNode>node).readonlyToken) ||
208208
visitNode(cbNode, (<MappedTypeNode>node).typeParameter) ||
209+
visitNode(cbNode, (<MappedTypeNode>node).nameType) ||
209210
visitNode(cbNode, (<MappedTypeNode>node).questionToken) ||
210211
visitNode(cbNode, (<MappedTypeNode>node).type);
211212
case SyntaxKind.LiteralType:
@@ -424,6 +425,10 @@ namespace ts {
424425
return visitNode(cbNode, (<TemplateExpression>node).head) || visitNodes(cbNode, cbNodes, (<TemplateExpression>node).templateSpans);
425426
case SyntaxKind.TemplateSpan:
426427
return visitNode(cbNode, (<TemplateSpan>node).expression) || visitNode(cbNode, (<TemplateSpan>node).literal);
428+
case SyntaxKind.TemplateLiteralType:
429+
return visitNode(cbNode, (<TemplateLiteralTypeNode>node).head) || visitNodes(cbNode, cbNodes, (<TemplateLiteralTypeNode>node).templateSpans);
430+
case SyntaxKind.TemplateLiteralTypeSpan:
431+
return visitNode(cbNode, (<TemplateLiteralTypeSpan>node).type) || visitNode(cbNode, (<TemplateLiteralTypeSpan>node).literal);
427432
case SyntaxKind.ComputedPropertyName:
428433
return visitNode(cbNode, (<ComputedPropertyName>node).expression);
429434
case SyntaxKind.HeritageClause:
@@ -2584,6 +2589,49 @@ namespace ts {
25842589
);
25852590
}
25862591

2592+
function parseTemplateType(): TemplateLiteralTypeNode {
2593+
const pos = getNodePos();
2594+
return finishNode(
2595+
factory.createTemplateLiteralType(
2596+
parseTemplateHead(/*isTaggedTemplate*/ false),
2597+
parseTemplateTypeSpans()
2598+
),
2599+
pos
2600+
);
2601+
}
2602+
2603+
function parseTemplateTypeSpans() {
2604+
const pos = getNodePos();
2605+
const list = [];
2606+
let node: TemplateLiteralTypeSpan;
2607+
do {
2608+
node = parseTemplateTypeSpan();
2609+
list.push(node);
2610+
}
2611+
while (node.literal.kind === SyntaxKind.TemplateMiddle);
2612+
return createNodeArray(list, pos);
2613+
}
2614+
2615+
function parseTemplateTypeSpan(): TemplateLiteralTypeSpan {
2616+
const pos = getNodePos();
2617+
return finishNode(
2618+
factory.createTemplateLiteralTypeSpan(
2619+
parseTemplateCasing(),
2620+
parseType(),
2621+
parseLiteralOfTemplateSpan(/*isTaggedTemplate*/ false)
2622+
),
2623+
pos
2624+
);
2625+
}
2626+
2627+
function parseTemplateCasing(): TemplateCasing {
2628+
return parseOptional(SyntaxKind.UppercaseKeyword) ? TemplateCasing.Uppercase :
2629+
parseOptional(SyntaxKind.LowercaseKeyword) ? TemplateCasing.Lowercase :
2630+
parseOptional(SyntaxKind.CapitalizeKeyword) ? TemplateCasing.Capitalize :
2631+
parseOptional(SyntaxKind.UncapitalizeKeyword) ? TemplateCasing.Uncapitalize :
2632+
TemplateCasing.None;
2633+
}
2634+
25872635
function parseLiteralOfTemplateSpan(isTaggedTemplate: boolean) {
25882636
if (token() === SyntaxKind.CloseBraceToken) {
25892637
reScanTemplateToken(isTaggedTemplate);
@@ -3252,6 +3300,7 @@ namespace ts {
32523300
}
32533301
parseExpected(SyntaxKind.OpenBracketToken);
32543302
const typeParameter = parseMappedTypeParameter();
3303+
const nameType = parseOptional(SyntaxKind.AsKeyword) ? parseType() : undefined;
32553304
parseExpected(SyntaxKind.CloseBracketToken);
32563305
let questionToken: QuestionToken | PlusToken | MinusToken | undefined;
32573306
if (token() === SyntaxKind.QuestionToken || token() === SyntaxKind.PlusToken || token() === SyntaxKind.MinusToken) {
@@ -3263,7 +3312,7 @@ namespace ts {
32633312
const type = parseTypeAnnotation();
32643313
parseSemicolon();
32653314
parseExpected(SyntaxKind.CloseBraceToken);
3266-
return finishNode(factory.createMappedTypeNode(readonlyToken, typeParameter, questionToken, type), pos);
3315+
return finishNode(factory.createMappedTypeNode(readonlyToken, typeParameter, nameType, questionToken, type), pos);
32673316
}
32683317

32693318
function parseTupleElementType() {
@@ -3444,6 +3493,8 @@ namespace ts {
34443493
return parseImportType();
34453494
case SyntaxKind.AssertsKeyword:
34463495
return lookAhead(nextTokenIsIdentifierOrKeywordOnSameLine) ? parseAssertsTypePredicate() : parseTypeReference();
3496+
case SyntaxKind.TemplateHead:
3497+
return parseTemplateType();
34473498
default:
34483499
return parseTypeReference();
34493500
}
@@ -3485,6 +3536,8 @@ namespace ts {
34853536
case SyntaxKind.InferKeyword:
34863537
case SyntaxKind.ImportKeyword:
34873538
case SyntaxKind.AssertsKeyword:
3539+
case SyntaxKind.NoSubstitutionTemplateLiteral:
3540+
case SyntaxKind.TemplateHead:
34883541
return true;
34893542
case SyntaxKind.FunctionKeyword:
34903543
return !inStartOfParameter;

src/compiler/scanner.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,10 @@ namespace ts {
151151
yield: SyntaxKind.YieldKeyword,
152152
async: SyntaxKind.AsyncKeyword,
153153
await: SyntaxKind.AwaitKeyword,
154+
uppercase: SyntaxKind.UppercaseKeyword,
155+
lowercase: SyntaxKind.LowercaseKeyword,
156+
capitalize: SyntaxKind.CapitalizeKeyword,
157+
uncapitalize: SyntaxKind.UncapitalizeKeyword,
154158
of: SyntaxKind.OfKeyword,
155159
};
156160

@@ -1508,9 +1512,9 @@ namespace ts {
15081512
}
15091513

15101514
function getIdentifierToken(): SyntaxKind.Identifier | KeywordSyntaxKind {
1511-
// Reserved words are between 2 and 11 characters long and start with a lowercase letter
1515+
// Reserved words are between 2 and 12 characters long and start with a lowercase letter
15121516
const len = tokenValue.length;
1513-
if (len >= 2 && len <= 11) {
1517+
if (len >= 2 && len <= 12) {
15141518
const ch = tokenValue.charCodeAt(0);
15151519
if (ch >= CharacterCodes.a && ch <= CharacterCodes.z) {
15161520
const keyword = textToKeyword.get(tokenValue);

0 commit comments

Comments
 (0)