data: save session discoveries (let* ordering, two error fixes)
ober
12489200a59c55068d7459800e5b21b013690e89
--- a/data/cookbooks.sexp +++ b/data/cookbooks.sexp @@ -4921,4 +4921,15 @@ "for-fold") ("title" . - "Compile Typed Jerboa scalar modules to LLVM IR and verify/run them"))) + "Compile Typed Jerboa scalar modules to LLVM IR and verify/run them")) + (("code" + . + "(import (jerboa prelude))\n\n;; A mutable counter standing in for any emitter/env state\n(def n 0)\n(def (next!) (set! n (+ n 1)) n)\n(def (peek) n)\n\n;; WRONG: R6RS leaves init order unspecified; Chez runs them RIGHT-TO-LEFT,\n;; so `snapshot` reads the counter BEFORE next! runs.\n(let ([value (next!)]\n [snapshot (peek)])\n (displayln (list value snapshot))) ;; => (1 0) -- snapshot is stale!\n\n;; RIGHT: let* guarantees top-to-bottom evaluation.\n(let* ([value (next!)]\n [snapshot (peek)])\n (displayln (list value snapshot))) ;; => (2 2)") ("id" . "chez-let-init-order-let-star") ("imports") + ("notes" + . + "Caused two real bugs in the typed LLVM IR emitter (branch llvmir, 2026-06-03): (1) capturing a basic-block label in the same let as the expression-lowering call that moves it produced malformed phi predecessors on nested if; (2) allocating a fresh SSA register in the same let as operand lowering numbered registers out of order. Both were valid-looking code that only failed on nested control flow. Rule: whenever one init mutates state another init reads (counters, labels, ports, env records), use let*. Add a nested-control-flow test case (if inside else) — flat cases mask the bug. Same hazard applies to plain function-call argument order; bind with let* first when arguments have effects.") + ("tags" "let" "let*" "evaluation-order" "mutation" "emitter" + "chez") + ("title" + . + "Plain let inits evaluate right-to-left in Chez — use let* in stateful code"))) --- a/data/error-fixes.sexp +++ b/data/error-fixes.sexp @@ -1985,4 +1985,30 @@ "(variable\\s+string\\-ends\\-with\\?\\s+is\\s+not\\s+bound|unbound\\s+identifier\\s+string\\-ends\\-with\\?)") ("related_divergence" . "hallucinated-string-ends-with") ("type" . "Hallucinated Identifier") - ("wrong_example" . "(string-ends-with? \"hello\" \"lo\")"))) + ("wrong_example" . "(string-ends-with? \"hello\" \"lo\")")) + (("code_example" + . + ";; FAILS: multiple definitions for llvm-function-symbol in body\n(defstruct llvm-function (symbol return-type))\n(def (llvm-function-symbol module-name def-name) ...)\n\n;; FIX: rename the field; accessor becomes llvm-function-name\n(defstruct llvm-function (name return-type))\n(def (llvm-function-symbol module-name def-name) ...)") + ("explanation" + . + "In a Chez library body, defstruct expands into define forms for make-<s>, <s>?, and per-field accessors/setters. If any generated name equals one of your own definitions, Chez raises 'multiple definitions for X in body' pointing at the library form, not at the colliding pair. Check defstruct field names first: the collision is usually <struct-name>-<field-name> vs a hand-written function.") + ("fix" + . + "A defstruct field generates <struct>-<field> accessors (and <struct>-<field>-set!) that can collide with your own def of the same name. Rename the field or your function. Example: (defstruct llvm-function (symbol ...)) generates llvm-function-symbol, colliding with (def (llvm-function-symbol ...) ...) — rename the field to name so the accessor becomes llvm-function-name.") + ("id" . "defstruct-accessor-name-collision") + ("pattern" + . + "multiple definitions for ([a-zA-Z0-9_?!<>*+-]+) in body") + ("type" . "compile")) + (("code_example" + . + ";; FAILS: branch-type-mismatch (-1 is Int; 1 and 0 are Nat)\n(def (sign (n : Int)) : Int\n (if (< n 0) -1 (if (> n 0) 1 0)))\n\n;; FIX: compute Int-typed 1 and 0 from the Int operand\n(def (sign (n : Int)) : Int\n (if (< n 0)\n -1\n (if (> n 0)\n (/ n n)\n (- n n))))") + ("explanation" + . + "The typed checker infers literal types by sign ((>= v 0) -> Nat, else Int) and requires if branches to agree exactly; there is no Nat<:Int subsumption or branch-type merging. This bites sign-like functions: (def (sign (n : Int)) : Int (if (< n 0) -1 (if (> n 0) 1 0))) fails because 1 and 0 are Nat while -1 is Int.") + ("fix" + . + "In Typed Jerboa, both if branches must have the SAME numeric type. Positive integer literals always infer as Nat and negative ones as Int, so (if test -1 1) mixes Int and Nat. Make every branch Int by computing the value from an Int operand — e.g. 1 as (/ n n), 0 as (- n n), or (+ -1 2) — or redesign with Nat-only values.") + ("id" . "typed-branch-type-mismatch-nat-int") + ("pattern" . "branch-type-mismatch") + ("type" . "typecheck")))