Skip to content

Commit 2ed1beb

Browse files
committed
A merged interface with an inherited member should satisfy an abstract base class member
1 parent e8966ce commit 2ed1beb

File tree

6 files changed

+237
-46
lines changed

6 files changed

+237
-46
lines changed

src/compiler/checker.ts

Lines changed: 57 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -29319,7 +29319,7 @@ namespace ts {
2931929319

2932029320
// NOTE: assignability is checked in checkClassDeclaration
2932129321
const baseProperties = getPropertiesOfType(baseType);
29322-
for (const baseProperty of baseProperties) {
29322+
outer: for (const baseProperty of baseProperties) {
2932329323
const base = getTargetSymbol(baseProperty);
2932429324

2932529325
if (base.flags & SymbolFlags.Prototype) {
@@ -29331,60 +29331,71 @@ namespace ts {
2933129331

2933229332
Debug.assert(!!derived, "derived should point to something, even if it is the base class' declaration.");
2933329333

29334-
if (derived) {
29335-
// In order to resolve whether the inherited method was overridden in the base class or not,
29336-
// we compare the Symbols obtained. Since getTargetSymbol returns the symbol on the *uninstantiated*
29337-
// type declaration, derived and base resolve to the same symbol even in the case of generic classes.
29338-
if (derived === base) {
29339-
// derived class inherits base without override/redeclaration
29340-
29341-
const derivedClassDecl = getClassLikeDeclarationOfSymbol(type.symbol)!;
29342-
29343-
// It is an error to inherit an abstract member without implementing it or being declared abstract.
29344-
// If there is no declaration for the derived class (as in the case of class expressions),
29345-
// then the class cannot be declared abstract.
29346-
if (baseDeclarationFlags & ModifierFlags.Abstract && (!derivedClassDecl || !hasModifier(derivedClassDecl, ModifierFlags.Abstract))) {
29347-
if (derivedClassDecl.kind === SyntaxKind.ClassExpression) {
29348-
error(derivedClassDecl, Diagnostics.Non_abstract_class_expression_does_not_implement_inherited_abstract_member_0_from_class_1,
29349-
symbolToString(baseProperty), typeToString(baseType));
29350-
}
29351-
else {
29352-
error(derivedClassDecl, Diagnostics.Non_abstract_class_0_does_not_implement_inherited_abstract_member_1_from_class_2,
29353-
typeToString(type), symbolToString(baseProperty), typeToString(baseType));
29334+
// In order to resolve whether the inherited method was overridden in the base class or not,
29335+
// we compare the Symbols obtained. Since getTargetSymbol returns the symbol on the *uninstantiated*
29336+
// type declaration, derived and base resolve to the same symbol even in the case of generic classes.
29337+
if (derived === base) {
29338+
// derived class inherits base without override/redeclaration
29339+
29340+
const derivedClassDecl = getClassLikeDeclarationOfSymbol(type.symbol)!;
29341+
29342+
// It is an error to inherit an abstract member without implementing it or being declared abstract.
29343+
// If there is no declaration for the derived class (as in the case of class expressions),
29344+
// then the class cannot be declared abstract.
29345+
if (baseDeclarationFlags & ModifierFlags.Abstract && (!derivedClassDecl || !hasModifier(derivedClassDecl, ModifierFlags.Abstract))) {
29346+
// getPropertyOfObjectType will return a single symbol even if the property can be found
29347+
// on multiple declarations, so it’s possible for derived === base to be true even when
29348+
// another base type introduced from declaration merging to contain the property as well,
29349+
// which should satisfy the abstract member requirement.
29350+
for (const otherBaseType of getBaseTypes(type)) {
29351+
if (otherBaseType === baseType) continue;
29352+
const baseSymbol = getPropertyOfObjectType(otherBaseType, base.escapedName);
29353+
const derivedElsewhere = baseSymbol && getTargetSymbol(baseSymbol);
29354+
if (derivedElsewhere && derivedElsewhere !== base) {
29355+
continue outer;
2935429356
}
2935529357
}
29356-
}
29357-
else {
29358-
// derived overrides base.
29359-
const derivedDeclarationFlags = getDeclarationModifierFlagsFromSymbol(derived);
29360-
if (baseDeclarationFlags & ModifierFlags.Private || derivedDeclarationFlags & ModifierFlags.Private) {
29361-
// either base or derived property is private - not override, skip it
29362-
continue;
29363-
}
2936429358

29365-
if (isPrototypeProperty(base) || base.flags & SymbolFlags.PropertyOrAccessor && derived.flags & SymbolFlags.PropertyOrAccessor) {
29366-
// method is overridden with method or property/accessor is overridden with property/accessor - correct case
29367-
continue;
29359+
if (derivedClassDecl.kind === SyntaxKind.ClassExpression) {
29360+
error(derivedClassDecl, Diagnostics.Non_abstract_class_expression_does_not_implement_inherited_abstract_member_0_from_class_1,
29361+
symbolToString(baseProperty), typeToString(baseType));
2936829362
}
29369-
29370-
let errorMessage: DiagnosticMessage;
29371-
if (isPrototypeProperty(base)) {
29372-
if (derived.flags & SymbolFlags.Accessor) {
29373-
errorMessage = Diagnostics.Class_0_defines_instance_member_function_1_but_extended_class_2_defines_it_as_instance_member_accessor;
29374-
}
29375-
else {
29376-
errorMessage = Diagnostics.Class_0_defines_instance_member_function_1_but_extended_class_2_defines_it_as_instance_member_property;
29377-
}
29363+
else {
29364+
error(derivedClassDecl, Diagnostics.Non_abstract_class_0_does_not_implement_inherited_abstract_member_1_from_class_2,
29365+
typeToString(type), symbolToString(baseProperty), typeToString(baseType));
2937829366
}
29379-
else if (base.flags & SymbolFlags.Accessor) {
29380-
errorMessage = Diagnostics.Class_0_defines_instance_member_accessor_1_but_extended_class_2_defines_it_as_instance_member_function;
29367+
}
29368+
}
29369+
else {
29370+
// derived overrides base.
29371+
const derivedDeclarationFlags = getDeclarationModifierFlagsFromSymbol(derived);
29372+
if (baseDeclarationFlags & ModifierFlags.Private || derivedDeclarationFlags & ModifierFlags.Private) {
29373+
// either base or derived property is private - not override, skip it
29374+
continue;
29375+
}
29376+
29377+
if (isPrototypeProperty(base) || base.flags & SymbolFlags.PropertyOrAccessor && derived.flags & SymbolFlags.PropertyOrAccessor) {
29378+
// method is overridden with method or property/accessor is overridden with property/accessor - correct case
29379+
continue;
29380+
}
29381+
29382+
let errorMessage: DiagnosticMessage;
29383+
if (isPrototypeProperty(base)) {
29384+
if (derived.flags & SymbolFlags.Accessor) {
29385+
errorMessage = Diagnostics.Class_0_defines_instance_member_function_1_but_extended_class_2_defines_it_as_instance_member_accessor;
2938129386
}
2938229387
else {
29383-
errorMessage = Diagnostics.Class_0_defines_instance_member_property_1_but_extended_class_2_defines_it_as_instance_member_function;
29388+
errorMessage = Diagnostics.Class_0_defines_instance_member_function_1_but_extended_class_2_defines_it_as_instance_member_property;
2938429389
}
29385-
29386-
error(getNameOfDeclaration(derived.valueDeclaration) || derived.valueDeclaration, errorMessage, typeToString(baseType), symbolToString(base), typeToString(type));
2938729390
}
29391+
else if (base.flags & SymbolFlags.Accessor) {
29392+
errorMessage = Diagnostics.Class_0_defines_instance_member_accessor_1_but_extended_class_2_defines_it_as_instance_member_function;
29393+
}
29394+
else {
29395+
errorMessage = Diagnostics.Class_0_defines_instance_member_property_1_but_extended_class_2_defines_it_as_instance_member_function;
29396+
}
29397+
29398+
error(getNameOfDeclaration(derived.valueDeclaration) || derived.valueDeclaration, errorMessage, typeToString(baseType), symbolToString(base), typeToString(type));
2938829399
}
2938929400
}
2939029401
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
tests/cases/conformance/interfaces/declarationMerging/mergedInheritedMembersSatisfyAbstractBase.ts(19,11): error TS2320: Interface 'IncorrectlyExtends' cannot simultaneously extend types 'BaseClass' and 'IncorrectGetters'.
2+
Named property 'bar' of types 'BaseClass' and 'IncorrectGetters' are not identical.
3+
4+
5+
==== tests/cases/conformance/interfaces/declarationMerging/mergedInheritedMembersSatisfyAbstractBase.ts (1 errors) ====
6+
abstract class BaseClass {
7+
abstract bar: number;
8+
}
9+
10+
class Broken extends BaseClass {}
11+
12+
// declaration merging should satisfy abstract bar
13+
interface IGetters {
14+
bar: number;
15+
}
16+
interface Broken extends IGetters {}
17+
18+
new Broken().bar
19+
20+
class IncorrectlyExtends extends BaseClass {}
21+
interface IncorrectGetters {
22+
bar: string;
23+
}
24+
interface IncorrectlyExtends extends IncorrectGetters {}
25+
~~~~~~~~~~~~~~~~~~
26+
!!! error TS2320: Interface 'IncorrectlyExtends' cannot simultaneously extend types 'BaseClass' and 'IncorrectGetters'.
27+
!!! error TS2320: Named property 'bar' of types 'BaseClass' and 'IncorrectGetters' are not identical.
28+
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
//// [mergedInheritedMembersSatisfyAbstractBase.ts]
2+
abstract class BaseClass {
3+
abstract bar: number;
4+
}
5+
6+
class Broken extends BaseClass {}
7+
8+
// declaration merging should satisfy abstract bar
9+
interface IGetters {
10+
bar: number;
11+
}
12+
interface Broken extends IGetters {}
13+
14+
new Broken().bar
15+
16+
class IncorrectlyExtends extends BaseClass {}
17+
interface IncorrectGetters {
18+
bar: string;
19+
}
20+
interface IncorrectlyExtends extends IncorrectGetters {}
21+
22+
23+
//// [mergedInheritedMembersSatisfyAbstractBase.js]
24+
var __extends = (this && this.__extends) || (function () {
25+
var extendStatics = function (d, b) {
26+
extendStatics = Object.setPrototypeOf ||
27+
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
28+
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
29+
return extendStatics(d, b);
30+
};
31+
return function (d, b) {
32+
extendStatics(d, b);
33+
function __() { this.constructor = d; }
34+
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
35+
};
36+
})();
37+
var BaseClass = /** @class */ (function () {
38+
function BaseClass() {
39+
}
40+
return BaseClass;
41+
}());
42+
var Broken = /** @class */ (function (_super) {
43+
__extends(Broken, _super);
44+
function Broken() {
45+
return _super !== null && _super.apply(this, arguments) || this;
46+
}
47+
return Broken;
48+
}(BaseClass));
49+
new Broken().bar;
50+
var IncorrectlyExtends = /** @class */ (function (_super) {
51+
__extends(IncorrectlyExtends, _super);
52+
function IncorrectlyExtends() {
53+
return _super !== null && _super.apply(this, arguments) || this;
54+
}
55+
return IncorrectlyExtends;
56+
}(BaseClass));
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
=== tests/cases/conformance/interfaces/declarationMerging/mergedInheritedMembersSatisfyAbstractBase.ts ===
2+
abstract class BaseClass {
3+
>BaseClass : Symbol(BaseClass, Decl(mergedInheritedMembersSatisfyAbstractBase.ts, 0, 0))
4+
5+
abstract bar: number;
6+
>bar : Symbol(BaseClass.bar, Decl(mergedInheritedMembersSatisfyAbstractBase.ts, 0, 26))
7+
}
8+
9+
class Broken extends BaseClass {}
10+
>Broken : Symbol(Broken, Decl(mergedInheritedMembersSatisfyAbstractBase.ts, 2, 1), Decl(mergedInheritedMembersSatisfyAbstractBase.ts, 9, 1))
11+
>BaseClass : Symbol(BaseClass, Decl(mergedInheritedMembersSatisfyAbstractBase.ts, 0, 0))
12+
13+
// declaration merging should satisfy abstract bar
14+
interface IGetters {
15+
>IGetters : Symbol(IGetters, Decl(mergedInheritedMembersSatisfyAbstractBase.ts, 4, 33))
16+
17+
bar: number;
18+
>bar : Symbol(IGetters.bar, Decl(mergedInheritedMembersSatisfyAbstractBase.ts, 7, 20))
19+
}
20+
interface Broken extends IGetters {}
21+
>Broken : Symbol(Broken, Decl(mergedInheritedMembersSatisfyAbstractBase.ts, 2, 1), Decl(mergedInheritedMembersSatisfyAbstractBase.ts, 9, 1))
22+
>IGetters : Symbol(IGetters, Decl(mergedInheritedMembersSatisfyAbstractBase.ts, 4, 33))
23+
24+
new Broken().bar
25+
>new Broken().bar : Symbol(BaseClass.bar, Decl(mergedInheritedMembersSatisfyAbstractBase.ts, 0, 26))
26+
>Broken : Symbol(Broken, Decl(mergedInheritedMembersSatisfyAbstractBase.ts, 2, 1), Decl(mergedInheritedMembersSatisfyAbstractBase.ts, 9, 1))
27+
>bar : Symbol(BaseClass.bar, Decl(mergedInheritedMembersSatisfyAbstractBase.ts, 0, 26))
28+
29+
class IncorrectlyExtends extends BaseClass {}
30+
>IncorrectlyExtends : Symbol(IncorrectlyExtends, Decl(mergedInheritedMembersSatisfyAbstractBase.ts, 12, 16), Decl(mergedInheritedMembersSatisfyAbstractBase.ts, 17, 1))
31+
>BaseClass : Symbol(BaseClass, Decl(mergedInheritedMembersSatisfyAbstractBase.ts, 0, 0))
32+
33+
interface IncorrectGetters {
34+
>IncorrectGetters : Symbol(IncorrectGetters, Decl(mergedInheritedMembersSatisfyAbstractBase.ts, 14, 45))
35+
36+
bar: string;
37+
>bar : Symbol(IncorrectGetters.bar, Decl(mergedInheritedMembersSatisfyAbstractBase.ts, 15, 28))
38+
}
39+
interface IncorrectlyExtends extends IncorrectGetters {}
40+
>IncorrectlyExtends : Symbol(IncorrectlyExtends, Decl(mergedInheritedMembersSatisfyAbstractBase.ts, 12, 16), Decl(mergedInheritedMembersSatisfyAbstractBase.ts, 17, 1))
41+
>IncorrectGetters : Symbol(IncorrectGetters, Decl(mergedInheritedMembersSatisfyAbstractBase.ts, 14, 45))
42+
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
=== tests/cases/conformance/interfaces/declarationMerging/mergedInheritedMembersSatisfyAbstractBase.ts ===
2+
abstract class BaseClass {
3+
>BaseClass : BaseClass
4+
5+
abstract bar: number;
6+
>bar : number
7+
}
8+
9+
class Broken extends BaseClass {}
10+
>Broken : Broken
11+
>BaseClass : BaseClass
12+
13+
// declaration merging should satisfy abstract bar
14+
interface IGetters {
15+
bar: number;
16+
>bar : number
17+
}
18+
interface Broken extends IGetters {}
19+
20+
new Broken().bar
21+
>new Broken().bar : number
22+
>new Broken() : Broken
23+
>Broken : typeof Broken
24+
>bar : number
25+
26+
class IncorrectlyExtends extends BaseClass {}
27+
>IncorrectlyExtends : IncorrectlyExtends
28+
>BaseClass : BaseClass
29+
30+
interface IncorrectGetters {
31+
bar: string;
32+
>bar : string
33+
}
34+
interface IncorrectlyExtends extends IncorrectGetters {}
35+
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
abstract class BaseClass {
2+
abstract bar: number;
3+
}
4+
5+
class Broken extends BaseClass {}
6+
7+
// declaration merging should satisfy abstract bar
8+
interface IGetters {
9+
bar: number;
10+
}
11+
interface Broken extends IGetters {}
12+
13+
new Broken().bar
14+
15+
class IncorrectlyExtends extends BaseClass {}
16+
interface IncorrectGetters {
17+
bar: string;
18+
}
19+
interface IncorrectlyExtends extends IncorrectGetters {}

0 commit comments

Comments
 (0)