From 80cde9f75853831c69c9fe748c26b7bb89185ed4 Mon Sep 17 00:00:00 2001 From: cs01 Date: Sun, 8 Mar 2026 11:04:31 -0700 Subject: [PATCH] fix: use getAllInterfaceFields instead of .fields to include inherited interface fields --- src/codegen/expressions/access/member.ts | 34 +++++++------- .../infrastructure/generator-context.ts | 5 ++ .../infrastructure/interface-allocator.ts | 5 +- .../infrastructure/variable-allocator.ts | 47 +++++++++++-------- src/codegen/llvm-generator.ts | 9 +++- src/codegen/types/objects/class.ts | 5 +- .../fixtures/interfaces/interface-extends.ts | 27 +++++++++++ 7 files changed, 91 insertions(+), 41 deletions(-) create mode 100644 tests/fixtures/interfaces/interface-extends.ts diff --git a/src/codegen/expressions/access/member.ts b/src/codegen/expressions/access/member.ts index 991b30e7..56c2abdd 100644 --- a/src/codegen/expressions/access/member.ts +++ b/src/codegen/expressions/access/member.ts @@ -156,6 +156,7 @@ export interface MemberAccessGeneratorContext { name: string, ): { keys: string[]; types: string[]; tsTypes: string[] } | null; getInterfaceDeclByName(name: string): InterfaceDeclaration | null; + getAllInterfaceFields(iface: InterfaceDeclaration): InterfaceField[]; isTypeAlias(name: string): boolean; getTypeAliasCommonProperties( name: string, @@ -1090,13 +1091,12 @@ export class MemberAccessGenerator { const keys: string[] = []; const tsTypes: string[] = []; const types: string[] = []; - if (interfaceDef.fields) { - for (let i = 0; i < interfaceDef.fields.length; i++) { - const f = interfaceDef.fields[i] as { name: string; type: string }; - keys.push(stripOptional(f.name)); - tsTypes.push(f.type); - types.push(tsTypeToLlvm(f.type)); - } + const allFields = this.ctx.getAllInterfaceFields(interfaceDef); + for (let i = 0; i < allFields.length; i++) { + const f = allFields[i] as { name: string; type: string }; + keys.push(stripOptional(f.name)); + tsTypes.push(f.type); + types.push(tsTypeToLlvm(f.type)); } this.ctx.setJsonObjectMetadata(register, { keys, types, tsTypes, interfaceType: undefined }); } else if (lookupType === "Expression" || lookupType === "Statement") { @@ -1626,10 +1626,11 @@ export class MemberAccessGenerator { const interfaceDef = interfaceDefResult as InterfaceDeclaration; if (!interfaceDef.fields) return null; + const allFields1628 = this.ctx.getAllInterfaceFields(interfaceDef); let propIndex = -1; let propTsType: string | undefined; - for (let i = 0; i < interfaceDef.fields.length; i++) { - const f = interfaceDef.fields[i] as { name: string; type: string }; + for (let i = 0; i < allFields1628.length; i++) { + const f = allFields1628[i] as { name: string; type: string }; const fName = stripOptional(f.name); if (fName === expr.property) { propIndex = i; @@ -2326,12 +2327,13 @@ export class MemberAccessGenerator { if (!interfaceDefResult) return null; const interfaceDef = interfaceDefResult as InterfaceDeclaration; if (!interfaceDef.fields) return null; + const allFields2328 = this.ctx.getAllInterfaceFields(interfaceDef); const objPtr = this.ctx.generateExpression(expr.object, params); let propIndex = -1; - for (let i = 0; i < interfaceDef.fields.length; i++) { - const f = interfaceDef.fields[i] as { name: string; type: string }; + for (let i = 0; i < allFields2328.length; i++) { + const f = allFields2328[i] as { name: string; type: string }; if (f.name === expr.property) { propIndex = i; break; @@ -2339,8 +2341,8 @@ export class MemberAccessGenerator { } if (propIndex === -1) { const fieldNames: string[] = []; - for (let i = 0; i < interfaceDef.fields.length; i++) { - const field = interfaceDef.fields[i] as { name: string; type: string }; + for (let i = 0; i < allFields2328.length; i++) { + const field = allFields2328[i] as { name: string; type: string }; fieldNames.push(field.name); } throw new Error( @@ -2348,11 +2350,11 @@ export class MemberAccessGenerator { ); } - const propField = interfaceDef.fields[propIndex] as { name: string; type: string }; + const propField = allFields2328[propIndex] as { name: string; type: string }; const propType = tsTypeToLlvm(propField.type); const structTypes: string[] = []; - for (let i = 0; i < interfaceDef.fields.length; i++) { - const field = interfaceDef.fields[i] as { name: string; type: string }; + for (let i = 0; i < allFields2328.length; i++) { + const field = allFields2328[i] as { name: string; type: string }; structTypes.push(tsTypeToLlvm(field.type)); } const structType = `{ ${structTypes.join(", ")} }`; diff --git a/src/codegen/infrastructure/generator-context.ts b/src/codegen/infrastructure/generator-context.ts index 4c77ca34..e64a09fe 100644 --- a/src/codegen/infrastructure/generator-context.ts +++ b/src/codegen/infrastructure/generator-context.ts @@ -28,6 +28,7 @@ import { MapNode, SetNode, InterfaceDeclaration, + InterfaceField, FunctionNode, ClassNode, TypeAliasDeclaration, @@ -627,6 +628,7 @@ export interface IGeneratorContext { name: string, ): { keys: string[]; types: string[]; tsTypes: string[] } | null; getInterfaceDeclByName(name: string): InterfaceDeclaration | null; + getAllInterfaceFields(iface: InterfaceDeclaration): InterfaceField[]; isTypeAlias(name: string): boolean; getTypeAliasCommonProperties( name: string, @@ -1113,6 +1115,9 @@ export class MockGeneratorContext implements IGeneratorContext { getInterfaceDeclByName(_name: string): InterfaceDeclaration | null { return null; } + getAllInterfaceFields(iface: InterfaceDeclaration): InterfaceField[] { + return iface.fields; + } isTypeAlias(_name: string): boolean { return false; } diff --git a/src/codegen/infrastructure/interface-allocator.ts b/src/codegen/infrastructure/interface-allocator.ts index d811badd..ad896c1a 100644 --- a/src/codegen/infrastructure/interface-allocator.ts +++ b/src/codegen/infrastructure/interface-allocator.ts @@ -277,8 +277,9 @@ export class InterfaceAllocator { const keys: string[] = []; const types: string[] = []; const tsTypes: string[] = []; - for (let i = 0; i < interfaceDef.fields.length; i++) { - const field = interfaceDef.fields[i] as { name: string; type: string }; + const allFields = this.getAllInterfaceFields(interfaceDef); + for (let i = 0; i < allFields.length; i++) { + const field = allFields[i] as { name: string; type: string }; keys.push(stripOptional(field.name)); types.push(this.convertTsType(field.type)); tsTypes.push(field.type); diff --git a/src/codegen/infrastructure/variable-allocator.ts b/src/codegen/infrastructure/variable-allocator.ts index acdb58ca..b4121516 100644 --- a/src/codegen/infrastructure/variable-allocator.ts +++ b/src/codegen/infrastructure/variable-allocator.ts @@ -337,7 +337,7 @@ export class VariableAllocator { return ret; } - private getAllInterfaceFields(iface: InterfaceDeclaration): InterfaceField[] { + public getAllInterfaceFields(iface: InterfaceDeclaration): InterfaceField[] { return this.interfaceAlloc.getAllInterfaceFields(iface); } @@ -651,8 +651,9 @@ export class VariableAllocator { const keys: string[] = []; const types: string[] = []; const tsTypes: string[] = []; - for (let fi = 0; fi < interfaceDef.fields.length; fi++) { - const fieldRaw = interfaceDef.fields[fi]; + const allFields = this.getAllInterfaceFields(interfaceDef); + for (let fi = 0; fi < allFields.length; fi++) { + const fieldRaw = allFields[fi]; if (!fieldRaw) continue; const field = fieldRaw as { name: string; type: string }; if (!field.name || !field.type) continue; @@ -1012,8 +1013,9 @@ export class VariableAllocator { } else { const interfaceDefResult = this.getInterface(interfaceName); const interfaceDef = interfaceDefResult as InterfaceDeclaration; - for (let i = 0; i < interfaceDef.fields.length; i++) { - const field = interfaceDef.fields[i] as { name: string; type: string }; + const allFields = this.getAllInterfaceFields(interfaceDef); + for (let i = 0; i < allFields.length; i++) { + const field = allFields[i] as { name: string; type: string }; keys.push(stripOptional(field.name)); types.push(this.convertTsType(field.type)); tsTypes.push(field.type); @@ -1108,8 +1110,9 @@ export class VariableAllocator { } else { const interfaceDefResult = this.getInterface(interfaceName); const interfaceDef = interfaceDefResult as InterfaceDeclaration; - for (let i = 0; i < interfaceDef.fields.length; i++) { - const field = interfaceDef.fields[i] as { name: string; type: string }; + const allFields = this.getAllInterfaceFields(interfaceDef); + for (let i = 0; i < allFields.length; i++) { + const field = allFields[i] as { name: string; type: string }; keys.push(stripOptional(field.name)); types.push(this.convertTsType(field.type)); tsTypes.push(field.type); @@ -1153,8 +1156,9 @@ export class VariableAllocator { const interfaceDefResult = this.getInterface(elementType); if (interfaceDefResult) { const interfaceDef = interfaceDefResult as InterfaceDeclaration; - for (let i = 0; i < interfaceDef.fields.length; i++) { - const field = interfaceDef.fields[i] as { name: string; type: string }; + const allFields = this.getAllInterfaceFields(interfaceDef); + for (let i = 0; i < allFields.length; i++) { + const field = allFields[i] as { name: string; type: string }; elementKeys.push(stripOptional(field.name)); elementTypes.push(this.convertTsType(field.type)); elementTsTypes.push(field.type); @@ -1264,8 +1268,9 @@ export class VariableAllocator { const keys: string[] = []; const types: string[] = []; const tsTypes: string[] = []; - for (let i = 0; i < interfaceDef.fields.length; i++) { - const field = interfaceDef.fields[i] as { name: string; type: string }; + const allFields = this.getAllInterfaceFields(interfaceDef); + for (let i = 0; i < allFields.length; i++) { + const field = allFields[i] as { name: string; type: string }; keys.push(stripOptional(field.name)); types.push(this.convertTsType(field.type)); tsTypes.push(field.type); @@ -1656,8 +1661,9 @@ export class VariableAllocator { const keys: string[] = []; const tsTypes: string[] = []; const types: string[] = []; - for (let i = 0; i < interfaceDef.fields.length; i++) { - const field = interfaceDef.fields[i] as { name: string; type: string }; + const allFields = this.getAllInterfaceFields(interfaceDef); + for (let i = 0; i < allFields.length; i++) { + const field = allFields[i] as { name: string; type: string }; keys.push(stripOptional(field.name)); tsTypes.push(field.type); types.push(this.convertTsTypeJson(field.type)); @@ -1691,8 +1697,9 @@ export class VariableAllocator { keys = []; types = []; tsTypes = []; - for (let i = 0; i < interfaceDef.fields.length; i++) { - const field = interfaceDef.fields[i] as { name: string; type: string }; + const allFields = this.getAllInterfaceFields(interfaceDef); + for (let i = 0; i < allFields.length; i++) { + const field = allFields[i] as { name: string; type: string }; keys.push(stripOptional(field.name)); types.push(this.convertTsType(field.type)); tsTypes.push(field.type); @@ -2252,8 +2259,9 @@ export class VariableAllocator { const keys: string[] = []; const types: string[] = []; const tsTypes: string[] = []; - for (let fi = 0; fi < interfaceDef.fields.length; fi++) { - const field = interfaceDef.fields[fi] as { name: string; type: string }; + const allFields = this.getAllInterfaceFields(interfaceDef); + for (let fi = 0; fi < allFields.length; fi++) { + const field = allFields[fi] as { name: string; type: string }; keys.push(stripOptional(field.name)); types.push(this.convertTsType(field.type)); tsTypes.push(field.type); @@ -2652,8 +2660,9 @@ export class VariableAllocator { const keys: string[] = []; const types: string[] = []; const tsTypes: string[] = []; - for (let i = 0; i < interfaceDef.fields.length; i++) { - const field = interfaceDef.fields[i] as { name: string; type: string }; + const allFields = this.getAllInterfaceFields(interfaceDef); + for (let i = 0; i < allFields.length; i++) { + const field = allFields[i] as { name: string; type: string }; keys.push(stripOptional(field.name)); types.push(this.convertTsType(field.type)); tsTypes.push(field.type); diff --git a/src/codegen/llvm-generator.ts b/src/codegen/llvm-generator.ts index 276bfb0e..bba64007 100644 --- a/src/codegen/llvm-generator.ts +++ b/src/codegen/llvm-generator.ts @@ -731,6 +731,10 @@ export class LLVMGenerator extends BaseGenerator implements IGeneratorContext { return null; } + public getAllInterfaceFields(iface: InterfaceDeclaration): InterfaceField[] { + return this.varAllocator.getAllInterfaceFields(iface); + } + public isTypeAlias(name: string): boolean { if (!this.ast || !this.ast.typeAliases) return false; for (let i = 0; i < this.ast.typeAliases.length; i++) { @@ -2228,8 +2232,9 @@ export class LLVMGenerator extends BaseGenerator implements IGeneratorContext { const keys: string[] = []; const tsTypes: string[] = []; const types: string[] = []; - for (let i = 0; i < interfaceDef.fields.length; i++) { - const field = interfaceDef.fields[i] as { name: string; type: string }; + const allFields = this.getAllInterfaceFields(interfaceDef); + for (let i = 0; i < allFields.length; i++) { + const field = allFields[i] as { name: string; type: string }; keys.push(stripOptional(field.name)); tsTypes.push(field.type); types.push(this.tsTypeToLlvmJsonWithEnums(field.type)); diff --git a/src/codegen/types/objects/class.ts b/src/codegen/types/objects/class.ts index ff2e30c4..fa1fa5ef 100644 --- a/src/codegen/types/objects/class.ts +++ b/src/codegen/types/objects/class.ts @@ -1442,8 +1442,9 @@ export class ClassGenerator { const keys: string[] = []; const types: string[] = []; const tsTypes: string[] = []; - for (let fi = 0; fi < interfaceDef.fields.length; fi++) { - const f = interfaceDef.fields[fi] as { name: string; type: string }; + const allFields = this.ctx.getAllInterfaceFields(interfaceDef); + for (let fi = 0; fi < allFields.length; fi++) { + const f = allFields[fi] as { name: string; type: string }; keys.push(stripOptional(f.name)); types.push(this.fieldTypeToLlvm(f.type)); tsTypes.push(f.type); diff --git a/tests/fixtures/interfaces/interface-extends.ts b/tests/fixtures/interfaces/interface-extends.ts new file mode 100644 index 00000000..a0cc33d8 --- /dev/null +++ b/tests/fixtures/interfaces/interface-extends.ts @@ -0,0 +1,27 @@ +interface Base { + id: number; + name: string; +} + +interface Extended extends Base { + extra: string; +} + +function testInterfaceExtends(): void { + const obj: Extended = { id: 42, name: "Alice", extra: "bonus" }; + if (obj.id !== 42) { + console.log("FAIL: id should be 42"); + process.exit(1); + } + if (obj.name !== "Alice") { + console.log("FAIL: name should be Alice"); + process.exit(1); + } + if (obj.extra !== "bonus") { + console.log("FAIL: extra should be bonus"); + process.exit(1); + } + console.log("TEST_PASSED"); +} + +testInterfaceExtends();