Skip to content

Commit a769ba7

Browse files
committed
Merge pull request #7184 from Microsoft/abstract-properties
Abstract properties
2 parents f029ae2 + b01a050 commit a769ba7

13 files changed

+515
-25
lines changed

src/compiler/checker.ts

+9-3
Original file line numberDiff line numberDiff line change
@@ -12058,6 +12058,9 @@ namespace ts {
1205812058
if (((node.flags & NodeFlags.AccessibilityModifier) !== (otherAccessor.flags & NodeFlags.AccessibilityModifier))) {
1205912059
error(node.name, Diagnostics.Getter_and_setter_accessors_do_not_agree_in_visibility);
1206012060
}
12061+
if (((node.flags & NodeFlags.Abstract) !== (otherAccessor.flags & NodeFlags.Abstract))) {
12062+
error(node.name, Diagnostics.Accessors_must_both_be_abstract_or_non_abstract);
12063+
}
1206112064

1206212065
const currentAccessorType = getAnnotatedAccessorType(node);
1206312066
const otherAccessorType = getAnnotatedAccessorType(otherAccessor);
@@ -16505,8 +16508,11 @@ namespace ts {
1650516508
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "abstract");
1650616509
}
1650716510
if (node.kind !== SyntaxKind.ClassDeclaration) {
16508-
if (node.kind !== SyntaxKind.MethodDeclaration) {
16509-
return grammarErrorOnNode(modifier, Diagnostics.abstract_modifier_can_only_appear_on_a_class_or_method_declaration);
16511+
if (node.kind !== SyntaxKind.MethodDeclaration &&
16512+
node.kind !== SyntaxKind.PropertyDeclaration &&
16513+
node.kind !== SyntaxKind.GetAccessor &&
16514+
node.kind !== SyntaxKind.SetAccessor) {
16515+
return grammarErrorOnNode(modifier, Diagnostics.abstract_modifier_can_only_appear_on_a_class_method_or_property_declaration);
1651016516
}
1651116517
if (!(node.parent.kind === SyntaxKind.ClassDeclaration && node.parent.flags & NodeFlags.Abstract)) {
1651216518
return grammarErrorOnNode(modifier, Diagnostics.Abstract_methods_can_only_appear_within_an_abstract_class);
@@ -17000,7 +17006,7 @@ namespace ts {
1700017006
else if (isInAmbientContext(accessor)) {
1700117007
return grammarErrorOnNode(accessor.name, Diagnostics.An_accessor_cannot_be_declared_in_an_ambient_context);
1700217008
}
17003-
else if (accessor.body === undefined) {
17009+
else if (accessor.body === undefined && !(accessor.flags & NodeFlags.Abstract)) {
1700417010
return grammarErrorAtPos(getSourceFileOfNode(accessor), accessor.end - 1, ";".length, Diagnostics._0_expected, "{");
1700517011
}
1700617012
else if (accessor.typeParameters) {

src/compiler/diagnosticMessages.json

+5-1
Original file line numberDiff line numberDiff line change
@@ -779,7 +779,7 @@
779779
"category": "Error",
780780
"code": 1241
781781
},
782-
"'abstract' modifier can only appear on a class or method declaration.": {
782+
"'abstract' modifier can only appear on a class, method, or property declaration.": {
783783
"category": "Error",
784784
"code": 1242
785785
},
@@ -1843,6 +1843,10 @@
18431843
"category": "Error",
18441844
"code": 2675
18451845
},
1846+
"Accessors must both be abstract or non-abstract.": {
1847+
"category": "Error",
1848+
"code": 2676
1849+
},
18461850
"Import declaration '{0}' is using private name '{1}'.": {
18471851
"category": "Error",
18481852
"code": 4000
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
//// [abstractProperty.ts]
2+
interface A {
3+
prop: string;
4+
raw: string;
5+
m(): void;
6+
}
7+
abstract class B implements A {
8+
abstract prop: string;
9+
abstract raw: string;
10+
abstract readonly ro: string;
11+
abstract get readonlyProp(): string;
12+
abstract set readonlyProp(val: string);
13+
abstract m(): void;
14+
}
15+
class C extends B {
16+
get prop() { return "foo"; }
17+
set prop(v) { }
18+
raw = "edge";
19+
readonly ro = "readonly please";
20+
readonlyProp: string; // don't have to give a value, in fact
21+
m() { }
22+
}
23+
24+
//// [abstractProperty.js]
25+
var __extends = (this && this.__extends) || function (d, b) {
26+
for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
27+
function __() { this.constructor = d; }
28+
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
29+
};
30+
var B = (function () {
31+
function B() {
32+
}
33+
Object.defineProperty(B.prototype, "readonlyProp", {
34+
get: function () { },
35+
set: function (val) { },
36+
enumerable: true,
37+
configurable: true
38+
});
39+
return B;
40+
}());
41+
var C = (function (_super) {
42+
__extends(C, _super);
43+
function C() {
44+
_super.apply(this, arguments);
45+
this.raw = "edge";
46+
this.ro = "readonly please";
47+
}
48+
Object.defineProperty(C.prototype, "prop", {
49+
get: function () { return "foo"; },
50+
set: function (v) { },
51+
enumerable: true,
52+
configurable: true
53+
});
54+
C.prototype.m = function () { };
55+
return C;
56+
}(B));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
=== tests/cases/compiler/abstractProperty.ts ===
2+
interface A {
3+
>A : Symbol(A, Decl(abstractProperty.ts, 0, 0))
4+
5+
prop: string;
6+
>prop : Symbol(prop, Decl(abstractProperty.ts, 0, 13))
7+
8+
raw: string;
9+
>raw : Symbol(raw, Decl(abstractProperty.ts, 1, 17))
10+
11+
m(): void;
12+
>m : Symbol(m, Decl(abstractProperty.ts, 2, 16))
13+
}
14+
abstract class B implements A {
15+
>B : Symbol(B, Decl(abstractProperty.ts, 4, 1))
16+
>A : Symbol(A, Decl(abstractProperty.ts, 0, 0))
17+
18+
abstract prop: string;
19+
>prop : Symbol(prop, Decl(abstractProperty.ts, 5, 31))
20+
21+
abstract raw: string;
22+
>raw : Symbol(raw, Decl(abstractProperty.ts, 6, 26))
23+
24+
abstract readonly ro: string;
25+
>ro : Symbol(ro, Decl(abstractProperty.ts, 7, 25))
26+
27+
abstract get readonlyProp(): string;
28+
>readonlyProp : Symbol(readonlyProp, Decl(abstractProperty.ts, 8, 33), Decl(abstractProperty.ts, 9, 40))
29+
30+
abstract set readonlyProp(val: string);
31+
>readonlyProp : Symbol(readonlyProp, Decl(abstractProperty.ts, 8, 33), Decl(abstractProperty.ts, 9, 40))
32+
>val : Symbol(val, Decl(abstractProperty.ts, 10, 30))
33+
34+
abstract m(): void;
35+
>m : Symbol(m, Decl(abstractProperty.ts, 10, 43))
36+
}
37+
class C extends B {
38+
>C : Symbol(C, Decl(abstractProperty.ts, 12, 1))
39+
>B : Symbol(B, Decl(abstractProperty.ts, 4, 1))
40+
41+
get prop() { return "foo"; }
42+
>prop : Symbol(prop, Decl(abstractProperty.ts, 13, 19), Decl(abstractProperty.ts, 14, 32))
43+
44+
set prop(v) { }
45+
>prop : Symbol(prop, Decl(abstractProperty.ts, 13, 19), Decl(abstractProperty.ts, 14, 32))
46+
>v : Symbol(v, Decl(abstractProperty.ts, 15, 13))
47+
48+
raw = "edge";
49+
>raw : Symbol(raw, Decl(abstractProperty.ts, 15, 19))
50+
51+
readonly ro = "readonly please";
52+
>ro : Symbol(ro, Decl(abstractProperty.ts, 16, 17))
53+
54+
readonlyProp: string; // don't have to give a value, in fact
55+
>readonlyProp : Symbol(readonlyProp, Decl(abstractProperty.ts, 17, 36))
56+
57+
m() { }
58+
>m : Symbol(m, Decl(abstractProperty.ts, 18, 25))
59+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
=== tests/cases/compiler/abstractProperty.ts ===
2+
interface A {
3+
>A : A
4+
5+
prop: string;
6+
>prop : string
7+
8+
raw: string;
9+
>raw : string
10+
11+
m(): void;
12+
>m : () => void
13+
}
14+
abstract class B implements A {
15+
>B : B
16+
>A : A
17+
18+
abstract prop: string;
19+
>prop : string
20+
21+
abstract raw: string;
22+
>raw : string
23+
24+
abstract readonly ro: string;
25+
>ro : string
26+
27+
abstract get readonlyProp(): string;
28+
>readonlyProp : string
29+
30+
abstract set readonlyProp(val: string);
31+
>readonlyProp : string
32+
>val : string
33+
34+
abstract m(): void;
35+
>m : () => void
36+
}
37+
class C extends B {
38+
>C : C
39+
>B : B
40+
41+
get prop() { return "foo"; }
42+
>prop : string
43+
>"foo" : string
44+
45+
set prop(v) { }
46+
>prop : string
47+
>v : string
48+
49+
raw = "edge";
50+
>raw : string
51+
>"edge" : string
52+
53+
readonly ro = "readonly please";
54+
>ro : string
55+
>"readonly please" : string
56+
57+
readonlyProp: string; // don't have to give a value, in fact
58+
>readonlyProp : string
59+
60+
m() { }
61+
>m : () => void
62+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
tests/cases/compiler/abstractPropertyNegative.ts(10,18): error TS2380: 'get' and 'set' accessor must have the same type.
2+
tests/cases/compiler/abstractPropertyNegative.ts(11,18): error TS2380: 'get' and 'set' accessor must have the same type.
3+
tests/cases/compiler/abstractPropertyNegative.ts(13,7): error TS2515: Non-abstract class 'C' does not implement inherited abstract member 'm' from class 'B'.
4+
tests/cases/compiler/abstractPropertyNegative.ts(13,7): error TS2515: Non-abstract class 'C' does not implement inherited abstract member 'mismatch' from class 'B'.
5+
tests/cases/compiler/abstractPropertyNegative.ts(13,7): error TS2515: Non-abstract class 'C' does not implement inherited abstract member 'prop' from class 'B'.
6+
tests/cases/compiler/abstractPropertyNegative.ts(13,7): error TS2515: Non-abstract class 'C' does not implement inherited abstract member 'readonlyProp' from class 'B'.
7+
tests/cases/compiler/abstractPropertyNegative.ts(15,5): error TS1244: Abstract methods can only appear within an abstract class.
8+
tests/cases/compiler/abstractPropertyNegative.ts(16,37): error TS1005: '{' expected.
9+
tests/cases/compiler/abstractPropertyNegative.ts(19,1): error TS2450: Left-hand side of assignment expression cannot be a constant or a read-only property.
10+
tests/cases/compiler/abstractPropertyNegative.ts(24,7): error TS2415: Class 'WrongTypePropertyImpl' incorrectly extends base class 'WrongTypeProperty'.
11+
Types of property 'num' are incompatible.
12+
Type 'string' is not assignable to type 'number'.
13+
tests/cases/compiler/abstractPropertyNegative.ts(30,7): error TS2415: Class 'WrongTypeAccessorImpl' incorrectly extends base class 'WrongTypeAccessor'.
14+
Types of property 'num' are incompatible.
15+
Type 'string' is not assignable to type 'number'.
16+
tests/cases/compiler/abstractPropertyNegative.ts(33,7): error TS2415: Class 'WrongTypeAccessorImpl2' incorrectly extends base class 'WrongTypeAccessor'.
17+
Types of property 'num' are incompatible.
18+
Type 'string' is not assignable to type 'number'.
19+
tests/cases/compiler/abstractPropertyNegative.ts(38,18): error TS2676: Accessors must both be abstract or non-abstract.
20+
tests/cases/compiler/abstractPropertyNegative.ts(39,9): error TS2676: Accessors must both be abstract or non-abstract.
21+
tests/cases/compiler/abstractPropertyNegative.ts(40,9): error TS2676: Accessors must both be abstract or non-abstract.
22+
tests/cases/compiler/abstractPropertyNegative.ts(41,18): error TS2676: Accessors must both be abstract or non-abstract.
23+
24+
25+
==== tests/cases/compiler/abstractPropertyNegative.ts (16 errors) ====
26+
interface A {
27+
prop: string;
28+
m(): string;
29+
}
30+
abstract class B implements A {
31+
abstract prop: string;
32+
public abstract readonly ro: string;
33+
abstract get readonlyProp(): string;
34+
abstract m(): string;
35+
abstract get mismatch(): string;
36+
~~~~~~~~
37+
!!! error TS2380: 'get' and 'set' accessor must have the same type.
38+
abstract set mismatch(val: number); // error, not same type
39+
~~~~~~~~
40+
!!! error TS2380: 'get' and 'set' accessor must have the same type.
41+
}
42+
class C extends B {
43+
~
44+
!!! error TS2515: Non-abstract class 'C' does not implement inherited abstract member 'm' from class 'B'.
45+
~
46+
!!! error TS2515: Non-abstract class 'C' does not implement inherited abstract member 'mismatch' from class 'B'.
47+
~
48+
!!! error TS2515: Non-abstract class 'C' does not implement inherited abstract member 'prop' from class 'B'.
49+
~
50+
!!! error TS2515: Non-abstract class 'C' does not implement inherited abstract member 'readonlyProp' from class 'B'.
51+
readonly ro = "readonly please";
52+
abstract notAllowed: string;
53+
~~~~~~~~
54+
!!! error TS1244: Abstract methods can only appear within an abstract class.
55+
get concreteWithNoBody(): string;
56+
~
57+
!!! error TS1005: '{' expected.
58+
}
59+
let c = new C();
60+
c.ro = "error: lhs of assignment can't be readonly";
61+
~~~~
62+
!!! error TS2450: Left-hand side of assignment expression cannot be a constant or a read-only property.
63+
64+
abstract class WrongTypeProperty {
65+
abstract num: number;
66+
}
67+
class WrongTypePropertyImpl extends WrongTypeProperty {
68+
~~~~~~~~~~~~~~~~~~~~~
69+
!!! error TS2415: Class 'WrongTypePropertyImpl' incorrectly extends base class 'WrongTypeProperty'.
70+
!!! error TS2415: Types of property 'num' are incompatible.
71+
!!! error TS2415: Type 'string' is not assignable to type 'number'.
72+
num = "nope, wrong";
73+
}
74+
abstract class WrongTypeAccessor {
75+
abstract get num(): number;
76+
}
77+
class WrongTypeAccessorImpl extends WrongTypeAccessor {
78+
~~~~~~~~~~~~~~~~~~~~~
79+
!!! error TS2415: Class 'WrongTypeAccessorImpl' incorrectly extends base class 'WrongTypeAccessor'.
80+
!!! error TS2415: Types of property 'num' are incompatible.
81+
!!! error TS2415: Type 'string' is not assignable to type 'number'.
82+
get num() { return "nope, wrong"; }
83+
}
84+
class WrongTypeAccessorImpl2 extends WrongTypeAccessor {
85+
~~~~~~~~~~~~~~~~~~~~~~
86+
!!! error TS2415: Class 'WrongTypeAccessorImpl2' incorrectly extends base class 'WrongTypeAccessor'.
87+
!!! error TS2415: Types of property 'num' are incompatible.
88+
!!! error TS2415: Type 'string' is not assignable to type 'number'.
89+
num = "nope, wrong";
90+
}
91+
92+
abstract class AbstractAccessorMismatch {
93+
abstract get p1(): string;
94+
~~
95+
!!! error TS2676: Accessors must both be abstract or non-abstract.
96+
set p1(val: string) { };
97+
~~
98+
!!! error TS2676: Accessors must both be abstract or non-abstract.
99+
get p2(): string { return "should work"; }
100+
~~
101+
!!! error TS2676: Accessors must both be abstract or non-abstract.
102+
abstract set p2(val: string);
103+
~~
104+
!!! error TS2676: Accessors must both be abstract or non-abstract.
105+
}
106+

0 commit comments

Comments
 (0)