Skip to content
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

Merged
merged 35 commits into from
Sep 10, 2020
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
1a6676e
Initial implementation of string template types
ahejlsberg Aug 24, 2020
27d9164
Accept new API baselines
ahejlsberg Aug 24, 2020
0b9c5f5
Accept new baselines
ahejlsberg Aug 24, 2020
815a408
Unified checking for large cross product union types
ahejlsberg Aug 25, 2020
032e1bf
Accept new baselines
ahejlsberg Aug 25, 2020
ced200f
Ensure errors from union type resolution are reported
ahejlsberg Aug 25, 2020
d2ad4c1
Accept new baselines
ahejlsberg Aug 25, 2020
b5b897d
Compute constraints for string template types
ahejlsberg Aug 25, 2020
3b14f3d
Support `as T` clause in mapped types
ahejlsberg Aug 27, 2020
6a8e070
Accept new API baselines
ahejlsberg Aug 27, 2020
a63a442
Add missing semicolon
ahejlsberg Aug 27, 2020
45ea576
Add checking of `as T` clauses
ahejlsberg Aug 28, 2020
05e2ef1
Support casing modifiers in string template types
ahejlsberg Aug 30, 2020
95ac3d4
Accept new baselines
ahejlsberg Aug 31, 2020
c95c000
Bump keyword maximum length
ahejlsberg Aug 31, 2020
b3178d4
fix anders
egamma Sep 1, 2020
8cac241
Revert "fix anders"
egamma Sep 1, 2020
4061ba9
Properly handle 'as T' clause with keyof for mapped type
ahejlsberg Sep 1, 2020
d68670b
Fix lint error
ahejlsberg Sep 2, 2020
b7b58c7
Single character inferences and anchored end span matching
ahejlsberg Sep 3, 2020
b12681d
Fewer array copy operations in template literal type resolution
ahejlsberg Sep 5, 2020
272b23f
Handle cases where 'as T' maps multiple properties onto one
ahejlsberg Sep 6, 2020
3b6e8c2
Fix lint error
ahejlsberg Sep 6, 2020
6dc0b2a
Store key type instead of type mapper in MappedSymbol
ahejlsberg Sep 6, 2020
9c1f9fb
No constraint on `in T` type when `as N` clause present
ahejlsberg Sep 6, 2020
b2518c6
Rename from TemplateType to TemplateLiteralType
ahejlsberg Sep 7, 2020
2d70ca2
Accept new API baselines
ahejlsberg Sep 7, 2020
096d0ae
Add tests
ahejlsberg Sep 7, 2020
1488f35
Accept new baselines
ahejlsberg Sep 7, 2020
66337ed
Merge branch 'master' of https://github.com/microsoft/TypeScript
ahejlsberg Sep 8, 2020
b5e75dd
Merge branch 'master' into templateTypes
ahejlsberg Sep 8, 2020
6c95951
Address CR feedback
ahejlsberg Sep 10, 2020
e81ebb4
Accept new API baselines
ahejlsberg Sep 10, 2020
6bd94b0
Merge branch 'master' of https://github.com/microsoft/TypeScript
ahejlsberg Sep 10, 2020
d38a388
Merge branch 'master' into templateTypes
ahejlsberg Sep 10, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
314 changes: 261 additions & 53 deletions src/compiler/checker.ts

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -3028,6 +3028,10 @@
"category": "Error",
"code": 2792
},
"Template type argument '{0}' is not literal type or a generic type.": {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"Template type argument '{0}' is not literal type or a generic type.": {
"Template literal type argument '{0}' is not literal type or a generic type.": {

"category": "Error",
"code": 2793
},

"Import declaration '{0}' is using private name '{1}'.": {
"category": "Error",
Expand Down
29 changes: 29 additions & 0 deletions src/compiler/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1313,6 +1313,8 @@ namespace ts {
return emitConstructSignature(<ConstructSignatureDeclaration>node);
case SyntaxKind.IndexSignature:
return emitIndexSignature(<IndexSignatureDeclaration>node);
case SyntaxKind.TemplateTypeSpan:
return emitTemplateTypeSpan(<TemplateTypeSpan>node);

// Types
case SyntaxKind.TypePredicate:
Expand Down Expand Up @@ -1357,6 +1359,8 @@ namespace ts {
return emitMappedType(<MappedTypeNode>node);
case SyntaxKind.LiteralType:
return emitLiteralType(<LiteralTypeNode>node);
case SyntaxKind.TemplateType:
return emitTemplateType(<TemplateTypeNode>node);
case SyntaxKind.ImportType:
return emitImportTypeNode(<ImportTypeNode>node);
case SyntaxKind.JSDocAllType:
Expand Down Expand Up @@ -2006,6 +2010,20 @@ namespace ts {
writeTrailingSemicolon();
}

function emitTemplateTypeSpan(node: TemplateTypeSpan) {
const keyword = node.casing === TemplateCasing.Uppercase ? "uppercase" :
node.casing === TemplateCasing.Lowercase ? "lowercase" :
node.casing === TemplateCasing.Capitalize ? "capitalize" :
node.casing === TemplateCasing.Uncapitalize ? "uncapitalize" :
undefined;
if (keyword) {
writeKeyword(keyword);
writeSpace();
}
emit(node.type);
emit(node.literal);
}

function emitSemicolonClassElement() {
writeTrailingSemicolon();
}
Expand Down Expand Up @@ -2198,6 +2216,12 @@ namespace ts {
writePunctuation("[");

pipelineEmit(EmitHint.MappedTypeParameter, node.typeParameter);
if (node.nameType) {
writeSpace();
writeKeyword("as");
writeSpace();
emit(node.nameType);
}

writePunctuation("]");
if (node.questionToken) {
Expand All @@ -2224,6 +2248,11 @@ namespace ts {
emitExpression(node.literal);
}

function emitTemplateType(node: TemplateTypeNode) {
emit(node.head);
emitList(node, node.templateSpans, ListFormat.TemplateExpressionSpans);
}

function emitImportTypeNode(node: ImportTypeNode) {
if (node.isTypeOf) {
writeKeyword("typeof");
Expand Down
48 changes: 45 additions & 3 deletions src/compiler/factory/nodeFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ namespace ts {
updateConstructSignature,
createIndexSignature,
updateIndexSignature,
createTemplateTypeSpan,
updateTemplateTypeSpan,
createKeywordTypeNode,
createTypePredicateNode,
updateTypePredicateNode,
Expand Down Expand Up @@ -138,6 +140,8 @@ namespace ts {
updateMappedTypeNode,
createLiteralTypeNode,
updateLiteralTypeNode,
createTemplateType,
updateTemplateType,
createObjectBindingPattern,
updateObjectBindingPattern,
createArrayBindingPattern,
Expand Down Expand Up @@ -1596,6 +1600,25 @@ namespace ts {
: node;
}

// @api
function createTemplateTypeSpan(casing: TemplateCasing, type: TypeNode, literal: TemplateMiddle | TemplateTail) {
const node = createBaseNode<TemplateTypeSpan>(SyntaxKind.TemplateTypeSpan);
node.casing = casing;
node.type = type;
node.literal = literal;
node.transformFlags = TransformFlags.ContainsTypeScript;
return node;
}

// @api
function updateTemplateTypeSpan(casing: TemplateCasing, node: TemplateTypeSpan, type: TypeNode, literal: TemplateMiddle | TemplateTail) {
return node.casing !== casing
|| node.type !== type
|| node.literal !== literal
? update(createTemplateTypeSpan(casing, type, literal), node)
: node;
}

//
// Types
//
Expand Down Expand Up @@ -1887,6 +1910,23 @@ namespace ts {
: node;
}

// @api
function createTemplateType(head: TemplateHead, templateSpans: readonly TemplateTypeSpan[]) {
const node = createBaseNode<TemplateTypeNode>(SyntaxKind.TemplateType);
node.head = head;
node.templateSpans = createNodeArray(templateSpans);
node.transformFlags = TransformFlags.ContainsTypeScript;
return node;
}

// @api
function updateTemplateType(node: TemplateTypeNode, head: TemplateHead, templateSpans: readonly TemplateTypeSpan[]) {
return node.head !== head
|| node.templateSpans !== templateSpans
? update(createTemplateType(head, templateSpans), node)
: node;
}

// @api
function createImportTypeNode(argument: TypeNode, qualifier?: EntityName, typeArguments?: readonly TypeNode[], isTypeOf = false) {
const node = createBaseNode<ImportTypeNode>(SyntaxKind.ImportType);
Expand Down Expand Up @@ -1964,23 +2004,25 @@ namespace ts {
}

// @api
function createMappedTypeNode(readonlyToken: ReadonlyKeyword | PlusToken | MinusToken | undefined, typeParameter: TypeParameterDeclaration, questionToken: QuestionToken | PlusToken | MinusToken | undefined, type: TypeNode | undefined): MappedTypeNode {
function createMappedTypeNode(readonlyToken: ReadonlyKeyword | PlusToken | MinusToken | undefined, typeParameter: TypeParameterDeclaration, nameType: TypeNode | undefined, questionToken: QuestionToken | PlusToken | MinusToken | undefined, type: TypeNode | undefined): MappedTypeNode {
const node = createBaseNode<MappedTypeNode>(SyntaxKind.MappedType);
node.readonlyToken = readonlyToken;
node.typeParameter = typeParameter;
node.nameType = nameType;
node.questionToken = questionToken;
node.type = type;
node.transformFlags = TransformFlags.ContainsTypeScript;
return node;
}

// @api
function updateMappedTypeNode(node: MappedTypeNode, readonlyToken: ReadonlyKeyword | PlusToken | MinusToken | undefined, typeParameter: TypeParameterDeclaration, questionToken: QuestionToken | PlusToken | MinusToken | undefined, type: TypeNode | undefined): MappedTypeNode {
function updateMappedTypeNode(node: MappedTypeNode, readonlyToken: ReadonlyKeyword | PlusToken | MinusToken | undefined, typeParameter: TypeParameterDeclaration, nameType: TypeNode | undefined, questionToken: QuestionToken | PlusToken | MinusToken | undefined, type: TypeNode | undefined): MappedTypeNode {
return node.readonlyToken !== readonlyToken
|| node.typeParameter !== typeParameter
|| node.nameType !== nameType
|| node.questionToken !== questionToken
|| node.type !== type
? update(createMappedTypeNode(readonlyToken, typeParameter, questionToken, type), node)
? update(createMappedTypeNode(readonlyToken, typeParameter, nameType, questionToken, type), node)
: node;
}

Expand Down
55 changes: 54 additions & 1 deletion src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ namespace ts {
case SyntaxKind.MappedType:
return visitNode(cbNode, (<MappedTypeNode>node).readonlyToken) ||
visitNode(cbNode, (<MappedTypeNode>node).typeParameter) ||
visitNode(cbNode, (<MappedTypeNode>node).nameType) ||
visitNode(cbNode, (<MappedTypeNode>node).questionToken) ||
visitNode(cbNode, (<MappedTypeNode>node).type);
case SyntaxKind.LiteralType:
Expand Down Expand Up @@ -424,6 +425,10 @@ namespace ts {
return visitNode(cbNode, (<TemplateExpression>node).head) || visitNodes(cbNode, cbNodes, (<TemplateExpression>node).templateSpans);
case SyntaxKind.TemplateSpan:
return visitNode(cbNode, (<TemplateSpan>node).expression) || visitNode(cbNode, (<TemplateSpan>node).literal);
case SyntaxKind.TemplateType:
return visitNode(cbNode, (<TemplateTypeNode>node).head) || visitNodes(cbNode, cbNodes, (<TemplateTypeNode>node).templateSpans);
case SyntaxKind.TemplateTypeSpan:
return visitNode(cbNode, (<TemplateTypeSpan>node).type) || visitNode(cbNode, (<TemplateTypeSpan>node).literal);
case SyntaxKind.ComputedPropertyName:
return visitNode(cbNode, (<ComputedPropertyName>node).expression);
case SyntaxKind.HeritageClause:
Expand Down Expand Up @@ -2579,6 +2584,49 @@ namespace ts {
);
}

function parseTemplateType(): TemplateTypeNode {
const pos = getNodePos();
return finishNode(
factory.createTemplateType(
parseTemplateHead(/*isTaggedTemplate*/ false),
parseTemplateTypeSpans()
),
pos
);
}

function parseTemplateTypeSpans() {
const pos = getNodePos();
const list = [];
let node: TemplateTypeSpan;
do {
node = parseTemplateTypeSpan();
list.push(node);
}
while (node.literal.kind === SyntaxKind.TemplateMiddle);
return createNodeArray(list, pos);
}

function parseTemplateTypeSpan(): TemplateTypeSpan {
const pos = getNodePos();
return finishNode(
factory.createTemplateTypeSpan(
parseTemplateCasing(),
parseType(),
parseLiteralOfTemplateSpan(/*isTaggedTemplate*/ false)
),
pos
);
}

function parseTemplateCasing(): TemplateCasing {
return parseOptional(SyntaxKind.UppercaseKeyword) ? TemplateCasing.Uppercase :
parseOptional(SyntaxKind.LowercaseKeyword) ? TemplateCasing.Lowercase :
parseOptional(SyntaxKind.CapitalizeKeyword) ? TemplateCasing.Capitalize :
parseOptional(SyntaxKind.UncapitalizeKeyword) ? TemplateCasing.Uncapitalize :
TemplateCasing.None;
}

function parseLiteralOfTemplateSpan(isTaggedTemplate: boolean) {
if (token() === SyntaxKind.CloseBraceToken) {
reScanTemplateToken(isTaggedTemplate);
Expand Down Expand Up @@ -3247,6 +3295,7 @@ namespace ts {
}
parseExpected(SyntaxKind.OpenBracketToken);
const typeParameter = parseMappedTypeParameter();
const nameType = parseOptional(SyntaxKind.AsKeyword) ? parseType() : undefined;
parseExpected(SyntaxKind.CloseBracketToken);
let questionToken: QuestionToken | PlusToken | MinusToken | undefined;
if (token() === SyntaxKind.QuestionToken || token() === SyntaxKind.PlusToken || token() === SyntaxKind.MinusToken) {
Expand All @@ -3258,7 +3307,7 @@ namespace ts {
const type = parseTypeAnnotation();
parseSemicolon();
parseExpected(SyntaxKind.CloseBraceToken);
return finishNode(factory.createMappedTypeNode(readonlyToken, typeParameter, questionToken, type), pos);
return finishNode(factory.createMappedTypeNode(readonlyToken, typeParameter, nameType, questionToken, type), pos);
}

function parseTupleElementType() {
Expand Down Expand Up @@ -3439,6 +3488,8 @@ namespace ts {
return parseImportType();
case SyntaxKind.AssertsKeyword:
return lookAhead(nextTokenIsIdentifierOrKeywordOnSameLine) ? parseAssertsTypePredicate() : parseTypeReference();
case SyntaxKind.TemplateHead:
return parseTemplateType();
default:
return parseTypeReference();
}
Expand Down Expand Up @@ -3480,6 +3531,8 @@ namespace ts {
case SyntaxKind.InferKeyword:
case SyntaxKind.ImportKeyword:
case SyntaxKind.AssertsKeyword:
case SyntaxKind.NoSubstitutionTemplateLiteral:
case SyntaxKind.TemplateHead:
return true;
case SyntaxKind.FunctionKeyword:
return !inStartOfParameter;
Expand Down
8 changes: 6 additions & 2 deletions src/compiler/scanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,10 @@ namespace ts {
yield: SyntaxKind.YieldKeyword,
async: SyntaxKind.AsyncKeyword,
await: SyntaxKind.AwaitKeyword,
uppercase: SyntaxKind.UppercaseKeyword,
lowercase: SyntaxKind.LowercaseKeyword,
capitalize: SyntaxKind.CapitalizeKeyword,
uncapitalize: SyntaxKind.UncapitalizeKeyword,
of: SyntaxKind.OfKeyword,
};

Expand Down Expand Up @@ -1508,9 +1512,9 @@ namespace ts {
}

function getIdentifierToken(): SyntaxKind.Identifier | KeywordSyntaxKind {
// Reserved words are between 2 and 11 characters long and start with a lowercase letter
// Reserved words are between 2 and 12 characters long and start with a lowercase letter
const len = tokenValue.length;
if (len >= 2 && len <= 11) {
if (len >= 2 && len <= 12) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that this change should be reverted since #40580 has removed uncapitalize and intrinsic is 9 characters long.

const ch = tokenValue.charCodeAt(0);
if (ch >= CharacterCodes.a && ch <= CharacterCodes.z) {
const keyword = textToKeyword.get(tokenValue);
Expand Down
Loading