-
Notifications
You must be signed in to change notification settings - Fork 12.9k
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
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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) { | ||
|
@@ -29331,60 +29331,70 @@ namespace ts { | |
|
||
Debug.assert(!!derived, "derived should point to something, even if it is the base class' declaration."); | ||
|
||
if (derived) { | ||
andrewbranch marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// 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. | ||
andrewbranch marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// (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) { | ||
weswigham marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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)); | ||
} | ||
} | ||
} | ||
|
28 changes: 28 additions & 0 deletions
28
tests/baselines/reference/mergedInheritedMembersSatisfyAbstractBase.errors.txt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. | ||
|
56 changes: 56 additions & 0 deletions
56
tests/baselines/reference/mergedInheritedMembersSatisfyAbstractBase.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); |
42 changes: 42 additions & 0 deletions
42
tests/baselines/reference/mergedInheritedMembersSatisfyAbstractBase.symbols
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)) | ||
|
35 changes: 35 additions & 0 deletions
35
tests/baselines/reference/mergedInheritedMembersSatisfyAbstractBase.types
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 {} | ||
|
19 changes: 19 additions & 0 deletions
19
...es/conformance/interfaces/declarationMerging/mergedInheritedMembersSatisfyAbstractBase.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 {} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.