Skip to content

Commit

Permalink
fix: accessor error in inheritance chain (#115)
Browse files Browse the repository at this point in the history
* fix: accessor error in inheritance chain

---------

Signed-off-by: ganjing <[email protected]>
Signed-off-by: Su Yihan <[email protected]>
Co-authored-by: Su Yihan <[email protected]>
  • Loading branch information
Shanks0224 and yviansu authored Dec 21, 2023
1 parent a48fd41 commit a6c6740
Show file tree
Hide file tree
Showing 8 changed files with 519 additions and 9 deletions.
18 changes: 17 additions & 1 deletion src/backend/binaryen/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1127,14 +1127,30 @@ export class WASMGen extends Ts2wasmBackend {
assert(implClassMeta, 'implClassMeta should not be undefined');

let methodName = member.name;
let implClassName = implClassMeta!.name;
/**
* the ACCESSOR member contains both getter and setter.
* getter and setter can be implemented in different classes with inheritance relationships.
*/
if (accessorKind !== undefined) {
if (accessorKind === 0) {
methodName = 'get_'.concat(member.name);
if (member.methodOrAccessor && member.methodOrAccessor.getter) {
const getterValue = member.methodOrAccessor
.getter as VarValue;
const getter = getterValue.ref as FunctionDeclareNode;
implClassName = getter.thisClassType!.meta.name;
}
} else if (accessorKind === 1) {
methodName = 'set_'.concat(member.name);
if (member.methodOrAccessor && member.methodOrAccessor.setter) {
const setterValue = member.methodOrAccessor
.setter as VarValue;
const setter = setterValue.ref as FunctionDeclareNode;
implClassName = setter.thisClassType!.meta.name;
}
}
}
let implClassName = implClassMeta!.name;
if (implClassName.includes('@')) {
implClassName = implClassName.slice(1);
}
Expand Down
22 changes: 22 additions & 0 deletions src/backend/binaryen/wasm_type_gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1259,6 +1259,17 @@ export class WASMTypeGen {
),
);
}
} else {
const getterTypeRef = binaryenCAPI._BinaryenTypeFuncref();
methodTypeRefs.push(getterTypeRef);
if (buildIndex === -1) {
vtableFuncs.push(
binaryenCAPI._BinaryenRefNull(
this.wasmComp.module.ptr,
getterTypeRef,
),
);
}
}

if (member.hasSetter) {
Expand All @@ -1283,6 +1294,17 @@ export class WASMTypeGen {
),
);
}
} else {
const setterTypeRef = binaryenCAPI._BinaryenTypeFuncref();
methodTypeRefs.push(setterTypeRef);
if (buildIndex === -1) {
vtableFuncs.push(
binaryenCAPI._BinaryenRefNull(
this.wasmComp.module.ptr,
setterTypeRef,
),
);
}
}
} else if (member.type === MemberType.FIELD) {
let defaultValue = FunctionalFuncs.getVarDefaultValue(
Expand Down
4 changes: 3 additions & 1 deletion src/semantics/builder_context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { Type, TypeKind, TSClass } from '../type.js';

import { SemanticsValue, SemanticsValueKind, VarValue } from './value.js';

import { ValueType } from './value_types.js';
import { ObjectType, ValueType } from './value_types.js';

import {
SemanticsNode,
Expand Down Expand Up @@ -93,6 +93,8 @@ export class BuildContext {
public enterScope: GlobalScope | undefined = undefined;
public startStmts = new Map<GlobalScope, SemanticsNode[]>();
public recClassTypeGroup = new Array<TSClass[]>();
// record objectDescription and corresponding objectType
public metaAndObjectTypeMap = new Map<ObjectDescription, ObjectType>();

addFunctionValue(var_func: VarValue) {
this.namedGlobalValues.set(var_func.index as string, var_func);
Expand Down
22 changes: 17 additions & 5 deletions src/semantics/expression_builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,7 @@ function buildPropertyAccessExpression(
}

return createDirectAccess(
context,
own,
shape_member,
member,
Expand Down Expand Up @@ -571,6 +572,7 @@ function createVTableAccess(
}

function createDirectAccess(
context: BuildContext,
own: SemanticsValue,
shape_member: ShapeMember,
member: MemberDescription,
Expand All @@ -588,6 +590,7 @@ function createDirectAccess(
);
} else {
return createDirectGet(
context,
own,
shape_member,
member,
Expand All @@ -598,6 +601,7 @@ function createDirectAccess(
}

function createDirectGet(
context: BuildContext,
own: SemanticsValue,
shape_member: ShapeMember,
member: MemberDescription,
Expand All @@ -616,9 +620,7 @@ function createDirectGet(
const getter = accessor.getter;
if (!getter) {
Logger.info('==== getter is not exist, access by shape');
if (isThisShape)
return createVTableAccess(own, member, false, true);
return createShapeAccess(own, member, false, true);
return new LiteralValue(Primitive.Undefined, undefined);
}
if (accessor.isOffset) {
return new OffsetGetterValue(
Expand All @@ -627,6 +629,17 @@ function createDirectGet(
accessor.getterOffset!,
);
} else {
const ownerType = context.metaAndObjectTypeMap.get(
(own as VarValue).shape!.meta,
)!;
const getterOwnerType = (
(getter as VarValue).ref as FunctionDeclareNode
).thisClassType!.instanceType!;
// if the value of 'isOwn' is false, it means that the getter and setter are both not reimplemented in the sub class.
// when only the setter is reimplemented in the sub class and the getter is inherited from the base class, the getter returns undefined.
if (member.isOwn && !ownerType.equals(getterOwnerType))
return new LiteralValue(Primitive.Undefined, undefined);

return new DirectGetterValue(
own,
member.getterType!,
Expand Down Expand Up @@ -1517,8 +1530,7 @@ export function shapeAssignCheck(left: ValueType, right: ValueType): boolean {
left.kind == ValueTypeKind.OBJECT &&
right.kind == ValueTypeKind.OBJECT
) {
if (left.equals(right)) return false;

if (left.equals(right)) return true;
const leftMeta = (left as ObjectType).meta;
const rightMeta = (right as ObjectType).meta;
if (rightMeta.members.length >= leftMeta.members.length) {
Expand Down
32 changes: 30 additions & 2 deletions src/semantics/type_creator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,7 @@ export function createObjectType(
inst_meta,
clazz.isLiteral ? ObjectTypeFlag.LITERAL : ObjectTypeFlag.OBJECT,
);
context.metaAndObjectTypeMap.set(inst_meta, inst_type);
inst_type.implId = DefaultTypeId;
if (impl_infc) {
inst_type.implId = impl_infc.typeId;
Expand Down Expand Up @@ -540,6 +541,7 @@ export function createObjectType(
clazz_meta,
ObjectTypeFlag.CLASS,
);
context.metaAndObjectTypeMap.set(clazz_meta, clazz_type);
clazz_type.implId = inst_type.implId;

clazz_type.instanceType = inst_type;
Expand Down Expand Up @@ -841,7 +843,6 @@ function updateMemberDescriptions(
) {
const is_setter = m.type.funcKind == FunctionKind.SETTER;
const name = `${is_setter ? 'set_' : 'get_'}${m.name}`;
const key = is_setter ? 'setter' : 'getter';
const globalName = is_interface
? `${clazz.className}|${name}`
: `${clazz.mangledName}|${name}`;
Expand Down Expand Up @@ -883,7 +884,34 @@ function updateMemberDescriptions(
accessor.getterType = field_type;
}

if (func) accessor.setAccessorFunction(func, is_setter);
if (func) {
accessor.setAccessorFunction(func, is_setter);
} else {
// when 'func' is empty, it means that the current getter/setter is inherited from the base class
if (!is_interface) {
let baseClass = clazz.getBase();
while (baseClass) {
const globalMethodName = `${baseClass.mangledName}|${name}`;
const funcValue = getGlobalFunction(
context,
globalMethodName,
);
if (funcValue) {
if (
(is_setter && !accessor.hasSetter) ||
(!is_setter && !accessor.hasGetter)
) {
accessor.setAccessorFunction(
funcValue,
is_setter,
);
}
break;
}
baseClass = baseClass.getBase();
}
}
}
if (!is_instance) {
if (is_setter) accessor.setterOffset = inst_offset;
else accessor.getterOffset = inst_offset;
Expand Down
36 changes: 36 additions & 0 deletions src/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2640,6 +2640,42 @@ export class TypeResolver {
tsFuncType.setBelongedScope(funcDef);
}
classType.overrideOrOwnMethods.add(nameWithPrefix);

// the accessor of the sub class are placed in front of the accessor of the base class for optimization.
const setterIndex = classType.getMethod(
methodName,
FunctionKind.SETTER,
).index;
const isSetterOverrideOrOwn = classType.overrideOrOwnMethods.has(
`${'set_'}${methodName}`,
);
const getterIndex = classType.getMethod(
methodName,
FunctionKind.GETTER,
).index;
const isGetterOverrideOrOwn = classType.overrideOrOwnMethods.has(
`${'get_'}${methodName}`,
);

if (setterIndex > -1 && getterIndex > -1) {
if (!isSetterOverrideOrOwn && setterIndex < getterIndex) {
[
classType.memberFuncs[setterIndex],
classType.memberFuncs[getterIndex],
] = [
classType.memberFuncs[getterIndex],
classType.memberFuncs[setterIndex],
];
} else if (!isGetterOverrideOrOwn && getterIndex < setterIndex) {
[
classType.memberFuncs[getterIndex],
classType.memberFuncs[setterIndex],
] = [
classType.memberFuncs[setterIndex],
classType.memberFuncs[getterIndex],
];
}
}
}

parseTypeParameters(
Expand Down
Loading

0 comments on commit a6c6740

Please sign in to comment.