Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .claude/rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
8 changes: 4 additions & 4 deletions src/analysis/semantic-analyzer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
3 changes: 1 addition & 2 deletions src/ast/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
4 changes: 2 additions & 2 deletions src/ast/visitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
24 changes: 24 additions & 0 deletions src/codegen/expressions/access/member.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
}
}
}
}

Expand Down
9 changes: 4 additions & 5 deletions src/codegen/infrastructure/closure-analyzer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down Expand Up @@ -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);
Expand Down
8 changes: 4 additions & 4 deletions src/codegen/statements/control-flow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)`);
Expand Down Expand Up @@ -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`);
Expand All @@ -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();
Expand Down
10 changes: 5 additions & 5 deletions src/parser-native/transformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 {
Expand Down
10 changes: 5 additions & 5 deletions src/parser-ts/handlers/statements.ts
Original file line number Diff line number Diff line change
Expand Up @@ -426,21 +426,21 @@ 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;
if (node.finallyBlock) {
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 {
Expand Down
Loading