diff --git a/src/builtins.ts b/src/builtins.ts index 11acfa41..df555860 100644 --- a/src/builtins.ts +++ b/src/builtins.ts @@ -2322,6 +2322,25 @@ export function compileCall( // thus must be used with care. it exists because it *might* be useful in specific scenarios. return module.createCallIndirect(arg0, operandExprs, typeName); } + case "instantiate": { + if (!(typeArguments && typeArguments.length == 1)) { + if (typeArguments && typeArguments.length) compiler.currentType = typeArguments[0]; + compiler.error( + DiagnosticCode.Expected_0_type_arguments_but_got_1, + reportNode.range, "1", typeArguments ? typeArguments.length.toString(10) : "0" + ); + return module.createUnreachable(); + } + let classInstance = typeArguments[0].classReference; + if (!classInstance) { + compiler.error( + DiagnosticCode.Operation_not_supported, + reportNode.range + ); + return module.createUnreachable(); + } + return compiler.compileInstantiate(classInstance, operands, reportNode); + } // user-defined diagnostic macros diff --git a/src/compiler.ts b/src/compiler.ts index fd1c80e8..1ad3cfef 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -6651,9 +6651,10 @@ export class Compiler extends DiagnosticEmitter { ); } if (!classInstance) return module.createUnreachable(); + return this.compileInstantiate(classInstance, expression.arguments, expression); + } - var expr: ExpressionRef; - + compileInstantiate(classInstance: Class, argumentExpressions: Expression[], reportNode: Node): ExpressionRef { // traverse to the top-most visible constructor var currentClassInstance: Class | null = classInstance; var constructorInstance: Function | null = null; @@ -6663,14 +6664,21 @@ export class Compiler extends DiagnosticEmitter { } while (currentClassInstance = currentClassInstance.base); // if a constructor is present, call it with a zero `this` + var expr: ExpressionRef; if (constructorInstance) { - expr = this.compileCallDirect(constructorInstance, expression.arguments, expression, - options.usizeType.toNativeZero(module) + expr = this.compileCallDirect(constructorInstance, argumentExpressions, reportNode, + this.options.usizeType.toNativeZero(this.module) ); // otherwise simply allocate a new instance and initialize its fields } else { - expr = this.makeAllocate(classInstance, expression); + if (argumentExpressions.length) { + this.error( + DiagnosticCode.Expected_0_arguments_but_got_1, + reportNode.range, "0", argumentExpressions.length.toString(10) + ); + } + expr = this.makeAllocate(classInstance, reportNode); } this.currentType = classInstance.type; diff --git a/std/assembly/builtins.ts b/std/assembly/builtins.ts index 08e1ca37..47236c90 100644 --- a/std/assembly/builtins.ts +++ b/std/assembly/builtins.ts @@ -41,6 +41,7 @@ @builtin export declare function assert(isTrueish: T, message?: string): T; @builtin export declare function unchecked(expr: T): T; @builtin export declare function call_indirect(target: void, ...args: void[]): T; +@builtin export declare function instantiate(...args: void[]): T; @builtin export declare function i8(value: void): i8; export namespace i8 { diff --git a/std/assembly/index.d.ts b/std/assembly/index.d.ts index 84bd53a5..253a7af7 100644 --- a/std/assembly/index.d.ts +++ b/std/assembly/index.d.ts @@ -112,6 +112,8 @@ declare function changetype(value: any): T; declare function unchecked(value: T): T; /** Emits a `call_indirect` instruction, calling the specified function in the function table by index with the specified arguments. Does result in a runtime error if the arguments do not match the called function. */ declare function call_indirect(target: Function | u32, ...args: any[]): T; +/** Instantiates a new instance of `T` using the specified constructor arguments. */ +declare function instantiate(...args: any[]): T; /** Tests if a 32-bit or 64-bit float is `NaN`. */ declare function isNaN(value: T): bool; /** Tests if a 32-bit or 64-bit float is finite, that is not `NaN` or +/-`Infinity`. */