Skip to content

Refactor static properties / use correct this arguments #1259

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
May 13, 2020
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
1 change: 0 additions & 1 deletion cli/asc.json
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,6 @@
"category": "Binaryen",
"description": "Skips validating the module using Binaryen.",
"type": "b",
"alias": "c",
"default": false
},

Expand Down
358 changes: 174 additions & 184 deletions src/compiler.ts

Large diffs are not rendered by default.

15 changes: 4 additions & 11 deletions src/definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,10 @@ export abstract class ExportsWalker {
break;
}
case ElementKind.PROPERTY_PROTOTYPE: {
this.visitPropertyInstances(name, <PropertyPrototype>element);
let propertyInstance = (<PropertyPrototype>element).instance;
if (!propertyInstance) break;
element = propertyInstance;
// fall-through
break;
}
case ElementKind.PROPERTY: {
Expand Down Expand Up @@ -164,16 +167,6 @@ export abstract class ExportsWalker {
}
}

private visitPropertyInstances(name: string, element: PropertyPrototype): void {
// var instances = element.instances;
// if (instances) {
// for (let instance of instances.values()) {
// if (instance.is(CommonFlags.COMPILED)) this.visitProperty(instance);
// }
// }
assert(false);
}

abstract visitGlobal(name: string, element: Global): void;
abstract visitEnum(name: string, element: Enum): void;
abstract visitFunction(name: string, element: Function): void;
Expand Down
7 changes: 4 additions & 3 deletions src/flow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -412,9 +412,10 @@ export class Flow {

/** Adds a new scoped alias for the specified local. For example `super` aliased to the `this` local. */
addScopedAlias(name: string, type: Type, index: i32, reportNode: Node | null = null): Local {
if (!this.scopedLocals) this.scopedLocals = new Map();
var scopedLocals = this.scopedLocals;
if (!scopedLocals) this.scopedLocals = scopedLocals = new Map();
else {
let existingLocal = this.scopedLocals.get(name);
let existingLocal = scopedLocals.get(name);
if (existingLocal) {
if (reportNode) {
if (!existingLocal.declaration.range.source.isNative) {
Expand All @@ -437,7 +438,7 @@ export class Flow {
assert(index < this.parentFunction.localsByIndex.length);
var scopedAlias = new Local(name, index, type, this.parentFunction);
// not flagged as SCOPED as it must not be free'd when the flow is finalized
this.scopedLocals.set(name, scopedAlias);
scopedLocals.set(name, scopedAlias);
return scopedAlias;
}

Expand Down
72 changes: 60 additions & 12 deletions src/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3210,7 +3210,10 @@ export class FunctionPrototype extends DeclaredElement {
get isBound(): bool {
var parent = this.parent;
return parent.kind == ElementKind.CLASS
|| parent.kind == ElementKind.PROPERTY_PROTOTYPE && parent.parent.kind == ElementKind.CLASS;
|| parent.kind == ElementKind.PROPERTY_PROTOTYPE && (
parent.parent.kind == ElementKind.CLASS ||
parent.parent.kind == ElementKind.INTERFACE
);
}

/** Creates a clone of this prototype that is bound to a concrete class instead. */
Expand Down Expand Up @@ -3415,8 +3418,9 @@ export class Function extends TypedElement {
/** Finalizes the function once compiled, releasing no longer needed resources. */
finalize(module: Module, ref: FunctionRef): void {
this.ref = ref;
assert(!this.breakStack || !this.breakStack.length); // internal error
this.breakStack = null;
var breakStack = this.breakStack;
assert(!breakStack || !breakStack.length); // internal error
this.breakStack = breakStack = null;
this.breakLabel = null;
this.tempI32s = this.tempI64s = this.tempF32s = this.tempF64s = null;
if (this.program.options.sourceMap) {
Expand Down Expand Up @@ -3571,13 +3575,18 @@ export class PropertyPrototype extends DeclaredElement {
getterPrototype: FunctionPrototype | null = null;
/** Setter prototype. */
setterPrototype: FunctionPrototype | null = null;
/** Property instance, if resolved. */
instance: Property | null = null;

/** Clones of this prototype that are bound to specific classes. */
private boundPrototypes: Map<Class,PropertyPrototype> | null = null;

/** Constructs a new property prototype. */
constructor(
/** Simple name. */
name: string,
/** Parent class. */
parent: ClassPrototype,
/** Parent element. Either a class prototype or instance. */
parent: Element,
/** Declaration of the getter or setter introducing the property. */
firstDeclaration: FunctionDeclaration
) {
Expand All @@ -3596,6 +3605,42 @@ export class PropertyPrototype extends DeclaredElement {
lookup(name: string): Element | null {
return this.parent.lookup(name);
}

/** Tests if this prototype is bound to a class. */
get isBound(): bool {
switch (this.parent.kind) {
case ElementKind.CLASS:
case ElementKind.INTERFACE: return true;
}
return false;
}

/** Creates a clone of this prototype that is bound to a concrete class instead. */
toBound(classInstance: Class): PropertyPrototype {
assert(this.is(CommonFlags.INSTANCE));
assert(!this.isBound);
var boundPrototypes = this.boundPrototypes;
if (!boundPrototypes) this.boundPrototypes = boundPrototypes = new Map();
else if (boundPrototypes.has(classInstance)) return assert(boundPrototypes.get(classInstance));
var firstDeclaration = this.declaration;
assert(firstDeclaration.kind == NodeKind.METHODDECLARATION);
var bound = new PropertyPrototype(
this.name,
classInstance, // !
<MethodDeclaration>firstDeclaration
);
bound.flags = this.flags;
var getterPrototype = this.getterPrototype;
if (getterPrototype) {
bound.getterPrototype = getterPrototype.toBound(classInstance);
}
var setterPrototype = this.setterPrototype;
if (setterPrototype) {
bound.setterPrototype = setterPrototype.toBound(classInstance);
}
boundPrototypes.set(classInstance, bound);
return bound;
}
}

/** A resolved property. */
Expand Down Expand Up @@ -3631,7 +3676,9 @@ export class Property extends VariableLikeElement {
this.prototype = prototype;
this.flags = prototype.flags;
this.decoratorFlags = prototype.decoratorFlags;
registerConcreteElement(this.program, this);
if (this.is(CommonFlags.INSTANCE)) {
registerConcreteElement(this.program, this);
}
}

/* @override */
Expand Down Expand Up @@ -3860,8 +3907,8 @@ export class Class extends TypedElement {
return lengthField !== null && (
lengthField.kind == ElementKind.FIELD ||
(
lengthField.kind == ElementKind.PROPERTY &&
(<Property>lengthField).getterInstance !== null // TODO: resolve & check type?
lengthField.kind == ElementKind.PROPERTY_PROTOTYPE &&
(<PropertyPrototype>lengthField).getterPrototype !== null // TODO: resolve & check type?
)
) && (
this.lookupOverload(OperatorKind.INDEXED_GET) !== null ||
Expand Down Expand Up @@ -3908,9 +3955,10 @@ export class Class extends TypedElement {
throw new Error("type argument count mismatch");
}
if (numTypeArguments) {
if (!this.contextualTypeArguments) this.contextualTypeArguments = new Map();
let contextualTypeArguments = this.contextualTypeArguments;
if (!contextualTypeArguments) this.contextualTypeArguments = contextualTypeArguments = new Map();
for (let i = 0; i < numTypeArguments; ++i) {
this.contextualTypeArguments.set(typeParameters[i].name.text, typeArguments[i]);
contextualTypeArguments.set(typeParameters[i].name.text, typeArguments[i]);
}
}
} else if (typeParameters !== null && typeParameters.length > 0) {
Expand Down Expand Up @@ -4402,8 +4450,8 @@ export function mangleInternalName(name: string, parent: Element, isInstance: bo
assert(!isInstance);
return parent.internalName + INNER_DELIMITER + name;
}
case ElementKind.PROPERTY_PROTOTYPE:
case ElementKind.PROPERTY: {
case ElementKind.PROPERTY_PROTOTYPE: // properties are just containers
case ElementKind.PROPERTY: { //
parent = parent.parent;
// fall-through
}
Expand Down
123 changes: 62 additions & 61 deletions src/resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1313,32 +1313,10 @@ export class Resolver extends DiagnosticEmitter {
break;
}
case ElementKind.PROPERTY_PROTOTYPE: { // SomeClass.prop
let propertyPrototype = <PropertyPrototype>target;
let getterInstance = this.resolveFunction( // reports
assert(propertyPrototype.getterPrototype), // must have a getter
null,
makeMap<string,Type>(),
reportMode
);
if (!getterInstance) return null;
let type = getterInstance.signature.returnType;
let classReference = type.classReference;
if (!classReference) {
let wrapperClasses = this.program.wrapperClasses;
if (wrapperClasses.has(type)) {
classReference = assert(wrapperClasses.get(type));
} else {
if (reportMode == ReportMode.REPORT) {
this.error(
DiagnosticCode.Property_0_does_not_exist_on_type_1,
node.property.range, propertyName, type.toString()
);
}
return null;
}
}
target = classReference;
break;
let propertyInstance = this.resolveProperty(<PropertyPrototype>target, reportMode);
if (!propertyInstance) return null;
target = propertyInstance;
// fall-through
}
case ElementKind.PROPERTY: { // someInstance.prop
let propertyInstance = <Property>target;
Expand Down Expand Up @@ -1420,9 +1398,21 @@ export class Resolver extends DiagnosticEmitter {
do {
let members = target.members;
if (members !== null && members.has(propertyName)) {
this.currentThisExpression = targetNode;
let member = assert(members.get(propertyName));
if (member.kind == ElementKind.PROPERTY_PROTOTYPE) {
let propertyInstance = this.resolveProperty(<PropertyPrototype>member, reportMode);
if (!propertyInstance) return null;
member = propertyInstance;
if (propertyInstance.is(CommonFlags.STATIC)) {
this.currentThisExpression = null;
} else {
this.currentThisExpression = targetNode;
}
} else {
this.currentThisExpression = targetNode;
}
this.currentElementExpression = null;
return assert(members.get(propertyName)); // instance FIELD, static GLOBAL, FUNCTION_PROTOTYPE...
return member; // instance FIELD, static GLOBAL, FUNCTION_PROTOTYPE, PROPERTY...
}
// traverse inherited static members on the base prototype if target is a class prototype
if (
Expand Down Expand Up @@ -3129,39 +3119,8 @@ export class Resolver extends DiagnosticEmitter {
break;
}
case ElementKind.PROPERTY_PROTOTYPE: {
let propertyPrototype = <PropertyPrototype>member;
let propertyInstance = new Property(propertyPrototype, instance);
properties.push(propertyInstance);
let getterPrototype = propertyPrototype.getterPrototype;
if (getterPrototype) {
let getterInstance = this.resolveFunction(
getterPrototype.toBound(instance),
null,
makeMap(instance.contextualTypeArguments),
reportMode
);
if (getterInstance) {
propertyInstance.getterInstance = getterInstance;
propertyInstance.setType(getterInstance.signature.returnType);
}
}
let setterPrototype = propertyPrototype.setterPrototype;
if (setterPrototype) {
let setterInstance = this.resolveFunction(
setterPrototype.toBound(instance),
null,
makeMap(instance.contextualTypeArguments),
reportMode
);
if (setterInstance) {
propertyInstance.setterInstance = setterInstance;
if (!propertyInstance.is(CommonFlags.RESOLVED)) {
assert(setterInstance.signature.parameterTypes.length == 1);
propertyInstance.setType(setterInstance.signature.parameterTypes[0]);
}
}
}
instance.add(propertyInstance.name, propertyInstance); // reports
let boundPrototype = (<PropertyPrototype>member).toBound(instance);
instance.add(boundPrototype.name, boundPrototype); // reports
break;
}
default: assert(false);
Expand Down Expand Up @@ -3368,4 +3327,46 @@ export class Resolver extends DiagnosticEmitter {
reportMode
);
}

/** Resolves a property prototype. */
resolveProperty(
/** The prototype of the property. */
prototype: PropertyPrototype,
/** How to proceed with eventual diagnostics. */
reportMode: ReportMode = ReportMode.REPORT
): Property | null {
var instance = prototype.instance;
if (instance) return instance;
prototype.instance = instance = new Property(prototype, prototype);
var getterPrototype = prototype.getterPrototype;
if (getterPrototype) {
let getterInstance = this.resolveFunction(
getterPrototype,
null,
makeMap<string,Type>(),
reportMode
);
if (getterInstance) {
instance.getterInstance = getterInstance;
instance.setType(getterInstance.signature.returnType);
}
}
var setterPrototype = prototype.setterPrototype;
if (setterPrototype) {
let setterInstance = this.resolveFunction(
setterPrototype,
null,
makeMap<string,Type>(),
reportMode
);
if (setterInstance) {
instance.setterInstance = setterInstance;
if (!instance.is(CommonFlags.RESOLVED)) {
assert(setterInstance.signature.parameterTypes.length == 1);
instance.setType(setterInstance.signature.parameterTypes[0]);
}
}
}
return instance;
}
}
6 changes: 3 additions & 3 deletions tests/compiler/builtins.optimized.wat
Original file line number Diff line number Diff line change
Expand Up @@ -592,9 +592,9 @@
i32.const 5
f64.const 0
f64.const 0
f64.const 29
f64.const 30
f64.const 30
f64.const 23
f64.const 24
f64.const 24
call $~lib/builtins/trace
i32.const 1216
i32.const 1216
Expand Down
8 changes: 4 additions & 4 deletions tests/compiler/builtins.untouched.wat
Original file line number Diff line number Diff line change
Expand Up @@ -1727,11 +1727,11 @@
local.set $0
i32.const 0
local.set $1
i32.const 29
i32.const 23
local.set $6
i32.const 30
i32.const 24
local.set $7
i32.const 30
i32.const 24
local.set $8
i32.const 128
i32.const 5
Expand Down Expand Up @@ -1771,7 +1771,7 @@
unreachable
end
local.get $6
i32.const 29
i32.const 23
i32.eq
i32.eqz
if
Expand Down
Loading