Skip to content
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

fix: incorrect variable resolving for class default initializer #2791

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
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 @@ -165,6 +165,7 @@ export namespace CommonNames {
// objects
export const this_ = "this";
export const super_ = "super";
export const defaultConstructor = "constructor|default";
export const constructor = "constructor";
// constants
export const ASC_TARGET = "ASC_TARGET";
Expand Down
337 changes: 180 additions & 157 deletions src/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8738,6 +8738,63 @@ export class Compiler extends DiagnosticEmitter {
return this.compileInstantiate(ctor, expression.args, constraints, expression);
}

ensureDefaultConstructor(
/** Class wanting a constructor. */
classInstance: Class,
): Function | null {
let nonParameterFields = classInstance.nonParameterMembers;
// should not have default constructor when there are not nonParameterMembers
if (!nonParameterFields) return null;

let module = this.module;
let sizeTypeRef = this.options.sizeTypeRef;
let instance = classInstance.makeNativeMethod(
CommonNames.defaultConstructor,
[],
Type.void
);
instance.set(CommonFlags.Compiled);
instance.prototype.setResolvedInstance("", instance);
let stmts = new Array<ExpressionRef>();

let previousFlow = this.currentFlow;
let flow = instance.flow;
this.currentFlow = flow;

let thisLocalIndex = flow.lookupLocal(CommonNames.this_)!.index;
for (let i = 0, k = nonParameterFields.length; i < k; ++i) {
let field = unchecked(nonParameterFields[i]);
let fieldType = field.type;
let fieldPrototype = field.prototype;
let initializerNode = fieldPrototype.initializerNode;
assert(fieldPrototype.parameterIndex < 0);
let setterInstance = assert(field.setterInstance);
let expr = this.makeCallDirect(setterInstance, [
module.local_get(thisLocalIndex, sizeTypeRef),
initializerNode // use initializer if present, otherwise initialize with zero
? this.compileExpression(initializerNode, fieldType, Constraints.ConvImplicit)
: this.makeZero(fieldType)
], field.identifierNode, true);
if (this.currentType != Type.void) { // in case
expr = module.drop(expr);
}
stmts.push(expr);
}

let signature = instance.signature;
let funcRef = module.addFunction(
instance.internalName,
signature.paramRefs,
signature.resultRefs,
typesToRefs(instance.getNonParameterLocalTypes()),
module.flatten(stmts)
);
instance.finalize(module, funcRef);

this.currentFlow = previousFlow;
return instance;
}

/** Gets the compiled constructor of the specified class or generates one if none is present. */
ensureConstructor(
/** Class wanting a constructor. */
Expand All @@ -8751,120 +8808,112 @@ export class Compiler extends DiagnosticEmitter {
if (instance.is(CommonFlags.Compiled)) return instance;
// do not attempt to compile if inlined anyway
if (!instance.hasDecorator(DecoratorFlags.Inline)) this.compileFunction(instance);
} 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(
return instance;
}
// 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,
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
);
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
);
}
// otherwise make a default constructor
} else {
instance = classInstance.makeNativeMethod(
CommonNames.constructor,
[],
classInstance.type,
CommonFlags.Constructor
);
}

instance.set(CommonFlags.Compiled);
instance.prototype.setResolvedInstance("", instance);
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("constructor", instance.prototype);
instance.set(CommonFlags.Compiled);
instance.prototype.setResolvedInstance("", instance);
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("constructor", instance.prototype);

let previousFlow = this.currentFlow;
let flow = instance.flow;
this.currentFlow = flow;
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
// }
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());
}
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)
module.local_set(0,
this.makeCallDirect(assert(baseClass.constructorInstance), operands, reportNode, false),
baseClass.type.isManaged
)
);
instance.finalize(module, funcRef);
}
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);
return instance;
}

Expand Down Expand Up @@ -10265,71 +10314,45 @@ export class Compiler extends DiagnosticEmitter {

let module = this.module;
let flow = this.currentFlow;
let isInline = flow.isInline;
let thisLocalIndex = isInline ? flow.lookupLocal(CommonNames.this_)!.index : 0;
let thisLocalIndex = flow.lookupLocal(CommonNames.this_)!.index;
let sizeTypeRef = this.options.sizeTypeRef;
let nonParameterFields: Property[] | null = null;

// TODO: for (let member of members.values()) {
for (let _values = Map_values(members), i = 0, k = _values.length; i < k; ++i) {
let member = unchecked(_values[i]);
if (member.kind != ElementKind.PropertyPrototype) continue;
// only interested in fields (resolved during class finalization)
let property = (<PropertyPrototype>member).instance;
if (!property || !property.isField || property.getBoundClassOrInterface() != classInstance) continue;
assert(!property.isAny(CommonFlags.Const));
let fieldPrototype = property.prototype;
let parameterIndex = fieldPrototype.parameterIndex;

// Defer non-parameter fields until parameter fields are initialized
if (parameterIndex < 0) {
if (!nonParameterFields) nonParameterFields = new Array();
nonParameterFields.push(property);
continue;
}

// Initialize constructor parameter field
let fieldType = property.type;
let fieldTypeRef = fieldType.toRef();
assert(!fieldPrototype.initializerNode);
let setterInstance = assert(property.setterInstance);
let expr = this.makeCallDirect(setterInstance, [
module.local_get(thisLocalIndex, sizeTypeRef),
module.local_get(
isInline
? flow.lookupLocal(property.name)!.index
: 1 + parameterIndex, // `this` is local 0
fieldTypeRef
)
], setterInstance.identifierNode, true);
if (this.currentType != Type.void) { // in case
expr = module.drop(expr);
}
stmts.push(expr);
}

// Initialize deferred non-parameter fields
if (nonParameterFields) {
for (let i = 0, k = nonParameterFields.length; i < k; ++i) {
let field = unchecked(nonParameterFields[i]);
let fieldType = field.type;
let fieldPrototype = field.prototype;
let initializerNode = fieldPrototype.initializerNode;
assert(fieldPrototype.parameterIndex < 0);
let setterInstance = assert(field.setterInstance);
// Initialize constructor parameter field
let parameterFields: Property[] | null = classInstance.parameterMembers;
if (parameterFields) {
for (let i = 0, k = parameterFields.length; i < k; ++i) {
let property = unchecked(parameterFields[i]);
let fieldPrototype = property.prototype;
let fieldType = property.type;
let fieldTypeRef = fieldType.toRef();
assert(!fieldPrototype.initializerNode);
let setterInstance = assert(property.setterInstance);
let expr = this.makeCallDirect(setterInstance, [
module.local_get(thisLocalIndex, sizeTypeRef),
initializerNode // use initializer if present, otherwise initialize with zero
? this.compileExpression(initializerNode, fieldType, Constraints.ConvImplicit)
: this.makeZero(fieldType)
], field.identifierNode, true);
module.local_get(
flow.lookupLocal(property.name)!.index,
fieldTypeRef
)
], setterInstance.identifierNode, true);
if (this.currentType != Type.void) { // in case
expr = module.drop(expr);
}
stmts.push(expr);
}
}

// Initialize deferred non-parameter fields
let defaultConstructor = this.ensureDefaultConstructor(classInstance);
if (defaultConstructor) {
stmts.push(
this.makeCallDirect(
defaultConstructor,
[module.local_get(thisLocalIndex, sizeTypeRef)],
classInstance.declaration,
true
)
);
}
this.currentType = Type.void;
return stmts;
}
Expand Down