From eee739d61dd350c609253d3c07d1678de098ff45 Mon Sep 17 00:00:00 2001 From: Chad Smith Date: Thu, 19 Feb 2026 08:06:29 -0800 Subject: [PATCH 01/10] add canonicalTypeToLlvm and rewrite existing type mappers as thin wrappers --- src/codegen/infrastructure/type-system.ts | 112 ++++++++++++---------- 1 file changed, 63 insertions(+), 49 deletions(-) diff --git a/src/codegen/infrastructure/type-system.ts b/src/codegen/infrastructure/type-system.ts index 2ee416e5..640d97b5 100644 --- a/src/codegen/infrastructure/type-system.ts +++ b/src/codegen/infrastructure/type-system.ts @@ -122,27 +122,7 @@ export function createFloatType(): ResolvedType { } export function tsTypeToLlvm(tsType: string): string { - if (tsType === null || tsType === undefined || tsType === '') return 'i8*'; - if (tsType === 'string') return 'i8*'; - if (tsType === 'number') return 'double'; - if (tsType === 'boolean') return 'double'; - if (tsType === 'void') return 'void'; - if (tsType === 'string[]') return '%StringArray*'; - if (tsType === 'number[]' || tsType === 'boolean[]') return '%Array*'; - if (tsType === 'Uint8Array') return '%Uint8Array*'; - if (tsType.endsWith('[]')) return '%ObjectArray*'; - if (tsType.startsWith('Set<')) return '%StringSet*'; - if (tsType.startsWith('Map<')) return '%StringMap*'; - if (tsType.startsWith("'") || tsType.startsWith('"')) return 'i8*'; - if (tsType.indexOf(' | ') !== -1) { - const parts = tsType.split(' | '); - for (let i = 0; i < parts.length; i++) { - const part = parts[i].trim(); - if (part === 'null' || part === 'undefined') continue; - return tsTypeToLlvm(part); - } - } - return 'i8*'; + return canonicalTypeToLlvm(tsType, { mode: 'default' }); } export function resolvedTypeToLlvm(rt: ResolvedType): string { @@ -163,16 +143,71 @@ export function resolvedTypeToLlvm(rt: ResolvedType): string { return 'i8*'; } -export function tsTypeToLlvmJson(tsType: string): string { - if (tsType === null || tsType === undefined || tsType === '') return 'i8*'; +export type TypeMappingMode = 'default' | 'param' | 'return' | 'struct_field' | 'json'; + +export interface TypeMappingOptions { + mode: TypeMappingMode; + isEnum?: boolean; + isInterface?: boolean; + fieldName?: string; +} + +export function canonicalTypeToLlvm(tsType: string, options: TypeMappingOptions): string { + if (tsType === null || tsType === undefined || tsType === '') { + if (options.mode === 'return') return 'double'; + return 'i8*'; + } + + if (options.fieldName === 'nodePtr' || options.fieldName === 'treePtr') return 'i8*'; + + if (options.mode === 'param') { + if (tsType === 'any' || tsType === 'unknown') { + throw new Error(`Parameter type '${tsType}' is not allowed — add explicit type annotations or fix the parser`); + } + } + + if (options.isEnum) return 'double'; + if (tsType === 'string') return 'i8*'; - if (tsType === 'number') return 'double'; - if (tsType === 'boolean') return 'double'; + if (tsType === 'number' || tsType === 'boolean') return 'double'; + if (tsType === 'void') return 'void'; if (tsType === 'string[]') return '%StringArray*'; - if (tsType === 'number[]') return '%Array*'; + if (tsType === 'number[]' || tsType === 'boolean[]') return '%Array*'; + if (tsType === 'Uint8Array') return '%Uint8Array*'; + if (tsType.endsWith('[]')) return '%ObjectArray*'; + if (tsType.startsWith('Set<')) return '%StringSet*'; + if (tsType.startsWith('Map<')) return '%StringMap*'; + if (tsType.startsWith("'") || tsType.startsWith('"')) return 'i8*'; + + if (tsType.indexOf(' | ') !== -1) { + const parts = tsType.split(' | '); + for (let i = 0; i < parts.length; i++) { + const part = parts[i].trim(); + if (part === 'null' || part === 'undefined') continue; + return canonicalTypeToLlvm(part, options); + } + } + + if (options.isInterface && (options.mode === 'param' || options.mode === 'struct_field')) { + return `%${tsType}*`; + } + + if (options.mode === 'return') { + if (tsType !== 'number' && tsType !== 'boolean') return 'i8*'; + return 'double'; + } + + if (options.mode === 'json') { + return 'i8*'; + } + return 'i8*'; } +export function tsTypeToLlvmJson(tsType: string): string { + return canonicalTypeToLlvm(tsType, { mode: 'json' }); +} + export function checkUnsafeUnionType(typeStr: string): string | null { if (!typeStr || typeStr.indexOf(' | ') === -1) return null; @@ -308,35 +343,14 @@ export function mapParamTypeToLLVM( paramIsEnum: boolean, paramIsInterface: boolean ): string { - if (paramName === 'nodePtr' || paramName === 'treePtr') return 'i8*'; - if (paramType === 'any' || paramType === 'unknown') { - throw new Error(`Parameter type '${paramType}' is not allowed — add explicit type annotations or fix the parser`); - } - if (paramIsEnum) return 'double'; - if (paramType === 'string') return 'i8*'; - if (paramType === 'number' || paramType === 'boolean') return 'double'; - if (paramType === 'string[]') return '%StringArray*'; - if (paramType === 'number[]' || paramType === 'boolean[]') return '%Array*'; - if (paramType === 'Uint8Array') return '%Uint8Array*'; - if (paramType.endsWith('[]')) return '%ObjectArray*'; - if (paramType.startsWith('Set<')) return '%StringSet*'; - if (paramType.startsWith('Map<')) return '%StringMap*'; - if (paramIsInterface) return `%${paramType}*`; - return 'i8*'; + return canonicalTypeToLlvm(paramType, { mode: 'param', isEnum: paramIsEnum, isInterface: paramIsInterface, fieldName: paramName }); } export function mapReturnTypeToLLVM( returnType: string, returnIsEnum: boolean ): string { - if (returnType === 'string') return 'i8*'; - if (returnType === 'void') return 'void'; - if (returnType === 'string[]') return '%StringArray*'; - if (returnType === 'number[]' || returnType === 'boolean[]') return '%Array*'; - if (returnType === 'Uint8Array') return '%Uint8Array*'; - if (returnType.endsWith('[]')) return '%ObjectArray*'; - if (returnType !== '' && returnType !== 'number' && returnType !== 'boolean' && !returnIsEnum) return 'i8*'; - return 'double'; + return canonicalTypeToLlvm(returnType, { mode: 'return', isEnum: returnIsEnum }); } function parseGenericTypeString(s: string): { base: string; params: string } | null { From b102091398f9240da9b3287e152ad13678421bd9 Mon Sep 17 00:00:00 2001 From: Chad Smith Date: Thu, 19 Feb 2026 08:08:00 -0800 Subject: [PATCH 02/10] fix paramIsInterface hardcoded false in calls.ts to use real interface check --- src/codegen/expressions/calls.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/codegen/expressions/calls.ts b/src/codegen/expressions/calls.ts index 513049c3..f34fbeb3 100644 --- a/src/codegen/expressions/calls.ts +++ b/src/codegen/expressions/calls.ts @@ -524,7 +524,7 @@ export class CallExpressionGenerator { for (let i = 0; i < func.paramTypes.length; i++) { const p = func.paramTypes[i] as string; const paramName = func.params[i] || ''; - paramTypes.push(mapParamTypeToLLVM(p, paramName, this.ctx.isEnumType(stripNullable(p)), false)); + paramTypes.push(mapParamTypeToLLVM(p, paramName, this.ctx.isEnumType(stripNullable(p)), this.ctx.interfaceStructGenHasInterface(stripNullable(p)))); } } else { const funcNode = this.getFunctionFromAST(expr.name); From 0f9b61bb7e053cc5a39a7f47f55dcc9109b94cb5 Mon Sep 17 00:00:00 2001 From: Chad Smith Date: Thu, 19 Feb 2026 08:09:02 -0800 Subject: [PATCH 03/10] fix getClassType returning i32* instead of i8* --- src/codegen/infrastructure/type-context.ts | 2 +- tests/unit/type-context.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/codegen/infrastructure/type-context.ts b/src/codegen/infrastructure/type-context.ts index 2777cc4a..05a1a75c 100644 --- a/src/codegen/infrastructure/type-context.ts +++ b/src/codegen/infrastructure/type-context.ts @@ -84,7 +84,7 @@ export class TypeContext { } getClassType(name: string): ResolvedType { - return this.intern(name, 'i32*'); + return this.intern(name, 'i8*'); } getNullableType(base: ResolvedType): ResolvedType { diff --git a/tests/unit/type-context.test.ts b/tests/unit/type-context.test.ts index dfb5c50f..936abb2b 100644 --- a/tests/unit/type-context.test.ts +++ b/tests/unit/type-context.test.ts @@ -82,7 +82,7 @@ describe('TypeContext', () => { const cls1 = ctx.getClassType('MyClass'); const cls2 = ctx.getClassType('MyClass'); assert.strictEqual(cls1, cls2); - assert.strictEqual(cls1.cachedLlvmType, 'i32*'); + assert.strictEqual(cls1.cachedLlvmType, 'i8*'); }); it('should resolve type strings', () => { From 0eed6d99e219168b574bd42da08d6ac1a528a469 Mon Sep 17 00:00:00 2001 From: Chad Smith Date: Thu, 19 Feb 2026 08:18:19 -0800 Subject: [PATCH 04/10] replace member access if/else dispatch chain with handler registry --- src/codegen/expressions/access/member.ts | 190 ++++++++++++----------- 1 file changed, 103 insertions(+), 87 deletions(-) diff --git a/src/codegen/expressions/access/member.ts b/src/codegen/expressions/access/member.ts index 236df401..8cff0b53 100644 --- a/src/codegen/expressions/access/member.ts +++ b/src/codegen/expressions/access/member.ts @@ -157,6 +157,8 @@ export interface MemberAccessGeneratorContext { getTargetArch(): string; } +export type MemberAccessHandlerFn = (expr: MemberAccessNode, ctx: MemberAccessGeneratorContext, params: string[]) => string | null; + /** * MemberAccessGenerator * @@ -170,7 +172,104 @@ export interface MemberAccessGeneratorContext { * - TypeScript interface-based property access */ export class MemberAccessGenerator { - constructor(private ctx: MemberAccessGeneratorContext) {} + private handlers: MemberAccessHandlerFn[]; + + constructor(private ctx: MemberAccessGeneratorContext) { + this.handlers = this.buildHandlers(); + } + + private buildHandlers(): MemberAccessHandlerFn[] { + const handlers: MemberAccessHandlerFn[] = []; + + handlers.push((expr, ctx, _params) => this.handleEnumMemberAccess(expr)); + + handlers.push((expr, ctx, _params) => this.handleTypedJsonStructAccess(expr)); + + handlers.push((expr, ctx, _params) => { + if (isProcessArgv(expr)) return this.handleProcessArgv(); + return null; + }); + + handlers.push((expr, ctx, _params) => { + if (isProcessPlatform(expr)) { + const platformStr = ctx.getTargetOS() || process.platform; + return ctx.stringGen.doCreateStringConstant(platformStr); + } + return null; + }); + + handlers.push((expr, ctx, _params) => { + if (isProcessEnvAccess(expr)) return this.handleProcessEnvAccess(expr); + return null; + }); + + handlers.push((expr, ctx, _params) => handleProcessSimpleProperty(ctx, expr)); + + handlers.push((expr, ctx, params) => this.handleClassPropertyAccess(expr, params)); + + handlers.push((expr, ctx, params) => { + const exprObjBase = expr.object as ExprBase; + const exprObjType = exprObjBase ? exprObjBase.type : null; + if (exprObjType === null || exprObjType === undefined) return '0.0'; + if (exprObjType === 'variable' && ctx.symbolTable.isJSON((expr.object as VariableNode).name)) { + return this.handleJsonPropertyAccess(expr, params); + } + return null; + }); + + handlers.push((expr, ctx, params) => { + const exprObjBase = expr.object as ExprBase; + const exprObjType = exprObjBase ? exprObjBase.type : null; + if (exprObjType === 'member_access') { + const chainedResult = this.handleChainedInterfaceAccess(expr, params); + if (chainedResult !== null) return chainedResult; + const classFieldChainResult = this.handleClassFieldChainedAccess(expr, params); + if (classFieldChainResult !== null) return classFieldChainResult; + const nestedResult = this.handleNestedJsonAccess(expr, params); + if (nestedResult !== null) return nestedResult; + } + return null; + }); + + handlers.push((expr, ctx, params) => { + const exprObjBase = expr.object as ExprBase; + const exprObjType = exprObjBase ? exprObjBase.type : null; + if (exprObjType === 'index_access') return this.handleIndexAccessPropertyAccess(expr, params); + return null; + }); + + handlers.push((expr, ctx, params) => { + const exprObjBase = expr.object as ExprBase; + const exprObjType = exprObjBase ? exprObjBase.type : null; + if (exprObjType === 'type_assertion') return this.handleTypeAssertionPropertyAccess(expr, params); + return null; + }); + + handlers.push((expr, ctx, params) => { + const exprObjBase = expr.object as ExprBase; + const exprObjType = exprObjBase ? exprObjBase.type : null; + if (exprObjType === 'method_call') return this.handleMethodCallResultPropertyAccess(expr, params); + return null; + }); + + handlers.push((expr, ctx, params) => this.handleObjectPropertyAccess(expr, params)); + + handlers.push((expr, ctx, params) => { + if (expr.property === 'length') return this.handleLengthProperty(expr, params); + return null; + }); + + handlers.push((expr, ctx, params) => { + if (expr.property === 'size') return this.handleSizeProperty(expr, params); + return null; + }); + + handlers.push((expr, ctx, _params) => this.handleResponseProperty(expr)); + + handlers.push((expr, ctx, _params) => this.handleStatProperty(expr)); + + return handlers; + } private hasObjectInfo(name: string): boolean { if (!this.ctx.symbolTable.isObject(name) && !this.ctx.symbolTable.isJSON(name)) return false; @@ -314,94 +413,11 @@ export class MemberAccessGenerator { return this.ctx.generateExpression(expr.object, params); } - const enumResult = this.handleEnumMemberAccess(expr); - if (enumResult !== null) return enumResult; - - const typedJsonResult = this.handleTypedJsonStructAccess(expr); - if (typedJsonResult !== null) return typedJsonResult; - - if (this.isProcessArgv(expr)) { - return this.handleProcessArgv(); - } - - if (this.isProcessPlatform(expr)) { - const platformStr = this.ctx.getTargetOS() || process.platform; - return this.ctx.stringGen.doCreateStringConstant(platformStr); - } - - if (this.isProcessEnvAccess(expr)) { - return this.handleProcessEnvAccess(expr); - } - - const processSimple = this.handleProcessSimpleProperty(expr); - if (processSimple !== null) return processSimple; - - const classResult = this.handleClassPropertyAccess(expr, params); - if (classResult !== null) return classResult; - - const exprObjBase = expr.object as ExprBase; - const exprObjType = exprObjBase ? exprObjBase.type : null; - if (exprObjType === null || exprObjType === undefined) { - return '0.0'; - } - - if (exprObjType === 'variable' && this.ctx.symbolTable.isJSON((expr.object as VariableNode).name)) { - return this.handleJsonPropertyAccess(expr, params); - } - - // Handle nested JSON object access - if (exprObjType === 'member_access') { - const chainedResult = this.handleChainedInterfaceAccess(expr, params); - if (chainedResult !== null) return chainedResult; - - const classFieldChainResult = this.handleClassFieldChainedAccess(expr, params); - if (classFieldChainResult !== null) return classFieldChainResult; - - const nestedResult = this.handleNestedJsonAccess(expr, params); - if (nestedResult !== null) return nestedResult; - } - - // Handle indexed access to object array elements (e.g., arr[i].property) - if (exprObjType === 'index_access') { - const indexResult = this.handleIndexAccessPropertyAccess(expr, params); - if (indexResult !== null) return indexResult; - } - - // Handle type assertion property access (e.g., (expr as Type).property) - if (exprObjType === 'type_assertion') { - const assertResult = this.handleTypeAssertionPropertyAccess(expr, params); - if (assertResult !== null) return assertResult; - } - - // Handle method call result property access (e.g., map.get(key)?.property) - if (exprObjType === 'method_call') { - const methodResult = this.handleMethodCallResultPropertyAccess(expr, params); - if (methodResult !== null) return methodResult; - } - - // Handle regular object property access - const objResult = this.handleObjectPropertyAccess(expr, params); - if (objResult !== null) return objResult; - - // Handle .length property - if (expr.property === 'length') { - return this.handleLengthProperty(expr, params); - } - - // Handle .size property (Map/Set) - if (expr.property === 'size') { - const sizeResult = this.handleSizeProperty(expr, params); - if (sizeResult !== null) return sizeResult; + for (let i = 0; i < this.handlers.length; i++) { + const result = this.handlers[i](expr, this.ctx, params); + if (result !== null) return result; } - // Handle Response properties - const responseResult = this.handleResponseProperty(expr); - if (responseResult !== null) return responseResult; - - const statResult = this.handleStatProperty(expr); - if (statResult !== null) return statResult; - - // Handle TypeScript parameter property access return this.handleParameterPropertyAccess(expr, params); } From 3079351e0367e503dc479f641e4f1b29e4fceb0b Mon Sep 17 00:00:00 2001 From: Chad Smith Date: Thu, 19 Feb 2026 08:25:46 -0800 Subject: [PATCH 05/10] add sourceFile field to FunctionNode and ClassNode in AST and parsers --- src/ast/types.ts | 4 +++- src/native-compiler-lib.ts | 2 +- src/parser-native/transformer.ts | 18 +++++++++++------- src/parser-ts/transformer.ts | 4 ++++ 4 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/ast/types.ts b/src/ast/types.ts index 1fd5f9af..4042f7f5 100644 --- a/src/ast/types.ts +++ b/src/ast/types.ts @@ -343,6 +343,7 @@ export interface FunctionNode { async?: boolean; parameters?: FunctionParameter[]; loc?: SourceLocation; + sourceFile?: string; } export interface ClassMethod { @@ -367,9 +368,10 @@ export interface ClassNode { name: string; extends?: string; implements?: string[]; - fields: ClassField[]; // Explicit field declarations + fields: ClassField[]; methods: ClassMethod[]; loc?: SourceLocation; + sourceFile?: string; } export interface ImportSpecifier { diff --git a/src/native-compiler-lib.ts b/src/native-compiler-lib.ts index 8aadfbb5..7a611a30 100644 --- a/src/native-compiler-lib.ts +++ b/src/native-compiler-lib.ts @@ -220,7 +220,7 @@ export function compileMultiFile(entryFile: string, compiledFiles: string[]): AS if (verbose) { console.log('Parsing: ' + absPath); } const code = fs.readFileSync(absPath); const tree = parseSource(code); - const ast = transformTree(tree); + const ast = transformTree(tree, absPath); const mergedAST: AST = { imports: [], diff --git a/src/parser-native/transformer.ts b/src/parser-native/transformer.ts index 2cfc5450..81d2d852 100644 --- a/src/parser-native/transformer.ts +++ b/src/parser-native/transformer.ts @@ -52,11 +52,11 @@ function getExprType(expr: Expression | null | undefined): string { return (expr as ExprBase).type; } -export function transformTree(tree: TreeSitterTree): AST { - return transformProgram(tree.rootNode); +export function transformTree(tree: TreeSitterTree, sourceFile?: string): AST { + return transformProgram(tree.rootNode, sourceFile || ''); } -function transformProgram(node: TreeSitterNode): AST { +function transformProgram(node: TreeSitterNode, sourceFile: string): AST { const ast: AST = { imports: [], functions: [], @@ -79,14 +79,14 @@ function transformProgram(node: TreeSitterNode): AST { i = i + 1; continue; } - transformTopLevelNode(child, ast); + transformTopLevelNode(child, ast, sourceFile); i = i + 1; } return ast; } -function transformTopLevelNode(node: TreeSitterNode, ast: AST): void { +function transformTopLevelNode(node: TreeSitterNode, ast: AST, sourceFile: string): void { switch (node.type) { case 'import_statement': const importDecl = transformImportStatement(node); @@ -98,6 +98,7 @@ function transformTopLevelNode(node: TreeSitterNode, ast: AST): void { case 'function_declaration': const func = transformFunctionDeclaration(node); if (func) { + func.sourceFile = sourceFile; ast.functions.push(func); } break; @@ -105,6 +106,7 @@ function transformTopLevelNode(node: TreeSitterNode, ast: AST): void { case 'class_declaration': const cls = transformClassDeclaration(node); if (cls) { + cls.sourceFile = sourceFile; ast.classes.push(cls); } break; @@ -199,7 +201,7 @@ function transformTopLevelNode(node: TreeSitterNode, ast: AST): void { break; case 'export_statement': - handleExportStatement(node, ast); + handleExportStatement(node, ast, sourceFile); break; } } @@ -242,7 +244,7 @@ function handleExpressionStatement(node: TreeSitterNode, ast: AST): void { } } -function handleExportStatement(node: TreeSitterNode, ast: AST): void { +function handleExportStatement(node: TreeSitterNode, ast: AST, sourceFile: string): void { const nodeText = (node as NodeBase).text; const isTypeOnly = nodeText.startsWith('export type ') || nodeText.startsWith('export type{'); @@ -257,12 +259,14 @@ function handleExportStatement(node: TreeSitterNode, ast: AST): void { if (c.type === 'function_declaration') { const func = transformFunctionDeclaration(child); if (func) { + func.sourceFile = sourceFile; ast.functions.push(func); ast.exports.push({ type: 'export', declaration: func }); } } else if (c.type === 'class_declaration') { const cls = transformClassDeclaration(child); if (cls) { + cls.sourceFile = sourceFile; ast.classes.push(cls); ast.exports.push({ type: 'export', declaration: cls }); } diff --git a/src/parser-ts/transformer.ts b/src/parser-ts/transformer.ts index 75f49aa9..5c6d573a 100644 --- a/src/parser-ts/transformer.ts +++ b/src/parser-ts/transformer.ts @@ -74,6 +74,7 @@ function transformTopLevelStatement(node: ts.Statement, ast: AST, checker: ts.Ty } const func = transformFunctionDeclaration(funcDecl, checker); if (func) { + func.sourceFile = currentSourceFile ? currentSourceFile.fileName : ''; ast.functions.push(func); } break; @@ -83,6 +84,7 @@ function transformTopLevelStatement(node: ts.Statement, ast: AST, checker: ts.Ty const classDecl = node as ts.ClassDeclaration; const cls = transformClassDeclaration(classDecl, checker); if (cls) { + cls.sourceFile = currentSourceFile ? currentSourceFile.fileName : ''; ast.classes.push(cls); } break; @@ -234,11 +236,13 @@ function handleExportedDeclaration(node: ts.Statement, ast: AST, checker: ts.Typ if (ts.isFunctionDeclaration(node) && hasExportModifier(node)) { const func = transformFunctionDeclaration(node, checker); if (func) { + func.sourceFile = currentSourceFile ? currentSourceFile.fileName : ''; ast.exports.push({ type: 'export', declaration: func }); } } else if (ts.isClassDeclaration(node) && hasExportModifier(node)) { const cls = transformClassDeclaration(node, checker); if (cls) { + cls.sourceFile = currentSourceFile ? currentSourceFile.fileName : ''; ast.exports.push({ type: 'export', declaration: cls }); } } From ac893d9b0f0a08db04c892ba207ff4455f2782c3 Mon Sep 17 00:00:00 2001 From: Chad Smith Date: Thu, 19 Feb 2026 08:28:40 -0800 Subject: [PATCH 06/10] build exportedNames set and add optional sourceFile param to mangleUserName --- .../infrastructure/function-generator.ts | 2 +- .../infrastructure/generator-context.ts | 4 +- src/codegen/llvm-generator.ts | 42 ++++++++++++++++++- 3 files changed, 44 insertions(+), 4 deletions(-) diff --git a/src/codegen/infrastructure/function-generator.ts b/src/codegen/infrastructure/function-generator.ts index ca1f40b0..fb761537 100644 --- a/src/codegen/infrastructure/function-generator.ts +++ b/src/codegen/infrastructure/function-generator.ts @@ -50,7 +50,7 @@ export interface FunctionGeneratorContext { pushOutput(line: string): void; lastInstructionIsTerminator(): boolean; createEmptyStringConstant(): string; - mangleUserName(name: string): string; + mangleUserName(name: string, sourceFile?: string): string; getSubprogramDbgRef(): string; getUsesTestRunner(): boolean; ensureDouble(value: string): string; diff --git a/src/codegen/infrastructure/generator-context.ts b/src/codegen/infrastructure/generator-context.ts index 034fc72d..6d06f614 100644 --- a/src/codegen/infrastructure/generator-context.ts +++ b/src/codegen/infrastructure/generator-context.ts @@ -719,7 +719,7 @@ export interface IGeneratorContext { */ resolveImportAlias(localName: string): string; - mangleUserName(name: string): string; + mangleUserName(name: string, sourceFile?: string): string; /** * Access to class generator for field type lookups @@ -1570,7 +1570,7 @@ export class MockGeneratorContext implements IGeneratorContext { return value; } - mangleUserName(name: string): string { + mangleUserName(name: string, _sourceFile?: string): string { if (name.startsWith('__')) return name; return `_cs_${name}`; } diff --git a/src/codegen/llvm-generator.ts b/src/codegen/llvm-generator.ts index 36463563..30795781 100644 --- a/src/codegen/llvm-generator.ts +++ b/src/codegen/llvm-generator.ts @@ -928,6 +928,7 @@ export class LLVMGenerator extends BaseGenerator implements IGeneratorContext { private usesTreeSitter: boolean = false; public sourceCode: string = ''; public filename: string = ''; + private exportedNames: Set = new Set(); constructor(ast: AST, typeChecker: TypeChecker | null, options: LLVMGeneratorOptions) { super(); @@ -954,6 +955,16 @@ export class LLVMGenerator extends BaseGenerator implements IGeneratorContext { this.ast = ast; + this.exportedNames = new Set(); + if (ast.exports) { + for (let ei = 0; ei < ast.exports.length; ei++) { + const exp = ast.exports[ei]; + if (exp && exp.declaration && exp.declaration.name) { + this.exportedNames.add(exp.declaration.name); + } + } + } + // Cache all counts BEFORE storing - empty arrays become garbage after assignment this.topLevelStatementsCount = ast.topLevelStatements.length; this.topLevelExpressionsCount = ast.topLevelExpressions.length; @@ -1182,11 +1193,40 @@ export class LLVMGenerator extends BaseGenerator implements IGeneratorContext { return original || localName; } - mangleUserName(name: string): string { + mangleUserName(name: string, sourceFile?: string): string { if (name.startsWith('__')) return name; + if (sourceFile && sourceFile.length > 0 && !this.exportedNames.has(name)) { + const slug = this.fileSlug(sourceFile); + return `_cs_${slug}_${name}`; + } return `_cs_${name}`; } + private fileSlug(sourceFile: string): string { + let start = 0; + for (let i = 0; i < sourceFile.length; i++) { + if (sourceFile[i] === '/' || sourceFile[i] === '\\') { + start = i + 1; + } + } + let end = sourceFile.length; + const dotIdx = sourceFile.lastIndexOf('.'); + if (dotIdx > start) { + end = dotIdx; + } + let slug = sourceFile.substring(start, end); + let result = ''; + for (let i = 0; i < slug.length; i++) { + const ch = slug[i]; + if (ch === '-' || ch === '.') { + result += '_'; + } else { + result += ch; + } + } + return result; + } + createEmptyStringConstant(): string { return this.stringGen.doCreateStringConstant(''); } From 939f644eab6ffb3ad67046528a9d3ded82b0f5d6 Mon Sep 17 00:00:00 2001 From: Chad Smith Date: Thu, 19 Feb 2026 08:32:12 -0800 Subject: [PATCH 07/10] thread sourceFile through function definition and call sites --- src/codegen/expressions/calls.ts | 6 ++++-- src/codegen/infrastructure/function-generator.ts | 2 +- src/parser-ts/transformer.ts | 4 ---- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/codegen/expressions/calls.ts b/src/codegen/expressions/calls.ts index f34fbeb3..82599c6c 100644 --- a/src/codegen/expressions/calls.ts +++ b/src/codegen/expressions/calls.ts @@ -582,13 +582,15 @@ export class CallExpressionGenerator { } } + const calleeSourceFile = funcResult ? func.sourceFile : undefined; + if (returnType === 'void') { - this.ctx.emit(`call void @${this.ctx.mangleUserName(resolvedFuncName)}(${argsList.join(', ')})`); + this.ctx.emit(`call void @${this.ctx.mangleUserName(resolvedFuncName, calleeSourceFile)}(${argsList.join(', ')})`); return '0'; } const temp = this.ctx.nextTemp(); - this.ctx.emit(`${temp} = call ${returnType} @${this.ctx.mangleUserName(resolvedFuncName)}(${argsList.join(', ')})`); + this.ctx.emit(`${temp} = call ${returnType} @${this.ctx.mangleUserName(resolvedFuncName, calleeSourceFile)}(${argsList.join(', ')})`); this.ctx.setVariableType(temp, returnType); return temp; diff --git a/src/codegen/infrastructure/function-generator.ts b/src/codegen/infrastructure/function-generator.ts index fb761537..aeee12d0 100644 --- a/src/codegen/infrastructure/function-generator.ts +++ b/src/codegen/infrastructure/function-generator.ts @@ -200,7 +200,7 @@ export class FunctionGenerator { } } - let ir = `define ${returnType} @${this.ctx.mangleUserName(funcName)}(`; + let ir = `define ${returnType} @${this.ctx.mangleUserName(funcName, func.sourceFile)}(`; const paramStrings: string[] = []; if (hasClosure) { paramStrings.push('i8* %__env'); diff --git a/src/parser-ts/transformer.ts b/src/parser-ts/transformer.ts index 5c6d573a..75f49aa9 100644 --- a/src/parser-ts/transformer.ts +++ b/src/parser-ts/transformer.ts @@ -74,7 +74,6 @@ function transformTopLevelStatement(node: ts.Statement, ast: AST, checker: ts.Ty } const func = transformFunctionDeclaration(funcDecl, checker); if (func) { - func.sourceFile = currentSourceFile ? currentSourceFile.fileName : ''; ast.functions.push(func); } break; @@ -84,7 +83,6 @@ function transformTopLevelStatement(node: ts.Statement, ast: AST, checker: ts.Ty const classDecl = node as ts.ClassDeclaration; const cls = transformClassDeclaration(classDecl, checker); if (cls) { - cls.sourceFile = currentSourceFile ? currentSourceFile.fileName : ''; ast.classes.push(cls); } break; @@ -236,13 +234,11 @@ function handleExportedDeclaration(node: ts.Statement, ast: AST, checker: ts.Typ if (ts.isFunctionDeclaration(node) && hasExportModifier(node)) { const func = transformFunctionDeclaration(node, checker); if (func) { - func.sourceFile = currentSourceFile ? currentSourceFile.fileName : ''; ast.exports.push({ type: 'export', declaration: func }); } } else if (ts.isClassDeclaration(node) && hasExportModifier(node)) { const cls = transformClassDeclaration(node, checker); if (cls) { - cls.sourceFile = currentSourceFile ? currentSourceFile.fileName : ''; ast.exports.push({ type: 'export', declaration: cls }); } } From 3068850793a31583107a81a6fcf698591d99668d Mon Sep 17 00:00:00 2001 From: Chad Smith Date: Thu, 19 Feb 2026 08:33:53 -0800 Subject: [PATCH 08/10] refactor handler registry to use dispatchHandlers method for stage 0 compat --- src/codegen/expressions/access/member.ts | 167 +++++++++++------------ 1 file changed, 77 insertions(+), 90 deletions(-) diff --git a/src/codegen/expressions/access/member.ts b/src/codegen/expressions/access/member.ts index 8cff0b53..1bfe6632 100644 --- a/src/codegen/expressions/access/member.ts +++ b/src/codegen/expressions/access/member.ts @@ -162,113 +162,102 @@ export type MemberAccessHandlerFn = (expr: MemberAccessNode, ctx: MemberAccessGe /** * MemberAccessGenerator * - * Handles property access expressions: - * - process.argv (special case) - * - Class instance properties (this.field, instance.field) - * - JSON object properties - * - Regular object properties - * - Array/String .length property - * - Map/Set .size property - * - TypeScript interface-based property access + * Handles property access expressions via a priority-ordered dispatch chain. + * Each handler returns string | null (null = didn't handle, try next). + * + * Handler order (priority): + * 1. Enum member access + * 2. Typed JSON struct access + * 3. process.argv / process.platform / process.env / process.simple + * 4. Class property access + * 5. JSON property access (variable flagged as JSON) + * 6. Chained access (member_access: interface, class field, nested JSON) + * 7. Index access (arr[i].property) + * 8. Type assertion access ((expr as Type).property) + * 9. Method call result access (map.get(key).property) + * 10. Object property access + * 11. .length property + * 12. .size property (Map/Set) + * 13. Response properties + * 14. Stat properties + * 15. Parameter property access (fallback) */ export class MemberAccessGenerator { - private handlers: MemberAccessHandlerFn[]; + constructor(private ctx: MemberAccessGeneratorContext) {} - constructor(private ctx: MemberAccessGeneratorContext) { - this.handlers = this.buildHandlers(); - } + private dispatchHandlers(expr: MemberAccessNode, params: string[]): string | null { + let result: string | null; - private buildHandlers(): MemberAccessHandlerFn[] { - const handlers: MemberAccessHandlerFn[] = []; + result = this.handleEnumMemberAccess(expr); + if (result !== null) return result; - handlers.push((expr, ctx, _params) => this.handleEnumMemberAccess(expr)); + result = this.handleTypedJsonStructAccess(expr); + if (result !== null) return result; - handlers.push((expr, ctx, _params) => this.handleTypedJsonStructAccess(expr)); + if (isProcessArgv(expr)) return this.handleProcessArgv(); - handlers.push((expr, ctx, _params) => { - if (isProcessArgv(expr)) return this.handleProcessArgv(); - return null; - }); + if (isProcessPlatform(expr)) { + const platformStr = this.ctx.getTargetOS() || process.platform; + return this.ctx.stringGen.doCreateStringConstant(platformStr); + } - handlers.push((expr, ctx, _params) => { - if (isProcessPlatform(expr)) { - const platformStr = ctx.getTargetOS() || process.platform; - return ctx.stringGen.doCreateStringConstant(platformStr); - } - return null; - }); + if (isProcessEnvAccess(expr)) return this.handleProcessEnvAccess(expr); - handlers.push((expr, ctx, _params) => { - if (isProcessEnvAccess(expr)) return this.handleProcessEnvAccess(expr); - return null; - }); + result = handleProcessSimpleProperty(this.ctx, expr); + if (result !== null) return result; - handlers.push((expr, ctx, _params) => handleProcessSimpleProperty(ctx, expr)); + result = this.handleClassPropertyAccess(expr, params); + if (result !== null) return result; - handlers.push((expr, ctx, params) => this.handleClassPropertyAccess(expr, params)); + const exprObjBase = expr.object as ExprBase; + const exprObjType = exprObjBase ? exprObjBase.type : null; + if (exprObjType === null || exprObjType === undefined) return '0.0'; - handlers.push((expr, ctx, params) => { - const exprObjBase = expr.object as ExprBase; - const exprObjType = exprObjBase ? exprObjBase.type : null; - if (exprObjType === null || exprObjType === undefined) return '0.0'; - if (exprObjType === 'variable' && ctx.symbolTable.isJSON((expr.object as VariableNode).name)) { - return this.handleJsonPropertyAccess(expr, params); - } - return null; - }); - - handlers.push((expr, ctx, params) => { - const exprObjBase = expr.object as ExprBase; - const exprObjType = exprObjBase ? exprObjBase.type : null; - if (exprObjType === 'member_access') { - const chainedResult = this.handleChainedInterfaceAccess(expr, params); - if (chainedResult !== null) return chainedResult; - const classFieldChainResult = this.handleClassFieldChainedAccess(expr, params); - if (classFieldChainResult !== null) return classFieldChainResult; - const nestedResult = this.handleNestedJsonAccess(expr, params); - if (nestedResult !== null) return nestedResult; - } - return null; - }); + if (exprObjType === 'variable' && this.ctx.symbolTable.isJSON((expr.object as VariableNode).name)) { + return this.handleJsonPropertyAccess(expr, params); + } - handlers.push((expr, ctx, params) => { - const exprObjBase = expr.object as ExprBase; - const exprObjType = exprObjBase ? exprObjBase.type : null; - if (exprObjType === 'index_access') return this.handleIndexAccessPropertyAccess(expr, params); - return null; - }); + if (exprObjType === 'member_access') { + result = this.handleChainedInterfaceAccess(expr, params); + if (result !== null) return result; + result = this.handleClassFieldChainedAccess(expr, params); + if (result !== null) return result; + result = this.handleNestedJsonAccess(expr, params); + if (result !== null) return result; + } - handlers.push((expr, ctx, params) => { - const exprObjBase = expr.object as ExprBase; - const exprObjType = exprObjBase ? exprObjBase.type : null; - if (exprObjType === 'type_assertion') return this.handleTypeAssertionPropertyAccess(expr, params); - return null; - }); + if (exprObjType === 'index_access') { + result = this.handleIndexAccessPropertyAccess(expr, params); + if (result !== null) return result; + } - handlers.push((expr, ctx, params) => { - const exprObjBase = expr.object as ExprBase; - const exprObjType = exprObjBase ? exprObjBase.type : null; - if (exprObjType === 'method_call') return this.handleMethodCallResultPropertyAccess(expr, params); - return null; - }); + if (exprObjType === 'type_assertion') { + result = this.handleTypeAssertionPropertyAccess(expr, params); + if (result !== null) return result; + } - handlers.push((expr, ctx, params) => this.handleObjectPropertyAccess(expr, params)); + if (exprObjType === 'method_call') { + result = this.handleMethodCallResultPropertyAccess(expr, params); + if (result !== null) return result; + } - handlers.push((expr, ctx, params) => { - if (expr.property === 'length') return this.handleLengthProperty(expr, params); - return null; - }); + result = this.handleObjectPropertyAccess(expr, params); + if (result !== null) return result; - handlers.push((expr, ctx, params) => { - if (expr.property === 'size') return this.handleSizeProperty(expr, params); - return null; - }); + if (expr.property === 'length') return this.handleLengthProperty(expr, params); + + if (expr.property === 'size') { + result = this.handleSizeProperty(expr, params); + if (result !== null) return result; + } - handlers.push((expr, ctx, _params) => this.handleResponseProperty(expr)); + result = this.handleResponseProperty(expr); + if (result !== null) return result; - handlers.push((expr, ctx, _params) => this.handleStatProperty(expr)); + result = this.handleStatProperty(expr); + if (result !== null) return result; - return handlers; + return null; } private hasObjectInfo(name: string): boolean { @@ -413,10 +402,8 @@ export class MemberAccessGenerator { return this.ctx.generateExpression(expr.object, params); } - for (let i = 0; i < this.handlers.length; i++) { - const result = this.handlers[i](expr, this.ctx, params); - if (result !== null) return result; - } + const result = this.dispatchHandlers(expr, params); + if (result !== null) return result; return this.handleParameterPropertyAccess(expr, params); } From 437b16b7dcf1dbcfd9b092a3c1b3b059b7f8264e Mon Sep 17 00:00:00 2001 From: Chad Smith Date: Thu, 19 Feb 2026 08:54:33 -0800 Subject: [PATCH 09/10] refactor canonicalTypeToLlvm to use plain parameters for stage 0 compat --- src/codegen/infrastructure/type-system.ts | 33 +++++++++-------------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/src/codegen/infrastructure/type-system.ts b/src/codegen/infrastructure/type-system.ts index 640d97b5..a99b291a 100644 --- a/src/codegen/infrastructure/type-system.ts +++ b/src/codegen/infrastructure/type-system.ts @@ -122,7 +122,7 @@ export function createFloatType(): ResolvedType { } export function tsTypeToLlvm(tsType: string): string { - return canonicalTypeToLlvm(tsType, { mode: 'default' }); + return canonicalTypeToLlvm(tsType, 'default', false, false, ''); } export function resolvedTypeToLlvm(rt: ResolvedType): string { @@ -145,28 +145,21 @@ export function resolvedTypeToLlvm(rt: ResolvedType): string { export type TypeMappingMode = 'default' | 'param' | 'return' | 'struct_field' | 'json'; -export interface TypeMappingOptions { - mode: TypeMappingMode; - isEnum?: boolean; - isInterface?: boolean; - fieldName?: string; -} - -export function canonicalTypeToLlvm(tsType: string, options: TypeMappingOptions): string { +export function canonicalTypeToLlvm(tsType: string, mode: string, isEnum: boolean, isInterface: boolean, fieldName: string): string { if (tsType === null || tsType === undefined || tsType === '') { - if (options.mode === 'return') return 'double'; + if (mode === 'return') return 'double'; return 'i8*'; } - if (options.fieldName === 'nodePtr' || options.fieldName === 'treePtr') return 'i8*'; + if (fieldName === 'nodePtr' || fieldName === 'treePtr') return 'i8*'; - if (options.mode === 'param') { + if (mode === 'param') { if (tsType === 'any' || tsType === 'unknown') { throw new Error(`Parameter type '${tsType}' is not allowed — add explicit type annotations or fix the parser`); } } - if (options.isEnum) return 'double'; + if (isEnum) return 'double'; if (tsType === 'string') return 'i8*'; if (tsType === 'number' || tsType === 'boolean') return 'double'; @@ -184,20 +177,20 @@ export function canonicalTypeToLlvm(tsType: string, options: TypeMappingOptions) for (let i = 0; i < parts.length; i++) { const part = parts[i].trim(); if (part === 'null' || part === 'undefined') continue; - return canonicalTypeToLlvm(part, options); + return canonicalTypeToLlvm(part, mode, isEnum, isInterface, fieldName); } } - if (options.isInterface && (options.mode === 'param' || options.mode === 'struct_field')) { + if (isInterface && (mode === 'param' || mode === 'struct_field')) { return `%${tsType}*`; } - if (options.mode === 'return') { + if (mode === 'return') { if (tsType !== 'number' && tsType !== 'boolean') return 'i8*'; return 'double'; } - if (options.mode === 'json') { + if (mode === 'json') { return 'i8*'; } @@ -205,7 +198,7 @@ export function canonicalTypeToLlvm(tsType: string, options: TypeMappingOptions) } export function tsTypeToLlvmJson(tsType: string): string { - return canonicalTypeToLlvm(tsType, { mode: 'json' }); + return canonicalTypeToLlvm(tsType, 'json', false, false, ''); } export function checkUnsafeUnionType(typeStr: string): string | null { @@ -343,14 +336,14 @@ export function mapParamTypeToLLVM( paramIsEnum: boolean, paramIsInterface: boolean ): string { - return canonicalTypeToLlvm(paramType, { mode: 'param', isEnum: paramIsEnum, isInterface: paramIsInterface, fieldName: paramName }); + return canonicalTypeToLlvm(paramType, 'param', paramIsEnum, paramIsInterface, paramName); } export function mapReturnTypeToLLVM( returnType: string, returnIsEnum: boolean ): string { - return canonicalTypeToLlvm(returnType, { mode: 'return', isEnum: returnIsEnum }); + return canonicalTypeToLlvm(returnType, 'return', returnIsEnum, false, ''); } function parseGenericTypeString(s: string): { base: string; params: string } | null { From 81220466af6d213a69d75fdb17718445e8387fcb Mon Sep 17 00:00:00 2001 From: Chad Smith Date: Thu, 19 Feb 2026 15:18:10 -0800 Subject: [PATCH 10/10] update test scripts to use chad instead of chadc --- scripts/test.js | 10 +++++----- tests/compiler.test.ts | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/scripts/test.js b/scripts/test.js index e20734aa..92573849 100644 --- a/scripts/test.js +++ b/scripts/test.js @@ -15,11 +15,11 @@ try { process.exit(1); } -const chadc = path.join(projectRoot, '.build', 'chadc'); -if (!fs.existsSync(chadc)) { - console.log('Building native compiler (.build/chadc)...'); +const chad = path.join(projectRoot, '.build', 'chad'); +if (!fs.existsSync(chad)) { + console.log('Building native compiler (.build/chad)...'); try { - execSync('node dist/chadc-node.js src/chadc-native.ts -o .build/chadc', { cwd: projectRoot, stdio: 'inherit' }); + execSync('node dist/chad-node.js build src/chad-native.ts -o .build/chad', { cwd: projectRoot, stdio: 'inherit' }); } catch (error) { console.error('Native compiler build failed'); process.exit(1); @@ -54,7 +54,7 @@ child.on('exit', (code) => { const child2 = spawn('node', ['--import', 'tsx', '--test', 'tests/compiler.test.ts'], { stdio: 'inherit', shell: false, - env: { ...process.env, CHADC_COMPILER: 'node dist/chadc-node.js' } + env: { ...process.env, CHAD_COMPILER: 'node dist/chad-node.js build' } }); child2.on('exit', (code2) => { process.exit(code2); diff --git a/tests/compiler.test.ts b/tests/compiler.test.ts index cf6caa64..e54b395a 100644 --- a/tests/compiler.test.ts +++ b/tests/compiler.test.ts @@ -10,8 +10,8 @@ import { testCases } from './test-fixtures'; const execAsync = promisify(exec); -const compiler = fsSync.existsSync('.build/chad') ? '.build/chad build' : 'node dist/chad-node.js build'; -const compilerLabel = fsSync.existsSync('.build/chad') ? 'native' : 'node'; +const compiler = process.env.CHAD_COMPILER || (fsSync.existsSync('.build/chad') ? '.build/chad build' : 'node dist/chad-node.js build'); +const compilerLabel = process.env.CHAD_COMPILER ? 'node' : (fsSync.existsSync('.build/chad') ? 'native' : 'node'); describe(`ChadScript Compiler (${compilerLabel})`, () => { describe('Compilation and Execution', { concurrency: 32 }, () => {