Skip to content

Commit 3e8f803

Browse files
authored
fix(codegen): better union-arm selection for object-literal return (#518)
* fix(codegen): better union-arm selection for object-literal return — pick smallest superset by field set instead of count equality * fix(codegen): propagate declared interface type through function/method/constructor args so object literals in scrambled field order get correct struct layout (#519) Co-authored-by: cs01 <[email protected]> --------- Co-authored-by: cs01 <[email protected]>
1 parent 05ff8b8 commit 3e8f803

6 files changed

Lines changed: 155 additions & 5 deletions

File tree

src/codegen/expressions/calls.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -866,6 +866,7 @@ export class CallExpressionGenerator {
866866
const resolvedFuncName = this.ctx.resolveImportAlias(expr.name);
867867
let returnType = "double";
868868
let paramTypes: string[] = [];
869+
const paramTsTypes: string[] = [];
869870

870871
const funcResult = this.getFunctionFromAST(expr.name);
871872
const func = funcResult as FunctionNode;
@@ -903,6 +904,7 @@ export class CallExpressionGenerator {
903904
this.ctx.interfaceStructGenHasInterface(stripNullable(p)),
904905
),
905906
);
907+
paramTsTypes.push(stripNullable(p));
906908
}
907909
// Integer-specialized callee: every double param/return becomes i64.
908910
// The existing FFI coercion paths in this loop already handle paramType
@@ -935,6 +937,7 @@ export class CallExpressionGenerator {
935937
false,
936938
),
937939
);
940+
paramTsTypes.push(stripNullable(pType));
938941
}
939942
} else if (funcNode.paramTypes) {
940943
for (let i = 0; i < funcNode.paramTypes.length; i++) {
@@ -943,6 +946,7 @@ export class CallExpressionGenerator {
943946
paramTypes.push(
944947
mapParamTypeToLLVM(t, paramName, this.ctx.isEnumType(stripNullable(t)), false),
945948
);
949+
paramTsTypes.push(stripNullable(t));
946950
}
947951
}
948952
if (funcNode.intSpecialized) {
@@ -967,7 +971,21 @@ export class CallExpressionGenerator {
967971
for (let i = 0; i < loopLimit; i++) {
968972
if (i < expr.args.length) {
969973
const paramType = paramTypes[i] || "double";
974+
const argExpr = expr.args[i] as { type: string };
975+
let savedDeclaredIface: string | undefined = undefined;
976+
let wrappedDeclaredIface = false;
977+
if (argExpr.type === "object" && i < paramTsTypes.length) {
978+
const tsParamType = paramTsTypes[i];
979+
if (tsParamType && this.ctx.interfaceStructGenHasInterface(tsParamType)) {
980+
savedDeclaredIface = this.ctx.getCurrentDeclaredInterfaceType();
981+
this.ctx.setCurrentDeclaredInterfaceType(tsParamType);
982+
wrappedDeclaredIface = true;
983+
}
984+
}
970985
const result = this.ctx.generateExpression(expr.args[i], params);
986+
if (wrappedDeclaredIface) {
987+
this.ctx.setCurrentDeclaredInterfaceType(savedDeclaredIface);
988+
}
971989
const resultType = this.ctx.getVariableType(result);
972990
if (paramType === "double" && resultType === "i8*") {
973991
argsList.push(`double 0.0`);

src/codegen/llvm-generator.ts

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3744,21 +3744,57 @@ export class LLVMGenerator extends BaseGenerator implements IGeneratorContext {
37443744
}
37453745
}
37463746
if (returnTypeName === this.currentFunctionTsReturnType) {
3747-
const numObjFields = objLit.properties ? objLit.properties.length : 0;
3747+
const objKeys: string[] = [];
3748+
if (objLit.properties) {
3749+
for (let k = 0; k < objLit.properties.length; k++) {
3750+
objKeys.push(objLit.properties[k].key);
3751+
}
3752+
}
37483753
let bestMatch: string | null = null;
3754+
let bestMatchSize = -1;
3755+
let exactMatch: string | null = null;
37493756
for (let i = 0; i < parts.length; i++) {
37503757
const part = parts[i].trim();
37513758
if (part === "null" || part === "undefined") continue;
37523759
if (!bestMatch) bestMatch = part;
37533760
if (this.interfaceStructGen) {
37543761
const ifaceInfo = this.interfaceStructGen.getInterfaceStruct(part);
3755-
if (ifaceInfo && ifaceInfo.fields && ifaceInfo.fields.length === numObjFields) {
3756-
bestMatch = part;
3757-
break;
3762+
if (ifaceInfo && ifaceInfo.fields) {
3763+
const fieldNames: string[] = [];
3764+
for (let f = 0; f < ifaceInfo.fields.length; f++) {
3765+
const field = ifaceInfo.fields[f] as { name: string };
3766+
fieldNames.push(field.name);
3767+
}
3768+
let isSuperset = true;
3769+
for (let k = 0; k < objKeys.length; k++) {
3770+
let found = false;
3771+
for (let f = 0; f < fieldNames.length; f++) {
3772+
if (fieldNames[f] === objKeys[k]) {
3773+
found = true;
3774+
break;
3775+
}
3776+
}
3777+
if (!found) {
3778+
isSuperset = false;
3779+
break;
3780+
}
3781+
}
3782+
if (isSuperset) {
3783+
if (ifaceInfo.fields.length === objKeys.length) {
3784+
exactMatch = part;
3785+
break;
3786+
}
3787+
if (bestMatchSize === -1 || ifaceInfo.fields.length < bestMatchSize) {
3788+
bestMatch = part;
3789+
bestMatchSize = ifaceInfo.fields.length;
3790+
}
3791+
}
37583792
}
37593793
}
37603794
}
3761-
if (bestMatch) {
3795+
if (exactMatch) {
3796+
returnTypeName = exactMatch;
3797+
} else if (bestMatch) {
37623798
returnTypeName = bestMatch;
37633799
}
37643800
}

src/codegen/types/objects/class.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1160,7 +1160,21 @@ export class ClassGenerator {
11601160
for (let ai = 0; ai < ctorLoopLimit; ai++) {
11611161
if (ai < args.length) {
11621162
const arg = args[ai];
1163+
const argBase = arg as { type: string };
1164+
let savedDeclaredIface: string | undefined = undefined;
1165+
let wrappedDeclaredIface = false;
1166+
if (argBase.type === "object" && ai < paramTypes.length) {
1167+
const tsParamType = paramTypes[ai];
1168+
if (tsParamType && this.ctx.interfaceStructGenHasInterface(tsParamType)) {
1169+
savedDeclaredIface = this.ctx.getCurrentDeclaredInterfaceType();
1170+
this.ctx.setCurrentDeclaredInterfaceType(tsParamType);
1171+
wrappedDeclaredIface = true;
1172+
}
1173+
}
11631174
const val = this.ctx.generateExpression(arg, params);
1175+
if (wrappedDeclaredIface) {
1176+
this.ctx.setCurrentDeclaredInterfaceType(savedDeclaredIface);
1177+
}
11641178
const argType = ai < paramLLVMTypes.length ? paramLLVMTypes[ai] : "double";
11651179
if (argType === "double") {
11661180
argParts.push(argType + " " + this.ctx.ensureDouble(val));
@@ -1257,9 +1271,22 @@ export class ClassGenerator {
12571271
methodName === "json" &&
12581272
className === "Context" &&
12591273
!this.ctx.isStringExpression(arg);
1274+
let savedDeclaredIfaceM: string | undefined = undefined;
1275+
let wrappedDeclaredIfaceM = false;
1276+
if (argTyped.type === "object" && ai < paramTypes.length && !autoSerialize) {
1277+
const tsParamTypeM = paramTypes[ai];
1278+
if (tsParamTypeM && this.ctx.interfaceStructGenHasInterface(tsParamTypeM)) {
1279+
savedDeclaredIfaceM = this.ctx.getCurrentDeclaredInterfaceType();
1280+
this.ctx.setCurrentDeclaredInterfaceType(tsParamTypeM);
1281+
wrappedDeclaredIfaceM = true;
1282+
}
1283+
}
12601284
const val = autoSerialize
12611285
? this.ctx.jsonGen.generateStringifyExpr(arg, params)
12621286
: this.ctx.generateExpression(arg, params);
1287+
if (wrappedDeclaredIfaceM) {
1288+
this.ctx.setCurrentDeclaredInterfaceType(savedDeclaredIfaceM);
1289+
}
12631290
this.ctx.setExpectedCallbackParamType(null);
12641291

12651292
let argType = "double";
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
interface Config {
2+
name: string;
3+
port: number;
4+
host: string;
5+
}
6+
7+
function boot(cfg: Config): void {
8+
if (cfg.name === "n1" && cfg.host === "h1" && cfg.port === 99) {
9+
console.log("TEST_PASSED");
10+
}
11+
}
12+
13+
function main(): void {
14+
boot({ host: "h1", port: 99, name: "n1" });
15+
}
16+
17+
main();
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
interface Point {
2+
x: number;
3+
y: number;
4+
}
5+
6+
class Vec {
7+
dx: number;
8+
dy: number;
9+
constructor(p: Point) {
10+
this.dx = p.x;
11+
this.dy = p.y;
12+
}
13+
dot(other: Point): number {
14+
return this.dx * other.x + this.dy * other.y;
15+
}
16+
}
17+
18+
function main(): void {
19+
const v = new Vec({ y: 3, x: 4 });
20+
const r = v.dot({ y: 10, x: 5 });
21+
if (v.dx === 4 && v.dy === 3 && r === 4 * 5 + 3 * 10) {
22+
console.log("TEST_PASSED");
23+
}
24+
}
25+
26+
main();
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
interface Big {
2+
a: number;
3+
b: number;
4+
c: number;
5+
}
6+
7+
interface Small {
8+
a: number;
9+
}
10+
11+
function pick(flag: boolean): Big | Small {
12+
if (flag) {
13+
return { a: 1, b: 2, c: 3 };
14+
}
15+
return { a: 7 };
16+
}
17+
18+
function main(): void {
19+
const big = pick(true) as Big;
20+
const small = pick(false) as Small;
21+
if (big.a === 1 && big.b === 2 && big.c === 3 && small.a === 7) {
22+
console.log("TEST_PASSED");
23+
}
24+
}
25+
26+
main();

0 commit comments

Comments
 (0)