Skip to content

Commit bb8378f

Browse files
committed
Support 'readonly' type modifier on array and tuple types
1 parent 903863a commit bb8378f

File tree

5 files changed

+31
-11
lines changed

5 files changed

+31
-11
lines changed

src/compiler/checker.ts

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3592,14 +3592,15 @@ namespace ts {
35923592

35933593
function typeReferenceToTypeNode(type: TypeReference) {
35943594
const typeArguments: ReadonlyArray<Type> = type.typeArguments || emptyArray;
3595-
if (type.target === globalArrayType) {
3595+
if (type.target === globalArrayType || type.target === globalReadonlyArrayType) {
35963596
if (context.flags & NodeBuilderFlags.WriteArrayAsGenericType) {
35973597
const typeArgumentNode = typeToTypeNodeHelper(typeArguments[0], context);
3598-
return createTypeReferenceNode("Array", [typeArgumentNode]);
3598+
return createTypeReferenceNode(type.target === globalArrayType ? "Array" : "ReadonlyArray", [typeArgumentNode]);
35993599
}
36003600

36013601
const elementType = typeToTypeNodeHelper(typeArguments[0], context);
3602-
return createArrayTypeNode(elementType);
3602+
const arrayType = createArrayTypeNode(elementType);
3603+
return type.target === globalArrayType ? arrayType : createTypeOperatorNode(SyntaxKind.ReadonlyKeyword, arrayType);
36033604
}
36043605
else if (type.target.objectFlags & ObjectFlags.Tuple) {
36053606
if (typeArguments.length > 0) {
@@ -3613,11 +3614,12 @@ namespace ts {
36133614
createOptionalTypeNode(tupleConstituentNodes[i]);
36143615
}
36153616
const tupleTypeNode = createTupleTypeNode(tupleConstituentNodes);
3616-
return (<TupleType>type.target).readonly ? createTypeReferenceNode("Readonly", [tupleTypeNode]) : tupleTypeNode;
3617+
return (<TupleType>type.target).readonly ? createTypeOperatorNode(SyntaxKind.ReadonlyKeyword, tupleTypeNode) : tupleTypeNode;
36173618
}
36183619
}
36193620
if (context.encounteredError || (context.flags & NodeBuilderFlags.AllowEmptyTuple)) {
3620-
return createTupleTypeNode([]);
3621+
const tupleTypeNode = createTupleTypeNode([]);
3622+
return (<TupleType>type.target).readonly ? createTypeOperatorNode(SyntaxKind.ReadonlyKeyword, tupleTypeNode) : tupleTypeNode;
36213623
}
36223624
context.encounteredError = true;
36233625
return undefined!; // TODO: GH#18217
@@ -9030,11 +9032,15 @@ namespace ts {
90309032
function getTypeFromArrayTypeNode(node: ArrayTypeNode): Type {
90319033
const links = getNodeLinks(node);
90329034
if (!links.resolvedType) {
9033-
links.resolvedType = createArrayType(getTypeFromTypeNode(node.elementType));
9035+
links.resolvedType = createArrayType(getTypeFromTypeNode(node.elementType), isReadonlyTypeOperator(node.parent));
90349036
}
90359037
return links.resolvedType;
90369038
}
90379039

9040+
function isReadonlyTypeOperator(node: Node) {
9041+
return isTypeOperatorNode(node) && node.operator === SyntaxKind.ReadonlyKeyword;
9042+
}
9043+
90389044
// We represent tuple types as type references to synthesized generic interface types created by
90399045
// this function. The types are of the form:
90409046
//
@@ -9114,7 +9120,7 @@ namespace ts {
91149120
const type = getTypeFromTypeNode(n);
91159121
return n === restElement && getIndexTypeOfType(type, IndexKind.Number) || type;
91169122
});
9117-
links.resolvedType = createTupleType(elementTypes, minLength, !!restElement);
9123+
links.resolvedType = createTupleType(elementTypes, minLength, !!restElement, isReadonlyTypeOperator(node.parent));
91189124
}
91199125
return links.resolvedType;
91209126
}
@@ -9667,6 +9673,9 @@ namespace ts {
96679673
? getESSymbolLikeTypeForNode(walkUpParenthesizedTypes(node.parent))
96689674
: errorType;
96699675
break;
9676+
case SyntaxKind.ReadonlyKeyword:
9677+
links.resolvedType = getTypeFromTypeNode(node.type);
9678+
break;
96709679
}
96719680
}
96729681
return links.resolvedType!; // TODO: GH#18217
@@ -30513,6 +30522,11 @@ namespace ts {
3051330522
return grammarErrorOnNode(node, Diagnostics.unique_symbol_types_are_not_allowed_here);
3051430523
}
3051530524
}
30525+
else if (node.operator === SyntaxKind.ReadonlyKeyword) {
30526+
if (node.type.kind !== SyntaxKind.ArrayType && node.type.kind !== SyntaxKind.TupleType) {
30527+
return grammarErrorOnFirstToken(node, Diagnostics.readonly_type_modifier_is_only_permitted_on_array_and_typle_types, tokenToString(SyntaxKind.SymbolKeyword));
30528+
}
30529+
}
3051630530
}
3051730531

3051830532
function checkGrammarForInvalidDynamicName(node: DeclarationName, message: DiagnosticMessage) {

src/compiler/diagnosticMessages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1023,6 +1023,10 @@
10231023
"category": "Error",
10241024
"code": 1353
10251025
},
1026+
"'readonly' type modifier is only permitted on array and typle types.": {
1027+
"category": "Error",
1028+
"code": 1354
1029+
},
10261030

10271031
"Duplicate identifier '{0}'.": {
10281032
"category": "Error",

src/compiler/factory.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -876,8 +876,8 @@ namespace ts {
876876
}
877877

878878
export function createTypeOperatorNode(type: TypeNode): TypeOperatorNode;
879-
export function createTypeOperatorNode(operator: SyntaxKind.KeyOfKeyword | SyntaxKind.UniqueKeyword, type: TypeNode): TypeOperatorNode;
880-
export function createTypeOperatorNode(operatorOrType: SyntaxKind.KeyOfKeyword | SyntaxKind.UniqueKeyword | TypeNode, type?: TypeNode) {
879+
export function createTypeOperatorNode(operator: SyntaxKind.KeyOfKeyword | SyntaxKind.UniqueKeyword | SyntaxKind.ReadonlyKeyword, type: TypeNode): TypeOperatorNode;
880+
export function createTypeOperatorNode(operatorOrType: SyntaxKind.KeyOfKeyword | SyntaxKind.UniqueKeyword | SyntaxKind.ReadonlyKeyword | TypeNode, type?: TypeNode) {
881881
const node = createSynthesizedNode(SyntaxKind.TypeOperator) as TypeOperatorNode;
882882
node.operator = typeof operatorOrType === "number" ? operatorOrType : SyntaxKind.KeyOfKeyword;
883883
node.type = parenthesizeElementTypeMember(typeof operatorOrType === "number" ? type! : operatorOrType);

src/compiler/parser.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2962,6 +2962,7 @@ namespace ts {
29622962
case SyntaxKind.NumberKeyword:
29632963
case SyntaxKind.BigIntKeyword:
29642964
case SyntaxKind.BooleanKeyword:
2965+
case SyntaxKind.ReadonlyKeyword:
29652966
case SyntaxKind.SymbolKeyword:
29662967
case SyntaxKind.UniqueKeyword:
29672968
case SyntaxKind.VoidKeyword:
@@ -3051,7 +3052,7 @@ namespace ts {
30513052
return finishNode(postfix);
30523053
}
30533054

3054-
function parseTypeOperator(operator: SyntaxKind.KeyOfKeyword | SyntaxKind.UniqueKeyword) {
3055+
function parseTypeOperator(operator: SyntaxKind.KeyOfKeyword | SyntaxKind.UniqueKeyword | SyntaxKind.ReadonlyKeyword) {
30553056
const node = <TypeOperatorNode>createNode(SyntaxKind.TypeOperator);
30563057
parseExpected(operator);
30573058
node.operator = operator;
@@ -3073,6 +3074,7 @@ namespace ts {
30733074
switch (operator) {
30743075
case SyntaxKind.KeyOfKeyword:
30753076
case SyntaxKind.UniqueKeyword:
3077+
case SyntaxKind.ReadonlyKeyword:
30763078
return parseTypeOperator(operator);
30773079
case SyntaxKind.InferKeyword:
30783080
return parseInferType();

src/compiler/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1233,7 +1233,7 @@ namespace ts {
12331233

12341234
export interface TypeOperatorNode extends TypeNode {
12351235
kind: SyntaxKind.TypeOperator;
1236-
operator: SyntaxKind.KeyOfKeyword | SyntaxKind.UniqueKeyword;
1236+
operator: SyntaxKind.KeyOfKeyword | SyntaxKind.UniqueKeyword | SyntaxKind.ReadonlyKeyword;
12371237
type: TypeNode;
12381238
}
12391239

0 commit comments

Comments
 (0)