diff --git a/.claude/rules.md b/.claude/rules.md index 76b8ef31..e98ad31b 100644 --- a/.claude/rules.md +++ b/.claude/rules.md @@ -130,6 +130,11 @@ available on `BaseGenerator`, `LLVMGenerator`, and `MockGeneratorContext` for ty 6. **`ret void` not `unreachable`** at end of void functions 7. **Class structs: boolean is `i1`; Interface structs: boolean is `double`** +## Code Style + +- One-line comments are helpful on dense codegen blocks — explain the "why" or the LLVM IR pattern, not the "what" +- Use named AST types from `src/ast/types.ts` for type assertions instead of inline `as { ... }` structs + ## Patterns That Crash Native Code 1. **`new` in class field initializers is silently dropped** — codegen only emits type-based defaults. Move `new` calls to constructors. When removing a `new X()` initializer, you MUST add a constructor init. diff --git a/src/analysis/semantic-analyzer.ts b/src/analysis/semantic-analyzer.ts index ef9aafc3..574d77b4 100644 --- a/src/analysis/semantic-analyzer.ts +++ b/src/analysis/semantic-analyzer.ts @@ -381,14 +381,14 @@ export class SemanticAnalyzer { } else if (stmtType === 'try') { const tryStmt = stmt as TryStatement; if (tryStmt.tryBlock) this.analyzeBlock(tryStmt.tryBlock); - if (tryStmt.catchParam) { - this.symbols.set(tryStmt.catchParam, { - name: tryStmt.catchParam, + if (tryStmt.catchClause) { + this.symbols.set(tryStmt.catchClause.param, { + name: tryStmt.catchClause.param, type: 'string', llvmType: 'i8*', }); + this.analyzeBlock(tryStmt.catchClause.body); } - if (tryStmt.catchBody) this.analyzeBlock(tryStmt.catchBody); if (tryStmt.finallyBlock) this.analyzeBlock(tryStmt.finallyBlock); } else if (stmtType === 'return') { const retStmt = stmt as ReturnStatement; diff --git a/src/ast/types.ts b/src/ast/types.ts index 1fd5f9af..e9644bca 100644 --- a/src/ast/types.ts +++ b/src/ast/types.ts @@ -304,8 +304,7 @@ export interface ThrowStatement { export interface TryStatement { type: 'try'; tryBlock: BlockStatement; - catchParam: string | null; - catchBody: BlockStatement | null; + catchClause: { param: string; body: BlockStatement } | null; finallyBlock: BlockStatement | null; loc?: SourceLocation; } diff --git a/src/ast/visitor.ts b/src/ast/visitor.ts index fd3a7104..60643492 100644 --- a/src/ast/visitor.ts +++ b/src/ast/visitor.ts @@ -210,8 +210,8 @@ export class RecursiveASTVisitor { visitTryStatement(node: TryStatement): void { this.visitBlock(node.tryBlock); - if (node.catchBody) { - this.visitBlock(node.catchBody); + if (node.catchClause) { + this.visitBlock(node.catchClause.body); } if (node.finallyBlock) { this.visitBlock(node.finallyBlock); diff --git a/src/codegen/expressions/access/member.ts b/src/codegen/expressions/access/member.ts index e7c1d675..1ffb59a6 100644 --- a/src/codegen/expressions/access/member.ts +++ b/src/codegen/expressions/access/member.ts @@ -940,6 +940,30 @@ export class MemberAccessGenerator { this.ctx.setJsonObjectMetadata(register, { keys, types, tsTypes, interfaceType: undefined }); } else if (tsType === 'Expression' || tsType === 'Statement') { this.ctx.setJsonObjectMetadata(register, { keys: ['type'], types: ['i8*'], tsTypes: ['string'], interfaceType: undefined }); + } else { + let strippedType = tsType; + if (strippedType.includes(' | ')) { + const parts = strippedType.split(' | '); + for (let i = 0; i < parts.length; i++) { + const p = parts[i].trim(); + if (p.startsWith('{')) { strippedType = p; break; } + } + } + if (strippedType.startsWith('{') && strippedType.endsWith('}')) { + const inlineFields = this.parseInlineObjectTypeForAssertion(strippedType); + if (inlineFields && inlineFields.length > 0) { + const keys: string[] = []; + const tsTypes: string[] = []; + const types: string[] = []; + for (let i = 0; i < inlineFields.length; i++) { + const f = inlineFields[i] as InterfaceField; + keys.push(f.name); + tsTypes.push(f.type); + types.push(this.convertTsType(f.type)); + } + this.ctx.setJsonObjectMetadata(register, { keys, types, tsTypes, interfaceType: undefined }); + } + } } } diff --git a/src/codegen/infrastructure/closure-analyzer.ts b/src/codegen/infrastructure/closure-analyzer.ts index f01724a0..3163e971 100644 --- a/src/codegen/infrastructure/closure-analyzer.ts +++ b/src/codegen/infrastructure/closure-analyzer.ts @@ -78,8 +78,7 @@ interface CatchHandler { interface TryNode { type: string; tryBlock: BlockStatement; - catchParam: string | null; - catchBody: BlockStatement | null; + catchClause: { param: string; body: BlockStatement } | null; finallyBlock: BlockStatement | null; } @@ -323,10 +322,10 @@ export class ClosureAnalyzer { this.walkExpression(s.iterable); this.walkBlock(s.body); } else if (stmtType === 'try') { - const tryStmt = stmt as { type: string; tryBlock: BlockStatement; catchParam: string | null; catchBody: BlockStatement | null; finallyBlock: BlockStatement | null }; + const tryStmt = stmt as { type: string; tryBlock: BlockStatement; catchClause: { param: string; body: BlockStatement } | null; finallyBlock: BlockStatement | null }; this.walkBlock(tryStmt.tryBlock); - if (tryStmt.catchBody !== null) { - this.walkBlock(tryStmt.catchBody); + if (tryStmt.catchClause !== null) { + this.walkBlock(tryStmt.catchClause.body); } if (tryStmt.finallyBlock !== null) { this.walkBlock(tryStmt.finallyBlock); diff --git a/src/codegen/statements/control-flow.ts b/src/codegen/statements/control-flow.ts index 0e70ae09..ff2eb040 100644 --- a/src/codegen/statements/control-flow.ts +++ b/src/codegen/statements/control-flow.ts @@ -1329,7 +1329,7 @@ export class ControlFlowGenerator { if (stmt.type !== 'try') { throw new Error('Expected try statement'); } - const tryStmt = stmt as { type: string; tryBlock: BlockStatement; catchParam: string | null; catchBody: BlockStatement | null; finallyBlock: BlockStatement | null }; + const tryStmt = stmt as { type: string; tryBlock: BlockStatement; catchClause: { param: string; body: BlockStatement } | null; finallyBlock: BlockStatement | null }; const frameRaw = this.nextTemp(); this.emit(`${frameRaw} = call i8* @GC_malloc(i64 216)`); @@ -1369,8 +1369,8 @@ export class ControlFlowGenerator { this.ctx.setCurrentLabel(catchEntryLabel); this.emit(`store i8* ${prevFrame}, i8** @__exception_stack`); - if (tryStmt.catchBody) { - const paramName = tryStmt.catchParam; + if (tryStmt.catchClause) { + const paramName = tryStmt.catchClause.param; if (paramName) { const excMsg = this.nextTemp(); this.emit(`${excMsg} = load i8*, i8** @__exception_message`); @@ -1379,7 +1379,7 @@ export class ControlFlowGenerator { this.emit(`store i8* ${excMsg}, i8** ${paramAlloca}`); this.ctx.defineVariable(paramName, paramAlloca, 'i8*', SymbolKind.String, 'local'); } - this.ctx.generateBlock(tryStmt.catchBody, params); + this.ctx.generateBlock(tryStmt.catchClause.body, params); } const catchHasTerminator = this.ctx.lastInstructionIsTerminator(); diff --git a/src/parser-native/transformer.ts b/src/parser-native/transformer.ts index 2cfc5450..1604289f 100644 --- a/src/parser-native/transformer.ts +++ b/src/parser-native/transformer.ts @@ -1624,14 +1624,14 @@ function transformTryStatement(node: TreeSitterNode): TryStatement { const tryBlock = bodyNode ? transformStatementBlock(bodyNode) : createEmptyBlock(); - let catchParam: string | null = null; - let catchBody: BlockStatement | null = null; + let catchClause: { param: string; body: BlockStatement } | null = null; if (handlerNode) { const paramNode = getChildByFieldName(handlerNode, 'parameter'); const catchBodyNode = getChildByFieldName(handlerNode, 'body'); - catchParam = paramNode ? (paramNode as NodeBase).text : 'e'; - catchBody = catchBodyNode ? transformStatementBlock(catchBodyNode) : createEmptyBlock(); + const param = paramNode ? (paramNode as NodeBase).text : 'e'; + const body = catchBodyNode ? transformStatementBlock(catchBodyNode) : createEmptyBlock(); + catchClause = { param, body }; } let finallyBlock: BlockStatement | null = null; @@ -1642,7 +1642,7 @@ function transformTryStatement(node: TreeSitterNode): TryStatement { } } - return { type: 'try', tryBlock, catchParam, catchBody, finallyBlock }; + return { type: 'try', tryBlock, catchClause, finallyBlock }; } function transformSwitchStatement(node: TreeSitterNode): BlockStatement { diff --git a/src/parser-ts/handlers/statements.ts b/src/parser-ts/handlers/statements.ts index e71cf60b..8ad38757 100644 --- a/src/parser-ts/handlers/statements.ts +++ b/src/parser-ts/handlers/statements.ts @@ -426,13 +426,13 @@ function transformThrowStatement(node: ts.ThrowStatement, checker: ts.TypeChecke function transformTryStatement(node: ts.TryStatement, checker: ts.TypeChecker | undefined): TryStatement { const tryBlock = transformBlock(node.tryBlock, checker); - let catchParam: string | null = null; - let catchBody: BlockStatement | null = null; + let catchClause: { param: string; body: BlockStatement } | null = null; if (node.catchClause) { - catchParam = node.catchClause.variableDeclaration && ts.isIdentifier(node.catchClause.variableDeclaration.name) + const param = node.catchClause.variableDeclaration && ts.isIdentifier(node.catchClause.variableDeclaration.name) ? node.catchClause.variableDeclaration.name.text : 'e'; - catchBody = transformBlock(node.catchClause.block, checker); + const body = transformBlock(node.catchClause.block, checker); + catchClause = { param, body }; } let finallyBlock: BlockStatement | null = null; @@ -440,7 +440,7 @@ function transformTryStatement(node: ts.TryStatement, checker: ts.TypeChecker | finallyBlock = transformBlock(node.finallyBlock, checker); } - return { type: 'try', tryBlock, catchParam, catchBody, finallyBlock, loc: getLoc(node) }; + return { type: 'try', tryBlock, catchClause, finallyBlock, loc: getLoc(node) }; } export function transformBlock(block: ts.Block, checker: ts.TypeChecker | undefined): BlockStatement {