From bb8378fddf040db9f777443b8ef1ede7ba481c2e Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 15 Jan 2019 09:43:39 -0800 Subject: [PATCH] Support 'readonly' type modifier on array and tuple types --- src/compiler/checker.ts | 28 +++++++++++++++++++++------- src/compiler/diagnosticMessages.json | 4 ++++ src/compiler/factory.ts | 4 ++-- src/compiler/parser.ts | 4 +++- src/compiler/types.ts | 2 +- 5 files changed, 31 insertions(+), 11 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a6de26cc57c..f15eb66af6a 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -3592,14 +3592,15 @@ namespace ts { function typeReferenceToTypeNode(type: TypeReference) { const typeArguments: ReadonlyArray = type.typeArguments || emptyArray; - if (type.target === globalArrayType) { + if (type.target === globalArrayType || type.target === globalReadonlyArrayType) { if (context.flags & NodeBuilderFlags.WriteArrayAsGenericType) { const typeArgumentNode = typeToTypeNodeHelper(typeArguments[0], context); - return createTypeReferenceNode("Array", [typeArgumentNode]); + return createTypeReferenceNode(type.target === globalArrayType ? "Array" : "ReadonlyArray", [typeArgumentNode]); } const elementType = typeToTypeNodeHelper(typeArguments[0], context); - return createArrayTypeNode(elementType); + const arrayType = createArrayTypeNode(elementType); + return type.target === globalArrayType ? arrayType : createTypeOperatorNode(SyntaxKind.ReadonlyKeyword, arrayType); } else if (type.target.objectFlags & ObjectFlags.Tuple) { if (typeArguments.length > 0) { @@ -3613,11 +3614,12 @@ namespace ts { createOptionalTypeNode(tupleConstituentNodes[i]); } const tupleTypeNode = createTupleTypeNode(tupleConstituentNodes); - return (type.target).readonly ? createTypeReferenceNode("Readonly", [tupleTypeNode]) : tupleTypeNode; + return (type.target).readonly ? createTypeOperatorNode(SyntaxKind.ReadonlyKeyword, tupleTypeNode) : tupleTypeNode; } } if (context.encounteredError || (context.flags & NodeBuilderFlags.AllowEmptyTuple)) { - return createTupleTypeNode([]); + const tupleTypeNode = createTupleTypeNode([]); + return (type.target).readonly ? createTypeOperatorNode(SyntaxKind.ReadonlyKeyword, tupleTypeNode) : tupleTypeNode; } context.encounteredError = true; return undefined!; // TODO: GH#18217 @@ -9030,11 +9032,15 @@ namespace ts { function getTypeFromArrayTypeNode(node: ArrayTypeNode): Type { const links = getNodeLinks(node); if (!links.resolvedType) { - links.resolvedType = createArrayType(getTypeFromTypeNode(node.elementType)); + links.resolvedType = createArrayType(getTypeFromTypeNode(node.elementType), isReadonlyTypeOperator(node.parent)); } return links.resolvedType; } + function isReadonlyTypeOperator(node: Node) { + return isTypeOperatorNode(node) && node.operator === SyntaxKind.ReadonlyKeyword; + } + // We represent tuple types as type references to synthesized generic interface types created by // this function. The types are of the form: // @@ -9114,7 +9120,7 @@ namespace ts { const type = getTypeFromTypeNode(n); return n === restElement && getIndexTypeOfType(type, IndexKind.Number) || type; }); - links.resolvedType = createTupleType(elementTypes, minLength, !!restElement); + links.resolvedType = createTupleType(elementTypes, minLength, !!restElement, isReadonlyTypeOperator(node.parent)); } return links.resolvedType; } @@ -9667,6 +9673,9 @@ namespace ts { ? getESSymbolLikeTypeForNode(walkUpParenthesizedTypes(node.parent)) : errorType; break; + case SyntaxKind.ReadonlyKeyword: + links.resolvedType = getTypeFromTypeNode(node.type); + break; } } return links.resolvedType!; // TODO: GH#18217 @@ -30513,6 +30522,11 @@ namespace ts { return grammarErrorOnNode(node, Diagnostics.unique_symbol_types_are_not_allowed_here); } } + else if (node.operator === SyntaxKind.ReadonlyKeyword) { + if (node.type.kind !== SyntaxKind.ArrayType && node.type.kind !== SyntaxKind.TupleType) { + return grammarErrorOnFirstToken(node, Diagnostics.readonly_type_modifier_is_only_permitted_on_array_and_typle_types, tokenToString(SyntaxKind.SymbolKeyword)); + } + } } function checkGrammarForInvalidDynamicName(node: DeclarationName, message: DiagnosticMessage) { diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 9a918507dc4..e0ac3eb77f8 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -1023,6 +1023,10 @@ "category": "Error", "code": 1353 }, + "'readonly' type modifier is only permitted on array and typle types.": { + "category": "Error", + "code": 1354 + }, "Duplicate identifier '{0}'.": { "category": "Error", diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index ec19d2c906c..c7f56407494 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -876,8 +876,8 @@ namespace ts { } export function createTypeOperatorNode(type: TypeNode): TypeOperatorNode; - export function createTypeOperatorNode(operator: SyntaxKind.KeyOfKeyword | SyntaxKind.UniqueKeyword, type: TypeNode): TypeOperatorNode; - export function createTypeOperatorNode(operatorOrType: SyntaxKind.KeyOfKeyword | SyntaxKind.UniqueKeyword | TypeNode, type?: TypeNode) { + export function createTypeOperatorNode(operator: SyntaxKind.KeyOfKeyword | SyntaxKind.UniqueKeyword | SyntaxKind.ReadonlyKeyword, type: TypeNode): TypeOperatorNode; + export function createTypeOperatorNode(operatorOrType: SyntaxKind.KeyOfKeyword | SyntaxKind.UniqueKeyword | SyntaxKind.ReadonlyKeyword | TypeNode, type?: TypeNode) { const node = createSynthesizedNode(SyntaxKind.TypeOperator) as TypeOperatorNode; node.operator = typeof operatorOrType === "number" ? operatorOrType : SyntaxKind.KeyOfKeyword; node.type = parenthesizeElementTypeMember(typeof operatorOrType === "number" ? type! : operatorOrType); diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index f976f77ea98..28d915ed6cd 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -2962,6 +2962,7 @@ namespace ts { case SyntaxKind.NumberKeyword: case SyntaxKind.BigIntKeyword: case SyntaxKind.BooleanKeyword: + case SyntaxKind.ReadonlyKeyword: case SyntaxKind.SymbolKeyword: case SyntaxKind.UniqueKeyword: case SyntaxKind.VoidKeyword: @@ -3051,7 +3052,7 @@ namespace ts { return finishNode(postfix); } - function parseTypeOperator(operator: SyntaxKind.KeyOfKeyword | SyntaxKind.UniqueKeyword) { + function parseTypeOperator(operator: SyntaxKind.KeyOfKeyword | SyntaxKind.UniqueKeyword | SyntaxKind.ReadonlyKeyword) { const node = createNode(SyntaxKind.TypeOperator); parseExpected(operator); node.operator = operator; @@ -3073,6 +3074,7 @@ namespace ts { switch (operator) { case SyntaxKind.KeyOfKeyword: case SyntaxKind.UniqueKeyword: + case SyntaxKind.ReadonlyKeyword: return parseTypeOperator(operator); case SyntaxKind.InferKeyword: return parseInferType(); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index c7d71cf50f9..23caf03f55a 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1233,7 +1233,7 @@ namespace ts { export interface TypeOperatorNode extends TypeNode { kind: SyntaxKind.TypeOperator; - operator: SyntaxKind.KeyOfKeyword | SyntaxKind.UniqueKeyword; + operator: SyntaxKind.KeyOfKeyword | SyntaxKind.UniqueKeyword | SyntaxKind.ReadonlyKeyword; type: TypeNode; }