Skip to content

A merged interface with an inherited member should satisfy an abstract base class member #32539

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Aug 1, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 56 additions & 46 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29319,7 +29319,7 @@ namespace ts {

// NOTE: assignability is checked in checkClassDeclaration
const baseProperties = getPropertiesOfType(baseType);
for (const baseProperty of baseProperties) {
basePropertyCheck: for (const baseProperty of baseProperties) {
const base = getTargetSymbol(baseProperty);

if (base.flags & SymbolFlags.Prototype) {
Expand All @@ -29331,60 +29331,70 @@ namespace ts {

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

if (derived) {
// In order to resolve whether the inherited method was overridden in the base class or not,
// we compare the Symbols obtained. Since getTargetSymbol returns the symbol on the *uninstantiated*
// type declaration, derived and base resolve to the same symbol even in the case of generic classes.
if (derived === base) {
// derived class inherits base without override/redeclaration

const derivedClassDecl = getClassLikeDeclarationOfSymbol(type.symbol)!;

// It is an error to inherit an abstract member without implementing it or being declared abstract.
// If there is no declaration for the derived class (as in the case of class expressions),
// then the class cannot be declared abstract.
if (baseDeclarationFlags & ModifierFlags.Abstract && (!derivedClassDecl || !hasModifier(derivedClassDecl, ModifierFlags.Abstract))) {
if (derivedClassDecl.kind === SyntaxKind.ClassExpression) {
error(derivedClassDecl, Diagnostics.Non_abstract_class_expression_does_not_implement_inherited_abstract_member_0_from_class_1,
symbolToString(baseProperty), typeToString(baseType));
}
else {
error(derivedClassDecl, Diagnostics.Non_abstract_class_0_does_not_implement_inherited_abstract_member_1_from_class_2,
typeToString(type), symbolToString(baseProperty), typeToString(baseType));
// In order to resolve whether the inherited method was overridden in the base class or not,
// we compare the Symbols obtained. Since getTargetSymbol returns the symbol on the *uninstantiated*
// type declaration, derived and base resolve to the same symbol even in the case of generic classes.
if (derived === base) {
// derived class inherits base without override/redeclaration

const derivedClassDecl = getClassLikeDeclarationOfSymbol(type.symbol)!;

// It is an error to inherit an abstract member without implementing it or being declared abstract.
// If there is no declaration for the derived class (as in the case of class expressions),
// then the class cannot be declared abstract.
if (baseDeclarationFlags & ModifierFlags.Abstract && (!derivedClassDecl || !hasModifier(derivedClassDecl, ModifierFlags.Abstract))) {
// Searches other base types for a declaration that would satisfy the inherited abstract member.
// (The class may have more than one base type via declaration merging with an interface with the
// same name.)
for (const otherBaseType of getBaseTypes(type)) {
if (otherBaseType === baseType) continue;
const baseSymbol = getPropertyOfObjectType(otherBaseType, base.escapedName);
const derivedElsewhere = baseSymbol && getTargetSymbol(baseSymbol);
if (derivedElsewhere && derivedElsewhere !== base) {
continue basePropertyCheck;
}
}
}
else {
// derived overrides base.
const derivedDeclarationFlags = getDeclarationModifierFlagsFromSymbol(derived);
if (baseDeclarationFlags & ModifierFlags.Private || derivedDeclarationFlags & ModifierFlags.Private) {
// either base or derived property is private - not override, skip it
continue;
}

if (isPrototypeProperty(base) || base.flags & SymbolFlags.PropertyOrAccessor && derived.flags & SymbolFlags.PropertyOrAccessor) {
// method is overridden with method or property/accessor is overridden with property/accessor - correct case
continue;
if (derivedClassDecl.kind === SyntaxKind.ClassExpression) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This diff is really wonky if you don’t have “Hide whitespace changes” turned on.

error(derivedClassDecl, Diagnostics.Non_abstract_class_expression_does_not_implement_inherited_abstract_member_0_from_class_1,
symbolToString(baseProperty), typeToString(baseType));
}

let errorMessage: DiagnosticMessage;
if (isPrototypeProperty(base)) {
if (derived.flags & SymbolFlags.Accessor) {
errorMessage = Diagnostics.Class_0_defines_instance_member_function_1_but_extended_class_2_defines_it_as_instance_member_accessor;
}
else {
errorMessage = Diagnostics.Class_0_defines_instance_member_function_1_but_extended_class_2_defines_it_as_instance_member_property;
}
else {
error(derivedClassDecl, Diagnostics.Non_abstract_class_0_does_not_implement_inherited_abstract_member_1_from_class_2,
typeToString(type), symbolToString(baseProperty), typeToString(baseType));
}
else if (base.flags & SymbolFlags.Accessor) {
errorMessage = Diagnostics.Class_0_defines_instance_member_accessor_1_but_extended_class_2_defines_it_as_instance_member_function;
}
}
else {
// derived overrides base.
const derivedDeclarationFlags = getDeclarationModifierFlagsFromSymbol(derived);
if (baseDeclarationFlags & ModifierFlags.Private || derivedDeclarationFlags & ModifierFlags.Private) {
// either base or derived property is private - not override, skip it
continue;
}

if (isPrototypeProperty(base) || base.flags & SymbolFlags.PropertyOrAccessor && derived.flags & SymbolFlags.PropertyOrAccessor) {
// method is overridden with method or property/accessor is overridden with property/accessor - correct case
continue;
}

let errorMessage: DiagnosticMessage;
if (isPrototypeProperty(base)) {
if (derived.flags & SymbolFlags.Accessor) {
errorMessage = Diagnostics.Class_0_defines_instance_member_function_1_but_extended_class_2_defines_it_as_instance_member_accessor;
}
else {
errorMessage = Diagnostics.Class_0_defines_instance_member_property_1_but_extended_class_2_defines_it_as_instance_member_function;
errorMessage = Diagnostics.Class_0_defines_instance_member_function_1_but_extended_class_2_defines_it_as_instance_member_property;
}

error(getNameOfDeclaration(derived.valueDeclaration) || derived.valueDeclaration, errorMessage, typeToString(baseType), symbolToString(base), typeToString(type));
}
else if (base.flags & SymbolFlags.Accessor) {
errorMessage = Diagnostics.Class_0_defines_instance_member_accessor_1_but_extended_class_2_defines_it_as_instance_member_function;
}
else {
errorMessage = Diagnostics.Class_0_defines_instance_member_property_1_but_extended_class_2_defines_it_as_instance_member_function;
}

error(getNameOfDeclaration(derived.valueDeclaration) || derived.valueDeclaration, errorMessage, typeToString(baseType), symbolToString(base), typeToString(type));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
tests/cases/conformance/interfaces/declarationMerging/mergedInheritedMembersSatisfyAbstractBase.ts(19,11): error TS2320: Interface 'IncorrectlyExtends' cannot simultaneously extend types 'BaseClass' and 'IncorrectGetters'.
Named property 'bar' of types 'BaseClass' and 'IncorrectGetters' are not identical.


==== tests/cases/conformance/interfaces/declarationMerging/mergedInheritedMembersSatisfyAbstractBase.ts (1 errors) ====
abstract class BaseClass {
abstract bar: number;
}

class Broken extends BaseClass {}

// declaration merging should satisfy abstract bar
interface IGetters {
bar: number;
}
interface Broken extends IGetters {}

new Broken().bar

class IncorrectlyExtends extends BaseClass {}
interface IncorrectGetters {
bar: string;
}
interface IncorrectlyExtends extends IncorrectGetters {}
~~~~~~~~~~~~~~~~~~
!!! error TS2320: Interface 'IncorrectlyExtends' cannot simultaneously extend types 'BaseClass' and 'IncorrectGetters'.
!!! error TS2320: Named property 'bar' of types 'BaseClass' and 'IncorrectGetters' are not identical.

Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//// [mergedInheritedMembersSatisfyAbstractBase.ts]
abstract class BaseClass {
abstract bar: number;
}

class Broken extends BaseClass {}

// declaration merging should satisfy abstract bar
interface IGetters {
bar: number;
}
interface Broken extends IGetters {}

new Broken().bar

class IncorrectlyExtends extends BaseClass {}
interface IncorrectGetters {
bar: string;
}
interface IncorrectlyExtends extends IncorrectGetters {}


//// [mergedInheritedMembersSatisfyAbstractBase.js]
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var BaseClass = /** @class */ (function () {
function BaseClass() {
}
return BaseClass;
}());
var Broken = /** @class */ (function (_super) {
__extends(Broken, _super);
function Broken() {
return _super !== null && _super.apply(this, arguments) || this;
}
return Broken;
}(BaseClass));
new Broken().bar;
var IncorrectlyExtends = /** @class */ (function (_super) {
__extends(IncorrectlyExtends, _super);
function IncorrectlyExtends() {
return _super !== null && _super.apply(this, arguments) || this;
}
return IncorrectlyExtends;
}(BaseClass));
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
=== tests/cases/conformance/interfaces/declarationMerging/mergedInheritedMembersSatisfyAbstractBase.ts ===
abstract class BaseClass {
>BaseClass : Symbol(BaseClass, Decl(mergedInheritedMembersSatisfyAbstractBase.ts, 0, 0))

abstract bar: number;
>bar : Symbol(BaseClass.bar, Decl(mergedInheritedMembersSatisfyAbstractBase.ts, 0, 26))
}

class Broken extends BaseClass {}
>Broken : Symbol(Broken, Decl(mergedInheritedMembersSatisfyAbstractBase.ts, 2, 1), Decl(mergedInheritedMembersSatisfyAbstractBase.ts, 9, 1))
>BaseClass : Symbol(BaseClass, Decl(mergedInheritedMembersSatisfyAbstractBase.ts, 0, 0))

// declaration merging should satisfy abstract bar
interface IGetters {
>IGetters : Symbol(IGetters, Decl(mergedInheritedMembersSatisfyAbstractBase.ts, 4, 33))

bar: number;
>bar : Symbol(IGetters.bar, Decl(mergedInheritedMembersSatisfyAbstractBase.ts, 7, 20))
}
interface Broken extends IGetters {}
>Broken : Symbol(Broken, Decl(mergedInheritedMembersSatisfyAbstractBase.ts, 2, 1), Decl(mergedInheritedMembersSatisfyAbstractBase.ts, 9, 1))
>IGetters : Symbol(IGetters, Decl(mergedInheritedMembersSatisfyAbstractBase.ts, 4, 33))

new Broken().bar
>new Broken().bar : Symbol(BaseClass.bar, Decl(mergedInheritedMembersSatisfyAbstractBase.ts, 0, 26))
>Broken : Symbol(Broken, Decl(mergedInheritedMembersSatisfyAbstractBase.ts, 2, 1), Decl(mergedInheritedMembersSatisfyAbstractBase.ts, 9, 1))
>bar : Symbol(BaseClass.bar, Decl(mergedInheritedMembersSatisfyAbstractBase.ts, 0, 26))

class IncorrectlyExtends extends BaseClass {}
>IncorrectlyExtends : Symbol(IncorrectlyExtends, Decl(mergedInheritedMembersSatisfyAbstractBase.ts, 12, 16), Decl(mergedInheritedMembersSatisfyAbstractBase.ts, 17, 1))
>BaseClass : Symbol(BaseClass, Decl(mergedInheritedMembersSatisfyAbstractBase.ts, 0, 0))

interface IncorrectGetters {
>IncorrectGetters : Symbol(IncorrectGetters, Decl(mergedInheritedMembersSatisfyAbstractBase.ts, 14, 45))

bar: string;
>bar : Symbol(IncorrectGetters.bar, Decl(mergedInheritedMembersSatisfyAbstractBase.ts, 15, 28))
}
interface IncorrectlyExtends extends IncorrectGetters {}
>IncorrectlyExtends : Symbol(IncorrectlyExtends, Decl(mergedInheritedMembersSatisfyAbstractBase.ts, 12, 16), Decl(mergedInheritedMembersSatisfyAbstractBase.ts, 17, 1))
>IncorrectGetters : Symbol(IncorrectGetters, Decl(mergedInheritedMembersSatisfyAbstractBase.ts, 14, 45))

Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
=== tests/cases/conformance/interfaces/declarationMerging/mergedInheritedMembersSatisfyAbstractBase.ts ===
abstract class BaseClass {
>BaseClass : BaseClass

abstract bar: number;
>bar : number
}

class Broken extends BaseClass {}
>Broken : Broken
>BaseClass : BaseClass

// declaration merging should satisfy abstract bar
interface IGetters {
bar: number;
>bar : number
}
interface Broken extends IGetters {}

new Broken().bar
>new Broken().bar : number
>new Broken() : Broken
>Broken : typeof Broken
>bar : number

class IncorrectlyExtends extends BaseClass {}
>IncorrectlyExtends : IncorrectlyExtends
>BaseClass : BaseClass

interface IncorrectGetters {
bar: string;
>bar : string
}
interface IncorrectlyExtends extends IncorrectGetters {}

Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
abstract class BaseClass {
abstract bar: number;
}

class Broken extends BaseClass {}

// declaration merging should satisfy abstract bar
interface IGetters {
bar: number;
}
interface Broken extends IGetters {}

new Broken().bar

class IncorrectlyExtends extends BaseClass {}
interface IncorrectGetters {
bar: string;
}
interface IncorrectlyExtends extends IncorrectGetters {}