Skip to content

Commit 3afcce8

Browse files
fix: Properly handle multiple implemented interfaces (#2510)
Co-authored-by: dcode <dcode@dcode.io>
1 parent dc797a4 commit 3afcce8

File tree

5 files changed

+783
-76
lines changed

5 files changed

+783
-76
lines changed

src/program.ts

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3007,18 +3007,38 @@ export abstract class DeclaredElement extends Element {
30073007
isCompatibleOverride(base: DeclaredElement): bool {
30083008
var self: DeclaredElement = this; // TS
30093009
var kind = self.kind;
3010+
var checkCompatibleOverride = false;
30103011
if (kind == base.kind) {
30113012
switch (kind) {
3013+
case ElementKind.FUNCTION_PROTOTYPE : {
3014+
let selfFunction = this.program.resolver.resolveFunction(<FunctionPrototype>self, null);
3015+
if (!selfFunction) return false;
3016+
let baseFunction = this.program.resolver.resolveFunction(<FunctionPrototype>base, null);
3017+
if (!baseFunction) return false;
3018+
self = selfFunction;
3019+
base = baseFunction;
3020+
checkCompatibleOverride = true;
3021+
// fall-through
3022+
}
30123023
case ElementKind.FUNCTION: {
3013-
return (<Function>self).signature.isAssignableTo((<Function>base).signature);
3024+
return (<Function>self).signature.isAssignableTo((<Function>base).signature, checkCompatibleOverride);
3025+
}
3026+
case ElementKind.PROPERTY_PROTOTYPE: {
3027+
let selfProperty = this.program.resolver.resolveProperty(<PropertyPrototype>self);
3028+
if (!selfProperty) return false;
3029+
let baseProperty = this.program.resolver.resolveProperty(<PropertyPrototype>base);
3030+
if (!baseProperty) return false;
3031+
self = selfProperty;
3032+
base = baseProperty;
3033+
// fall-through
30143034
}
30153035
case ElementKind.PROPERTY: {
30163036
let selfProperty = <Property>self;
30173037
let baseProperty = <Property>base;
30183038
let selfGetter = selfProperty.getterInstance;
30193039
let baseGetter = baseProperty.getterInstance;
30203040
if (selfGetter) {
3021-
if (!baseGetter || !selfGetter.signature.isAssignableTo(baseGetter.signature)) {
3041+
if (!baseGetter || !selfGetter.signature.isAssignableTo(baseGetter.signature, true)) {
30223042
return false;
30233043
}
30243044
} else if (baseGetter) {
@@ -3027,14 +3047,18 @@ export abstract class DeclaredElement extends Element {
30273047
let selfSetter = selfProperty.setterInstance;
30283048
let baseSetter = baseProperty.setterInstance;
30293049
if (selfSetter) {
3030-
if (!baseSetter || !selfSetter.signature.isAssignableTo(baseSetter.signature)) {
3050+
if (!baseSetter || !selfSetter.signature.isAssignableTo(baseSetter.signature, true)) {
30313051
return false;
30323052
}
30333053
} else if (baseSetter) {
30343054
return false;
30353055
}
30363056
return true;
30373057
}
3058+
// TODO: Implement properties overriding fields and vice-versa. Challenge is that anything overridable requires
3059+
// a virtual stub, but fields aren't functions. Either all (such) fields should become property-like, with a
3060+
// getter and a setter that can participate as a virtual stub, or it's allowed one-way, with fields integrated
3061+
// into what can be a virtual stub as get=load and set=store, then not necessarily with own accessor functions.
30383062
}
30393063
}
30403064
return false;
@@ -4294,6 +4318,11 @@ export class Class extends TypedElement {
42944318
);
42954319
}
42964320

4321+
/** Tests if this is an interface. */
4322+
get isInterface(): bool {
4323+
return this.kind == ElementKind.INTERFACE;
4324+
}
4325+
42974326
/** Constructs a new class. */
42984327
constructor(
42994328
/** Name incl. type parameters, i.e. `Foo<i32>`. */

src/types.ts

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,27 @@ export class Type {
502502
return this.kind == target.kind;
503503
}
504504

505+
/** Tests if this type can extend or implement the given type. */
506+
canExtendOrImplement(base: Type): bool {
507+
// Both must be class types
508+
var thisClass = this.getClass();
509+
var baseClass = base.getClass();
510+
if (!thisClass || !baseClass) return false;
511+
// Both types must be either managed or unmanaged
512+
if (this.isManaged != base.isManaged) return false;
513+
// Both types must be either internal or external references
514+
if (this.isInternalReference) {
515+
if (!base.isInternalReference) return false;
516+
} else if (this.isExternalReference) {
517+
if (!base.isExternalReference) return false;
518+
} else {
519+
return false;
520+
}
521+
// Interfaces can only extend interfaces
522+
if (thisClass.isInterface && !baseClass.isInterface) return false;
523+
return true;
524+
}
525+
505526
/** Determines the common denominator type of two types, if there is any. */
506527
static commonDenominator(left: Type, right: Type, signednessIsImportant: bool): Type | null {
507528
if (right.isAssignableTo(left, signednessIsImportant)) return left;
@@ -926,17 +947,27 @@ export class Signature {
926947
}
927948

928949
/** Tests if a value of this function type is assignable to a target of the specified function type. */
929-
isAssignableTo(target: Signature): bool {
930-
931-
// check `this` type
950+
isAssignableTo(target: Signature, checkCompatibleOverride: bool = false): bool {
932951
var thisThisType = this.thisType;
933952
var targetThisType = target.thisType;
934-
if (thisThisType) {
935-
if (!targetThisType || !thisThisType.isAssignableTo(targetThisType)) {
953+
if (!checkCompatibleOverride) {
954+
// check exact `this` type
955+
if (thisThisType) {
956+
if (!targetThisType || !thisThisType.isAssignableTo(targetThisType)) {
957+
return false;
958+
}
959+
} else if (targetThisType) {
960+
return false;
961+
}
962+
} else {
963+
// check kind of `this` type
964+
if (thisThisType) {
965+
if (!targetThisType || !thisThisType.canExtendOrImplement(targetThisType)) {
966+
return false;
967+
}
968+
} else if (targetThisType) {
936969
return false;
937970
}
938-
} else if (targetThisType) {
939-
return false;
940971
}
941972

942973
// check rest parameter

0 commit comments

Comments
 (0)