diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 0d3b1ad3c0f..e80be0bfe45 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -11877,8 +11877,14 @@ namespace ts { if (symbol.flags & SymbolFlags.Property && (expr.kind === SyntaxKind.PropertyAccessExpression || expr.kind === SyntaxKind.ElementAccessExpression) && (expr as PropertyAccessExpression | ElementAccessExpression).expression.kind === SyntaxKind.ThisKeyword) { + // Look for if this is the constructor for the class that `symbol` is a property of. const func = getContainingFunction(expr); - return !(func && func.kind === SyntaxKind.Constructor && func.parent === symbol.valueDeclaration.parent); + if (!(func && func.kind === SyntaxKind.Constructor)) + return true; + // If func.parent is a class and symbol is a (readonly) property of that class, or + // if func is a constructor and symbol is a (readonly) parameter property declared in it, + // then symbol is writeable here. + return !(func.parent === symbol.valueDeclaration.parent || func === symbol.valueDeclaration.parent); } return true; } diff --git a/tests/baselines/reference/readonlyConstructorAssignment.errors.txt b/tests/baselines/reference/readonlyConstructorAssignment.errors.txt new file mode 100644 index 00000000000..e764fe21eda --- /dev/null +++ b/tests/baselines/reference/readonlyConstructorAssignment.errors.txt @@ -0,0 +1,50 @@ +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. +tests/cases/conformance/classes/constructorDeclarations/constructorParameters/readonlyConstructorAssignment.ts(33,7): error TS2415: Class 'E' incorrectly extends base class 'D'. + Property 'x' is private in type 'D' but not in type 'E'. + + +==== tests/cases/conformance/classes/constructorDeclarations/constructorParameters/readonlyConstructorAssignment.ts (2 errors) ==== + // Tests that readonly parameter properties behave like regular readonly properties + + class A { + constructor(readonly x: number) { + this.x = 0; + } + } + + class B extends A { + constructor(x: number) { + super(x); + // Fails, x is readonly + this.x = 1; + ~~~~~~ +!!! error TS2450: Left-hand side of assignment expression cannot be a constant or a read-only property. + } + } + + class C extends A { + // This is the usual behavior of readonly properties: + // if one is redeclared in a base class, then it can be assigned to. + constructor(readonly x: number) { + super(x); + this.x = 1; + } + } + + class D { + constructor(private readonly x: number) { + this.x = 0; + } + } + + // Fails, can't redeclare readonly property + class E extends D { + ~ +!!! error TS2415: Class 'E' incorrectly extends base class 'D'. +!!! error TS2415: Property 'x' is private in type 'D' but not in type 'E'. + constructor(readonly x: number) { + super(x); + this.x = 1; + } + } + \ No newline at end of file diff --git a/tests/baselines/reference/readonlyConstructorAssignment.js b/tests/baselines/reference/readonlyConstructorAssignment.js new file mode 100644 index 00000000000..40c292e4ce8 --- /dev/null +++ b/tests/baselines/reference/readonlyConstructorAssignment.js @@ -0,0 +1,92 @@ +//// [readonlyConstructorAssignment.ts] +// Tests that readonly parameter properties behave like regular readonly properties + +class A { + constructor(readonly x: number) { + this.x = 0; + } +} + +class B extends A { + constructor(x: number) { + super(x); + // Fails, x is readonly + this.x = 1; + } +} + +class C extends A { + // This is the usual behavior of readonly properties: + // if one is redeclared in a base class, then it can be assigned to. + constructor(readonly x: number) { + super(x); + this.x = 1; + } +} + +class D { + constructor(private readonly x: number) { + this.x = 0; + } +} + +// Fails, can't redeclare readonly property +class E extends D { + constructor(readonly x: number) { + super(x); + this.x = 1; + } +} + + +//// [readonlyConstructorAssignment.js] +// Tests that readonly parameter properties behave like regular readonly properties +var __extends = (this && this.__extends) || function (d, b) { + for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); +}; +var A = (function () { + function A(x) { + this.x = x; + this.x = 0; + } + return A; +}()); +var B = (function (_super) { + __extends(B, _super); + function B(x) { + _super.call(this, x); + // Fails, x is readonly + this.x = 1; + } + return B; +}(A)); +var C = (function (_super) { + __extends(C, _super); + // This is the usual behavior of readonly properties: + // if one is redeclared in a base class, then it can be assigned to. + function C(x) { + _super.call(this, x); + this.x = x; + this.x = 1; + } + return C; +}(A)); +var D = (function () { + function D(x) { + this.x = x; + this.x = 0; + } + return D; +}()); +// Fails, can't redeclare readonly property +var E = (function (_super) { + __extends(E, _super); + function E(x) { + _super.call(this, x); + this.x = x; + this.x = 1; + } + return E; +}(D)); diff --git a/tests/cases/conformance/classes/constructorDeclarations/constructorParameters/readonlyConstructorAssignment.ts b/tests/cases/conformance/classes/constructorDeclarations/constructorParameters/readonlyConstructorAssignment.ts new file mode 100644 index 00000000000..aeecf085f2f --- /dev/null +++ b/tests/cases/conformance/classes/constructorDeclarations/constructorParameters/readonlyConstructorAssignment.ts @@ -0,0 +1,38 @@ +// Tests that readonly parameter properties behave like regular readonly properties + +class A { + constructor(readonly x: number) { + this.x = 0; + } +} + +class B extends A { + constructor(x: number) { + super(x); + // Fails, x is readonly + this.x = 1; + } +} + +class C extends A { + // This is the usual behavior of readonly properties: + // if one is redeclared in a base class, then it can be assigned to. + constructor(readonly x: number) { + super(x); + this.x = 1; + } +} + +class D { + constructor(private readonly x: number) { + this.x = 0; + } +} + +// Fails, can't redeclare readonly property +class E extends D { + constructor(readonly x: number) { + super(x); + this.x = 1; + } +}