Skip to content

Commit 146f828

Browse files
authored
Merge pull request microsoft#17710 from Microsoft/add-readonly-check-to-property-access-of-index-signature
Add readonly check to property access of index signature
2 parents 34a5589 + 7809398 commit 146f828

9 files changed

Lines changed: 79 additions & 12 deletions

src/compiler/checker.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7576,7 +7576,6 @@ namespace ts {
75767576
if (indexInfo) {
75777577
if (accessExpression && indexInfo.isReadonly && (isAssignmentTarget(accessExpression) || isDeleteTarget(accessExpression))) {
75787578
error(accessExpression, Diagnostics.Index_signature_in_type_0_only_permits_reading, typeToString(objectType));
7579-
return unknownType;
75807579
}
75817580
return indexInfo.type;
75827581
}
@@ -7617,7 +7616,6 @@ namespace ts {
76177616
}
76187617
if (accessNode.kind === SyntaxKind.ElementAccessExpression && isAssignmentTarget(accessNode) && type.declaration.readonlyToken) {
76197618
error(accessNode, Diagnostics.Index_signature_in_type_0_only_permits_reading, typeToString(type));
7620-
return unknownType;
76217619
}
76227620
}
76237621
const mapper = createTypeMapper([getTypeParameterFromMappedType(type)], [indexType]);
@@ -14615,9 +14613,12 @@ namespace ts {
1461514613
}
1461614614
const prop = getPropertyOfType(apparentType, right.escapedText);
1461714615
if (!prop) {
14618-
const stringIndexType = getIndexTypeOfType(apparentType, IndexKind.String);
14619-
if (stringIndexType) {
14620-
return stringIndexType;
14616+
const indexInfo = getIndexInfoOfType(apparentType, IndexKind.String);
14617+
if (indexInfo && indexInfo.type) {
14618+
if (indexInfo.isReadonly && (isAssignmentTarget(node) || isDeleteTarget(node))) {
14619+
error(node, Diagnostics.Index_signature_in_type_0_only_permits_reading, typeToString(apparentType));
14620+
}
14621+
return indexInfo.type;
1462114622
}
1462214623
if (right.escapedText && !checkAndReportErrorForExtendingInterface(node)) {
1462314624
reportNonexistentProperty(right, type.flags & TypeFlags.TypeParameter && (type as TypeParameter).isThisType ? apparentType : type);
@@ -17138,7 +17139,9 @@ namespace ts {
1713817139
if (operandType === silentNeverType) {
1713917140
return silentNeverType;
1714017141
}
17141-
const ok = checkArithmeticOperandType(node.operand, checkNonNullType(operandType, node.operand),
17142+
const ok = checkArithmeticOperandType(
17143+
node.operand,
17144+
checkNonNullType(operandType, node.operand),
1714217145
Diagnostics.An_arithmetic_operand_must_be_of_type_any_number_or_an_enum_type);
1714317146
if (ok) {
1714417147
// run check only if former checks succeeded to avoid reporting cascading errors

tests/baselines/reference/decrementOperatorWithEnumType.errors.txt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
tests/cases/conformance/expressions/unaryOperators/decrementOperator/decrementOperatorWithEnumType.ts(6,31): error TS2540: Cannot assign to 'A' because it is a constant or a read-only property.
22
tests/cases/conformance/expressions/unaryOperators/decrementOperator/decrementOperatorWithEnumType.ts(7,29): error TS2540: Cannot assign to 'A' because it is a constant or a read-only property.
33
tests/cases/conformance/expressions/unaryOperators/decrementOperator/decrementOperatorWithEnumType.ts(10,9): error TS2540: Cannot assign to 'A' because it is a constant or a read-only property.
4+
tests/cases/conformance/expressions/unaryOperators/decrementOperator/decrementOperatorWithEnumType.ts(12,1): error TS2356: An arithmetic operand must be of type 'any', 'number' or an enum type.
45
tests/cases/conformance/expressions/unaryOperators/decrementOperator/decrementOperatorWithEnumType.ts(12,1): error TS2542: Index signature in type 'typeof ENUM1' only permits reading.
56
tests/cases/conformance/expressions/unaryOperators/decrementOperator/decrementOperatorWithEnumType.ts(12,7): error TS2304: Cannot find name 'A'.
67

78

8-
==== tests/cases/conformance/expressions/unaryOperators/decrementOperator/decrementOperatorWithEnumType.ts (5 errors) ====
9+
==== tests/cases/conformance/expressions/unaryOperators/decrementOperator/decrementOperatorWithEnumType.ts (6 errors) ====
910
// -- operator on enum type
1011

1112
enum ENUM1 { A, B, "" };
@@ -25,6 +26,9 @@ tests/cases/conformance/expressions/unaryOperators/decrementOperator/decrementOp
2526

2627
ENUM1[A]--;
2728
~~~~~~~~
29+
!!! error TS2356: An arithmetic operand must be of type 'any', 'number' or an enum type.
30+
~~~~~~~~
2831
!!! error TS2542: Index signature in type 'typeof ENUM1' only permits reading.
2932
~
30-
!!! error TS2304: Cannot find name 'A'.
33+
!!! error TS2304: Cannot find name 'A'.
34+

tests/baselines/reference/decrementOperatorWithEnumType.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ var ResultIsNumber2 = ENUM1.A--;
1010
// miss assignment operator
1111
--ENUM1["A"];
1212

13-
ENUM1[A]--;
13+
ENUM1[A]--;
14+
1415

1516
//// [decrementOperatorWithEnumType.js]
1617
// -- operator on enum type

tests/baselines/reference/mappedTypeRelationships.errors.txt

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,19 @@ tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(46,5): error TS2
5858
Type 'T' is not assignable to type 'U'.
5959
tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(51,5): error TS2542: Index signature in type 'Readonly<T>' only permits reading.
6060
tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(56,5): error TS2542: Index signature in type 'Readonly<T>' only permits reading.
61+
tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(61,5): error TS2322: Type 'T[keyof T]' is not assignable to type 'U[keyof T]'.
62+
Type 'T[string]' is not assignable to type 'U[keyof T]'.
63+
Type 'T[string]' is not assignable to type 'U[string]'.
64+
Type 'T[keyof T]' is not assignable to type 'U[string]'.
65+
Type 'T[string]' is not assignable to type 'U[string]'.
66+
Type 'T' is not assignable to type 'U'.
6167
tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(61,5): error TS2542: Index signature in type 'Readonly<U>' only permits reading.
68+
tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(66,5): error TS2322: Type 'T[K]' is not assignable to type 'U[K]'.
69+
Type 'T[string]' is not assignable to type 'U[K]'.
70+
Type 'T[string]' is not assignable to type 'U[string]'.
71+
Type 'T[K]' is not assignable to type 'U[string]'.
72+
Type 'T[string]' is not assignable to type 'U[string]'.
73+
Type 'T' is not assignable to type 'U'.
6274
tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(66,5): error TS2542: Index signature in type 'Readonly<U>' only permits reading.
6375
tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(72,5): error TS2322: Type 'Partial<T>' is not assignable to type 'T'.
6476
tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(78,5): error TS2322: Type 'Partial<Thing>' is not assignable to type 'Partial<T>'.
@@ -88,7 +100,7 @@ tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(168,5): error TS
88100
Type 'T' is not assignable to type 'U'.
89101

90102

91-
==== tests/cases/conformance/types/mapped/mappedTypeRelationships.ts (28 errors) ====
103+
==== tests/cases/conformance/types/mapped/mappedTypeRelationships.ts (30 errors) ====
92104
function f1<T>(x: T, k: keyof T) {
93105
return x[k];
94106
}
@@ -227,13 +239,27 @@ tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(168,5): error TS
227239
x[k] = y[k];
228240
y[k] = x[k]; // Error
229241
~~~~
242+
!!! error TS2322: Type 'T[keyof T]' is not assignable to type 'U[keyof T]'.
243+
!!! error TS2322: Type 'T[string]' is not assignable to type 'U[keyof T]'.
244+
!!! error TS2322: Type 'T[string]' is not assignable to type 'U[string]'.
245+
!!! error TS2322: Type 'T[keyof T]' is not assignable to type 'U[string]'.
246+
!!! error TS2322: Type 'T[string]' is not assignable to type 'U[string]'.
247+
!!! error TS2322: Type 'T' is not assignable to type 'U'.
248+
~~~~
230249
!!! error TS2542: Index signature in type 'Readonly<U>' only permits reading.
231250
}
232251

233252
function f23<T, U extends T, K extends keyof T>(x: T, y: Readonly<U>, k: K) {
234253
x[k] = y[k];
235254
y[k] = x[k]; // Error
236255
~~~~
256+
!!! error TS2322: Type 'T[K]' is not assignable to type 'U[K]'.
257+
!!! error TS2322: Type 'T[string]' is not assignable to type 'U[K]'.
258+
!!! error TS2322: Type 'T[string]' is not assignable to type 'U[string]'.
259+
!!! error TS2322: Type 'T[K]' is not assignable to type 'U[string]'.
260+
!!! error TS2322: Type 'T[string]' is not assignable to type 'U[string]'.
261+
!!! error TS2322: Type 'T' is not assignable to type 'U'.
262+
~~~~
237263
!!! error TS2542: Index signature in type 'Readonly<U>' only permits reading.
238264
}
239265

tests/baselines/reference/objectFreeze.errors.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1+
tests/cases/compiler/objectFreeze.ts(9,1): error TS2322: Type 'string' is not assignable to type 'number'.
12
tests/cases/compiler/objectFreeze.ts(9,1): error TS2542: Index signature in type 'ReadonlyArray<number>' only permits reading.
23
tests/cases/compiler/objectFreeze.ts(12,3): error TS2540: Cannot assign to 'b' because it is a constant or a read-only property.
34

45

5-
==== tests/cases/compiler/objectFreeze.ts (2 errors) ====
6+
==== tests/cases/compiler/objectFreeze.ts (3 errors) ====
67
const f = Object.freeze(function foo(a: number, b: string) { return false; });
78
f(1, "") === false;
89

@@ -13,6 +14,8 @@ tests/cases/compiler/objectFreeze.ts(12,3): error TS2540: Cannot assign to 'b' b
1314
const a = Object.freeze([1, 2, 3]);
1415
a[0] = a[2].toString();
1516
~~~~
17+
!!! error TS2322: Type 'string' is not assignable to type 'number'.
18+
~~~~
1619
!!! error TS2542: Index signature in type 'ReadonlyArray<number>' only permits reading.
1720

1821
const o = Object.freeze({ a: 1, b: "string" });
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
tests/cases/compiler/propertyAccessOfReadonlyIndexSignature.ts(6,1): error TS2542: Index signature in type 'Test' only permits reading.
2+
3+
4+
==== tests/cases/compiler/propertyAccessOfReadonlyIndexSignature.ts (1 errors) ====
5+
interface Test {
6+
readonly [key: string]: string;
7+
}
8+
9+
declare var a: Test;
10+
a.foo = 'baz';
11+
~~~~~
12+
!!! error TS2542: Index signature in type 'Test' only permits reading.
13+
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
//// [propertyAccessOfReadonlyIndexSignature.ts]
2+
interface Test {
3+
readonly [key: string]: string;
4+
}
5+
6+
declare var a: Test;
7+
a.foo = 'baz';
8+
9+
10+
//// [propertyAccessOfReadonlyIndexSignature.js]
11+
a.foo = 'baz';
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
interface Test {
2+
readonly [key: string]: string;
3+
}
4+
5+
declare var a: Test;
6+
a.foo = 'baz';

tests/cases/conformance/expressions/unaryOperators/decrementOperator/decrementOperatorWithEnumType.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,4 @@ var ResultIsNumber2 = ENUM1.A--;
99
// miss assignment operator
1010
--ENUM1["A"];
1111

12-
ENUM1[A]--;
12+
ENUM1[A]--;

0 commit comments

Comments
 (0)