From 332e78045e3018e803e69783363c4e99739130c1 Mon Sep 17 00:00:00 2001 From: cs01 Date: Tue, 10 Mar 2026 09:00:12 -0700 Subject: [PATCH 1/4] add runtime array bounds checking for numeric and uint8 arrays --- src/codegen/expressions/access/index.ts | 72 ++++++++----------- .../infrastructure/llvm-declarations.ts | 38 ++++++++++ src/codegen/llvm-generator.ts | 4 ++ tests/fixtures/arrays/array-bounds-check.ts | 4 ++ 4 files changed, 77 insertions(+), 41 deletions(-) create mode 100644 tests/fixtures/arrays/array-bounds-check.ts diff --git a/src/codegen/expressions/access/index.ts b/src/codegen/expressions/access/index.ts index 1ebab7bc..db85b560 100644 --- a/src/codegen/expressions/access/index.ts +++ b/src/codegen/expressions/access/index.ts @@ -165,20 +165,34 @@ export class IndexAccessGenerator { return arg; } - private generateStringArrayIndex(expr: IndexAccessNode, params: string[]): string { - const stringArrayPtr = this.ctx.generateExpression(expr.object, params); - const indexDouble = this.ctx.generateExpression(expr.index, params); - - // Convert double index to i32 for getelementptr - const indexType = this.ctx.getVariableType(indexDouble); - let index = indexDouble; + private toI32Index(indexValue: string): string { + const indexType = this.ctx.getVariableType(indexValue); if (indexType === "double") { - index = this.ctx.nextTemp(); - this.ctx.emit(`${index} = fptosi double ${indexDouble} to i32`); + const temp = this.ctx.nextTemp(); + this.ctx.emit(`${temp} = fptosi double ${indexValue} to i32`); + return temp; } else if (indexType === "i64") { - index = this.ctx.nextTemp(); - this.ctx.emit(`${index} = trunc i64 ${indexDouble} to i32`); + const temp = this.ctx.nextTemp(); + this.ctx.emit(`${temp} = trunc i64 ${indexValue} to i32`); + return temp; } + return indexValue; + } + + private emitBoundsCheck(arrayPtr: string, arrayType: string, index: string): void { + const lenPtr = this.ctx.nextTemp(); + this.ctx.emit( + `${lenPtr} = getelementptr inbounds ${arrayType}, ${arrayType}* ${arrayPtr}, i32 0, i32 1`, + ); + const len = this.ctx.nextTemp(); + this.ctx.emit(`${len} = load i32, i32* ${lenPtr}`); + this.ctx.emit(`call void @__cs_bounds_check(i32 ${index}, i32 ${len})`); + } + + private generateStringArrayIndex(expr: IndexAccessNode, params: string[]): string { + const stringArrayPtr = this.ctx.generateExpression(expr.object, params); + const indexDouble = this.ctx.generateExpression(expr.index, params); + const index = this.toI32Index(indexDouble); const dataPtr = this.ctx.nextTemp(); this.ctx.emit( @@ -193,7 +207,6 @@ export class IndexAccessGenerator { const elem = this.ctx.nextTemp(); this.ctx.emit(`${elem} = load i8*, i8** ${elemPtr}`); - // Track that this loaded value is a string this.ctx.setVariableType(elem, "i8*"); return elem; } @@ -201,17 +214,9 @@ export class IndexAccessGenerator { private generateNumericArrayIndex(expr: IndexAccessNode, params: string[]): string { const arrayPtr = this.ctx.generateExpression(expr.object, params); const indexDouble = this.ctx.generateExpression(expr.index, params); + const index = this.toI32Index(indexDouble); - // Convert double index to i32 for getelementptr - const indexType = this.ctx.getVariableType(indexDouble); - let index = indexDouble; - if (indexType === "double") { - index = this.ctx.nextTemp(); - this.ctx.emit(`${index} = fptosi double ${indexDouble} to i32`); - } else if (indexType === "i64") { - index = this.ctx.nextTemp(); - this.ctx.emit(`${index} = trunc i64 ${indexDouble} to i32`); - } + this.emitBoundsCheck(arrayPtr, "%Array", index); const dataPtr = this.ctx.nextTemp(); this.ctx.emit(`${dataPtr} = getelementptr inbounds %Array, %Array* ${arrayPtr}, i32 0, i32 0`); @@ -222,7 +227,6 @@ export class IndexAccessGenerator { const elemPtr = this.ctx.nextTemp(); this.ctx.emit(`${elemPtr} = getelementptr inbounds double, double* ${data}, i32 ${index}`); - // Load double element const elem = this.ctx.nextTemp(); this.ctx.emit(`${elem} = load double, double* ${elemPtr}`); this.ctx.setVariableType(elem, "double"); @@ -233,15 +237,7 @@ export class IndexAccessGenerator { const arrayPtr = this.ctx.generateExpression(expr.object, params); const indexDouble = this.ctx.generateExpression(expr.index, params); - const indexType = this.ctx.getVariableType(indexDouble); - let index = indexDouble; - if (indexType === "double") { - index = this.ctx.nextTemp(); - this.ctx.emit(`${index} = fptosi double ${indexDouble} to i32`); - } else if (indexType === "i64") { - index = this.ctx.nextTemp(); - this.ctx.emit(`${index} = trunc i64 ${indexDouble} to i32`); - } + const index = this.toI32Index(indexDouble); const arrayType = this.ctx.getVariableType(arrayPtr); if (arrayType === "%ObjectArray*") { @@ -313,15 +309,9 @@ export class IndexAccessGenerator { const arrayPtr = this.ctx.generateExpression(expr.object, params); const indexDouble = this.ctx.generateExpression(expr.index, params); - const indexType = this.ctx.getVariableType(indexDouble); - let index = indexDouble; - if (indexType === "double") { - index = this.ctx.nextTemp(); - this.ctx.emit(`${index} = fptosi double ${indexDouble} to i32`); - } else if (indexType === "i64") { - index = this.ctx.nextTemp(); - this.ctx.emit(`${index} = trunc i64 ${indexDouble} to i32`); - } + const index = this.toI32Index(indexDouble); + + this.emitBoundsCheck(arrayPtr, "%Uint8Array", index); const dataFieldPtr = this.ctx.nextTemp(); this.ctx.emit( diff --git a/src/codegen/infrastructure/llvm-declarations.ts b/src/codegen/infrastructure/llvm-declarations.ts index 3f05b279..8866df79 100644 --- a/src/codegen/infrastructure/llvm-declarations.ts +++ b/src/codegen/infrastructure/llvm-declarations.ts @@ -321,6 +321,44 @@ export function getSafeStringHelper(): string { return ir; } +export function getBoundsCheckHelper(): string { + let ir = ""; + ir += '@.str.oob_fmt = private unnamed_addr constant [49 x i8] c"Error: array index %d out of bounds (length %d)\\0A\\00", align 1\n'; + ir += "define void @__cs_bounds_check(i32 %index, i32 %length) {\n"; + ir += "entry:\n"; + ir += " %too_high = icmp sge i32 %index, %length\n"; + ir += " %too_low = icmp slt i32 %index, 0\n"; + ir += " %oob = or i1 %too_high, %too_low\n"; + ir += " br i1 %oob, label %fail, label %ok\n"; + ir += "fail:\n"; + ir += " %stderr = load i8*, i8** @stderr\n"; + ir += ' call i32 (i8*, i8*, ...) @fprintf(i8* %stderr, i8* getelementptr([49 x i8], [49 x i8]* @.str.oob_fmt, i32 0, i32 0), i32 %index, i32 %length)\n'; + ir += " call void @exit(i32 1)\n"; + ir += " unreachable\n"; + ir += "ok:\n"; + ir += " ret void\n"; + ir += "}\n\n"; + return ir; +} + +export function getNullCheckHelper(): string { + let ir = ""; + ir += '@.str.null_fmt = private unnamed_addr constant [39 x i8] c"Error: cannot access property of null\\0A\\00", align 1\n'; + ir += "define void @__cs_null_check(i8* %ptr) {\n"; + ir += "entry:\n"; + ir += " %is_null = icmp eq i8* %ptr, null\n"; + ir += " br i1 %is_null, label %fail, label %ok\n"; + ir += "fail:\n"; + ir += " %stderr = load i8*, i8** @stderr\n"; + ir += ' call i32 (i8*, i8*, ...) @fprintf(i8* %stderr, i8* getelementptr([39 x i8], [39 x i8]* @.str.null_fmt, i32 0, i32 0))\n'; + ir += " call void @exit(i32 1)\n"; + ir += " unreachable\n"; + ir += "ok:\n"; + ir += " ret void\n"; + ir += "}\n\n"; + return ir; +} + export function getStringHashHelper(): string { let ir = ""; ir += "; DJB2 hash function for strings\n"; diff --git a/src/codegen/llvm-generator.ts b/src/codegen/llvm-generator.ts index a592c023..2f44c6fa 100644 --- a/src/codegen/llvm-generator.ts +++ b/src/codegen/llvm-generator.ts @@ -69,6 +69,8 @@ import { getSafeStringHelper, getDoubleToStringHelper, getStringHashHelper, + getBoundsCheckHelper, + getNullCheckHelper, getGlobalVariables, } from "./infrastructure/llvm-declarations.js"; import { @@ -2558,6 +2560,8 @@ export class LLVMGenerator extends BaseGenerator implements IGeneratorContext { if (strHash) { irParts.push(strHash); } + irParts.push(getBoundsCheckHelper()); + irParts.push(getNullCheckHelper()); irParts.push(this.fsGen.generateReaddirSyncHelper()); irParts.push(this.fsGen.generateStatSyncHelper()); diff --git a/tests/fixtures/arrays/array-bounds-check.ts b/tests/fixtures/arrays/array-bounds-check.ts new file mode 100644 index 00000000..fd6d2b90 --- /dev/null +++ b/tests/fixtures/arrays/array-bounds-check.ts @@ -0,0 +1,4 @@ +// @test-exit-code: 1 +const arr: number[] = [1, 2, 3]; +const x = arr[10]; +console.log(x); From 84d74a111deb0a07a499862b62c70758225b9bde Mon Sep 17 00:00:00 2001 From: cs01 Date: Tue, 10 Mar 2026 09:05:38 -0700 Subject: [PATCH 2/4] convert throw new error to emiterror in array method codegen for proper compiler diagnostics --- .../types/collections/array/combine.ts | 4 ++-- .../types/collections/array/iteration.ts | 20 +++++++++---------- .../types/collections/array/literal.ts | 2 +- .../types/collections/array/mutators.ts | 4 ++-- .../types/collections/array/reorder.ts | 6 +++--- .../collections/array/search-predicate.ts | 14 ++++++------- src/codegen/types/collections/array/search.ts | 6 +++--- src/codegen/types/collections/array/sort.ts | 4 ++-- src/codegen/types/collections/array/splice.ts | 5 +++-- 9 files changed, 33 insertions(+), 32 deletions(-) diff --git a/src/codegen/types/collections/array/combine.ts b/src/codegen/types/collections/array/combine.ts index 6f93767b..695eb693 100644 --- a/src/codegen/types/collections/array/combine.ts +++ b/src/codegen/types/collections/array/combine.ts @@ -365,7 +365,7 @@ export function generateArrayJoin( params: string[], ): string { if (expr.args.length > 1) { - throw new Error("join() accepts 0 or 1 arguments (separator)"); + return gen.emitError("join() accepts 0 or 1 arguments (separator)", expr.loc); } const arrayPtr = gen.generateExpression(expr.object, params); @@ -812,7 +812,7 @@ export function generateArrayConcat( } if (expr.args.length !== 1) { - throw new Error("concat() requires exactly 1 argument"); + return gen.emitError("concat() requires exactly 1 argument", expr.loc); } const otherArrayPtr = gen.generateExpression(expr.args[0], params); diff --git a/src/codegen/types/collections/array/iteration.ts b/src/codegen/types/collections/array/iteration.ts index 9a687130..61391e80 100644 --- a/src/codegen/types/collections/array/iteration.ts +++ b/src/codegen/types/collections/array/iteration.ts @@ -28,7 +28,7 @@ export function generateArrayFilter( params: string[], ): string { if (expr.args.length !== 1) { - throw new Error("filter() requires exactly 1 argument (predicate function)"); + return gen.emitError("filter() requires exactly 1 argument (predicate function)", expr.loc); } const arrayPtr = gen.generateExpression(expr.object, params); @@ -45,7 +45,7 @@ export function generateArrayFilter( predicateFn = gen.generateExpression(callbackArg, params); gen.setExpectedCallbackParamType(null); } else { - throw new Error("filter() argument must be a function name or inline function"); + return gen.emitError("filter() argument must be a function name or inline function", expr.loc); } let result: string; @@ -271,7 +271,7 @@ export function generateArrayForEach( params: string[], ): string { if (expr.args.length !== 1) { - throw new Error("forEach() requires exactly 1 argument (callback function)"); + return gen.emitError("forEach() requires exactly 1 argument (callback function)", expr.loc); } const arrayPtr = gen.generateExpression(expr.object, params); @@ -288,7 +288,7 @@ export function generateArrayForEach( callbackFn = gen.generateExpression(callbackArg, params); gen.setExpectedCallbackParamType(null); } else { - throw new Error("forEach() argument must be a function name or inline function"); + return gen.emitError("forEach() argument must be a function name or inline function", expr.loc); } let result: string; @@ -402,7 +402,7 @@ export function generateArrayReduce( params: string[], ): string { if (expr.args.length < 1 || expr.args.length > 2) { - throw new Error("reduce() requires 1-2 arguments (callback, optional initialValue)"); + return gen.emitError("reduce() requires 1-2 arguments (callback, optional initialValue)", expr.loc); } const arrayPtr = gen.generateExpression(expr.object, params); @@ -430,7 +430,7 @@ export function generateArrayReduce( callbackFn = gen.generateExpression(callbackArg, params); gen.setExpectedCallbackParamType(null); } else { - throw new Error("reduce() argument must be a function name or inline function"); + return gen.emitError("reduce() argument must be a function name or inline function", expr.loc); } let initialValue: string | null = null; @@ -595,7 +595,7 @@ export function generateArrayMap( params: string[], ): string { if (expr.args.length !== 1) { - throw new Error("map() requires exactly 1 argument (callback function)"); + return gen.emitError("map() requires exactly 1 argument (callback function)", expr.loc); } const arrayPtr = gen.generateExpression(expr.object, params); @@ -616,7 +616,7 @@ export function generateArrayMap( gen.setExpectedCallbackParamType(null); gen.setExpectedCallbackReturnType(null); } else { - throw new Error("map() argument must be a function name or inline function"); + return gen.emitError("map() argument must be a function name or inline function", expr.loc); } let result: string; @@ -635,7 +635,7 @@ export function generateStringArrayMap( params: string[], ): string { if (expr.args.length !== 1) { - throw new Error("map() requires exactly 1 argument (callback function)"); + return gen.emitError("map() requires exactly 1 argument (callback function)", expr.loc); } const arrayPtr = gen.generateExpression(expr.object, params); @@ -651,7 +651,7 @@ export function generateStringArrayMap( gen.setExpectedCallbackParamType(null); gen.setExpectedCallbackReturnType(null); } else { - throw new Error("map() argument must be a function name or inline function"); + return gen.emitError("map() argument must be a function name or inline function", expr.loc); } const result = generateStringArrayMapImpl(gen, arrayPtr, callbackFn); diff --git a/src/codegen/types/collections/array/literal.ts b/src/codegen/types/collections/array/literal.ts index 5e7ccbfe..d951aa99 100644 --- a/src/codegen/types/collections/array/literal.ts +++ b/src/codegen/types/collections/array/literal.ts @@ -33,7 +33,7 @@ export function generateArrayLiteral( ): string { const e = expr as ExprBase; if (e.type !== "array") { - throw new Error("Expected array literal"); + return gen.emitError("Expected array literal"); } const arrExpr = expr as ArrayExpr; diff --git a/src/codegen/types/collections/array/mutators.ts b/src/codegen/types/collections/array/mutators.ts index ab8ada21..1b0a4c9e 100644 --- a/src/codegen/types/collections/array/mutators.ts +++ b/src/codegen/types/collections/array/mutators.ts @@ -19,7 +19,7 @@ export function generateArrayPush( ): string { // arr.push(value) - adds value to array and returns new length if (expr.args.length !== 1) { - throw new Error("push() requires exactly 1 argument"); + return gen.emitError("push() requires exactly 1 argument", expr.loc); } const arrayPtr = gen.generateExpression(expr.object, params); @@ -69,7 +69,7 @@ export function generateArrayPop( ): string { // arr.pop() - removes and returns last element if (expr.args.length !== 0) { - throw new Error("pop() requires 0 arguments"); + return gen.emitError("pop() requires 0 arguments", expr.loc); } const arrayPtr = gen.generateExpression(expr.object, params); diff --git a/src/codegen/types/collections/array/reorder.ts b/src/codegen/types/collections/array/reorder.ts index 00b9873c..20fe2580 100644 --- a/src/codegen/types/collections/array/reorder.ts +++ b/src/codegen/types/collections/array/reorder.ts @@ -14,7 +14,7 @@ export function generateArrayReverse( params: string[], ): string { if (expr.args.length !== 0) { - throw new Error("reverse() requires 0 arguments"); + return gen.emitError("reverse() requires 0 arguments", expr.loc); } const arrayPtr = gen.generateExpression(expr.object, params); @@ -161,7 +161,7 @@ export function generateArrayShift( params: string[], ): string { if (expr.args.length !== 0) { - throw new Error("shift() requires 0 arguments"); + return gen.emitError("shift() requires 0 arguments", expr.loc); } const arrayPtr = gen.generateExpression(expr.object, params); @@ -363,7 +363,7 @@ export function generateArrayUnshift( params: string[], ): string { if (expr.args.length !== 1) { - throw new Error("unshift() requires exactly 1 argument"); + return gen.emitError("unshift() requires exactly 1 argument", expr.loc); } const arrayPtr = gen.generateExpression(expr.object, params); diff --git a/src/codegen/types/collections/array/search-predicate.ts b/src/codegen/types/collections/array/search-predicate.ts index bcd10c6b..d66687a8 100644 --- a/src/codegen/types/collections/array/search-predicate.ts +++ b/src/codegen/types/collections/array/search-predicate.ts @@ -28,7 +28,7 @@ export function generateArrayFind( params: string[], ): string { if (expr.args.length !== 1) { - throw new Error("find() requires exactly 1 argument (predicate function)"); + return gen.emitError("find() requires exactly 1 argument (predicate function)", expr.loc); } const arrayPtr = gen.generateExpression(expr.object, params); @@ -45,7 +45,7 @@ export function generateArrayFind( predicateFn = gen.generateExpression(predicateArg, params); gen.setExpectedCallbackParamType(null); } else { - throw new Error("find() argument must be a function name or inline function"); + return gen.emitError("find() argument must be a function name or inline function", expr.loc); } let result: string; @@ -200,7 +200,7 @@ export function generateArraySome( params: string[], ): string { if (expr.args.length !== 1) { - throw new Error("some() requires exactly 1 argument (predicate function)"); + return gen.emitError("some() requires exactly 1 argument (predicate function)", expr.loc); } const arrayPtr = gen.generateExpression(expr.object, params); @@ -217,7 +217,7 @@ export function generateArraySome( predicateFn = gen.generateExpression(predicateArg, params); gen.setExpectedCallbackParamType(null); } else { - throw new Error("some() argument must be a function name or inline function"); + return gen.emitError("some() argument must be a function name or inline function", expr.loc); } let result: string; @@ -376,7 +376,7 @@ export function generateArrayEvery( params: string[], ): string { if (expr.args.length !== 1) { - throw new Error("every() requires exactly 1 argument (predicate function)"); + return gen.emitError("every() requires exactly 1 argument (predicate function)", expr.loc); } const arrayPtr = gen.generateExpression(expr.object, params); @@ -393,7 +393,7 @@ export function generateArrayEvery( predicateFn = gen.generateExpression(predicateArg, params); gen.setExpectedCallbackParamType(null); } else { - throw new Error("every() argument must be a function name or inline function"); + return gen.emitError("every() argument must be a function name or inline function", expr.loc); } let result: string; @@ -552,7 +552,7 @@ export function generateArrayIncludes( params: string[], ): string { if (expr.args.length !== 1) { - throw new Error("includes() requires exactly 1 argument"); + return gen.emitError("includes() requires exactly 1 argument", expr.loc); } const arrayPtr = gen.generateExpression(expr.object, params); diff --git a/src/codegen/types/collections/array/search.ts b/src/codegen/types/collections/array/search.ts index a7417ab5..6397a3df 100644 --- a/src/codegen/types/collections/array/search.ts +++ b/src/codegen/types/collections/array/search.ts @@ -24,7 +24,7 @@ export function generateArrayIndexOf( params: string[], ): string { if (expr.args.length < 1 || expr.args.length > 2) { - throw new Error("indexOf() requires 1 or 2 arguments"); + return gen.emitError("indexOf() requires 1 or 2 arguments", expr.loc); } const arrayPtr = gen.generateExpression(expr.object, params); @@ -195,7 +195,7 @@ export function generateArrayFindIndex( params: string[], ): string { if (expr.args.length !== 1) { - throw new Error("findIndex() requires exactly 1 argument (predicate function)"); + return gen.emitError("findIndex() requires exactly 1 argument (predicate function)", expr.loc); } const arrayPtr = gen.generateExpression(expr.object, params); @@ -219,7 +219,7 @@ export function generateArrayFindIndex( predicateFn = gen.generateExpression(predicateArg, params); gen.setExpectedCallbackParamType(null); } else { - throw new Error("findIndex() argument must be a function name or inline function"); + return gen.emitError("findIndex() argument must be a function name or inline function", expr.loc); } let result: string; diff --git a/src/codegen/types/collections/array/sort.ts b/src/codegen/types/collections/array/sort.ts index c5dadf60..62110274 100644 --- a/src/codegen/types/collections/array/sort.ts +++ b/src/codegen/types/collections/array/sort.ts @@ -57,7 +57,7 @@ export function generateArraySort( params: string[], ): string { if (expr.args.length > 1) { - throw new Error("sort() expects 0 or 1 arguments"); + return gen.emitError("sort() expects 0 or 1 arguments", expr.loc); } const arrayPtr = gen.generateExpression(expr.object, params); @@ -89,7 +89,7 @@ export function generateArraySort( } else if (predicateArg.type === "arrow_function") { compareFn = gen.generateExpression(predicateArg, params); } else { - throw new Error("sort() comparator must be a function name or inline function"); + return gen.emitError("sort() comparator must be a function name or inline function", expr.loc); } // Capture env ptr for inline lambda closures, then clear it diff --git a/src/codegen/types/collections/array/splice.ts b/src/codegen/types/collections/array/splice.ts index 7a1d4ea6..9522395c 100644 --- a/src/codegen/types/collections/array/splice.ts +++ b/src/codegen/types/collections/array/splice.ts @@ -1,4 +1,4 @@ -import { Expression, MethodCallNode, VariableNode } from "../../../../ast/types.js"; +import { Expression, MethodCallNode, SourceLocation, VariableNode } from "../../../../ast/types.js"; interface ExprBase { type: string; @@ -18,6 +18,7 @@ interface ArraySpliceContext { setVariableType(name: string, type: string): void; generateExpression(expr: Expression, params: string[]): string; ensureDouble(value: string): string; + emitError(message: string, loc?: SourceLocation, suggestion?: string): never; } export function generateArraySplice( @@ -26,7 +27,7 @@ export function generateArraySplice( params: string[], ): string { if (expr.args.length < 1 || expr.args.length > 2) { - throw new Error("splice() requires 1 or 2 arguments (start, deleteCount)"); + return gen.emitError("splice() requires 1 or 2 arguments (start, deleteCount)", expr.loc); } const arrayPtr = gen.generateExpression(expr.object, params); From ac97c1d512709093d06f1dea0fcec4cbd225969e Mon Sep 17 00:00:00 2001 From: cs01 Date: Tue, 10 Mar 2026 09:32:40 -0700 Subject: [PATCH 3/4] add autonomous pr workflow instructions to claude.md --- CLAUDE.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/CLAUDE.md b/CLAUDE.md index 993ede05..05a444a0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -10,6 +10,21 @@ cd .worktrees/ # do work, commit, then open a PR ``` +## Autonomous PR Workflow + +Agents can work autonomously end-to-end: create worktrees, make changes, push branches, create PRs, +monitor CI, and merge when green. You have push access to feature branches and merge access to PRs. + +1. Create a worktree and branch +2. Make changes, run `npm run verify:quick`, commit +3. `git push origin ` — push to remote +4. `gh pr create` — open a PR +5. `gh pr checks ` — monitor CI +6. When CI is green: `gh pr merge --squash` — merge to main +7. Pull main and continue with next task + +**Never push to main directly.** Always go through PRs. + ## Testing & Commit Workflow After completing each todo: From 4892ced64cf6717cdd0c1f77bef3bc9e07e35aa1 Mon Sep 17 00:00:00 2001 From: cs01 Date: Tue, 10 Mar 2026 09:34:22 -0700 Subject: [PATCH 4/4] fix formatting --- src/codegen/infrastructure/llvm-declarations.ts | 12 ++++++++---- src/codegen/types/collections/array/iteration.ts | 5 ++++- src/codegen/types/collections/array/search.ts | 5 ++++- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/codegen/infrastructure/llvm-declarations.ts b/src/codegen/infrastructure/llvm-declarations.ts index 8866df79..6183e1ba 100644 --- a/src/codegen/infrastructure/llvm-declarations.ts +++ b/src/codegen/infrastructure/llvm-declarations.ts @@ -323,7 +323,8 @@ export function getSafeStringHelper(): string { export function getBoundsCheckHelper(): string { let ir = ""; - ir += '@.str.oob_fmt = private unnamed_addr constant [49 x i8] c"Error: array index %d out of bounds (length %d)\\0A\\00", align 1\n'; + ir += + '@.str.oob_fmt = private unnamed_addr constant [49 x i8] c"Error: array index %d out of bounds (length %d)\\0A\\00", align 1\n'; ir += "define void @__cs_bounds_check(i32 %index, i32 %length) {\n"; ir += "entry:\n"; ir += " %too_high = icmp sge i32 %index, %length\n"; @@ -332,7 +333,8 @@ export function getBoundsCheckHelper(): string { ir += " br i1 %oob, label %fail, label %ok\n"; ir += "fail:\n"; ir += " %stderr = load i8*, i8** @stderr\n"; - ir += ' call i32 (i8*, i8*, ...) @fprintf(i8* %stderr, i8* getelementptr([49 x i8], [49 x i8]* @.str.oob_fmt, i32 0, i32 0), i32 %index, i32 %length)\n'; + ir += + " call i32 (i8*, i8*, ...) @fprintf(i8* %stderr, i8* getelementptr([49 x i8], [49 x i8]* @.str.oob_fmt, i32 0, i32 0), i32 %index, i32 %length)\n"; ir += " call void @exit(i32 1)\n"; ir += " unreachable\n"; ir += "ok:\n"; @@ -343,14 +345,16 @@ export function getBoundsCheckHelper(): string { export function getNullCheckHelper(): string { let ir = ""; - ir += '@.str.null_fmt = private unnamed_addr constant [39 x i8] c"Error: cannot access property of null\\0A\\00", align 1\n'; + ir += + '@.str.null_fmt = private unnamed_addr constant [39 x i8] c"Error: cannot access property of null\\0A\\00", align 1\n'; ir += "define void @__cs_null_check(i8* %ptr) {\n"; ir += "entry:\n"; ir += " %is_null = icmp eq i8* %ptr, null\n"; ir += " br i1 %is_null, label %fail, label %ok\n"; ir += "fail:\n"; ir += " %stderr = load i8*, i8** @stderr\n"; - ir += ' call i32 (i8*, i8*, ...) @fprintf(i8* %stderr, i8* getelementptr([39 x i8], [39 x i8]* @.str.null_fmt, i32 0, i32 0))\n'; + ir += + " call i32 (i8*, i8*, ...) @fprintf(i8* %stderr, i8* getelementptr([39 x i8], [39 x i8]* @.str.null_fmt, i32 0, i32 0))\n"; ir += " call void @exit(i32 1)\n"; ir += " unreachable\n"; ir += "ok:\n"; diff --git a/src/codegen/types/collections/array/iteration.ts b/src/codegen/types/collections/array/iteration.ts index 61391e80..047ea24b 100644 --- a/src/codegen/types/collections/array/iteration.ts +++ b/src/codegen/types/collections/array/iteration.ts @@ -402,7 +402,10 @@ export function generateArrayReduce( params: string[], ): string { if (expr.args.length < 1 || expr.args.length > 2) { - return gen.emitError("reduce() requires 1-2 arguments (callback, optional initialValue)", expr.loc); + return gen.emitError( + "reduce() requires 1-2 arguments (callback, optional initialValue)", + expr.loc, + ); } const arrayPtr = gen.generateExpression(expr.object, params); diff --git a/src/codegen/types/collections/array/search.ts b/src/codegen/types/collections/array/search.ts index 6397a3df..7166fcb6 100644 --- a/src/codegen/types/collections/array/search.ts +++ b/src/codegen/types/collections/array/search.ts @@ -219,7 +219,10 @@ export function generateArrayFindIndex( predicateFn = gen.generateExpression(predicateArg, params); gen.setExpectedCallbackParamType(null); } else { - return gen.emitError("findIndex() argument must be a function name or inline function", expr.loc); + return gen.emitError( + "findIndex() argument must be a function name or inline function", + expr.loc, + ); } let result: string;