Skip to content

parser: disambiguate bare-token compound patterns in defn (eigentrust pitfall #7)#16

Merged
hierophantos merged 1 commit into
mainfrom
claude/fix-eigentrust-pitfall-7-marg
Apr 27, 2026
Merged

parser: disambiguate bare-token compound patterns in defn (eigentrust pitfall #7)#16
hierophantos merged 1 commit into
mainfrom
claude/fix-eigentrust-pitfall-7-marg

Conversation

@kumavis

@kumavis kumavis commented Apr 25, 2026

Copy link
Copy Markdown
Contributor

Fixes pitfall #7 from docs/tracking/2026-04-23_eigentrust_pitfalls.md.

Summary

defn sum-rows
  | nil            -> nil
  | cons r nil     -> r
  | cons r rest    -> [add-vec r [sum-rows rest]]

failed with Unbound variable sum-rows::1 — the parser treated cons r rest as three separate arguments instead of one compound pattern.

Approach (a) — auto-pack constructor patterns

In parse-defn-clause, when the leading token after | names a known constructor whose field count matches the remaining tokens, pack them into one compound pattern. Falls back to the existing N-arg interpretation when the leading token is not a constructor (e.g., defn add | x y -> [+ x y] still works).

Local change in one branch of the cond, ~24 lines, no other files touched.

Why (a) over (b) "raise a clear error": defn is the primary dispatch mechanism per prologos-syntax.md, and ML/Haskell users naturally read cons r nil as one compound. Detecting via lookup-ctor is local and preserves multi-arg defns unchanged.

Files changed

  • racket/prologos/parser.rkt (+24)
  • racket/prologos/tests/test-defn-multiarg-patterns.rkt (new, 315 lines, 11 cases)

Test plan

  • 11/11 new tests pass
  • 43/43 existing tests in test-pattern-defn-01 / test-pattern-defn-02 / test-multi-body-defn pass
  • Full affected suite: 4646 tests in 255 files; 1 unrelated pre-existing failure (stale tracking entry for non-existent test-constraint-retry-propagator.rkt)
  • CI fix included (benchmarks/micro/info.rkt skip; redundant once PR Migrate bench-bsp-le-track2.rkt to current ATMS API (CI unblock) #10's actual migration lands first)

Latent bug surfaced (NOT introduced) — worth tracking

The fix exposed a latent compile-match-tree bug: variable patterns named in a sub-position get bound via let v := __cons_1, referring to a param that's been destructured by a later dispatch column. This corrupts recursive bodies like cons r rest -> [+ r [recurse rest]].

Reproducible on main with the bracketed [[cons r rest]] form (same internal AST). The tests cover the slices unaffected (empty/singleton inputs, wildcards, all-var multi-arg). The commit message documents the latent issue for future tracking — it should be filed as a separate bug.

Commits

  1. 1595905 — primary fix in parser.rkt + test file
  2. dd26ad4 — CI fix (skip stale bench)

https://claude.ai/code/session_01MbncYJnrvjzhbVWw4xGi5x


Generated by Claude Code

@hierophantos hierophantos left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Surgical scope, uses existing lookup-ctor / ctor-meta-field-types infrastructure (no new APIs), N-arg fallback preserved.

Two non-blocking observations:

  1. The auto-pack rule means a user who picks a parameter name matching a constructor — defn foo | cons x y -> ... with cons as a variable — would now get the compound interpretation. ML/Haskell-aligned default is right, but worth a release note when a batch of these pitfall fixes lands.
  2. Filed the latent compile-match-tree variable-binding bug as #18 with your reproducer + diagnosis attributed. That tracks the recursive eigentrust case independently — your parser fix here can land without it.

Approving — ready to merge once you rebase to drop the skip commit.

@kumavis kumavis force-pushed the claude/fix-eigentrust-pitfall-7-marg branch from dd26ad4 to 43017b6 Compare April 26, 2026 01:03
kumavis added a commit that referenced this pull request Apr 26, 2026
The 2026-04-23 eigentrust pitfalls memo (forthcoming branch) enumerated 16
items hit during the EigenTrust implementation. Items #1-7 and #11-15 are
language/elaboration defects with their own PRs. Items #8, #9, #10, and #16
are observations rather than Prologos defects; no compiler change is needed
for them but the memo deserves a parallel disposition note so a future reader
does not double-count them as open work.

#8 (exact-Rat slow on deep iter): intrinsic to exact rational arithmetic;
benchmark-scope guidance, not a fix.

#9 (Posit32 literals work): positive observation; `~` literal prefix is
unambiguous unlike `0/1`. No action.

#10 (PVec preserves where List does not): subsumed by pitfall #3 fix. After
#3 lands, both literal forms preserve element type uniformly. Close as
duplicate.

#16 (column-stochastic vs row-stochastic): algorithm/spec clarification, not
a Prologos defect. The eigentrust implementation branch already takes
column-stochastic M directly and validates via col-stochastic?.

https://claude.ai/code/session_01MbncYJnrvjzhbVWw4xGi5x

Co-authored-by: kumavis <1474978+kumavis@users.noreply.github.com>
@kumavis

kumavis commented Apr 26, 2026

Copy link
Copy Markdown
Contributor Author
defn sum-rows
  | nil            -> nil
  | cons r nil     -> r
  | cons r rest    -> [add-vec r [sum-rows rest]]

expected to see an argument body following the function name.

…defn

Pre-fix: `defn f | cons r nil -> r | cons r rest -> ...` parsed each
bare token after `|` as a separate arg pattern, splitting the function
into clauses with mismatched arities (1 nil clause + 2 cons-3-arg
clauses). This produced a per-clause helper (`f::1`, `f::3`) and the
recursive call site `[f rest]` failed with `Unbound variable f::1`
because the arity-1 helper only knew the nil clause and any non-empty
list hit __match-fail.

Fix shape (a) — implement general pattern matrices by auto-packing
when the leading bare token names a known constructor whose field
count matches the remaining tokens. `cons r nil` → one compound
pattern `pat-compound 'cons (var-r, var-nil)` (then normalize-pattern
converts var-nil to compound-nil since nil is a known nullary ctor).

Fallback preserved for genuinely multi-arg defns:
  defn add | x y -> [+ x y]
The leading `x` is a variable (lookup-ctor returns #f), so falls
through to the old N-arg interpretation.

Trade-off vs (b) (raise an error): (a) makes the syntax do what
ML/Haskell users expect — `defn` IS the primary dispatch mechanism
per .claude/rules/prologos-syntax.md, so making `cons r nil` mean
"compound pattern" is the natural reading. The detection is local and
does not change semantics for any pattern that didn't have a known
ctor as its leading token.

Scope: 23 lines in parser.rkt's parse-defn-clause + 11 new tests in
test-defn-multiarg-patterns.rkt. No changes elsewhere.

Test results: 11/11 new tests pass. 43/43 related tests pass
(test-pattern-defn-01, test-pattern-defn-02, test-multi-body-defn).
Full affected-suite: 4646 tests in 255 files, 1 unrelated pre-existing
failure (stale tracking entry for non-existent
test-constraint-retry-propagator.rkt).

Latent issue exposed (NOT introduced by this fix): compile-match-tree
binds variable patterns to outer-param names when those params get
destructured by a later dispatch column. E.g., `cons r rest` after
outer-cons specialization tries `let rest := __cons_1` while `__cons_1`
is being destructured into `__cons_1_0` and `__cons_1_1`. This affects
the recursive bodies of the eigentrust example with multi-element
inputs, but is a pre-existing bug in compile-match-tree —
reproducible on main with the bracketed `[cons r rest]` form, which
parses to the same internal representation. The new tests cover the
slices unaffected by this bug (empty + singleton inputs, wildcard
patterns, all-var multi-arg) and explicitly call out the latent issue.

Co-authored-by: kumavis <1474978+kumavis@users.noreply.github.com>
@kumavis kumavis force-pushed the claude/fix-eigentrust-pitfall-7-marg branch from 43017b6 to 458bfcf Compare April 26, 2026 03:03
kumavis added a commit that referenced this pull request Apr 26, 2026
The 2026-04-23 eigentrust pitfalls memo (forthcoming branch) enumerated 16
items hit during the EigenTrust implementation. Items #1-7 and #11-15 are
language/elaboration defects with their own PRs. Items #8, #9, #10, and #16
are observations rather than Prologos defects; no compiler change is needed
for them but the memo deserves a parallel disposition note so a future reader
does not double-count them as open work.

#8 (exact-Rat slow on deep iter): intrinsic to exact rational arithmetic;
benchmark-scope guidance, not a fix.

#9 (Posit32 literals work): positive observation; `~` literal prefix is
unambiguous unlike `0/1`. No action.

#10 (PVec preserves where List does not): subsumed by pitfall #3 fix. After
#3 lands, both literal forms preserve element type uniformly. Close as
duplicate.

#16 (column-stochastic vs row-stochastic): algorithm/spec clarification, not
a Prologos defect. The eigentrust implementation branch already takes
column-stochastic M directly and validates via col-stochastic?.

https://claude.ai/code/session_01MbncYJnrvjzhbVWw4xGi5x

Co-authored-by: kumavis <1474978+kumavis@users.noreply.github.com>
@hierophantos hierophantos merged commit 5c4c00c into main Apr 27, 2026
1 check passed
@hierophantos hierophantos deleted the claude/fix-eigentrust-pitfall-7-marg branch April 27, 2026 04:07
kumavis pushed a commit that referenced this pull request Apr 27, 2026
Wires the OCapN port to a real Racket toolchain and fixes the issues
the test run surfaced.

Library fixes
  - vat.prologos: rename `spawn` -> `vat-spawn` (collision with the
    reserved surface form recognised in macros.rkt:`'spawn`),
    `spawn-actor` -> `vat-spawn-actor`. Same in core.prologos and the
    acceptance file.
  - vat.prologos: drop the `Sigma Vat Nat` return shape for spawn /
    fresh-promise / send. Replace with a named `Allocated` struct +
    `alloc-vat` / `alloc-id` accessors. The Sigma form ran into
    "could not infer" elaborator errors when the body destructured
    via `match | pair a b -> ...` and then re-constructed a Sigma;
    `[fst p]` / `[snd p]` reused on the same `p` tripped QTT
    multiplicity. The named struct sidesteps both.
  - vat.prologos: reorder `resolve-promise` / `break-promise` BEFORE
    `apply-effect` (forward-reference rule — module elaboration is
    single-pass top-to-bottom). Also reorder `step-after-act` before
    `deliver-msg` and `list-length-helper` before `queue-length`.
  - vat.prologos: drop the queued-pipeline-flush in resolve-promise /
    break-promise. PromiseState's queue is `List SyrupValue` (wire
    repr); the vat queue is `List VatMsg` (decoded); flushing across
    the boundary would need re-encoding. Phase 1.

Test-fixture fix (load-bearing)
  - All 8 OCapN test files were updated to capture and restore
    `current-ctor-registry` and `current-type-meta` across the setup-
    -> run boundary. The standard fixture pattern from
    `test-hashable-01.rkt` does NOT preserve these — fine for tests
    that only declare traits, but breaks once a preamble's imports
    declare new `data` types (every `data` in our 8 modules). Without
    it, the reducer sees a stale ctor-registry and refuses to fire
    pattern arms over user constructors; results print as un-reduced
    `[reduce ... | vat x y z a -> x] : Nat` strings.
    Documented as goblin-pitfall #12; the canonical fixture in
    test-support.rkt should grow this for every future test.

Compat fence
  - driver.rkt: guard
    `(current-parallel-executor (make-parallel-thread-fire-all))` with
    a feature-detection try/catch on `thread #:pool 'own`. Racket 9
    ships parallel threads; Racket 8 does not. Fence preserves the
    Racket-9 fast path and falls back to sequential firing on 8.

Acceptance
  - examples/2026-04-27-ocapn-acceptance.prologos updated to match
    the new vat-spawn/Allocated API and verified to run clean via
    process-file.

Pitfalls catalogue (docs/tracking/2026-04-27_GOBLIN_PITFALLS.md)
  - #0 (sandbox/no-Racket): closed.
  - +#11 — Racket-8 vs Racket-9 `thread #:pool` compat
  - +#12 — test fixture loses ctor-registry/type-meta across calls
           [highest-impact; canonical fixture pattern needs update]
  - +#13 — `spawn` is a reserved surface keyword; collides silently
  - +#14 — `match | pair a b ->` on Sigma + Sigma reconstruction =>
           "could not infer"
  - +#15 — QTT multiplicity on `[fst p]`/`[snd p]` reused thrice
  - +#16 — single-pass module elaboration: forward references error
  - +#17 — promise-queue (Syrup) vs vat-queue (VatMsg) type clash
           on flush — design pitfall, scope cut

Test results
  refr      6/6   syrup    22/22  promise   16/16  message  19/19
  behavior 13/13  vat     21/21   pipeline   5/5   captp     7/7
  e2e       8/8                                  total  117/117 PASS
kumavis pushed a commit that referenced this pull request Apr 27, 2026
Per user direction:
- Replace the body of every DELETED entry with a single-sentence
  explanation. Numbers reserved per prior instruction.
- Delete #15 (QTT multiplicity on fst/snd thrice). I re-tested
  with a real Racket — `pair [snd p] [fst p]` then a third use of
  `fst p` works fine; no multiplicity error. The failure I had
  conflated this with was actually #14's "match-and-reconstruct
  Sigma" issue.

Result: pitfalls doc shrinks from 765 to 534 lines. Remaining
real claims: #1, #4, #5, #11, #12, #13, #14, #16, #17, #18, #19,
#20 (the user has reviewed only #0-10 so far; #11-20 still
pending their review).
kumavis pushed a commit that referenced this pull request Apr 28, 2026
Builds on the lambda-FFI track (PR #35): the affine combination
  out[j] := bias[j] + Σ_i  weight[j][i] · prev[i]
is now a Prologos `defn affine-step` lambda passed across the FFI on
each `net-add-prop` install. The Racket shim's broadcast item-fn
invokes it once per peer per fire.

What stays in Racket (the truly irreducible core):
  * cell-value carrier (gen-tagged immutable Posit32 vector)
  * the propagator's fire-fn (a Racket closure that reads input cell,
    invokes the Prologos kernel via the FFI bridge, and writes the
    output cell — pure plumbing, NO algorithmic content)
  * FFI marshalling glue (cons/nil chain walking, Posit32 bit-pattern
    extraction, per-row IR list construction)
  * handle/cell registries

What's now in Prologos (the entire algorithm):
  * matrix transpose, decay scaling, bias computation
  * the per-row affine kernel itself
  * iteration driver
  * initial-zero vector

`net-add-prop` is purpose-AGNOSTIC — it's a generic broadcast affine
propagator wired to a domain-specific Prologos kernel. The same shim
would serve any algorithm whose per-row update is `bias + Σ w·x`.

CI integration:
  * New `tests/test-eigentrust.rkt` runs the .prologos file via
    `process-file` and asserts the converged scores match the Python
    reference within 1e-2 — auto-picked up by `tools/run-affected-tests
    --all` (the CI test command).
  * 5 power iterations lands within ~6e-3 of the steady-state
    eigenvector; runs in ~16s, under the test runner's 30s first-result
    guard.

New pitfalls captured (docs/tracking/2026-04-28_ETPROP_PITFALLS.md):
  * #0 — FFI-call AST caching collapses identical side-effecting calls
    onto the same physical cell. Fixed via a "freshness tag" arg on
    `net-new-cell`. Surfaced when distinct layer cells turned out to
    be the same physical cell (self-loop).
  * #16 — FFI-callback overhead per fire is the bottleneck. Each
    kernel invocation runs `nf` on the lambda body. Documented as
    expected scaffolding cost of the off-network FFI bridge; future
    work is propagator-native callbacks via cell subscription.

Test results:
  * tests/test-eigentrust.rkt: 2/2 pass (15.4s)
  * regression set (foreign / foreign-block / pvec / foreign-callback
    / eigentrust): 111/111 pass (21s)
  * Final converged scores match Python reference within 1e-2:
      [0.0712, 0.4288, 0.0712, 0.4288]   (steady state ≈ [0.0652, 0.4348])
kumavis pushed a commit that referenced this pull request May 4, 2026
Wires the OCapN port to a real Racket toolchain and fixes the issues
the test run surfaced.

Library fixes
  - vat.prologos: rename `spawn` -> `vat-spawn` (collision with the
    reserved surface form recognised in macros.rkt:`'spawn`),
    `spawn-actor` -> `vat-spawn-actor`. Same in core.prologos and the
    acceptance file.
  - vat.prologos: drop the `Sigma Vat Nat` return shape for spawn /
    fresh-promise / send. Replace with a named `Allocated` struct +
    `alloc-vat` / `alloc-id` accessors. The Sigma form ran into
    "could not infer" elaborator errors when the body destructured
    via `match | pair a b -> ...` and then re-constructed a Sigma;
    `[fst p]` / `[snd p]` reused on the same `p` tripped QTT
    multiplicity. The named struct sidesteps both.
  - vat.prologos: reorder `resolve-promise` / `break-promise` BEFORE
    `apply-effect` (forward-reference rule — module elaboration is
    single-pass top-to-bottom). Also reorder `step-after-act` before
    `deliver-msg` and `list-length-helper` before `queue-length`.
  - vat.prologos: drop the queued-pipeline-flush in resolve-promise /
    break-promise. PromiseState's queue is `List SyrupValue` (wire
    repr); the vat queue is `List VatMsg` (decoded); flushing across
    the boundary would need re-encoding. Phase 1.

Test-fixture fix (load-bearing)
  - All 8 OCapN test files were updated to capture and restore
    `current-ctor-registry` and `current-type-meta` across the setup-
    -> run boundary. The standard fixture pattern from
    `test-hashable-01.rkt` does NOT preserve these — fine for tests
    that only declare traits, but breaks once a preamble's imports
    declare new `data` types (every `data` in our 8 modules). Without
    it, the reducer sees a stale ctor-registry and refuses to fire
    pattern arms over user constructors; results print as un-reduced
    `[reduce ... | vat x y z a -> x] : Nat` strings.
    Documented as goblin-pitfall #12; the canonical fixture in
    test-support.rkt should grow this for every future test.

Compat fence
  - driver.rkt: guard
    `(current-parallel-executor (make-parallel-thread-fire-all))` with
    a feature-detection try/catch on `thread #:pool 'own`. Racket 9
    ships parallel threads; Racket 8 does not. Fence preserves the
    Racket-9 fast path and falls back to sequential firing on 8.

Acceptance
  - examples/2026-04-27-ocapn-acceptance.prologos updated to match
    the new vat-spawn/Allocated API and verified to run clean via
    process-file.

Pitfalls catalogue (docs/tracking/2026-04-27_GOBLIN_PITFALLS.md)
  - #0 (sandbox/no-Racket): closed.
  - +#11 — Racket-8 vs Racket-9 `thread #:pool` compat
  - +#12 — test fixture loses ctor-registry/type-meta across calls
           [highest-impact; canonical fixture pattern needs update]
  - +#13 — `spawn` is a reserved surface keyword; collides silently
  - +#14 — `match | pair a b ->` on Sigma + Sigma reconstruction =>
           "could not infer"
  - +#15 — QTT multiplicity on `[fst p]`/`[snd p]` reused thrice
  - +#16 — single-pass module elaboration: forward references error
  - +#17 — promise-queue (Syrup) vs vat-queue (VatMsg) type clash
           on flush — design pitfall, scope cut

Test results
  refr      6/6   syrup    22/22  promise   16/16  message  19/19
  behavior 13/13  vat     21/21   pipeline   5/5   captp     7/7
  e2e       8/8                                  total  117/117 PASS
kumavis pushed a commit that referenced this pull request May 4, 2026
Per user direction:
- Replace the body of every DELETED entry with a single-sentence
  explanation. Numbers reserved per prior instruction.
- Delete #15 (QTT multiplicity on fst/snd thrice). I re-tested
  with a real Racket — `pair [snd p] [fst p]` then a third use of
  `fst p` works fine; no multiplicity error. The failure I had
  conflated this with was actually #14's "match-and-reconstruct
  Sigma" issue.

Result: pitfalls doc shrinks from 765 to 534 lines. Remaining
real claims: #1, #4, #5, #11, #12, #13, #14, #16, #17, #18, #19,
#20 (the user has reviewed only #0-10 so far; #11-20 still
pending their review).
kumavis pushed a commit that referenced this pull request May 6, 2026
…o-increment imports-refcount

Wires the bridge dispatcher to recognize desc:export / desc:answer
in inbound op:deliver args and increment imports-refcount per
occurrence. Combined with Phase 34c (refcount table) and Phase 34e
(release wired to bs-decr-import, next), this gives us the auto-
tracking half of distributed reference counting.

  captp-bridge.prologos:

    + match-payload-as-export SyrupValue -> List Refr
    + match-payload-as-answer SyrupValue -> List Refr
        Pattern-match SyrupValue payloads (typically syrup-nat).

    + extract-refrs-from-tagged String SyrupValue -> List Refr
        Tag-based dispatch: "desc:export" → match-payload-as-export,
        "desc:answer" → match-payload-as-answer, anything else → nil.

    + walk-toplevel-list [List SyrupValue] -> List Refr
        One-level walk over a SyrupValue list, calling shallow-refr
        per element.

    + shallow-refr SyrupValue -> List Refr
        Per-element check: top-level desc:* tag yields a refr;
        anything else (including nested syrup-list) yields nil.

    + extract-refrs-from-args SyrupValue -> List Refr
        Top-level entry point: handles atomic, single tag, and
        one-level-deep list.

    + bs-incr-import-by-refr Refr BridgeState -> BridgeState
    + bs-incr-imports [List Refr] BridgeState -> BridgeState

    captp-incoming-with-state op-deliver / op-deliver-only /
    op-deliver-to-answer arms: extract refrs from args + bulk-
    increment imports-refcount before dispatching.

Limitation (documented inline): refrs nested deeper than one list
level aren't extracted. Common OCapN args shapes (top-level refr OR
list of refrs+primitives) are covered; deeper nesting (refrs inside
records inside lists) deferred to a future phase that adds
generic-walker recursion.

Lessons learned (codified):

1. Pitfall #16 (forward references / mutual recursion): true mutual
   recursion (`extract-refrs-from-args` ↔ `walk-toplevel-list`)
   isn't supported. Worked around by making the inner walker call
   only `shallow-refr` (which doesn't recurse into syrup-list).
   Trade-off: the walker is one level deep instead of full tree.

2. Multi-arity defn with constructor cons-pattern + multi-line body:
   `defn name | nil -> ... | [cons hd tl] -> body` triggered
   ??__match-fail. Worked around by using bracketed-arg + inline
   `match`: `defn name [xs] (match xs | nil -> ... | cons hd tl -> body)`.
   Pattern is the same shape as data/list's `concat`. Codified.

Tests:
  + 5 new unit tests (69 total in test-ocapn-bridge.rkt):
    - extract-refrs-from-args returns nil for atomic args
    - extracts a top-level desc:export
    - walks one-level into syrup-list (2 refrs in mixed list)
    - extracts top-level desc:answer
    - captp-incoming-with-state op-deliver auto-increments
      imports-refcount[7] when args = <desc:export 7>

Verification:
  - 73 OCapN tests pass across 5 files (40 s combined)
  - bridge tests: 69/69 (5 new + 64 prior)
  - All interop tests still pass — auto-increment is non-disruptive
    for tests that don't pass refrs in args (count stays 0)
kumavis pushed a commit that referenced this pull request May 6, 2026
…it, `->` in identifiers silently fails

Two new Prologos elaborator/reader pitfalls discovered during the
refr-import track (Phase 34a + 34d). Both cost ~15 minutes each
to debug because the failure modes were silent or had misleading
error messages.

  Pitfall #34 — `data` constructor signatures have IMPLICIT return type.

    Writing `ctor : T1 -> T2 -> Result` is INTERPRETED as "takes
    3 args (T1, T2, Result), returns Result." The user usually
    means "takes 2 args, returns Result." The convention in this
    codebase (Listener, QEntry, etc) is to drop the trailing
    `-> Result`: `ctor : T1 -> T2`.

    Discovered Phase 34a (commit 3d8c069). My initial
    `refr : Nat -> Nat -> Refr` caused smart constructors to fail
    with "Type mismatch [Pi Nat Refr -> Refr]". Fix: drop the
    trailing `-> Refr`.

  Pitfall #35 — Function names containing `->` silently fail.

    Identifiers with `->` (like `refr->syrup`, common in ML/Lisp
    converter naming) are SILENTLY DROPPED by the elaborator. The
    WS-mode reader parses `->` as the function-arrow type operator
    inside the identifier, splitting the symbol. spec/defn forms
    can't bind anything sensible and produce no output at all —
    callers get "Unbound variable" downstream.

    Discovered Phase 34d (commit 7c797e6). My `refr->syrup` was
    silently dropped; renamed to `refr-to-syrup` worked. Codebase
    convention: use `-to-` for converters.

  Also documented (no new pitfall, recurrence-only):
    - #18 (multi-arity defn cons-pattern) hit on add's
      `suc a b -> ...`; fix: bracket as `[suc a] b -> ...`.
    - #21 (multi-line clause body → ??__match-fail) hit on
      extract-refrs-from-list; fix: `defn name [arg] (match arg ...)`
      same shape as data/list's `concat`.
    - #16 (mutual recursion) hit on extract-refrs-from-args ↔
      extract-refrs-from-list; fix: one-way recursion via
      shallow-refr.
    - Issue #60 (multi-constructor cross-module inference) hit
      on 5-arg helpers; fix: 2-3 arg shape via BridgeStep.

  Updated .claude/rules/prologos-syntax.md:
    - Added "NEVER use `->` in identifiers" to the Naming section
    - Added a new "Data type definitions" section with the
      implicit-return-type convention

These two are filed in the pitfalls doc but not as GitHub issues
yet — both are reasonably easy elaborator/reader fixes that
upstream Prologos work could close. Triage decision deferred.
kumavis pushed a commit that referenced this pull request May 9, 2026
Three new entries in 2026-04-27_GOBLIN_PITFALLS.md:

- #36: Multi-line constructor / function application — continuation
  args on a separate line are eaten as an inner application. Hit
  twice in this branch (Phase 41 `bs-add-pipeline-msg`, Phase 48
  `bs-gc-listeners-by-notified`); the second occurrence triggered
  codification per the workflow rule "codify a 2-occurrence pattern
  within a track immediately."

- #37: Single-arg multi-arity `defn` over `data` patterns sometimes
  infers a phantom 2nd parameter. Hit on `resolution-syrup-of-pst`
  (Phase 48); the fix was to switch to `defn name [arg] match arg`
  shape, which pinned the inferred type back to the spec.

- #38: `let X := EXPR` value can't span multiple lines. Hit on
  `drive-break-with-two-ops` (Phase 49); same workaround family as
  #21 and #36 — collapse to one line. Recorded separately because
  the error message ("missing value after :=") points at a
  different line than the actual broken `let`.

Plus a "Recurrences during Phase 47-49" section noting that
pitfall #16 (forward references) was hit again on `member-nat?` —
existing entry confirmed correct.

Also updated `.claude/rules/prologos-syntax.md` § "Application
style" with two new bullets cross-referencing #36/#37/#38, so
future implementations catch these at write time rather than at
load-time error. Per the workflow rule "if a workaround is needed
twice in the same track, add it to the pitfalls log AND to the
relevant rule file immediately."

https://claude.ai/code/session_01YM6gc3cMNH2Ymor4jdZY8u
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants