Skip to content

Commit 0957546

Browse files
committed
fix: generate correct override stub for interface when implementation from base class
1 parent 5a125c7 commit 0957546

File tree

8 files changed

+4367
-96
lines changed

8 files changed

+4367
-96
lines changed

src/compiler.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -612,7 +612,7 @@ export class Compiler extends DiagnosticEmitter {
612612
resolver.discoveredOverride = false;
613613
for (let _values = Set_values(overrideStubs), i = 0, k = _values.length; i < k; ++i) {
614614
let instance = unchecked(_values[i]);
615-
let overrideInstances = resolver.resolveOverrides(instance);
615+
let overrideInstances = resolver.resolveOverridesOrImplementations(instance);
616616
if (overrideInstances) {
617617
for (let i = 0, k = overrideInstances.length; i < k; ++i) {
618618
this.compileFunction(overrideInstances[i]);
@@ -6719,7 +6719,7 @@ export class Compiler extends DiagnosticEmitter {
67196719
TypeRef.I32
67206720
)
67216721
);
6722-
let overrideInstances = this.resolver.resolveOverrides(instance);
6722+
let overrideInstances = this.resolver.resolveOverridesOrImplementations(instance);
67236723
if (overrideInstances) {
67246724
let mostRecentInheritanceMapping = new Map<Class, Class>();
67256725
for (let i = 0, k = overrideInstances.length; i < k; ++i) {

src/program.ts

Lines changed: 141 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1440,7 +1440,7 @@ export class Program extends DiagnosticEmitter {
14401440
}
14411441
if (interfacePrototypes) {
14421442
for (let j = 0, l = interfacePrototypes.length; j < l; ++j) {
1443-
this.processOverrides(thisPrototype, interfacePrototypes[j]);
1443+
this.processImplements(thisPrototype, interfacePrototypes[j]);
14441444
}
14451445
}
14461446
}
@@ -1497,6 +1497,124 @@ export class Program extends DiagnosticEmitter {
14971497
}
14981498
}
14991499

1500+
private processImplements(thisPrototype: ClassPrototype, interfacePrototype: InterfacePrototype): void {
1501+
let interfaceInstanceMembers = interfacePrototype.instanceMembers;
1502+
if (interfaceInstanceMembers) {
1503+
for (let _values = Map_values(interfaceInstanceMembers), i = 0, k = _values.length; i < k; ++i) {
1504+
let interfaceMember = unchecked(_values[i]);
1505+
const implMember = this.searchImplementation(thisPrototype, interfaceMember.name);
1506+
if (!implMember) continue;
1507+
this.doProcessImplementation(thisPrototype, implMember, interfacePrototype, interfaceMember);
1508+
}
1509+
}
1510+
const interfaceBasePrototype = interfacePrototype.basePrototype;
1511+
if (interfaceBasePrototype) {
1512+
assert(interfaceBasePrototype.kind == ElementKind.InterfacePrototype);
1513+
this.processImplements(thisPrototype, <InterfacePrototype>interfaceBasePrototype);
1514+
}
1515+
}
1516+
1517+
private searchImplementation(thisPrototype: ClassPrototype, name: string): DeclaredElement | null {
1518+
let currentPrototype: ClassPrototype | null = thisPrototype;
1519+
while (currentPrototype) {
1520+
let currentInstanceMembers = currentPrototype.instanceMembers;
1521+
if (currentInstanceMembers && currentInstanceMembers.has(name)) {
1522+
return assert(currentInstanceMembers.get(name));
1523+
}
1524+
currentPrototype = currentPrototype.basePrototype;
1525+
}
1526+
return null;
1527+
}
1528+
1529+
private doProcessImplementation(
1530+
thisClass: ClassPrototype,
1531+
implMember: DeclaredElement,
1532+
interfacePrototype: InterfacePrototype,
1533+
interfaceMember: DeclaredElement
1534+
): void {
1535+
if (implMember.kind == ElementKind.FunctionPrototype && interfaceMember.kind == ElementKind.FunctionPrototype) {
1536+
const implMethod = <FunctionPrototype>implMember;
1537+
const interfaceMethod = <FunctionPrototype>interfaceMember;
1538+
if (!implMethod.visibilityEquals(interfaceMethod)) {
1539+
this.errorRelated(
1540+
DiagnosticCode.Overload_signatures_must_all_be_public_private_or_protected,
1541+
implMethod.declaration.name.range,
1542+
interfaceMethod.declaration.name.range
1543+
);
1544+
}
1545+
interfaceMethod.addUnboundImplementations(thisClass, implMethod);
1546+
interfaceMethod.setOverrideFlag();
1547+
} else if (
1548+
implMember.kind == ElementKind.PropertyPrototype &&
1549+
interfaceMember.kind == ElementKind.PropertyPrototype
1550+
) {
1551+
const implProperty = <PropertyPrototype>implMember;
1552+
const interfaceProperty = <PropertyPrototype>interfaceMember;
1553+
if (!implProperty.visibilityEquals(interfaceProperty)) {
1554+
this.errorRelated(
1555+
DiagnosticCode.Overload_signatures_must_all_be_public_private_or_protected,
1556+
implProperty.declaration.name.range,
1557+
interfaceProperty.declaration.name.range
1558+
);
1559+
}
1560+
if (interfaceProperty.parent.kind != ElementKind.InterfacePrototype) {
1561+
// Interface fields/properties can be impled by either, but other
1562+
// members must match to retain compatiblity with TS/JS.
1563+
const implIsField = implProperty.isField;
1564+
if (implIsField != interfaceProperty.isField) {
1565+
if (implIsField) {
1566+
// base is property
1567+
this.errorRelated(
1568+
DiagnosticCode._0_is_defined_as_an_accessor_in_class_1_but_is_overridden_here_in_2_as_an_instance_property,
1569+
implProperty.declaration.name.range,
1570+
interfaceProperty.declaration.name.range,
1571+
implProperty.name,
1572+
interfacePrototype.internalName,
1573+
thisClass.internalName
1574+
);
1575+
} else {
1576+
// this is property, base is field
1577+
this.errorRelated(
1578+
DiagnosticCode._0_is_defined_as_a_property_in_class_1_but_is_overridden_here_in_2_as_an_accessor,
1579+
implProperty.declaration.name.range,
1580+
interfaceProperty.declaration.name.range,
1581+
implProperty.name,
1582+
interfacePrototype.internalName,
1583+
thisClass.internalName
1584+
);
1585+
}
1586+
return;
1587+
} else if (implIsField) {
1588+
// base is also field
1589+
// Fields don't override other fields and can only be redeclared
1590+
return;
1591+
}
1592+
}
1593+
interfaceProperty.set(CommonFlags.Overridden);
1594+
const interfaceGetter = interfaceProperty.getterPrototype;
1595+
if (interfaceGetter) {
1596+
const implGetter = implProperty.getterPrototype;
1597+
if (implGetter) interfaceGetter.addUnboundImplementations(thisClass, implGetter);
1598+
interfaceGetter.setOverrideFlag();
1599+
}
1600+
const interfaceSetter = interfaceProperty.setterPrototype;
1601+
if (interfaceSetter && implProperty.setterPrototype) {
1602+
const implSetter = implProperty.setterPrototype;
1603+
if (implSetter) interfaceSetter.addUnboundImplementations(thisClass, implSetter);
1604+
interfaceSetter.setOverrideFlag();
1605+
}
1606+
} else {
1607+
this.errorRelated(
1608+
DiagnosticCode.Property_0_in_type_1_is_not_assignable_to_the_same_property_in_base_type_2,
1609+
implMember.declaration.name.range,
1610+
interfaceMember.declaration.name.range,
1611+
implMember.name,
1612+
thisClass.internalName,
1613+
interfacePrototype.internalName
1614+
);
1615+
}
1616+
}
1617+
15001618
/** Processes overridden members by this class in a base class. */
15011619
private processOverrides(
15021620
thisPrototype: ClassPrototype,
@@ -1526,7 +1644,7 @@ export class Program extends DiagnosticEmitter {
15261644
for (let i = 0, k = baseInterfacePrototypes.length; i < k; ++i) {
15271645
let baseInterfacePrototype = baseInterfacePrototypes[i];
15281646
if (baseInterfacePrototype != basePrototype) {
1529-
this.processOverrides(thisPrototype, baseInterfacePrototype);
1647+
this.processImplements(thisPrototype, baseInterfacePrototype);
15301648
}
15311649
}
15321650
}
@@ -3667,6 +3785,8 @@ export class FunctionPrototype extends DeclaredElement {
36673785
instances: Map<string,Function> | null = null;
36683786
/** Methods overriding this one, if any. These are unbound. */
36693787
unboundOverrides: Set<FunctionPrototype> | null = null;
3788+
/** Methods implement this one, if any. These are unbound. */
3789+
unboundImplementations: Map<ClassPrototype, FunctionPrototype> | null = null;
36703790

36713791
/** Clones of this prototype that are bound to specific classes. */
36723792
private boundPrototypes: Map<Class,FunctionPrototype> | null = null;
@@ -3731,6 +3851,7 @@ export class FunctionPrototype extends DeclaredElement {
37313851
bound.flags = this.flags;
37323852
bound.operatorKind = this.operatorKind;
37333853
bound.unboundOverrides = this.unboundOverrides;
3854+
bound.unboundImplementations = this.unboundImplementations;
37343855
// NOTE: this.instances holds instances per bound class / unbound
37353856
boundPrototypes.set(classInstance, bound);
37363857
return bound;
@@ -3750,6 +3871,24 @@ export class FunctionPrototype extends DeclaredElement {
37503871
else assert(!instances.has(instanceKey));
37513872
instances.set(instanceKey, instance);
37523873
}
3874+
3875+
3876+
setOverrideFlag(): void {
3877+
this.set(CommonFlags.Overridden);
3878+
let instances = this.instances;
3879+
if (instances) {
3880+
for (let _values = Map_values(instances), a = 0, b = _values.length; a < b; ++a) {
3881+
let instance = _values[a];
3882+
instance.set(CommonFlags.Overridden);
3883+
}
3884+
}
3885+
}
3886+
3887+
addUnboundImplementations(thisClass: ClassPrototype, implementMember: DeclaredElement): void {
3888+
let unboundImplementations = this.unboundImplementations;
3889+
if (!unboundImplementations) this.unboundImplementations = unboundImplementations = new Map();
3890+
unboundImplementations.set(thisClass, <FunctionPrototype>implementMember);
3891+
}
37533892
}
37543893

37553894
/** A resolved function. */

src/resolver.ts

Lines changed: 72 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3014,12 +3014,21 @@ export class Resolver extends DiagnosticEmitter {
30143014
);
30153015
}
30163016

3017+
/** Resolves reachable overrides or implementations of the given instance method. */
3018+
resolveOverridesOrImplementations(instance: Function): Function[] | null {
3019+
if (instance.parent.kind == ElementKind.Interface) {
3020+
return this.resolveImplementations(instance);
3021+
} else {
3022+
assert(instance.parent.kind == ElementKind.Class);
3023+
return this.resolveOverrides(instance);
3024+
}
3025+
}
3026+
3027+
30173028
/** Resolves reachable overrides of the given instance method. */
3018-
resolveOverrides(instance: Function): Function[] | null {
3029+
private resolveOverrides(instance: Function): Function[] | null {
30193030
let overridePrototypes = instance.prototype.unboundOverrides;
30203031
if (!overridePrototypes) return null;
3021-
3022-
let parentClassInstance = assert(instance.getBoundClassOrInterface());
30233032
let overrides = new Set<Function>();
30243033

30253034
// A method's `overrides` property contains its unbound override prototypes
@@ -3034,35 +3043,72 @@ export class Resolver extends DiagnosticEmitter {
30343043
classInstances = (<ClassPrototype>unboundOverrideParent).instances;
30353044
if (!classInstances) continue;
30363045
for (let _values = Map_values(classInstances), j = 0, l = _values.length; j < l; ++j) {
3037-
let classInstance = _values[j];
3038-
// Check if the parent class is a subtype of instance's class
3039-
if (!classInstance.isAssignableTo(parentClassInstance)) continue;
3040-
let overrideInstance: Function | null = null;
3041-
if (instance.isAny(CommonFlags.Get | CommonFlags.Set)) {
3042-
let propertyName = instance.declaration.name.text;
3043-
let boundPropertyPrototype = assert(classInstance.getMember(propertyName));
3044-
assert(boundPropertyPrototype.kind == ElementKind.PropertyPrototype);
3045-
let boundPropertyInstance = this.resolveProperty(<PropertyPrototype>boundPropertyPrototype);
3046-
if (!boundPropertyInstance) continue;
3047-
if (instance.is(CommonFlags.Get)) {
3048-
overrideInstance = boundPropertyInstance.getterInstance;
3049-
} else {
3050-
assert(instance.is(CommonFlags.Set));
3051-
overrideInstance = boundPropertyInstance.setterInstance;
3052-
}
3053-
} else {
3054-
let boundPrototype = classInstance.getMember(unboundOverridePrototype.name);
3055-
if (boundPrototype) { // might have errored earlier and wasn't added
3056-
assert(boundPrototype.kind == ElementKind.FunctionPrototype);
3057-
overrideInstance = this.resolveFunction(<FunctionPrototype>boundPrototype, instance.typeArguments);
3058-
}
3059-
}
3046+
const overrideInstance = this.doResolveOverrideOrImplementation(instance, _values[j], unboundOverridePrototype);
30603047
if (overrideInstance) overrides.add(overrideInstance);
30613048
}
30623049
}
30633050
return Set_values(overrides);
30643051
}
30653052

3053+
private resolveImplementations(instance: Function): Function[] | null {
3054+
let unboundImplementations = instance.prototype.unboundImplementations;
3055+
if (!unboundImplementations) return null;
3056+
let implementations = new Set<Function>();
3057+
for (
3058+
let _keys = Map_keys(unboundImplementations),
3059+
_values = Map_values(unboundImplementations),
3060+
i = 0,
3061+
k = _keys.length;
3062+
i < k;
3063+
++i
3064+
) {
3065+
/** in {@link classPrototype}, we implement {@link instance} by {@link unboundImplPrototype} */
3066+
const classPrototype = _keys[i];
3067+
const unboundImplPrototype = _values[i];
3068+
assert(!unboundImplPrototype.isBound);
3069+
3070+
let classInstances = classPrototype.instances;
3071+
if (!classInstances) continue;
3072+
for (let _values = Map_values(classInstances), j = 0, l = _values.length; j < l; ++j) {
3073+
const implInstance = this.doResolveOverrideOrImplementation(instance, _values[j], unboundImplPrototype);
3074+
if (implInstance) implementations.add(implInstance);
3075+
}
3076+
}
3077+
return Set_values(implementations);
3078+
}
3079+
3080+
private doResolveOverrideOrImplementation(
3081+
instance: Function,
3082+
classInstance: Class,
3083+
unboundPrototype: FunctionPrototype
3084+
): Function | null {
3085+
const parentClassInstance = assert(instance.getBoundClassOrInterface());
3086+
// Check if the parent class is a subtype of instance's class
3087+
if (!classInstance.isAssignableTo(parentClassInstance)) return null;
3088+
let ret: Function | null = null;
3089+
if (instance.isAny(CommonFlags.Get | CommonFlags.Set)) {
3090+
let propertyName = instance.identifierNode.text;
3091+
let boundPropertyPrototype = assert(classInstance.getMember(propertyName));
3092+
assert(boundPropertyPrototype.kind == ElementKind.PropertyPrototype);
3093+
let boundPropertyInstance = this.resolveProperty(<PropertyPrototype>boundPropertyPrototype);
3094+
if (!boundPropertyInstance) return null;
3095+
if (instance.is(CommonFlags.Get)) {
3096+
ret = boundPropertyInstance.getterInstance;
3097+
} else {
3098+
assert(instance.is(CommonFlags.Set));
3099+
ret = boundPropertyInstance.setterInstance;
3100+
}
3101+
} else {
3102+
let boundPrototype = classInstance.getMember(unboundPrototype.name);
3103+
if (boundPrototype) {
3104+
// might have errored earlier and wasn't added
3105+
assert(boundPrototype.kind == ElementKind.FunctionPrototype);
3106+
ret = this.resolveFunction(<FunctionPrototype>boundPrototype, instance.typeArguments);
3107+
}
3108+
}
3109+
return ret;
3110+
}
3111+
30663112
/** Currently resolving classes. */
30673113
private resolveClassPending: Set<Class> = new Set();
30683114

tests/compiler/class-implements.debug.wat

Lines changed: 24 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2331,9 +2331,6 @@
23312331
local.get $this
23322332
i32.load offset=4
23332333
)
2334-
(func $class-implements/K#foo (param $this i32) (result i32)
2335-
unreachable
2336-
)
23372334
(func $class-implements/G#foo (param $this i32) (result i32)
23382335
i32.const 1
23392336
return
@@ -2415,40 +2412,31 @@
24152412
(func $class-implements/J#foo@override (param $0 i32) (result i32)
24162413
(local $1 i32)
24172414
block $default
2418-
block $case3
2419-
block $case2
2420-
block $case1
2421-
block $case0
2422-
local.get $0
2423-
i32.const 8
2424-
i32.sub
2425-
i32.load
2426-
local.set $1
2427-
local.get $1
2428-
i32.const 13
2429-
i32.eq
2430-
br_if $case0
2431-
local.get $1
2432-
i32.const 11
2433-
i32.eq
2434-
br_if $case1
2435-
local.get $1
2436-
i32.const 8
2437-
i32.eq
2438-
br_if $case2
2439-
local.get $1
2440-
i32.const 10
2441-
i32.eq
2442-
br_if $case2
2443-
local.get $1
2444-
i32.const 12
2445-
i32.eq
2446-
br_if $case3
2447-
br $default
2448-
end
2415+
block $case2
2416+
block $case1
2417+
block $case0
24492418
local.get $0
2450-
call $class-implements/K#foo
2451-
return
2419+
i32.const 8
2420+
i32.sub
2421+
i32.load
2422+
local.set $1
2423+
local.get $1
2424+
i32.const 11
2425+
i32.eq
2426+
br_if $case0
2427+
local.get $1
2428+
i32.const 8
2429+
i32.eq
2430+
br_if $case1
2431+
local.get $1
2432+
i32.const 10
2433+
i32.eq
2434+
br_if $case1
2435+
local.get $1
2436+
i32.const 12
2437+
i32.eq
2438+
br_if $case2
2439+
br $default
24522440
end
24532441
local.get $0
24542442
call $class-implements/F#foo

0 commit comments

Comments
 (0)