Skip to content
Open
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
4 changes: 2 additions & 2 deletions src/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -612,7 +612,7 @@ export class Compiler extends DiagnosticEmitter {
resolver.discoveredOverride = false;
for (let _values = Set_values(overrideStubs), i = 0, k = _values.length; i < k; ++i) {
let instance = unchecked(_values[i]);
let overrideInstances = resolver.resolveOverrides(instance);
let overrideInstances = resolver.resolveOverridesOrImplementations(instance);
if (overrideInstances) {
for (let i = 0, k = overrideInstances.length; i < k; ++i) {
this.compileFunction(overrideInstances[i]);
Expand Down Expand Up @@ -6719,7 +6719,7 @@ export class Compiler extends DiagnosticEmitter {
TypeRef.I32
)
);
let overrideInstances = this.resolver.resolveOverrides(instance);
let overrideInstances = this.resolver.resolveOverridesOrImplementations(instance);
if (overrideInstances) {
let mostRecentInheritanceMapping = new Map<Class, Class>();
for (let i = 0, k = overrideInstances.length; i < k; ++i) {
Expand Down
143 changes: 141 additions & 2 deletions src/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1440,7 +1440,7 @@ export class Program extends DiagnosticEmitter {
}
if (interfacePrototypes) {
for (let j = 0, l = interfacePrototypes.length; j < l; ++j) {
this.processOverrides(thisPrototype, interfacePrototypes[j]);
this.processImplements(thisPrototype, interfacePrototypes[j]);
}
}
}
Expand Down Expand Up @@ -1497,6 +1497,124 @@ export class Program extends DiagnosticEmitter {
}
}

private processImplements(thisPrototype: ClassPrototype, interfacePrototype: InterfacePrototype): void {
let interfaceInstanceMembers = interfacePrototype.instanceMembers;
if (interfaceInstanceMembers) {
for (let _values = Map_values(interfaceInstanceMembers), i = 0, k = _values.length; i < k; ++i) {
let interfaceMember = unchecked(_values[i]);
const implMember = this.searchImplementation(thisPrototype, interfaceMember.name);
if (!implMember) continue;
this.doProcessImplementation(thisPrototype, implMember, interfacePrototype, interfaceMember);
}
}
const interfaceBasePrototype = interfacePrototype.basePrototype;
if (interfaceBasePrototype) {
assert(interfaceBasePrototype.kind == ElementKind.InterfacePrototype);
this.processImplements(thisPrototype, <InterfacePrototype>interfaceBasePrototype);
}
}

private searchImplementation(thisPrototype: ClassPrototype, name: string): DeclaredElement | null {
let currentPrototype: ClassPrototype | null = thisPrototype;
while (currentPrototype) {
let currentInstanceMembers = currentPrototype.instanceMembers;
if (currentInstanceMembers && currentInstanceMembers.has(name)) {
return assert(currentInstanceMembers.get(name));
}
currentPrototype = currentPrototype.basePrototype;
}
return null;
}

private doProcessImplementation(
thisClass: ClassPrototype,
implMember: DeclaredElement,
interfacePrototype: InterfacePrototype,
interfaceMember: DeclaredElement
): void {
if (implMember.kind == ElementKind.FunctionPrototype && interfaceMember.kind == ElementKind.FunctionPrototype) {
const implMethod = <FunctionPrototype>implMember;
const interfaceMethod = <FunctionPrototype>interfaceMember;
if (!implMethod.visibilityEquals(interfaceMethod)) {
this.errorRelated(
DiagnosticCode.Overload_signatures_must_all_be_public_private_or_protected,
implMethod.declaration.name.range,
interfaceMethod.declaration.name.range
);
}
interfaceMethod.addUnboundImplementations(thisClass, implMethod);
interfaceMethod.setOverrideFlag();
} else if (
implMember.kind == ElementKind.PropertyPrototype &&
interfaceMember.kind == ElementKind.PropertyPrototype
) {
const implProperty = <PropertyPrototype>implMember;
const interfaceProperty = <PropertyPrototype>interfaceMember;
if (!implProperty.visibilityEquals(interfaceProperty)) {
this.errorRelated(
DiagnosticCode.Overload_signatures_must_all_be_public_private_or_protected,
implProperty.declaration.name.range,
interfaceProperty.declaration.name.range
);
}
if (interfaceProperty.parent.kind != ElementKind.InterfacePrototype) {
// Interface fields/properties can be impled by either, but other
// members must match to retain compatiblity with TS/JS.
const implIsField = implProperty.isField;
if (implIsField != interfaceProperty.isField) {
if (implIsField) {
// base is property
this.errorRelated(
DiagnosticCode._0_is_defined_as_an_accessor_in_class_1_but_is_overridden_here_in_2_as_an_instance_property,
implProperty.declaration.name.range,
interfaceProperty.declaration.name.range,
implProperty.name,
interfacePrototype.internalName,
thisClass.internalName
);
} else {
// this is property, base is field
this.errorRelated(
DiagnosticCode._0_is_defined_as_a_property_in_class_1_but_is_overridden_here_in_2_as_an_accessor,
implProperty.declaration.name.range,
interfaceProperty.declaration.name.range,
implProperty.name,
interfacePrototype.internalName,
thisClass.internalName
);
}
return;
} else if (implIsField) {
// base is also field
// Fields don't override other fields and can only be redeclared
return;
}
}
interfaceProperty.set(CommonFlags.Overridden);
const interfaceGetter = interfaceProperty.getterPrototype;
if (interfaceGetter) {
const implGetter = implProperty.getterPrototype;
if (implGetter) interfaceGetter.addUnboundImplementations(thisClass, implGetter);
interfaceGetter.setOverrideFlag();
}
const interfaceSetter = interfaceProperty.setterPrototype;
if (interfaceSetter && implProperty.setterPrototype) {
const implSetter = implProperty.setterPrototype;
if (implSetter) interfaceSetter.addUnboundImplementations(thisClass, implSetter);
interfaceSetter.setOverrideFlag();
}
} else {
this.errorRelated(
DiagnosticCode.Property_0_in_type_1_is_not_assignable_to_the_same_property_in_base_type_2,
implMember.declaration.name.range,
interfaceMember.declaration.name.range,
implMember.name,
thisClass.internalName,
interfacePrototype.internalName
);
}
}

/** Processes overridden members by this class in a base class. */
private processOverrides(
thisPrototype: ClassPrototype,
Expand Down Expand Up @@ -1526,7 +1644,7 @@ export class Program extends DiagnosticEmitter {
for (let i = 0, k = baseInterfacePrototypes.length; i < k; ++i) {
let baseInterfacePrototype = baseInterfacePrototypes[i];
if (baseInterfacePrototype != basePrototype) {
this.processOverrides(thisPrototype, baseInterfacePrototype);
this.processImplements(thisPrototype, baseInterfacePrototype);
}
}
}
Expand Down Expand Up @@ -3667,6 +3785,8 @@ export class FunctionPrototype extends DeclaredElement {
instances: Map<string,Function> | null = null;
/** Methods overriding this one, if any. These are unbound. */
unboundOverrides: Set<FunctionPrototype> | null = null;
/** Methods implement this one, if any. These are unbound. */
unboundImplementations: Map<ClassPrototype, FunctionPrototype> | null = null;

/** Clones of this prototype that are bound to specific classes. */
private boundPrototypes: Map<Class,FunctionPrototype> | null = null;
Expand Down Expand Up @@ -3731,6 +3851,7 @@ export class FunctionPrototype extends DeclaredElement {
bound.flags = this.flags;
bound.operatorKind = this.operatorKind;
bound.unboundOverrides = this.unboundOverrides;
bound.unboundImplementations = this.unboundImplementations;
// NOTE: this.instances holds instances per bound class / unbound
boundPrototypes.set(classInstance, bound);
return bound;
Expand All @@ -3750,6 +3871,24 @@ export class FunctionPrototype extends DeclaredElement {
else assert(!instances.has(instanceKey));
instances.set(instanceKey, instance);
}


setOverrideFlag(): void {
this.set(CommonFlags.Overridden);
let instances = this.instances;
if (instances) {
for (let _values = Map_values(instances), a = 0, b = _values.length; a < b; ++a) {
let instance = _values[a];
instance.set(CommonFlags.Overridden);
}
}
}

addUnboundImplementations(thisClass: ClassPrototype, implementMember: DeclaredElement): void {
let unboundImplementations = this.unboundImplementations;
if (!unboundImplementations) this.unboundImplementations = unboundImplementations = new Map();
unboundImplementations.set(thisClass, <FunctionPrototype>implementMember);
}
}

/** A resolved function. */
Expand Down
98 changes: 72 additions & 26 deletions src/resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3014,12 +3014,21 @@ export class Resolver extends DiagnosticEmitter {
);
}

/** Resolves reachable overrides or implementations of the given instance method. */
resolveOverridesOrImplementations(instance: Function): Function[] | null {
if (instance.parent.kind == ElementKind.Interface) {
return this.resolveImplementations(instance);
} else {
assert(instance.parent.kind == ElementKind.Class);
return this.resolveOverrides(instance);
}
}


/** Resolves reachable overrides of the given instance method. */
resolveOverrides(instance: Function): Function[] | null {
private resolveOverrides(instance: Function): Function[] | null {
let overridePrototypes = instance.prototype.unboundOverrides;
if (!overridePrototypes) return null;

let parentClassInstance = assert(instance.getBoundClassOrInterface());
let overrides = new Set<Function>();

// A method's `overrides` property contains its unbound override prototypes
Expand All @@ -3034,35 +3043,72 @@ export class Resolver extends DiagnosticEmitter {
classInstances = (<ClassPrototype>unboundOverrideParent).instances;
if (!classInstances) continue;
for (let _values = Map_values(classInstances), j = 0, l = _values.length; j < l; ++j) {
let classInstance = _values[j];
// Check if the parent class is a subtype of instance's class
if (!classInstance.isAssignableTo(parentClassInstance)) continue;
let overrideInstance: Function | null = null;
if (instance.isAny(CommonFlags.Get | CommonFlags.Set)) {
let propertyName = instance.declaration.name.text;
let boundPropertyPrototype = assert(classInstance.getMember(propertyName));
assert(boundPropertyPrototype.kind == ElementKind.PropertyPrototype);
let boundPropertyInstance = this.resolveProperty(<PropertyPrototype>boundPropertyPrototype);
if (!boundPropertyInstance) continue;
if (instance.is(CommonFlags.Get)) {
overrideInstance = boundPropertyInstance.getterInstance;
} else {
assert(instance.is(CommonFlags.Set));
overrideInstance = boundPropertyInstance.setterInstance;
}
} else {
let boundPrototype = classInstance.getMember(unboundOverridePrototype.name);
if (boundPrototype) { // might have errored earlier and wasn't added
assert(boundPrototype.kind == ElementKind.FunctionPrototype);
overrideInstance = this.resolveFunction(<FunctionPrototype>boundPrototype, instance.typeArguments);
}
}
const overrideInstance = this.doResolveOverrideOrImplementation(instance, _values[j], unboundOverridePrototype);
if (overrideInstance) overrides.add(overrideInstance);
}
}
return Set_values(overrides);
}

private resolveImplementations(instance: Function): Function[] | null {
let unboundImplementations = instance.prototype.unboundImplementations;
if (!unboundImplementations) return null;
let implementations = new Set<Function>();
for (
let _keys = Map_keys(unboundImplementations),
_values = Map_values(unboundImplementations),
i = 0,
k = _keys.length;
i < k;
++i
) {
/** in {@link classPrototype}, we implement {@link instance} by {@link unboundImplPrototype} */
const classPrototype = _keys[i];
const unboundImplPrototype = _values[i];
assert(!unboundImplPrototype.isBound);

let classInstances = classPrototype.instances;
if (!classInstances) continue;
for (let _values = Map_values(classInstances), j = 0, l = _values.length; j < l; ++j) {
const implInstance = this.doResolveOverrideOrImplementation(instance, _values[j], unboundImplPrototype);
if (implInstance) implementations.add(implInstance);
}
}
return Set_values(implementations);
}

private doResolveOverrideOrImplementation(
instance: Function,
classInstance: Class,
unboundPrototype: FunctionPrototype
): Function | null {
const parentClassInstance = assert(instance.getBoundClassOrInterface());
// Check if the parent class is a subtype of instance's class
if (!classInstance.isAssignableTo(parentClassInstance)) return null;
let ret: Function | null = null;
if (instance.isAny(CommonFlags.Get | CommonFlags.Set)) {
let propertyName = instance.identifierNode.text;
let boundPropertyPrototype = assert(classInstance.getMember(propertyName));
assert(boundPropertyPrototype.kind == ElementKind.PropertyPrototype);
let boundPropertyInstance = this.resolveProperty(<PropertyPrototype>boundPropertyPrototype);
if (!boundPropertyInstance) return null;
if (instance.is(CommonFlags.Get)) {
ret = boundPropertyInstance.getterInstance;
} else {
assert(instance.is(CommonFlags.Set));
ret = boundPropertyInstance.setterInstance;
}
} else {
let boundPrototype = classInstance.getMember(unboundPrototype.name);
if (boundPrototype) {
// might have errored earlier and wasn't added
assert(boundPrototype.kind == ElementKind.FunctionPrototype);
ret = this.resolveFunction(<FunctionPrototype>boundPrototype, instance.typeArguments);
}
}
return ret;
}

/** Currently resolving classes. */
private resolveClassPending: Set<Class> = new Set();

Expand Down
60 changes: 24 additions & 36 deletions tests/compiler/class-implements.debug.wat
Original file line number Diff line number Diff line change
Expand Up @@ -2331,9 +2331,6 @@
local.get $this
i32.load offset=4
)
(func $class-implements/K#foo (param $this i32) (result i32)
unreachable
)
(func $class-implements/G#foo (param $this i32) (result i32)
i32.const 1
return
Expand Down Expand Up @@ -2415,40 +2412,31 @@
(func $class-implements/J#foo@override (param $0 i32) (result i32)
(local $1 i32)
block $default
block $case3
block $case2
block $case1
block $case0
local.get $0
i32.const 8
i32.sub
i32.load
local.set $1
local.get $1
i32.const 13
i32.eq
br_if $case0
local.get $1
i32.const 11
i32.eq
br_if $case1
local.get $1
i32.const 8
i32.eq
br_if $case2
local.get $1
i32.const 10
i32.eq
br_if $case2
local.get $1
i32.const 12
i32.eq
br_if $case3
br $default
end
block $case2
block $case1
block $case0
local.get $0
call $class-implements/K#foo
return
i32.const 8
i32.sub
i32.load
local.set $1
local.get $1
i32.const 11
i32.eq
br_if $case0
local.get $1
i32.const 8
i32.eq
br_if $case1
local.get $1
i32.const 10
i32.eq
br_if $case1
local.get $1
i32.const 12
i32.eq
br_if $case2
br $default
end
local.get $0
call $class-implements/F#foo
Expand Down
Loading