Skip to content

fix: create 2 kinds of constructor for instantiation and instantiation of inherited types #2936

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

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
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
1 change: 1 addition & 0 deletions src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ export namespace CommonNames {
export const this_ = "this";
export const super_ = "super";
export const constructor = "constructor";
export const raw_constructor = "raw_constructor";
// constants
export const ASC_TARGET = "ASC_TARGET";
export const ASC_RUNTIME = "ASC_RUNTIME";
Expand Down
238 changes: 133 additions & 105 deletions src/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8764,133 +8764,161 @@ export class Compiler extends DiagnosticEmitter {
return this.compileInstantiate(ctor, expression.args, constraints, expression);
}

/** Gets the compiled constructor of the specified class or generates one if none is present. */
ensureConstructor(
private createConstructorInstance(
/** true mean this constructor is externally visible, need to handle memory allocation */
isExternallyVisible: bool,
/** Class wanting a constructor. */
classInstance: Class,
/** Report node. */
reportNode: Node
): Function {
let instance = classInstance.constructorInstance;
if (instance) {
// shortcut if already compiled
if (instance.is(CommonFlags.Compiled)) return instance;
// do not attempt to compile if inlined anyway
if (!instance.hasDecorator(DecoratorFlags.Inline)) this.compileFunction(instance);
const name = isExternallyVisible ? CommonNames.constructor : CommonNames.raw_constructor;
let instance: Function | null = null;
// clone base constructor if a derived class. note that we cannot just
// call the base ctor since the derived class may have additional fields.
let baseClass = classInstance.base;
let contextualTypeArguments = cloneMap(classInstance.contextualTypeArguments);
if (baseClass) {
let baseCtor = this.ensureConstructor(baseClass, reportNode);
this.checkFieldInitialization(baseClass, reportNode);
instance = new Function(
name,
new FunctionPrototype(
name,
classInstance,
// declaration is important, i.e. to access optional parameter initializers
(<FunctionDeclaration>baseCtor.declaration).clone()
),
null,
Signature.create(
this.program,
baseCtor.signature.parameterTypes,
classInstance.type,
classInstance.type,
baseCtor.signature.requiredParameters,
baseCtor.signature.hasRest
),
contextualTypeArguments
);
// otherwise make a default constructor
} else {
// clone base constructor if a derived class. note that we cannot just
// call the base ctor since the derived class may have additional fields.
let baseClass = classInstance.base;
let contextualTypeArguments = cloneMap(classInstance.contextualTypeArguments);
if (baseClass) {
let baseCtor = this.ensureConstructor(baseClass, reportNode);
this.checkFieldInitialization(baseClass, reportNode);
instance = new Function(
CommonNames.constructor,
new FunctionPrototype(
CommonNames.constructor,
classInstance,
// declaration is important, i.e. to access optional parameter initializers
(<FunctionDeclaration>baseCtor.declaration).clone()
),
null,
Signature.create(
this.program,
baseCtor.signature.parameterTypes,
classInstance.type,
classInstance.type,
baseCtor.signature.requiredParameters,
baseCtor.signature.hasRest
),
contextualTypeArguments
);

// otherwise make a default constructor
} else {
instance = new Function(
CommonNames.constructor,
new FunctionPrototype(
CommonNames.constructor,
classInstance, // bound
this.program.makeNativeFunctionDeclaration(CommonNames.constructor,
CommonFlags.Instance | CommonFlags.Constructor
)
),
null,
Signature.create(this.program, [], classInstance.type, classInstance.type),
contextualTypeArguments
);
}
instance = new Function(
name,
new FunctionPrototype(
name,
classInstance, // bound
this.program.makeNativeFunctionDeclaration(name,
CommonFlags.Instance | CommonFlags.Constructor
)
),
null,
Signature.create(this.program, [], classInstance.type, classInstance.type),
contextualTypeArguments
);
}

instance.set(CommonFlags.Compiled);
instance.prototype.setResolvedInstance("", instance);
instance.prototype.setResolvedInstance("", instance);
if (isExternallyVisible) {
if (classInstance.is(CommonFlags.ModuleExport)) {
instance.set(CommonFlags.ModuleExport);
}
classInstance.constructorInstance = instance;
let members = classInstance.members;
if (!members) classInstance.members = members = new Map();
members.set(CommonNames.constructor, instance.prototype);
}
return instance;
}

let previousFlow = this.currentFlow;
let flow = instance.flow;
this.currentFlow = flow;
private compileConstructorInstance(
isExternallyVisible: bool,
instance: Function,
classInstance: Class,
reportNode: Node
): void {
instance.set(CommonFlags.Compiled);
let baseClass = classInstance.base;
let previousFlow = this.currentFlow;
let flow = instance.flow;
this.currentFlow = flow;

// generate body
let signature = instance.signature;
let module = this.module;
let sizeTypeRef = this.options.sizeTypeRef;
let stmts = new Array<ExpressionRef>();
// generate body
let signature = instance.signature;
let module = this.module;
let sizeTypeRef = this.options.sizeTypeRef;
let stmts = new Array<ExpressionRef>();

// {
// this = <COND_ALLOC>
// IF_DERIVED: this = super(this, ...args)
// this.a = X
// this.b = Y
// return this
// }
stmts.push(
this.makeConditionalAllocation(classInstance, 0)
);
if (baseClass) {
let parameterTypes = signature.parameterTypes;
let numParameters = parameterTypes.length;
let operands = new Array<ExpressionRef>(1 + numParameters);
operands[0] = module.local_get(0, sizeTypeRef);
for (let i = 1; i <= numParameters; ++i) {
operands[i] = module.local_get(i, parameterTypes[i - 1].toRef());
}
stmts.push(
module.local_set(0,
this.makeCallDirect(assert(baseClass.constructorInstance), operands, reportNode, false),
baseClass.type.isManaged
)
);
// {
// this = <COND_ALLOC>?
// IF_DERIVED: this = super(this, ...args)
// this.a = X
// this.b = Y
// return this
// }
if (isExternallyVisible) {
stmts.push(this.makeConditionalAllocation(classInstance, 0));
}
if (baseClass) {
let parameterTypes = signature.parameterTypes;
let numParameters = parameterTypes.length;
let forwardCallOperands = new Array<ExpressionRef>(1 + numParameters);
forwardCallOperands[0] = module.local_get(0, sizeTypeRef);
for (let i = 1; i <= numParameters; ++i) {
forwardCallOperands[i] = module.local_get(i, parameterTypes[i - 1].toRef());
}
this.makeFieldInitializationInConstructor(classInstance, stmts);
let baseCallConstructorInstance = baseClass.rawConstructorInstance;
if (baseCallConstructorInstance == null) baseCallConstructorInstance = assert(baseClass.constructorInstance);
stmts.push(
module.local_get(0, sizeTypeRef)
module.local_set(
0,
this.makeCallDirect(baseCallConstructorInstance, forwardCallOperands, reportNode, false),
baseClass.type.isManaged
)
);
this.currentFlow = previousFlow;
}
this.makeFieldInitializationInConstructor(classInstance, stmts);
stmts.push(module.local_get(0, sizeTypeRef));
this.currentFlow = previousFlow;

// make the function
let locals = instance.localsByIndex;
let varTypes = new Array<TypeRef>(); // of temp. vars added while compiling initializers
let numOperands = 1 + signature.parameterTypes.length;
let numLocals = locals.length;
if (numLocals > numOperands) {
for (let i = numOperands; i < numLocals; ++i) varTypes.push(locals[i].type.toRef());
}
let funcRef = module.addFunction(
instance.internalName,
signature.paramRefs,
signature.resultRefs,
varTypes,
module.flatten(stmts, sizeTypeRef)
);
instance.finalize(module, funcRef);
// make the function
let locals = instance.localsByIndex;
let varTypes = new Array<TypeRef>(); // of temp. vars added while compiling initializers
let numOperands = 1 + signature.parameterTypes.length;
let numLocals = locals.length;
if (numLocals > numOperands) {
for (let i = numOperands; i < numLocals; ++i) varTypes.push(locals[i].type.toRef());
}
let funcRef = module.addFunction(
instance.internalName,
signature.paramRefs,
signature.resultRefs,
varTypes,
module.flatten(stmts, sizeTypeRef)
);
instance.finalize(module, funcRef);
}

/** Gets the compiled constructor of the specified class or generates one if none is present. */
ensureConstructor(
/** Class wanting a constructor. */
classInstance: Class,
/** Report node. */
reportNode: Node
): Function {
let instance = classInstance.constructorInstance;
if (instance) {
// shortcut if already compiled
if (instance.is(CommonFlags.Compiled)) return instance;
// do not attempt to compile if inlined anyway
if (!instance.hasDecorator(DecoratorFlags.Inline)) this.compileFunction(instance);
} else {
instance = classInstance.constructorInstance = this.createConstructorInstance(true, classInstance, reportNode);
// TODO: maybe we can remove this function when the classInstance is finalized?
if (!classInstance.hasDecorator(DecoratorFlags.Final)) {
let rawInstance = classInstance.rawConstructorInstance = this.createConstructorInstance(false, classInstance, reportNode);
this.compileConstructorInstance(false, rawInstance, classInstance, reportNode);
}
this.compileConstructorInstance(true, instance, classInstance, reportNode);
}
return instance;
}

Expand Down
2 changes: 2 additions & 0 deletions src/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4367,6 +4367,8 @@ export class Class extends TypedElement {
nextMemoryOffset: u32 = 0;
/** Constructor instance. */
constructorInstance: Function | null = null;
/** Constructor instance for inheritance. */
rawConstructorInstance: Function | null = null;
/** Operator overloads. */
operatorOverloads: Map<OperatorKind,Function> | null = null;
/** Index signature, if present. */
Expand Down
Loading
Loading