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/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/codegen/expressions/access/member.ts b/src/codegen/expressions/access/member.ts index 236df401..f73a3cf7 100644 --- a/src/codegen/expressions/access/member.ts +++ b/src/codegen/expressions/access/member.ts @@ -19,7 +19,7 @@ import { import type { SymbolTable } from '../../infrastructure/symbol-table.js'; import type { TypeChecker } from '../../../typescript/type-checker.js'; import type { InterfaceStructGenerator, InterfaceFieldInfo, InterfaceStructInfo } from '../../types/interface-struct-generator.js'; -import { stripOptional, stripNullable, tsTypeToLlvm, parseMapTypeString } from '../../infrastructure/type-system.js'; +import { stripOptional, stripNullable, tsTypeToLlvm, parseMapTypeString, canonicalTypeToLlvm } from '../../infrastructure/type-system.js'; import type { IStringGenerator, IMapGenerator, ISetGenerator, IResponseGenerator } from '../../infrastructure/generator-context.js'; import { isProcessArgv, @@ -28,6 +28,7 @@ import { handleProcessEnvAccess, handleProcessSimpleProperty, handleProcessArgv, + handleProcessPlatform, } from './process-access.js'; import { handleLengthProperty, @@ -157,21 +158,130 @@ export interface MemberAccessGeneratorContext { getTargetArch(): string; } +export type MemberAccessHandlerFn = (expr: MemberAccessNode, ctx: MemberAccessGeneratorContext, params: string[]) => string | null; + /** * 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 { constructor(private ctx: MemberAccessGeneratorContext) {} + private dispatchHandlers(expr: MemberAccessNode, params: string[]): string | null { + let result: string | null; + + result = this.dispatchSpecialValues(expr, params); + if (result !== null) return result; + + result = this.handleClassPropertyAccess(expr, params); + if (result !== null) return result; + + const exprObjBase = expr.object as ExprBase; + const exprObjType = exprObjBase ? exprObjBase.type : null; + if (exprObjType === null || exprObjType === undefined) return '0.0'; + + result = this.dispatchByExpressionType(expr, params, exprObjType); + if (result !== null) return result; + + return this.dispatchPropertyHandlers(expr, params); + } + + private dispatchSpecialValues(expr: MemberAccessNode, params: string[]): string | null { + let result: string | null; + + result = this.handleEnumMemberAccess(expr); + if (result !== null) return result; + + result = this.handleTypedJsonStructAccess(expr); + if (result !== null) return result; + + if (isProcessArgv(expr)) return this.handleProcessArgv(); + + if (isProcessPlatform(expr)) return handleProcessPlatform(this.ctx); + + if (isProcessEnvAccess(expr)) return this.handleProcessEnvAccess(expr); + + result = handleProcessSimpleProperty(this.ctx, expr); + if (result !== null) return result; + + return null; + } + + private dispatchByExpressionType(expr: MemberAccessNode, params: string[], exprObjType: string): string | null { + let result: string | null; + + if (exprObjType === 'variable' && this.ctx.symbolTable.isJSON((expr.object as VariableNode).name)) { + return this.handleJsonPropertyAccess(expr, params); + } + + 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; + } + + if (exprObjType === 'index_access') { + result = this.handleIndexAccessPropertyAccess(expr, params); + if (result !== null) return result; + } + + if (exprObjType === 'type_assertion') { + result = this.handleTypeAssertionPropertyAccess(expr, params); + if (result !== null) return result; + } + + if (exprObjType === 'method_call') { + result = this.handleMethodCallResultPropertyAccess(expr, params); + if (result !== null) return result; + } + + result = this.handleObjectPropertyAccess(expr, params); + if (result !== null) return result; + + return null; + } + + private dispatchPropertyHandlers(expr: MemberAccessNode, params: string[]): string | null { + let result: string | null; + + if (expr.property === 'length') return this.handleLengthProperty(expr, params); + + if (expr.property === 'size') { + result = this.handleSizeProperty(expr, params); + if (result !== null) return result; + } + + result = this.handleResponseProperty(expr); + if (result !== null) return result; + + result = this.handleStatProperty(expr); + if (result !== null) return result; + + return null; + } + private hasObjectInfo(name: string): boolean { if (!this.ctx.symbolTable.isObject(name) && !this.ctx.symbolTable.isJSON(name)) return false; return this.ctx.symbolTable.getObjectMetadataKeys(name) !== undefined; @@ -314,94 +424,9 @@ 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 result = this.dispatchHandlers(expr, params); + if (result !== null) return result; - 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; - } - - // 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); } @@ -610,7 +635,7 @@ export class MemberAccessGenerator { for (let pi = 0; pi < nestedProps.length; pi++) { const p = nestedProps[pi] as { name: string; type: string }; keys.push(stripOptional(p.name)); - types.push(this.convertTsType(p.type)); + types.push(tsTypeToLlvm(p.type)); tsTypes.push(p.type); } this.ctx.setJsonObjectMetadata(value, { keys, types, tsTypes, interfaceType: undefined }); @@ -888,7 +913,7 @@ export class MemberAccessGenerator { const f = interfaceDef.fields[i] as { name: string; type: string }; keys.push(stripOptional(f.name)); tsTypes.push(f.type); - types.push(this.convertTsType(f.type)); + types.push(tsTypeToLlvm(f.type)); } } this.ctx.setJsonObjectMetadata(register, { keys, types, tsTypes, interfaceType: undefined }); @@ -913,7 +938,7 @@ export class MemberAccessGenerator { const f = inlineFields[i] as InterfaceField; keys.push(f.name); tsTypes.push(f.type); - types.push(this.convertTsType(f.type)); + types.push(tsTypeToLlvm(f.type)); } this.ctx.setJsonObjectMetadata(register, { keys, types, tsTypes, interfaceType: undefined }); } @@ -929,21 +954,11 @@ export class MemberAccessGenerator { return handleNestedInterfaceField(this.ctx, fieldItem, tsType); } - private convertTsType(t: string): string { - return tsTypeToLlvm(t); - } - private interfaceTsTypeToLlvm(t: string): string { - if (t === 'string') return 'i8*'; - if (t === 'number') return 'double'; - if (t === 'boolean') return 'double'; - if (t === 'string[]') return '%StringArray*'; - if (t === 'number[]' || t === 'boolean[]') return '%Array*'; - if (t.endsWith('[]')) return '%ObjectArray*'; const baseName = this.extractBaseTypeName(t); const props = this.ctx.getInterfaceProperties(baseName); - if (props && props.keys.length > 0) return `%${baseName}*`; - return 'i8*'; + const isInterface = props !== null && props.keys.length > 0; + return canonicalTypeToLlvm(t, 'struct_field', false, isInterface, ''); } private extractJsonFieldValue(fieldItem: string): string { @@ -1308,7 +1323,7 @@ export class MemberAccessGenerator { const innerPtr = this.ctx.generateExpression(expr.object, params); const propField = ifInfoProps[propIndex] as InterfaceProperty; const propType = propField.type; - const llvmType = this.convertTsType(propType); + const llvmType = tsTypeToLlvm(propType); const fieldPtr = this.ctx.nextTemp(); this.ctx.emit(`${fieldPtr} = getelementptr inbounds %${fieldInfo.tsType}, %${fieldInfo.tsType}* ${innerPtr}, i32 0, i32 ${propIndex}`); @@ -1350,7 +1365,7 @@ export class MemberAccessGenerator { if (propIndex === -1) return null; const innerPtr = this.ctx.generateExpression(expr.object, params); - const llvmType = this.convertTsType(propTsType || 'i8*'); + const llvmType = tsTypeToLlvm(propTsType || 'i8*'); const fieldPtr = this.ctx.nextTemp(); this.ctx.emit(`${fieldPtr} = getelementptr inbounds %${fieldInfo.tsType}, %${fieldInfo.tsType}* ${innerPtr}, i32 0, i32 ${propIndex}`); @@ -1604,7 +1619,7 @@ export class MemberAccessGenerator { const p = taProps[i] as InterfaceProperty; keys.push(stripOptional(p.name)); tsTypes.push(p.type); - types.push(this.convertTsType(p.type)); + types.push(tsTypeToLlvm(p.type)); } return { keys, types, tsTypes }; } @@ -1972,11 +1987,11 @@ export class MemberAccessGenerator { } const propField = interfaceDef.fields[propIndex] as { name: string; type: string }; - const propType = this.convertTsType(propField.type); + 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 }; - structTypes.push(this.convertTsType(field.type)); + structTypes.push(tsTypeToLlvm(field.type)); } const structType = `{ ${structTypes.join(', ')} }`; @@ -2476,12 +2491,12 @@ export class MemberAccessGenerator { if (fieldIndex === -1) return null; const field = fields[fieldIndex] as { name: string; type: string }; - const fieldLlvmType = this.convertTsType(field.type); + const fieldLlvmType = tsTypeToLlvm(field.type); const types: string[] = []; for (let i = 0; i < fields.length; i++) { const f = fields[i] as { name: string; type: string }; - types.push(this.convertTsType(f.type)); + types.push(tsTypeToLlvm(f.type)); } const structType = `{ ${types.join(', ')} }`; diff --git a/src/codegen/expressions/access/process-access.ts b/src/codegen/expressions/access/process-access.ts index 1441a229..101b9e9b 100644 --- a/src/codegen/expressions/access/process-access.ts +++ b/src/codegen/expressions/access/process-access.ts @@ -76,6 +76,11 @@ export function handleProcessSimpleProperty(ctx: MemberAccessGeneratorContext, e return null; } +export function handleProcessPlatform(ctx: MemberAccessGeneratorContext): string { + const platformStr = ctx.getTargetOS() || process.platform; + return ctx.stringGen.doCreateStringConstant(platformStr); +} + export function handleProcessArgv(ctx: MemberAccessGeneratorContext): string { const sizePtr = ctx.nextTemp(); ctx.emit(`${sizePtr} = getelementptr %StringArray, %StringArray* null, i32 1`); diff --git a/src/codegen/expressions/calls.ts b/src/codegen/expressions/calls.ts index 513049c3..7d16138a 100644 --- a/src/codegen/expressions/calls.ts +++ b/src/codegen/expressions/calls.ts @@ -26,6 +26,17 @@ export class CallExpressionGenerator { return null; } + private getClassSourceFile(className: string): string | undefined { + const ast = this.ctx.getAst(); + if (!ast || !ast.classes) return undefined; + for (let i = 0; i < ast.classes.length; i++) { + const c = ast.classes[i] as ClassNode; + if (!c || !c.name) continue; + if (c.name === className) return c.sourceFile; + } + return undefined; + } + /** * Generate function call expression * @param expr - Call expression node @@ -524,7 +535,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); @@ -582,13 +593,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; @@ -639,12 +652,13 @@ export class CallExpressionGenerator { return this.ctx.emitError('setTimeout() callback must be a function reference', expr.loc); } const callbackName = (callbackArg as VariableNode).name; + const callbackFunc = this.getFunctionFromAST(callbackName); const delayValue = this.ctx.generateExpression(expr.args[1], params); const dblDelay = this.ctx.ensureDouble(delayValue); const callbackPtr = this.ctx.nextTemp(); - this.ctx.emit(`${callbackPtr} = bitcast void ()* @${this.ctx.mangleUserName(callbackName)} to void ()*`); + this.ctx.emit(`${callbackPtr} = bitcast void ()* @${this.ctx.mangleUserName(callbackName, callbackFunc ? callbackFunc.sourceFile : undefined)} to void ()*`); const result = this.ctx.nextTemp(); this.ctx.emit(`${result} = call i8* @__setTimeout(void ()* ${callbackPtr}, double ${dblDelay})`); @@ -665,12 +679,13 @@ export class CallExpressionGenerator { return this.ctx.emitError('setInterval() callback must be a function reference', expr.loc); } const callbackName = (callbackArg as VariableNode).name; + const callbackFunc = this.getFunctionFromAST(callbackName); const intervalValue = this.ctx.generateExpression(expr.args[1], params); const dblInterval = this.ctx.ensureDouble(intervalValue); const callbackPtr = this.ctx.nextTemp(); - this.ctx.emit(`${callbackPtr} = bitcast void ()* @${this.ctx.mangleUserName(callbackName)} to void ()*`); + this.ctx.emit(`${callbackPtr} = bitcast void ()* @${this.ctx.mangleUserName(callbackName, callbackFunc ? callbackFunc.sourceFile : undefined)} to void ()*`); const result = this.ctx.nextTemp(); this.ctx.emit(`${result} = call i8* @__setInterval(void ()* ${callbackPtr}, double ${dblInterval})`); @@ -733,7 +748,9 @@ export class CallExpressionGenerator { let callbackFn: string; if (callbackArg.type === 'variable') { - callbackFn = this.ctx.mangleUserName((callbackArg as VariableNode).name); + const cbName = (callbackArg as VariableNode).name; + const cbFunc = this.getFunctionFromAST(cbName); + callbackFn = this.ctx.mangleUserName(cbName, cbFunc ? cbFunc.sourceFile : undefined); } else if (callbackArg.type === 'arrow_function') { callbackFn = this.ctx.generateExpression(callbackArg, params); } else { @@ -804,7 +821,9 @@ export class CallExpressionGenerator { let callbackFn: string; if (callbackArg.type === 'variable') { - callbackFn = this.ctx.mangleUserName((callbackArg as VariableNode).name); + const cbName = (callbackArg as VariableNode).name; + const cbFunc = this.getFunctionFromAST(cbName); + callbackFn = this.ctx.mangleUserName(cbName, cbFunc ? cbFunc.sourceFile : undefined); } else if (callbackArg.type === 'arrow_function') { callbackFn = this.ctx.generateExpression(callbackArg, params); } else { @@ -1039,11 +1058,12 @@ export class CallExpressionGenerator { argsWithTypesParts.push('i8* ' + argValues[ai]); } const argsWithTypes = argsWithTypesParts.join(', '); + const parentSourceFile = this.getClassSourceFile(parentClassName); const parentObj = this.ctx.nextTemp(); if (argValues.length === 0) { - this.ctx.emit(`${parentObj} = call ${parentStructType} @${this.ctx.mangleUserName(parentClassName)}_constructor()`); + this.ctx.emit(`${parentObj} = call ${parentStructType} @${this.ctx.mangleUserName(parentClassName, parentSourceFile)}_constructor()`); } else { - this.ctx.emit(`${parentObj} = call ${parentStructType} @${this.ctx.mangleUserName(parentClassName)}_constructor(${argsWithTypes})`); + this.ctx.emit(`${parentObj} = call ${parentStructType} @${this.ctx.mangleUserName(parentClassName, parentSourceFile)}_constructor(${argsWithTypes})`); } const parentFields = this.ctx.classGenGetClassFields(parentClassName); diff --git a/src/codegen/infrastructure/function-generator.ts b/src/codegen/infrastructure/function-generator.ts index ca1f40b0..dc10afa1 100644 --- a/src/codegen/infrastructure/function-generator.ts +++ b/src/codegen/infrastructure/function-generator.ts @@ -5,7 +5,7 @@ import type { TypeChecker } from '../../typescript/type-checker.js'; import type { StringGenerator } from '../types/collections/string.js'; import type { ControlFlowGenerator } from '../statements/control-flow.js'; import type { InterfaceStructGenerator } from '../types/interface-struct-generator.js'; -import { stripOptional, tsTypeToLlvm, mapParamTypeToLLVM } from './type-system.js'; +import { stripOptional, tsTypeToLlvm, mapParamTypeToLLVM, canonicalTypeToLlvm } from './type-system.js'; import { findI64EligibleVariables } from './integer-analysis.js'; interface LiftedFunction extends FunctionNode { @@ -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; @@ -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'); @@ -271,7 +271,7 @@ export class FunctionGenerator { if (!field || !field.name) continue; const fieldName = stripOptional(field.name); keys.push(fieldName); - types.push(this.convertTsTypeForField(fieldName, field.type)); + types.push(canonicalTypeToLlvm(field.type, 'default', this.ctx.isEnumType(field.type), false, fieldName)); } this.ctx.defineVariableWithMetadata(paramName, allocaReg, 'i8*', SymbolKind.Object, 'local', createObjectMetadataWithInterface({ keys, types }, paramTypes[i])); } else if (typeAliasCommonProps && typeAliasCommonProps.keys.length > 0) { @@ -571,23 +571,6 @@ export class FunctionGenerator { return this.ctx.isEnumType(typeName); } - private convertTsType(tsType: string): string { - if (this.isEnumType(tsType)) { - return 'double'; - } - return tsTypeToLlvm(tsType); - } - - private convertTsTypeForField(fieldName: string, tsType: string): string { - if (fieldName === 'nodePtr' || fieldName === 'treePtr') { - return 'i8*'; - } - if (this.isEnumType(tsType)) { - return 'double'; - } - return tsTypeToLlvm(tsType); - } - private llvmTypeToSymbolKind(llvmType: string): number { if (llvmType === 'double') return SymbolKind.Number; if (llvmType === 'i8*') return SymbolKind.String; @@ -659,7 +642,7 @@ export class FunctionGenerator { for (let i = 0; i < commonFields.length; i++) { const cf = commonFields[i] as CommonField; keys.push(stripOptional(cf.name)); - types.push(this.convertTsType(cf.type)); + types.push(canonicalTypeToLlvm(cf.type, 'default', this.ctx.isEnumType(cf.type), false, '')); } return { keys, types }; 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/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/src/codegen/infrastructure/type-resolver/type-resolver.ts b/src/codegen/infrastructure/type-resolver/type-resolver.ts index 5ebbd28c..095c58ed 100644 --- a/src/codegen/infrastructure/type-resolver/type-resolver.ts +++ b/src/codegen/infrastructure/type-resolver/type-resolver.ts @@ -2,7 +2,7 @@ import { AST, InterfaceDeclaration, InterfaceField, TypeAliasDeclaration, Expres import { SymbolTable, ObjectMetadata, SymbolKind, Symbol as SymbolEntry, MapMetadata, ObjectArrayMetadata } from '../symbol-table.js'; import type { TypeChecker } from '../../../typescript/type-checker.js'; import { FieldInfo, MapTypeInfo, SetTypeInfo, TypeGuardInfo, UnionCommonFields, ThisFieldMapInfo, ThisFieldSetInfo } from './types.js'; -import { ResolvedType, createResolvedType, parseTypeString, stripOptional, tsTypeToLlvm, tsTypeToLlvmJson, parseMapTypeString, parseSetTypeString, parseArrayTypeString } from '../type-system.js'; +import { ResolvedType, createResolvedType, parseTypeString, stripOptional, parseMapTypeString, parseSetTypeString, parseArrayTypeString, canonicalTypeToLlvm } from '../type-system.js'; interface ExprBase { type: string; } @@ -337,7 +337,7 @@ export class TypeResolver { for (let i = 0; i < builtinType.fields.length; i++) { const f = builtinType.fields[i]; keys.push(stripOptional(f.name)); - types.push(this.convertTsType(f.type)); + types.push(canonicalTypeToLlvm(f.type, 'default', this.isEnumType(f.type), false, '')); tsTypes.push(f.type); } return { keys, types, tsTypes }; @@ -351,7 +351,7 @@ export class TypeResolver { for (let i = 0; i < iface.fields.length; i++) { const f = iface.fields[i] as { name: string; type: string }; keys.push(stripOptional(f.name)); - types.push(this.convertTsType(f.type)); + types.push(canonicalTypeToLlvm(f.type, 'default', this.isEnumType(f.type), false, '')); tsTypes.push(f.type); } return { keys, types, tsTypes }; @@ -548,23 +548,12 @@ export class TypeResolver { for (let i = 0; i < commonFields.length; i++) { const f = commonFields[i] as CommonField; keys.push(stripOptional(f.name)); - types.push(this.convertTsType(f.type)); + types.push(canonicalTypeToLlvm(f.type, 'default', this.isEnumType(f.type), false, '')); tsTypes.push(f.type); } return { keys, types, tsTypes }; } - convertTsType(tsType: string): string { - if (this.isEnumType(tsType)) { - return 'double'; - } - return tsTypeToLlvm(tsType); - } - - convertTsTypeJson(tsType: string): string { - return tsTypeToLlvmJson(tsType); - } - getClassFieldInfo(className: string, fieldName: string): FieldInfo | null { return this.ctx.classGenGetFieldInfo(className, fieldName); } @@ -584,7 +573,7 @@ export class TypeResolver { keyType, valueType, llvmKeyType: keyType === 'string' ? 'i8*' : 'double', - llvmValueType: this.convertTsType(valueType) + llvmValueType: canonicalTypeToLlvm(valueType, 'default', this.isEnumType(valueType), false, '') }; } diff --git a/src/codegen/infrastructure/type-system.ts b/src/codegen/infrastructure/type-system.ts index 2ee416e5..a99b291a 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, 'default', false, false, ''); } export function resolvedTypeToLlvm(rt: ResolvedType): string { @@ -163,16 +143,64 @@ 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 function canonicalTypeToLlvm(tsType: string, mode: string, isEnum: boolean, isInterface: boolean, fieldName: string): string { + if (tsType === null || tsType === undefined || tsType === '') { + if (mode === 'return') return 'double'; + return 'i8*'; + } + + if (fieldName === 'nodePtr' || fieldName === 'treePtr') return 'i8*'; + + 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 (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, mode, isEnum, isInterface, fieldName); + } + } + + if (isInterface && (mode === 'param' || mode === 'struct_field')) { + return `%${tsType}*`; + } + + if (mode === 'return') { + if (tsType !== 'number' && tsType !== 'boolean') return 'i8*'; + return 'double'; + } + + if (mode === 'json') { + return 'i8*'; + } + return 'i8*'; } +export function tsTypeToLlvmJson(tsType: string): string { + return canonicalTypeToLlvm(tsType, 'json', false, false, ''); +} + export function checkUnsafeUnionType(typeStr: string): string | null { if (!typeStr || typeStr.indexOf(' | ') === -1) return null; @@ -308,35 +336,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, 'param', paramIsEnum, paramIsInterface, 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, 'return', returnIsEnum, false, ''); } function parseGenericTypeString(s: string): { base: string; params: string } | null { diff --git a/src/codegen/llvm-generator.ts b/src/codegen/llvm-generator.ts index 36463563..cde6da46 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,49 @@ 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; + } + + private getFunctionSourceFile(name: string): string | undefined { + if (!this.ast || !this.ast.functions) return undefined; + for (let i = 0; i < this.ast.functions.length; i++) { + const fn = this.ast.functions[i] as { name: string; sourceFile?: string }; + if (fn.name === name) return fn.sourceFile; + } + return undefined; + } + createEmptyStringConstant(): string { return this.stringGen.doCreateStringConstant(''); } @@ -1722,8 +1771,10 @@ export class LLVMGenerator extends BaseGenerator implements IGeneratorContext { if (this.httpHandlers.length > 0) { irParts.push('\n'); const wsHandler = this.wsHandlers.length > 0 ? this.wsHandlers[0] : undefined; - const mangledHttpHandler = this.mangleUserName(this.httpHandlers[0]); - const mangledWsHandler = wsHandler ? this.mangleUserName(wsHandler) : undefined; + const httpHandlerSourceFile = this.getFunctionSourceFile(this.httpHandlers[0]); + const mangledHttpHandler = this.mangleUserName(this.httpHandlers[0], httpHandlerSourceFile); + const wsHandlerSourceFile = wsHandler ? this.getFunctionSourceFile(wsHandler) : undefined; + const mangledWsHandler = wsHandler ? this.mangleUserName(wsHandler, wsHandlerSourceFile) : undefined; const httpServe = this.httpServerGen.generateHttpServeFunction(mangledWsHandler); if (httpServe) { irParts.push(httpServe); } irParts.push('\n'); @@ -2642,7 +2693,7 @@ export class LLVMGenerator extends BaseGenerator implements IGeneratorContext { // Call the runtime http_serve function // Handler now takes a single Request object (i8*) and returns Response object (i8*) const temp = this.nextTemp(); - this.emit(`${temp} = call i32 @http_serve(i32 ${portI32}, i8* (i8*)* @${this.mangleUserName(handlerName)})`); + this.emit(`${temp} = call i32 @http_serve(i32 ${portI32}, i8* (i8*)* @${this.mangleUserName(handlerName, this.getFunctionSourceFile(handlerName))})`); return temp; } diff --git a/src/codegen/types/interface-struct-generator.ts b/src/codegen/types/interface-struct-generator.ts index 140b55eb..23afc12b 100644 --- a/src/codegen/types/interface-struct-generator.ts +++ b/src/codegen/types/interface-struct-generator.ts @@ -1,5 +1,5 @@ import { InterfaceDeclaration } from '../../ast/types.js'; -import { tsTypeToLlvm } from '../infrastructure/type-system.js'; +import { canonicalTypeToLlvm } from '../infrastructure/type-system.js'; const BUILTIN_TYPES = [ 'Array', 'StringArray', 'Map', 'StringMap', 'Set', 'StringSet', @@ -146,10 +146,14 @@ export class InterfaceStructGenerator { } private tsTypeToLlvmForField(fieldName: string, tsType: string): string { - if (fieldName === 'nodePtr' || fieldName === 'treePtr') { + if (tsType === null || tsType === undefined || tsType === '') { return 'i8*'; } - return this.tsTypeToLlvm(tsType); + if (this.interfaceStructs.has(tsType)) { + return 'i8*'; + } + const isEnum = this.enumNames !== null && this.enumNames !== undefined && this.enumNames.has(tsType); + return canonicalTypeToLlvm(tsType, 'default', isEnum, false, fieldName); } private tsTypeToLlvm(tsType: string): string { @@ -159,10 +163,8 @@ export class InterfaceStructGenerator { if (this.interfaceStructs.has(tsType)) { return 'i8*'; } - if (this.enumNames !== null && this.enumNames !== undefined && this.enumNames.has(tsType)) { - return 'double'; - } - return tsTypeToLlvm(tsType); + const isEnum = this.enumNames !== null && this.enumNames !== undefined && this.enumNames.has(tsType); + return canonicalTypeToLlvm(tsType, 'default', isEnum, false, ''); } getInterfaceStruct(name: string): InterfaceStructInfo | undefined { diff --git a/src/codegen/types/objects/class.ts b/src/codegen/types/objects/class.ts index 32500cbe..7616507e 100644 --- a/src/codegen/types/objects/class.ts +++ b/src/codegen/types/objects/class.ts @@ -1,7 +1,7 @@ import { Expression, ClassNode, ClassMethod, ClassField, VariableNode, InterfaceDeclaration, CommonField } from '../../../ast/types.js'; import { IGeneratorContext } from '../../infrastructure/generator-context.js'; import { SymbolKind, createObjectMetadata, createObjectMetadataWithInterfaceAndPointerAlloca, createClassMetadata } from '../../infrastructure/symbol-table.js'; -import { stripOptional, tsTypeToLlvm } from '../../infrastructure/type-system.js'; +import { stripOptional, canonicalTypeToLlvm } from '../../infrastructure/type-system.js'; // ============================================ // CLASS GENERATOR - Class and instance operations @@ -29,6 +29,11 @@ export class ClassGenerator { return null; } + private getClassSourceFile(className: string): string | undefined { + const classNode = this.findClassNode(className); + return classNode ? classNode.sourceFile : undefined; + } + private getAllFieldsIncludingInherited(classNode: ClassNode): ClassField[] { const allFields: ClassField[] = []; if (classNode.extends) { @@ -393,7 +398,7 @@ export class ClassGenerator { const fieldsFromMap = this.classFields.get(className); const fields = fieldsFromMap || []; const structType = `%${className}_struct*`; - let ir = `define ${structType} @${this.ctx.mangleUserName(className)}_constructor(`; + let ir = `define ${structType} @${this.ctx.mangleUserName(className, this.getClassSourceFile(className))}_constructor(`; const paramLLVMTypes: string[] = []; let paramTsTypes: string[]; if (constructor.paramTypes) { @@ -584,7 +589,7 @@ export class ClassGenerator { private generateDefaultConstructorFromTypes(className: string, fieldLlvmTypes: string[], classFields?: ClassField[]): string { const structType = `%${className}_struct*`; - let ir = `define ${structType} @${this.ctx.mangleUserName(className)}_constructor() {` + '\n'; + let ir = `define ${structType} @${this.ctx.mangleUserName(className, this.getClassSourceFile(className))}_constructor() {` + '\n'; ir += 'entry:\n'; this.ctx.clearOutput(); @@ -671,7 +676,7 @@ export class ClassGenerator { } const thisType = `%${className}_struct*`; - let ir = `define ${returnLLVMType} @${this.ctx.mangleUserName(className)}_${method.name}(${thisType} %this`; + let ir = `define ${returnLLVMType} @${this.ctx.mangleUserName(className, this.getClassSourceFile(className))}_${method.name}(${thisType} %this`; const paramLLVMTypes: string[] = []; const paramTsTypes: string[] = method.paramTypes || []; @@ -866,7 +871,7 @@ export class ClassGenerator { const returnType = `%${className}_struct*`; const instance = this.nextTemp(); - this.emit(`${instance} = call ${returnType} @${this.ctx.mangleUserName(className)}_constructor(${argValues})`); + this.emit(`${instance} = call ${returnType} @${this.ctx.mangleUserName(className, this.getClassSourceFile(className))}_constructor(${argValues})`); return instance; } @@ -952,11 +957,11 @@ export class ClassGenerator { if (returnLLVMType === 'void') { // Void methods don't return a value - this.emit(`call void @${this.ctx.mangleUserName(methodOwnerClass)}_${methodName}(${thisType} ${actualInstancePtr}${argList})`); + this.emit(`call void @${this.ctx.mangleUserName(methodOwnerClass, this.getClassSourceFile(methodOwnerClass))}_${methodName}(${thisType} ${actualInstancePtr}${argList})`); return '0'; // Return dummy value for void calls } else { const result = this.nextTemp(); - this.emit(`${result} = call ${returnLLVMType} @${this.ctx.mangleUserName(methodOwnerClass)}_${methodName}(${thisType} ${actualInstancePtr}${argList})`); + this.emit(`${result} = call ${returnLLVMType} @${this.ctx.mangleUserName(methodOwnerClass, this.getClassSourceFile(methodOwnerClass))}_${methodName}(${thisType} ${actualInstancePtr}${argList})`); this.ctx.setVariableType(result, returnLLVMType); return result; } @@ -966,10 +971,7 @@ export class ClassGenerator { if (!tsType || tsType.length === 0) { return 'double'; } - if (this.isEnumType(tsType)) { - return 'double'; - } - return tsTypeToLlvm(tsType); + return canonicalTypeToLlvm(tsType, 'default', this.isEnumType(tsType), false, ''); } private isEnumType(typeName: string): boolean { 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/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 }, () => { 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', () => {