Skip to content

Commit 0a54a3b

Browse files
author
Andy
committed
Merge pull request microsoft#8712 from Microsoft/readonly_assignment_in_constructor
Allow assignment to readonly parameter property within the constructor
2 parents 5035df6 + 17009e4 commit 0a54a3b

File tree

4 files changed

+187
-1
lines changed

4 files changed

+187
-1
lines changed

src/compiler/checker.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11877,8 +11877,14 @@ namespace ts {
1187711877
if (symbol.flags & SymbolFlags.Property &&
1187811878
(expr.kind === SyntaxKind.PropertyAccessExpression || expr.kind === SyntaxKind.ElementAccessExpression) &&
1187911879
(expr as PropertyAccessExpression | ElementAccessExpression).expression.kind === SyntaxKind.ThisKeyword) {
11880+
// Look for if this is the constructor for the class that `symbol` is a property of.
1188011881
const func = getContainingFunction(expr);
11881-
return !(func && func.kind === SyntaxKind.Constructor && func.parent === symbol.valueDeclaration.parent);
11882+
if (!(func && func.kind === SyntaxKind.Constructor))
11883+
return true;
11884+
// If func.parent is a class and symbol is a (readonly) property of that class, or
11885+
// if func is a constructor and symbol is a (readonly) parameter property declared in it,
11886+
// then symbol is writeable here.
11887+
return !(func.parent === symbol.valueDeclaration.parent || func === symbol.valueDeclaration.parent);
1188211888
}
1188311889
return true;
1188411890
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
tests/cases/conformance/classes/constructorDeclarations/constructorParameters/readonlyConstructorAssignment.ts(13,9): error TS2450: Left-hand side of assignment expression cannot be a constant or a read-only property.
2+
tests/cases/conformance/classes/constructorDeclarations/constructorParameters/readonlyConstructorAssignment.ts(33,7): error TS2415: Class 'E' incorrectly extends base class 'D'.
3+
Property 'x' is private in type 'D' but not in type 'E'.
4+
5+
6+
==== tests/cases/conformance/classes/constructorDeclarations/constructorParameters/readonlyConstructorAssignment.ts (2 errors) ====
7+
// Tests that readonly parameter properties behave like regular readonly properties
8+
9+
class A {
10+
constructor(readonly x: number) {
11+
this.x = 0;
12+
}
13+
}
14+
15+
class B extends A {
16+
constructor(x: number) {
17+
super(x);
18+
// Fails, x is readonly
19+
this.x = 1;
20+
~~~~~~
21+
!!! error TS2450: Left-hand side of assignment expression cannot be a constant or a read-only property.
22+
}
23+
}
24+
25+
class C extends A {
26+
// This is the usual behavior of readonly properties:
27+
// if one is redeclared in a base class, then it can be assigned to.
28+
constructor(readonly x: number) {
29+
super(x);
30+
this.x = 1;
31+
}
32+
}
33+
34+
class D {
35+
constructor(private readonly x: number) {
36+
this.x = 0;
37+
}
38+
}
39+
40+
// Fails, can't redeclare readonly property
41+
class E extends D {
42+
~
43+
!!! error TS2415: Class 'E' incorrectly extends base class 'D'.
44+
!!! error TS2415: Property 'x' is private in type 'D' but not in type 'E'.
45+
constructor(readonly x: number) {
46+
super(x);
47+
this.x = 1;
48+
}
49+
}
50+
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
//// [readonlyConstructorAssignment.ts]
2+
// Tests that readonly parameter properties behave like regular readonly properties
3+
4+
class A {
5+
constructor(readonly x: number) {
6+
this.x = 0;
7+
}
8+
}
9+
10+
class B extends A {
11+
constructor(x: number) {
12+
super(x);
13+
// Fails, x is readonly
14+
this.x = 1;
15+
}
16+
}
17+
18+
class C extends A {
19+
// This is the usual behavior of readonly properties:
20+
// if one is redeclared in a base class, then it can be assigned to.
21+
constructor(readonly x: number) {
22+
super(x);
23+
this.x = 1;
24+
}
25+
}
26+
27+
class D {
28+
constructor(private readonly x: number) {
29+
this.x = 0;
30+
}
31+
}
32+
33+
// Fails, can't redeclare readonly property
34+
class E extends D {
35+
constructor(readonly x: number) {
36+
super(x);
37+
this.x = 1;
38+
}
39+
}
40+
41+
42+
//// [readonlyConstructorAssignment.js]
43+
// Tests that readonly parameter properties behave like regular readonly properties
44+
var __extends = (this && this.__extends) || function (d, b) {
45+
for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
46+
function __() { this.constructor = d; }
47+
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
48+
};
49+
var A = (function () {
50+
function A(x) {
51+
this.x = x;
52+
this.x = 0;
53+
}
54+
return A;
55+
}());
56+
var B = (function (_super) {
57+
__extends(B, _super);
58+
function B(x) {
59+
_super.call(this, x);
60+
// Fails, x is readonly
61+
this.x = 1;
62+
}
63+
return B;
64+
}(A));
65+
var C = (function (_super) {
66+
__extends(C, _super);
67+
// This is the usual behavior of readonly properties:
68+
// if one is redeclared in a base class, then it can be assigned to.
69+
function C(x) {
70+
_super.call(this, x);
71+
this.x = x;
72+
this.x = 1;
73+
}
74+
return C;
75+
}(A));
76+
var D = (function () {
77+
function D(x) {
78+
this.x = x;
79+
this.x = 0;
80+
}
81+
return D;
82+
}());
83+
// Fails, can't redeclare readonly property
84+
var E = (function (_super) {
85+
__extends(E, _super);
86+
function E(x) {
87+
_super.call(this, x);
88+
this.x = x;
89+
this.x = 1;
90+
}
91+
return E;
92+
}(D));
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Tests that readonly parameter properties behave like regular readonly properties
2+
3+
class A {
4+
constructor(readonly x: number) {
5+
this.x = 0;
6+
}
7+
}
8+
9+
class B extends A {
10+
constructor(x: number) {
11+
super(x);
12+
// Fails, x is readonly
13+
this.x = 1;
14+
}
15+
}
16+
17+
class C extends A {
18+
// This is the usual behavior of readonly properties:
19+
// if one is redeclared in a base class, then it can be assigned to.
20+
constructor(readonly x: number) {
21+
super(x);
22+
this.x = 1;
23+
}
24+
}
25+
26+
class D {
27+
constructor(private readonly x: number) {
28+
this.x = 0;
29+
}
30+
}
31+
32+
// Fails, can't redeclare readonly property
33+
class E extends D {
34+
constructor(readonly x: number) {
35+
super(x);
36+
this.x = 1;
37+
}
38+
}

0 commit comments

Comments
 (0)