diff --git a/cli/asc.json b/cli/asc.json index 6ebaeee4f5..a0a35a915e 100644 --- a/cli/asc.json +++ b/cli/asc.json @@ -243,7 +243,6 @@ "category": "Binaryen", "description": "Skips validating the module using Binaryen.", "type": "b", - "alias": "c", "default": false }, diff --git a/src/compiler.ts b/src/compiler.ts index 60cc66b10e..694ba98a6d 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -664,11 +664,8 @@ export class Compiler extends DiagnosticEmitter { break; } case ElementKind.PROPERTY_PROTOTYPE: { - let propertyPrototype = element; - let getterPrototype = propertyPrototype.getterPrototype; - if (getterPrototype) this.ensureModuleExport(GETTER_PREFIX + name, getterPrototype, prefix); - let setterPrototype = propertyPrototype.setterPrototype; - if (setterPrototype) this.ensureModuleExport(SETTER_PREFIX + name, setterPrototype, prefix); + let propertyInstance = (element).instance; + if (propertyInstance) this.ensureModuleExport(name, propertyInstance, prefix); break; } @@ -713,10 +710,10 @@ export class Compiler extends DiagnosticEmitter { } case ElementKind.PROPERTY: { let propertyInstance = element; - let getter = propertyInstance.getterInstance; - if (getter) this.ensureModuleExport(GETTER_PREFIX + name, getter, prefix); - let setter = propertyInstance.setterInstance; - if (setter) this.ensureModuleExport(SETTER_PREFIX + name, setter, prefix); + let getterInstance = propertyInstance.getterInstance; + if (getterInstance) this.ensureModuleExport(GETTER_PREFIX + name, getterInstance, prefix); + let setterInstance = propertyInstance.setterInstance; + if (setterInstance) this.ensureModuleExport(SETTER_PREFIX + name, setterInstance, prefix); break; } case ElementKind.FIELD: { @@ -796,32 +793,21 @@ export class Compiler extends DiagnosticEmitter { } case ElementKind.FUNCTION_PROTOTYPE: { if (!element.is(CommonFlags.GENERIC)) { - let instance = this.resolver.resolveFunction(element, null); - if (instance) this.compileFunction(instance); + let functionInstance = this.resolver.resolveFunction(element, null); + if (functionInstance) this.compileFunction(functionInstance); } break; } case ElementKind.CLASS_PROTOTYPE: { if (!element.is(CommonFlags.GENERIC)) { - let instance = this.resolver.resolveClass(element, null); - if (instance) this.compileClass(instance); + let classInstance = this.resolver.resolveClass(element, null); + if (classInstance) this.compileClass(classInstance); } break; } case ElementKind.PROPERTY_PROTOTYPE: { - let propertyPrototype = element; - let getterPrototype = propertyPrototype.getterPrototype; - if (getterPrototype) { - assert(!getterPrototype.is(CommonFlags.GENERIC)); - let instance = this.resolver.resolveFunction(getterPrototype, null); - if (instance) this.compileFunction(instance); - } - let setterPrototype = propertyPrototype.setterPrototype; - if (setterPrototype) { - assert(!setterPrototype.is(CommonFlags.GENERIC)); - let instance = this.resolver.resolveFunction(setterPrototype, null); - if (instance) this.compileFunction(instance); - } + let propertyInstance = this.resolver.resolveProperty(element); + if (propertyInstance) this.compileProperty(propertyInstance); break; } case ElementKind.NAMESPACE: @@ -1544,26 +1530,24 @@ export class Compiler extends DiagnosticEmitter { break; } case ElementKind.FUNCTION_PROTOTYPE: { - if (!element.is(CommonFlags.GENERIC)) { - let functionInstance = this.resolver.resolveFunction(element, null); - if (functionInstance) this.compileFunction(functionInstance); - } + if (element.is(CommonFlags.GENERIC)) break; + let functionInstance = this.resolver.resolveFunction(element, null); + if (!functionInstance) break; + element = functionInstance; + // fall-through + } + case ElementKind.FUNCTION: { + this.compileFunction(element); break; } case ElementKind.PROPERTY_PROTOTYPE: { - let propertyPrototype = element; - let getterPrototype = propertyPrototype.getterPrototype; - if (getterPrototype) { - assert(!getterPrototype.is(CommonFlags.GENERIC)); - let instance = this.resolver.resolveFunction(getterPrototype, null); - if (instance) this.compileFunction(instance); - } - let setterPrototype = propertyPrototype.setterPrototype; - if (setterPrototype) { - assert(!setterPrototype.is(CommonFlags.GENERIC)); - let instance = this.resolver.resolveFunction(setterPrototype, null); - if (instance) this.compileFunction(instance); - } + let propertyInstance = this.resolver.resolveProperty(element); + if (!propertyInstance) break; + element = propertyInstance; + // fall-through + } + case ElementKind.PROPERTY: { + this.compileProperty(element); break; } } @@ -1577,16 +1561,26 @@ export class Compiler extends DiagnosticEmitter { let element = unchecked(_values[i]); switch (element.kind) { case ElementKind.FUNCTION_PROTOTYPE: { - if (!element.is(CommonFlags.GENERIC)) { - let functionInstance = this.resolver.resolveFunction(element, null); - if (functionInstance) this.compileFunction(functionInstance); - } + if (element.is(CommonFlags.GENERIC)) break; + let functionInstance = this.resolver.resolveFunction(element, null); + if (!functionInstance) break; + element = functionInstance; + // fall-through + } + case ElementKind.FUNCTION: { + this.compileFunction(element); break; } case ElementKind.FIELD: { this.compileField(element); break; } + case ElementKind.PROPERTY_PROTOTYPE: { + let propertyInstance = this.resolver.resolveProperty(element); + if (!propertyInstance) break; + element = propertyInstance; + // fall-through + } case ElementKind.PROPERTY: { this.compileProperty(element); break; @@ -4683,8 +4677,8 @@ export class Compiler extends DiagnosticEmitter { expr = module.unreachable(); break; } - let prototype = namespace.members ? namespace.members.get(CommonNames.pow) : null; - if (!prototype) { + let namespaceMembers = namespace.members; + if (!namespaceMembers || !namespaceMembers.has(CommonNames.pow)) { this.error( DiagnosticCode.Cannot_find_name_0, expression.range, "Mathf.pow" @@ -4692,6 +4686,7 @@ export class Compiler extends DiagnosticEmitter { expr = module.unreachable(); break; } + let prototype = assert(namespaceMembers.get(CommonNames.pow)); assert(prototype.kind == ElementKind.FUNCTION_PROTOTYPE); this.f32PowInstance = instance = this.resolver.resolveFunction(prototype, null); } @@ -4718,8 +4713,8 @@ export class Compiler extends DiagnosticEmitter { expr = module.unreachable(); break; } - let prototype = namespace.members ? namespace.members.get(CommonNames.pow) : null; - if (!prototype) { + let namespaceMembers = namespace.members; + if (!namespaceMembers || !namespaceMembers.has(CommonNames.pow)) { this.error( DiagnosticCode.Cannot_find_name_0, expression.range, "Math.pow" @@ -4727,6 +4722,7 @@ export class Compiler extends DiagnosticEmitter { expr = module.unreachable(); break; } + let prototype = assert(namespaceMembers.get(CommonNames.pow)); assert(prototype.kind == ElementKind.FUNCTION_PROTOTYPE); this.f64PowInstance = instance = this.resolver.resolveFunction(prototype, null); } @@ -4966,8 +4962,8 @@ export class Compiler extends DiagnosticEmitter { expr = module.unreachable(); break; } - let prototype = namespace.members ? namespace.members.get(CommonNames.mod) : null; - if (!prototype) { + let namespaceMembers = namespace.members; + if (!namespaceMembers || !namespaceMembers.has(CommonNames.mod)) { this.error( DiagnosticCode.Cannot_find_name_0, expression.range, "Mathf.mod" @@ -4975,6 +4971,7 @@ export class Compiler extends DiagnosticEmitter { expr = module.unreachable(); break; } + let prototype = assert(namespaceMembers.get(CommonNames.mod)); assert(prototype.kind == ElementKind.FUNCTION_PROTOTYPE); this.f32ModInstance = instance = this.resolver.resolveFunction(prototype, null); } @@ -4997,8 +4994,8 @@ export class Compiler extends DiagnosticEmitter { expr = module.unreachable(); break; } - let prototype = namespace.members ? namespace.members.get(CommonNames.mod) : null; - if (!prototype) { + let namespaceMembers = namespace.members; + if (!namespaceMembers || !namespaceMembers.has(CommonNames.mod)) { this.error( DiagnosticCode.Cannot_find_name_0, expression.range, "Math.mod" @@ -5006,6 +5003,7 @@ export class Compiler extends DiagnosticEmitter { expr = module.unreachable(); break; } + let prototype = assert(namespaceMembers.get(CommonNames.mod)); assert(prototype.kind == ElementKind.FUNCTION_PROTOTYPE); this.f64ModInstance = instance = this.resolver.resolveFunction(prototype, null); } @@ -5826,24 +5824,14 @@ export class Compiler extends DiagnosticEmitter { if (target.hasDecorator(DecoratorFlags.UNSAFE)) this.checkUnsafe(expression); break; } - case ElementKind.PROPERTY_PROTOTYPE: { // static property + case ElementKind.PROPERTY_PROTOTYPE: { let propertyPrototype = target; - let setterPrototype = propertyPrototype.setterPrototype; - if (!setterPrototype) { - this.error( - DiagnosticCode.Cannot_assign_to_0_because_it_is_a_constant_or_a_read_only_property, - expression.range, propertyPrototype.internalName - ); - return this.module.unreachable(); - } - let setterInstance = this.resolver.resolveFunction(setterPrototype, null, makeMap(), ReportMode.REPORT); - if (!setterInstance) return this.module.unreachable(); - assert(setterInstance.signature.parameterTypes.length == 1); // parser must guarantee this - targetType = setterInstance.signature.parameterTypes[0]; - if (setterPrototype.hasDecorator(DecoratorFlags.UNSAFE)) this.checkUnsafe(expression); - break; + let propertyInstance = resolver.resolveProperty(propertyPrototype); + if (!propertyInstance) return this.module.unreachable(); + target = propertyInstance; + // fall-through } - case ElementKind.PROPERTY: { // instance property + case ElementKind.PROPERTY: { let propertyInstance = target; let setterInstance = propertyInstance.setterInstance; if (!setterInstance) { @@ -5980,47 +5968,20 @@ export class Compiler extends DiagnosticEmitter { ); return module.unreachable(); } + let fieldParent = fieldInstance.parent; + assert(fieldParent.kind == ElementKind.CLASS); return this.makeFieldAssignment(fieldInstance, valueExpr, - // FIXME: explicit type (currently fails due to missing null checking) - this.compileExpression(assert(thisExpression), this.options.usizeType), + this.compileExpression( + assert(thisExpression), + (fieldParent).type, + Constraints.CONV_IMPLICIT + ), tee ); } - case ElementKind.PROPERTY_PROTOTYPE: { // static property - let propertyPrototype = target; - let setterPrototype = propertyPrototype.setterPrototype; - if (!setterPrototype) { - this.error( - DiagnosticCode.Cannot_assign_to_0_because_it_is_a_constant_or_a_read_only_property, - valueExpression.range, target.internalName - ); - return module.unreachable(); - } - let setterInstance = this.resolver.resolveFunction(setterPrototype, null, makeMap(), ReportMode.REPORT); - if (!setterInstance) return module.unreachable(); - assert(setterInstance.signature.parameterTypes.length == 1); - let valueType = setterInstance.signature.parameterTypes[0]; - if (this.skippedAutoreleases.has(valueExpr)) valueExpr = this.makeAutorelease(valueExpr, valueType, flow); // (*) - // call just the setter if the return value isn't of interest - if (!tee) return this.makeCallDirect(setterInstance, [ valueExpr ], valueExpression); - // otherwise call the setter first, then the getter - let getterPrototype = assert(propertyPrototype.getterPrototype); // must be present - let getterInstance = this.resolver.resolveFunction(getterPrototype, null, makeMap(), ReportMode.REPORT); - if (!getterInstance) return module.unreachable(); - let returnType = getterInstance.signature.returnType; - assert(valueType == returnType); - let nativeReturnType = returnType.toNativeType(); - return module.block(null, [ - this.makeCallDirect(setterInstance, [ valueExpr ], valueExpression), - this.makeCallDirect(getterInstance, null, valueExpression) // sets currentType - ], nativeReturnType); - } - case ElementKind.PROPERTY: { // instance property + case ElementKind.PROPERTY: { let propertyInstance = target; - let propertyInstanceParent = propertyInstance.parent; - assert(propertyInstanceParent.kind == ElementKind.CLASS || propertyInstanceParent.kind == ElementKind.INTERFACE); - let classInstance = propertyInstanceParent; let setterInstance = propertyInstance.setterInstance; if (!setterInstance) { this.error( @@ -6032,28 +5993,38 @@ export class Compiler extends DiagnosticEmitter { assert(setterInstance.signature.parameterTypes.length == 1); let valueType = setterInstance.signature.parameterTypes[0]; if (this.skippedAutoreleases.has(valueExpr)) valueExpr = this.makeAutorelease(valueExpr, valueType, flow); // (*) - // call just the setter if the return value isn't of interest - if (!tee) { - let thisExpr = this.compileExpression(assert(thisExpression), classInstance.type); - return this.makeCallDirect(setterInstance, [ thisExpr, valueExpr ], valueExpression); - } - // otherwise call the setter first, then the getter - let getterInstance = assert((target).getterInstance); // must be present - let returnType = getterInstance.signature.returnType; - let nativeReturnType = returnType.toNativeType(); - let thisExpr = this.compileExpression(assert(thisExpression), this.options.usizeType); - let temp = flow.getTempLocal(returnType); - let ret = module.block(null, [ - this.makeCallDirect(setterInstance, [ // set and remember the target - module.local_tee(temp.index, thisExpr), - valueExpr - ], valueExpression), - this.makeCallDirect(getterInstance, [ // get from remembered target - module.local_get(temp.index, nativeReturnType) - ], valueExpression) - ], nativeReturnType); - flow.freeTempLocal(temp); - return ret; + if (propertyInstance.is(CommonFlags.INSTANCE)) { + let thisType = assert(setterInstance.signature.thisType); + let thisExpr = this.compileExpression( + assert(thisExpression), + thisType, + Constraints.CONV_IMPLICIT + ); + if (!tee) return this.makeCallDirect(setterInstance, [ thisExpr, valueExpr ], valueExpression); + let getterInstance = assert((target).getterInstance); + assert(getterInstance.signature.thisType == thisType); + let returnType = getterInstance.signature.returnType; + let nativeReturnType = returnType.toNativeType(); + let tempThis = flow.getTempLocal(returnType); + let ret = module.block(null, [ + this.makeCallDirect(setterInstance, [ + module.local_tee(tempThis.index, thisExpr), + valueExpr + ], valueExpression), + this.makeCallDirect(getterInstance, [ + module.local_get(tempThis.index, nativeReturnType) + ], valueExpression) + ], nativeReturnType); + flow.freeTempLocal(tempThis); + return ret; + } else { + if (!tee) return this.makeCallDirect(setterInstance, [ valueExpr ], valueExpression); + let getterInstance = assert((target).getterInstance); + return module.block(null, [ + this.makeCallDirect(setterInstance, [ valueExpr ], valueExpression), + this.makeCallDirect(getterInstance, null, valueExpression) + ], getterInstance.signature.returnType.toNativeType()); + } } case ElementKind.INDEXSIGNATURE: { let indexSignature = target; @@ -6389,6 +6360,7 @@ export class Compiler extends DiagnosticEmitter { // otherwise resolve normally var target = this.resolver.lookupExpression(expression.expression, flow); // reports if (!target) return module.unreachable(); + var thisExpression = this.resolver.currentThisExpression; var signature: Signature | null; var indexArg: ExpressionRef; @@ -6397,22 +6369,30 @@ export class Compiler extends DiagnosticEmitter { // direct call: concrete function case ElementKind.FUNCTION_PROTOTYPE: { let functionPrototype = target; - - // builtins handle present respectively omitted type arguments on their own if (functionPrototype.hasDecorator(DecoratorFlags.BUILTIN)) { + // builtins handle present respectively omitted type arguments on their own return this.compileCallExpressionBuiltin(functionPrototype, expression, contextualType); } - - let thisExpression = this.resolver.currentThisExpression; // compileCallDirect may reset let functionInstance = this.resolver.maybeInferCall(expression, functionPrototype, flow); if (!functionInstance) return this.module.unreachable(); + target = functionInstance; + // fall-through + } + case ElementKind.FUNCTION: { + let functionInstance = target; + let thisArg: ExpressionRef = 0; + if (functionInstance.is(CommonFlags.INSTANCE)) { + thisArg = this.compileExpression( + assert(thisExpression), + assert(functionInstance.signature.thisType), + Constraints.CONV_IMPLICIT + ); + } return this.compileCallDirect( functionInstance, expression.arguments, expression, - functionInstance.is(CommonFlags.INSTANCE) - ? this.compileExpression(assert(thisExpression), this.options.usizeType) - : 0, + thisArg, constraints ); } @@ -6453,23 +6433,24 @@ export class Compiler extends DiagnosticEmitter { let fieldType = fieldInstance.type; signature = fieldType.signatureReference; if (signature) { - let thisExpression = assert(this.resolver.currentThisExpression); - let thisExpr = this.compileExpression(thisExpression, this.options.usizeType); - indexArg = module.load( - 4, - false, - thisExpr, + let fieldParent = fieldInstance.parent; + assert(fieldParent.kind == ElementKind.CLASS); + indexArg = module.load(4, false, + this.compileExpression( + assert(thisExpression), + (fieldParent).type, + Constraints.CONV_IMPLICIT + ), NativeType.I32, fieldInstance.memoryOffset ); break; - } else { - this.error( - DiagnosticCode.Cannot_invoke_an_expression_whose_type_lacks_a_call_signature_Type_0_has_no_compatible_call_signatures, - expression.range, fieldType.toString() - ); - return module.unreachable(); } + this.error( + DiagnosticCode.Cannot_invoke_an_expression_whose_type_lacks_a_call_signature_Type_0_has_no_compatible_call_signatures, + expression.range, fieldType.toString() + ); + return module.unreachable(); } case ElementKind.FUNCTION_TARGET: { let functionTarget = target; @@ -6478,28 +6459,24 @@ export class Compiler extends DiagnosticEmitter { break; } - case ElementKind.PROPERTY_PROTOTYPE: { // static property - let propertyPrototype = target; - let getterPrototype = assert(propertyPrototype.getterPrototype); - let getterInstance = this.resolver.resolveFunction(getterPrototype, null); - if (!getterInstance) return module.unreachable(); - indexArg = this.compileCallDirect(getterInstance, [], expression.expression); - signature = this.currentType.signatureReference; - if (!signature) { - this.error( - DiagnosticCode.Cannot_invoke_an_expression_whose_type_lacks_a_call_signature_Type_0_has_no_compatible_call_signatures, - expression.range, this.currentType.toString() - ); - return module.unreachable(); - } - break; + case ElementKind.PROPERTY_PROTOTYPE: { + let propertyInstance = this.resolver.resolveProperty(target); + if (!propertyInstance) return module.unreachable(); + target = propertyInstance; + // fall-through } - case ElementKind.PROPERTY: { // instance property + case ElementKind.PROPERTY: { let propertyInstance = target; let getterInstance = assert(propertyInstance.getterInstance); - indexArg = this.compileCallDirect(getterInstance, [], expression.expression, - this.compileExpression(assert(this.resolver.currentThisExpression), this.options.usizeType) - ); + let thisArg: ExpressionRef = 0; + if (propertyInstance.is(CommonFlags.INSTANCE)) { + thisArg = this.compileExpression( + assert(thisExpression), + assert(getterInstance.signature.thisType), + Constraints.CONV_IMPLICIT + ); + } + indexArg = this.compileCallDirect(getterInstance, [], expression.expression, thisArg); signature = this.currentType.signatureReference; if (!signature) { this.error( @@ -6529,7 +6506,7 @@ export class Compiler extends DiagnosticEmitter { } } return this.compileCallIndirect( - assert(signature), // FIXME: asc can't see this yet + assert(signature), // FIXME: bootstrap can't see this yet indexArg, expression.arguments, expression, @@ -7084,12 +7061,14 @@ export class Compiler extends DiagnosticEmitter { let overloadInstance: Function | null; if (isProperty) { let boundProperty = assert(classInstance.members!.get(unboundOverloadParent.name)); - assert(boundProperty.kind == ElementKind.PROPERTY); + assert(boundProperty.kind == ElementKind.PROPERTY_PROTOTYPE); + let boundPropertyInstance = this.resolver.resolveProperty(boundProperty); + if (!boundPropertyInstance) continue; if (instance.is(CommonFlags.GET)) { - overloadInstance = (boundProperty).getterInstance; + overloadInstance = boundPropertyInstance.getterInstance; } else { assert(instance.is(CommonFlags.SET)); - overloadInstance = (boundProperty).setterInstance; + overloadInstance = boundPropertyInstance.setterInstance; } } else { let boundPrototype = assert(classInstance.members!.get(unboundOverloadPrototype.name)); @@ -9174,6 +9153,7 @@ export class Compiler extends DiagnosticEmitter { var resolver = this.resolver; var target = resolver.lookupExpression(expression, flow, ctxType); // reports if (!target) return module.unreachable(); + var thisExpression = resolver.currentThisExpression; if (target.hasDecorator(DecoratorFlags.UNSAFE)) this.checkUnsafe(expression); switch (target.kind) { @@ -9205,12 +9185,18 @@ export class Compiler extends DiagnosticEmitter { assert(enumValue.type == Type.i32); return module.global_get(enumValue.internalName, NativeType.I32); } - case ElementKind.FIELD: { // instance field + case ElementKind.FIELD: { let fieldInstance = target; let fieldType = fieldInstance.type; assert(fieldInstance.memoryOffset >= 0); - let thisExpression = assert(this.resolver.currentThisExpression); - let thisExpr = this.compileExpression(thisExpression, this.options.usizeType); + let fieldParent = fieldInstance.parent; + assert(fieldParent.kind == ElementKind.CLASS); + thisExpression = assert(thisExpression); + let thisExpr = this.compileExpression( + thisExpression, + (fieldParent).type, + Constraints.CONV_IMPLICIT + ); let thisType = this.currentType; if (thisType.is(TypeFlags.NULLABLE)) { if (!flow.isNonnull(thisExpr, thisType)) { @@ -9234,21 +9220,25 @@ export class Compiler extends DiagnosticEmitter { fieldInstance.memoryOffset ); } - case ElementKind.PROPERTY_PROTOTYPE: {// static property + case ElementKind.PROPERTY_PROTOTYPE: { let propertyPrototype = target; - let getterPrototype = propertyPrototype.getterPrototype; - if (getterPrototype) { - let getter = this.resolver.resolveFunction(getterPrototype, null); - if (getter) return this.compileCallDirect(getter, [], expression, 0); - } - return module.unreachable(); + let propertyInstance = this.resolver.resolveProperty(propertyPrototype); + if (!propertyInstance) return module.unreachable(); + target = propertyInstance; + // fall-through } - case ElementKind.PROPERTY: { // instance property + case ElementKind.PROPERTY: { let propertyInstance = target; let getterInstance = assert(propertyInstance.getterInstance); - return this.compileCallDirect(getterInstance, [], expression, - this.compileExpression(assert(this.resolver.currentThisExpression), this.options.usizeType) - ); + let thisArg: ExpressionRef = 0; + if (getterInstance.is(CommonFlags.INSTANCE)) { + thisArg = this.compileExpression( + assert(thisExpression), + assert(getterInstance.signature.thisType), + Constraints.CONV_IMPLICIT + ); + } + return this.compileCallDirect(getterInstance, [], expression, thisArg); } case ElementKind.FUNCTION_PROTOTYPE: { let functionPrototype = target; diff --git a/src/definitions.ts b/src/definitions.ts index c9834c174d..0f9b80b5dd 100644 --- a/src/definitions.ts +++ b/src/definitions.ts @@ -122,7 +122,10 @@ export abstract class ExportsWalker { break; } case ElementKind.PROPERTY_PROTOTYPE: { - this.visitPropertyInstances(name, element); + let propertyInstance = (element).instance; + if (!propertyInstance) break; + element = propertyInstance; + // fall-through break; } case ElementKind.PROPERTY: { @@ -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; diff --git a/src/flow.ts b/src/flow.ts index 4324bdde69..8662757955 100644 --- a/src/flow.ts +++ b/src/flow.ts @@ -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) { @@ -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; } diff --git a/src/program.ts b/src/program.ts index 40db9332f5..1f7819972c 100644 --- a/src/program.ts +++ b/src/program.ts @@ -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. */ @@ -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) { @@ -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 | 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 ) { @@ -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, // ! + 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. */ @@ -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 */ @@ -3860,8 +3907,8 @@ export class Class extends TypedElement { return lengthField !== null && ( lengthField.kind == ElementKind.FIELD || ( - lengthField.kind == ElementKind.PROPERTY && - (lengthField).getterInstance !== null // TODO: resolve & check type? + lengthField.kind == ElementKind.PROPERTY_PROTOTYPE && + (lengthField).getterPrototype !== null // TODO: resolve & check type? ) ) && ( this.lookupOverload(OperatorKind.INDEXED_GET) !== null || @@ -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) { @@ -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 } diff --git a/src/resolver.ts b/src/resolver.ts index f6b374280f..fd79e42d40 100644 --- a/src/resolver.ts +++ b/src/resolver.ts @@ -1313,32 +1313,10 @@ export class Resolver extends DiagnosticEmitter { break; } case ElementKind.PROPERTY_PROTOTYPE: { // SomeClass.prop - let propertyPrototype = target; - let getterInstance = this.resolveFunction( // reports - assert(propertyPrototype.getterPrototype), // must have a getter - null, - makeMap(), - 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(target, reportMode); + if (!propertyInstance) return null; + target = propertyInstance; + // fall-through } case ElementKind.PROPERTY: { // someInstance.prop let propertyInstance = target; @@ -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(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 ( @@ -3129,39 +3119,8 @@ export class Resolver extends DiagnosticEmitter { break; } case ElementKind.PROPERTY_PROTOTYPE: { - let 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 = (member).toBound(instance); + instance.add(boundPrototype.name, boundPrototype); // reports break; } default: assert(false); @@ -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(), + reportMode + ); + if (getterInstance) { + instance.getterInstance = getterInstance; + instance.setType(getterInstance.signature.returnType); + } + } + var setterPrototype = prototype.setterPrototype; + if (setterPrototype) { + let setterInstance = this.resolveFunction( + setterPrototype, + null, + makeMap(), + 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; + } } diff --git a/tests/compiler/builtins.optimized.wat b/tests/compiler/builtins.optimized.wat index 5d9fd01018..a586c3d0e6 100644 --- a/tests/compiler/builtins.optimized.wat +++ b/tests/compiler/builtins.optimized.wat @@ -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 diff --git a/tests/compiler/builtins.untouched.wat b/tests/compiler/builtins.untouched.wat index 12af226ef1..dd72474616 100644 --- a/tests/compiler/builtins.untouched.wat +++ b/tests/compiler/builtins.untouched.wat @@ -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 @@ -1771,7 +1771,7 @@ unreachable end local.get $6 - i32.const 29 + i32.const 23 i32.eq i32.eqz if diff --git a/tests/compiler/resolve-binary.untouched.wat b/tests/compiler/resolve-binary.untouched.wat index 9e716591e0..89475a8686 100644 --- a/tests/compiler/resolve-binary.untouched.wat +++ b/tests/compiler/resolve-binary.untouched.wat @@ -4625,7 +4625,7 @@ (local $63 i32) i32.const 1 i32.const 2 - i32.lt_u + i32.lt_s call $~lib/number/Bool#toString local.tee $0 i32.const 32 @@ -4641,7 +4641,7 @@ end i32.const 1 i32.const 2 - i32.gt_u + i32.gt_s call $~lib/number/Bool#toString local.tee $1 i32.const 64 @@ -4657,7 +4657,7 @@ end i32.const 1 i32.const 2 - i32.le_u + i32.le_s call $~lib/number/Bool#toString local.tee $2 i32.const 32 @@ -4673,7 +4673,7 @@ end i32.const 1 i32.const 2 - i32.ge_u + i32.ge_s call $~lib/number/Bool#toString local.tee $3 i32.const 64 @@ -5048,7 +5048,7 @@ end i32.const 4 i32.const 2 - i32.div_u + i32.div_s call $~lib/number/I32#toString local.tee $24 i32.const 656 @@ -5064,7 +5064,7 @@ end i32.const 3 i32.const 2 - i32.rem_u + i32.rem_s call $~lib/number/I32#toString local.tee $25 i32.const 624 @@ -5078,8 +5078,7 @@ call $~lib/builtins/abort unreachable end - i32.const 2 - f64.convert_i32_u + f64.const 2 f64.const 2 call $~lib/math/NativeMath.pow i32.const 0 @@ -5114,7 +5113,7 @@ end i32.const 2 i32.const 1 - i32.shr_u + i32.shr_s call $~lib/number/I32#toString local.tee $28 i32.const 624 diff --git a/tests/compiler/resolve-propertyaccess.optimized.wat b/tests/compiler/resolve-propertyaccess.optimized.wat index 586d530108..2695cd1030 100644 --- a/tests/compiler/resolve-propertyaccess.optimized.wat +++ b/tests/compiler/resolve-propertyaccess.optimized.wat @@ -17,7 +17,9 @@ (data (i32.const 1328) "\02\00\00\00\01\00\00\00\01\00\00\00\02\00\00\004") (data (i32.const 1360) "\02\00\00\00\01\00\00\00\01\00\00\00\02\00\00\005") (data (i32.const 1392) "\04\00\00\00\01\00\00\00\01\00\00\00\04\00\00\005\005") - (data (i32.const 1424) "\02\00\00\00\01\00\00\00\01\00\00\00\02\00\00\006") + (data (i32.const 1424) "\02\00\00\00\01\00\00\00\01\00\00\00\02\00\00\007") + (data (i32.const 1456) "\02\00\00\00\01\00\00\00\01\00\00\00\02\00\00\006") + (data (i32.const 1488) "\02\00\00\00\01\00\00\00\01\00\00\00\02\00\00\008") (global $~lib/rt/stub/offset (mut i32) (i32.const 0)) (export "memory" (memory $0)) (start $~start) @@ -326,7 +328,7 @@ ) (func $start:resolve-propertyaccess (local $0 i32) - i32.const 1456 + i32.const 1520 global.set $~lib/rt/stub/offset i32.const 1 call $~lib/number/I32#toString @@ -427,7 +429,7 @@ if i32.const 0 i32.const 1104 - i32.const 70 + i32.const 72 i32.const 1 call $~lib/builtins/abort unreachable @@ -440,7 +442,20 @@ if i32.const 0 i32.const 1104 - i32.const 76 + i32.const 78 + i32.const 1 + call $~lib/builtins/abort + unreachable + end + i32.const 7 + call $~lib/number/I32#toString + i32.const 1440 + call $~lib/string/String.__eq + i32.eqz + if + i32.const 0 + i32.const 1104 + i32.const 84 i32.const 1 call $~lib/builtins/abort unreachable @@ -454,13 +469,26 @@ local.get $0 i32.load call $~lib/number/I32#toString - i32.const 1440 + i32.const 1472 call $~lib/string/String.__eq i32.eqz if i32.const 0 i32.const 1104 - i32.const 84 + i32.const 92 + i32.const 3 + call $~lib/builtins/abort + unreachable + end + i32.const 8 + call $~lib/number/I32#toString + i32.const 1504 + call $~lib/string/String.__eq + i32.eqz + if + i32.const 0 + i32.const 1104 + i32.const 97 i32.const 3 call $~lib/builtins/abort unreachable diff --git a/tests/compiler/resolve-propertyaccess.ts b/tests/compiler/resolve-propertyaccess.ts index c8ca701a02..b6ca082fa0 100644 --- a/tests/compiler/resolve-propertyaccess.ts +++ b/tests/compiler/resolve-propertyaccess.ts @@ -65,6 +65,8 @@ class Class { static staticField: i32 = 5; @lazy static lazyStaticField: i32 = 55; instanceField: i32 = 6; + static get staticProperty(): i32 { return 7; } + get instanceProperty(): i32 { return 8; } } assert( @@ -79,6 +81,12 @@ assert( "55" ); +assert( + (Class.staticProperty).toString() + == + "7" +); + { let instance = new Class(); assert( @@ -86,4 +94,9 @@ assert( == "6" ); + assert( + (instance.instanceProperty).toString() + == + "8" + ); } diff --git a/tests/compiler/resolve-propertyaccess.untouched.wat b/tests/compiler/resolve-propertyaccess.untouched.wat index 3dcedd8795..82abfb4b61 100644 --- a/tests/compiler/resolve-propertyaccess.untouched.wat +++ b/tests/compiler/resolve-propertyaccess.untouched.wat @@ -5,6 +5,7 @@ (type $i32_i32_=>_i32 (func (param i32 i32) (result i32))) (type $i32_i32_i32_=>_none (func (param i32 i32 i32))) (type $i32_i32_i32_i32_=>_none (func (param i32 i32 i32 i32))) + (type $none_=>_i32 (func (result i32))) (type $i32_i32_i32_i32_i32_=>_i32 (func (param i32 i32 i32 i32 i32) (result i32))) (import "env" "abort" (func $~lib/builtins/abort (param i32 i32 i32 i32))) (memory $0 1) @@ -20,7 +21,9 @@ (data (i32.const 720) "\02\00\00\00\01\00\00\00\01\00\00\00\02\00\00\004\00") (data (i32.const 752) "\02\00\00\00\01\00\00\00\01\00\00\00\02\00\00\005\00") (data (i32.const 784) "\04\00\00\00\01\00\00\00\01\00\00\00\04\00\00\005\005\00") - (data (i32.const 816) "\02\00\00\00\01\00\00\00\01\00\00\00\02\00\00\006\00") + (data (i32.const 816) "\02\00\00\00\01\00\00\00\01\00\00\00\02\00\00\007\00") + (data (i32.const 848) "\02\00\00\00\01\00\00\00\01\00\00\00\02\00\00\006\00") + (data (i32.const 880) "\02\00\00\00\01\00\00\00\01\00\00\00\02\00\00\008\00") (table $0 1 funcref) (global $resolve-propertyaccess/Namespace.member i32 (i32.const 1)) (global $~lib/rt/stub/startOffset (mut i32) (i32.const 0)) @@ -34,7 +37,7 @@ (global $resolve-propertyaccess/Enum.VALUE i32 (i32.const 4)) (global $resolve-propertyaccess/Class.staticField (mut i32) (i32.const 5)) (global $resolve-propertyaccess/Class.lazyStaticField (mut i32) (i32.const 55)) - (global $~lib/heap/__heap_base i32 (i32.const 836)) + (global $~lib/heap/__heap_base i32 (i32.const 900)) (export "memory" (memory $0)) (start $~start) (func $~lib/util/number/decimalCount32 (param $0 i32) (result i32) @@ -630,6 +633,9 @@ call $~lib/rt/stub/__release local.get $2 ) + (func $resolve-propertyaccess/Class.get:staticProperty (result i32) + i32.const 7 + ) (func $resolve-propertyaccess/Class#constructor (param $0 i32) (result i32) local.get $0 i32.eqz @@ -645,6 +651,9 @@ i32.store local.get $0 ) + (func $resolve-propertyaccess/Class#get:instanceProperty (param $0 i32) (result i32) + i32.const 8 + ) (func $start:resolve-propertyaccess (local $0 i32) (local $1 i32) @@ -657,6 +666,8 @@ (local $8 i32) (local $9 i32) (local $10 i32) + (local $11 i32) + (local $12 i32) global.get $~lib/heap/__heap_base i32.const 15 i32.add @@ -774,7 +785,7 @@ if i32.const 0 i32.const 496 - i32.const 70 + i32.const 72 i32.const 1 call $~lib/builtins/abort unreachable @@ -788,33 +799,64 @@ if i32.const 0 i32.const 496 - i32.const 76 + i32.const 78 + i32.const 1 + call $~lib/builtins/abort + unreachable + end + call $resolve-propertyaccess/Class.get:staticProperty + call $~lib/number/I32#toString + local.tee $9 + i32.const 832 + call $~lib/string/String.__eq + i32.eqz + if + i32.const 0 + i32.const 496 + i32.const 84 i32.const 1 call $~lib/builtins/abort unreachable end i32.const 0 call $resolve-propertyaccess/Class#constructor - local.set $9 - local.get $9 + local.set $10 + local.get $10 i32.load call $~lib/number/I32#toString - local.tee $10 - i32.const 832 + local.tee $11 + i32.const 864 call $~lib/string/String.__eq i32.eqz if i32.const 0 i32.const 496 - i32.const 84 + i32.const 92 + i32.const 3 + call $~lib/builtins/abort + unreachable + end + local.get $10 + call $resolve-propertyaccess/Class#get:instanceProperty + call $~lib/number/I32#toString + local.tee $12 + i32.const 896 + call $~lib/string/String.__eq + i32.eqz + if + i32.const 0 + i32.const 496 + i32.const 97 i32.const 3 call $~lib/builtins/abort unreachable end - local.get $9 - call $~lib/rt/stub/__release local.get $10 call $~lib/rt/stub/__release + local.get $11 + call $~lib/rt/stub/__release + local.get $12 + call $~lib/rt/stub/__release local.get $0 call $~lib/rt/stub/__release local.get $1 @@ -833,6 +875,8 @@ call $~lib/rt/stub/__release local.get $8 call $~lib/rt/stub/__release + local.get $9 + call $~lib/rt/stub/__release ) (func $~start call $start:resolve-propertyaccess diff --git a/tests/compiler/resolve-ternary.untouched.wat b/tests/compiler/resolve-ternary.untouched.wat index 4cc9ba29ab..855c9f4806 100644 --- a/tests/compiler/resolve-ternary.untouched.wat +++ b/tests/compiler/resolve-ternary.untouched.wat @@ -4837,8 +4837,7 @@ end global.get $resolve-ternary/b if (result f64) - i32.const 1 - f64.convert_i32_u + f64.const 1 else f64.const 2 end