Implement the following plan:
When a proc parameter is []CHAN BYTE in?, codegen emits in []<-chan byte. Callers passing []chan byte fail to compile because Go slices are not covariant — []chan byte cannot be assigned to []<-chan byte. This blocks transpilation of sort_pump.occ and other programs that pass channel arrays to direction-annotated params.
Generate []chan T instead of []<-chan T / []chan<- T for channel array parameters. Single-channel params (CHAN OF INT c?) continue to use direction prefixes since Go individual channels ARE covariant.
Rationale: The codegen has no type-tracking system to know whether an argument is already directional, making call-site conversion loops impractical. Occam enforces direction safety at the source level, so the transpiler doesn't need to re-enforce it in Go. This is a one-line fix with no risk of regression.
codegen/codegen.go line 2039 — Drop direction for channel arrays// Before:
goType = "[]" + chanDirPrefix(p.ChanDir) + g.occamTypeToGo(p.ChanElemType)
// After:
goType = "[]chan " + g.occamTypeToGo(p.ChanElemType)
codegen/codegen_test.go lines 617-629 — Update unit test assertionsUpdate TestChanArrayDirParamGen to expect []chan int for both ? and ! directions.
codegen/e2e_array_test.go — Add e2e testAdd TestE2E_ChanArrayDirParam that passes a [n]CHAN OF INT array to a proc with []CHAN OF INT in? and []CHAN OF INT out! params. Verifies the transpiled code compiles and runs correctly.
CLAUDE.md — Update mapping tableAdd row documenting that direction is dropped for channel array params.
go test ./codegen -run TestChanArrayDirParamGen # updated unit test
go test ./codegen -run TestE2E_ChanArrayDirParam # new e2e test
go test ./... # full suite
# Course module regression check:
./occam2go -I kroc/modules/course/libsrc -D TARGET.BITS.PER.WORD=32 -o /tmp/course_out.go kroc/modules/course/libsrc/course.module && go vet /tmp/course_out.go
# Repro from issue:
./occam2go -I kroc/modules/course/libsrc -D TARGET.BITS.PER.WORD=32 -o /tmp/sort_pump.go kroc/modules/course/examples/sort_pump.occ && go vet /tmp/sort_pump.go
If you need specific details from before exiting plan mode (like exact code snippets, error messages, or content you generated), read the full transcript at: /home/david/.claude/projects/-home-david-projects-code-associates-occam2go/2e133da4-2875-49f0-ade2-e7a0ac8c9763.jsonl
Let me implement the plan step by step. First, let me read the relevant files to understand the current code.
{
"file_path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
"offset": 2030,
"limit": 20
} 2030→ g.boolVars = oldBoolVars
2031→ g.retypesRenames = oldRenames
2032→}
2033→
2034→func (g *Generator) generateProcParams(params []ast.ProcParam) string {
2035→ var parts []string
2036→ for _, p := range params {
2037→ var goType string
2038→ if p.IsChanArray {
2039→ goType = "[]" + chanDirPrefix(p.ChanDir) + g.occamTypeToGo(p.ChanElemType)
2040→ } else if p.IsChan {
2041→ goType = chanDirPrefix(p.ChanDir) + g.occamTypeToGo(p.ChanElemType)
2042→ } else if p.IsOpenArray {
2043→ goType = "[]" + g.occamTypeToGo(p.Type)
2044→ } else if p.ArraySize != "" {
2045→ // Fixed-size array parameter: use slice for Go compatibility
2046→ // (occam [n]TYPE and []TYPE both map to Go slices)
2047→ goType = "[]" + g.occamTypeToGo(p.Type)
2048→ } else {
2049→ goType = g.occamTypeToGo(p.Type){
"file_path": "/home/david/projects/code-associates/occam2go/codegen/codegen_test.go",
"offset": 610,
"limit": 30
} 610→ output := transpile(t, input)
611→
612→ if !strings.Contains(output, "func worker(input <-chan int, output chan<- int)") {
613→ t.Errorf("expected directed channel types in output, got:\n%s", output)
614→ }
615→}
616→
617→func TestChanArrayDirParamGen(t *testing.T) {
618→ input := `PROC worker([]CHAN OF INT cs?, []CHAN OF INT out!)
619→ SKIP
620→`
621→ output := transpile(t, input)
622→
623→ if !strings.Contains(output, "cs []<-chan int") {
624→ t.Errorf("expected '[]<-chan int' for input chan array, got:\n%s", output)
625→ }
626→ if !strings.Contains(output, "out []chan<- int") {
627→ t.Errorf("expected '[]chan<- int' for output chan array, got:\n%s", output)
628→ }
629→}
630→
631→func TestRecordFieldAccessCodegen(t *testing.T) {
632→ input := `RECORD POINT
633→ INT x:
634→ INT y:
635→SEQ
636→ POINT p:
637→ INT v:
638→ v := p[x]
639→`{
"file_path": "/home/david/projects/code-associates/occam2go/codegen/e2e_array_test.go",
"offset": 1,
"limit": 30
} 1→package codegen
2→
3→import "testing"
4→
5→func TestE2E_ArrayBasic(t *testing.T) {
6→ // Test basic array: declare, store, load
7→ occam := `SEQ
8→ [5]INT arr:
9→ arr[0] := 42
10→ print.int(arr[0])
11→`
12→ output := transpileCompileRun(t, occam)
13→ expected := "42\n"
14→ if output != expected {
15→ t.Errorf("expected %q, got %q", expected, output)
16→ }
17→}
18→
19→func TestE2E_ArrayWithLoop(t *testing.T) {
20→ // Test filling array with replicated SEQ and printing all elements
21→ occam := `SEQ
22→ [5]INT arr:
23→ SEQ i = 0 FOR 5
24→ arr[i] := i * 10
25→ SEQ i = 0 FOR 5
26→ print.int(arr[i])
27→`
28→ output := transpileCompileRun(t, occam)
29→ expected := "0\n10\n20\n30\n40\n"
30→ if output != expected {wc -l /home/david/projects/code-associates/occam2go/codegen/e2e_array_test.go
{
"pattern": "PROC f\\(\\[\\]CHAN",
"path": "/home/david/projects/code-associates/occam2go/CLAUDE.md",
"output_mode": "content",
"-C": 2
}114-| `cs[i] ! 42` | `cs[i] <- 42` | 115-| `cs[i] ? x` | `x = <-cs[i]` | 116:| `PROC f([]CHAN OF INT cs)` | `func f(cs []chan int)` | 117-| `PROC f(CHAN OF INT c?)` | `func f(c <-chan int)` (input/receive-only) | 118-| `PROC f(CHAN OF INT c!)` | `func f(c chan<- int)` (output/send-only) |
{
"file_path": "/home/david/projects/code-associates/occam2go/codegen/e2e_array_test.go",
"offset": 298,
"limit": 15
} 298→ a, b, c := 10, 20, 30
299→ print.int(a)
300→ print.int(b)
301→ print.int(c)
302→`
303→ output := transpileCompileRun(t, occam)
304→ expected := "10\n20\n30\n"
305→ if output != expected {
306→ t.Errorf("expected %q, got %q", expected, output)
307→ }
308→}
309→{
"subject": "Fix channel array direction param codegen",
"activeForm": "Fixing channel array direction params"
}goType = "[]" + chanDirPrefix(p.ChanDir) + g.occamTypeToGo(p.ChanElemType)
goType = "[]chan " + g.occamTypeToGo(p.ChanElemType)
The file /home/david/projects/code-associates/occam2go/codegen/codegen.go has been updated successfully.
if !strings.Contains(output, "cs []<-chan int") {
t.Errorf("expected '[]<-chan int' for input chan array, got:\n%s", output)
}
if !strings.Contains(output, "out []chan<- int") {
t.Errorf("expected '[]chan<- int' for output chan array, got:\n%s", output)
} if !strings.Contains(output, "cs []chan int") {
t.Errorf("expected '[]chan int' for input chan array, got:\n%s", output)
}
if !strings.Contains(output, "out []chan int") {
t.Errorf("expected '[]chan int' for output chan array, got:\n%s", output)
}The file /home/david/projects/code-associates/occam2go/codegen/codegen_test.go has been updated successfully.
| `PROC f([]CHAN OF INT cs)` | `func f(cs []chan int)` | | `PROC f(CHAN OF INT c?)` | `func f(c <-chan int)` (input/receive-only) | | `PROC f(CHAN OF INT c!)` | `func f(c chan<- int)` (output/send-only) |
| `PROC f([]CHAN OF INT cs)` | `func f(cs []chan int)` | | `PROC f([]CHAN OF INT cs?)` | `func f(cs []chan int)` (direction dropped for array params) | | `PROC f([]CHAN OF INT cs!)` | `func f(cs []chan int)` (direction dropped for array params) | | `PROC f(CHAN OF INT c?)` | `func f(c <-chan int)` (input/receive-only) | | `PROC f(CHAN OF INT c!)` | `func f(c chan<- int)` (output/send-only) |
<tool_use_error>File has not been read yet. Read it first before writing to it.</tool_use_error>
{
"file_path": "/home/david/projects/code-associates/occam2go/CLAUDE.md",
"offset": 112,
"limit": 12
}112→| `<<` / `>>` | `<<` / `>>` (shifts) | 113→| `[5]CHAN OF INT cs:` | `cs := make([]chan int, 5)` + init loop | 114→| `cs[i] ! 42` | `cs[i] <- 42` | 115→| `cs[i] ? x` | `x = <-cs[i]` | 116→| `PROC f([]CHAN OF INT cs)` | `func f(cs []chan int)` | 117→| `PROC f(CHAN OF INT c?)` | `func f(c <-chan int)` (input/receive-only) | 118→| `PROC f(CHAN OF INT c!)` | `func f(c chan<- int)` (output/send-only) | 119→| `f(out!, in?)` (call-site dir) | `f(out, in)` (direction annotations ignored) | 120→| Non-VAL params | `*type` pointer params, callers pass `&arg` | 121→| `PROC f([]INT arr)` | `func f(arr []int)` (open array param, slice) | 122→| `PROC f(VAL []INT arr)` | `func f(arr []int)` (VAL open array, also slice) | 123→| `PROC f([2]INT arr)` | `func f(arr *[2]int)` (fixed-size array param) |
| `PROC f([]CHAN OF INT cs)` | `func f(cs []chan int)` | | `PROC f(CHAN OF INT c?)` | `func f(c <-chan int)` (input/receive-only) | | `PROC f(CHAN OF INT c!)` | `func f(c chan<- int)` (output/send-only) |
| `PROC f([]CHAN OF INT cs)` | `func f(cs []chan int)` | | `PROC f([]CHAN OF INT cs?)` | `func f(cs []chan int)` (direction dropped for array params) | | `PROC f([]CHAN OF INT cs!)` | `func f(cs []chan int)` (direction dropped for array params) | | `PROC f(CHAN OF INT c?)` | `func f(c <-chan int)` (input/receive-only) | | `PROC f(CHAN OF INT c!)` | `func f(c chan<- int)` (output/send-only) |
The file /home/david/projects/code-associates/occam2go/CLAUDE.md has been updated successfully.
a, b, c := 10, 20, 30
print.int(a)
print.int(b)
print.int(c)
`
output := transpileCompileRun(t, occam)
expected := "10\n20\n30\n"
if output != expected {
t.Errorf("expected %q, got %q", expected, output)
}
} a, b, c := 10, 20, 30
print.int(a)
print.int(b)
print.int(c)
`
output := transpileCompileRun(t, occam)
expected := "10\n20\n30\n"
if output != expected {
t.Errorf("expected %q, got %q", expected, output)
}
}
func TestE2E_ChanArrayDirParam(t *testing.T) {
// Channel arrays passed to direction-annotated params must compile
// (Go slices are not covariant, so direction is dropped for array params)
occam := `PROC sender([]CHAN OF INT out!)
SEQ i = 0 FOR SIZE out
out[i] ! i
:
PROC receiver([]CHAN OF INT in?)
SEQ i = 0 FOR SIZE in
INT v:
SEQ
in[i] ? v
print.int(v)
:
SEQ
[3]CHAN OF INT cs:
PAR
sender(cs)
receiver(cs)
`
output := transpileCompileRun(t, occam)
expected := "0\n1\n2\n"
if output != expected {
t.Errorf("expected %q, got %q", expected, output)
}
}The file /home/david/projects/code-associates/occam2go/codegen/e2e_array_test.go has been updated successfully.
go test ./codegen -run "TestChanArrayDirParamGen|TestE2E_ChanArrayDirParam" -v 2>&1
=== RUN TestChanArrayDirParamGen --- PASS: TestChanArrayDirParamGen (0.00s) === RUN TestE2E_ChanArrayDirParam --- PASS: TestE2E_ChanArrayDirParam (0.08s) PASS ok github.com/codeassociates/occam2go/codegen 0.084s
Both pass. Now let me run the full test suite and the course module regression check in parallel:
go build -o /tmp/occam2go . && /tmp/occam2go -I kroc/modules/course/libsrc -D TARGET.BITS.PER.WORD=32 -o /tmp/course_out.go kroc/modules/course/libsrc/course.module && go vet /tmp/course_out.go 2>&1
? github.com/codeassociates/occam2go [no test files] ? github.com/codeassociates/occam2go/ast [no test files] ok github.com/codeassociates/occam2go/codegen 18.008s ok github.com/codeassociates/occam2go/lexer (cached) ok github.com/codeassociates/occam2go/modgen (cached) ok github.com/codeassociates/occam2go/parser (cached) ok github.com/codeassociates/occam2go/preproc (cached)
All tests pass and the course module regression check succeeds. Let me also verify the sort_pump.occ repro from the issue:
/tmp/occam2go -I kroc/modules/course/libsrc -D TARGET.BITS.PER.WORD=32 -o /tmp/sort_pump.go kroc/modules/course/examples/sort_pump.occ && go vet /tmp/sort_pump.go 2>&1
All done. Here's a summary of the changes:
codegen/codegen.go:2039 — Changed channel array param generation from "[]" + chanDirPrefix(p.ChanDir) + ... to "[]chan " + ..., dropping direction prefixes for array params (Go slices aren't covariant).
codegen/codegen_test.go:623-628 — Updated TestChanArrayDirParamGen assertions to expect []chan int instead of []<-chan int / []chan<- int.
codegen/e2e_array_test.go — Added TestE2E_ChanArrayDirParam that passes a [3]CHAN OF INT array to procs with ? and ! direction annotations, verifying it compiles and runs correctly.
CLAUDE.md — Added two rows documenting that direction is dropped for []CHAN OF INT cs? and []CHAN OF INT cs! array params.
All tests pass, course module regression passes, and sort_pump.occ now transpiles and vets cleanly.
On branch main Your branch is up to date with 'origin/main'. Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git restore <file>..." to discard changes in working directory) modified: CLAUDE.md modified: codegen/codegen.go modified: codegen/codegen_test.go modified: codegen/e2e_array_test.go no changes added to commit (use "git add" and/or "git commit -a")
diff --git a/CLAUDE.md b/CLAUDE.md
index 4082424..8ab5188 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -114,6 +114,8 @@ Six packages, one pipeline:
| `cs[i] ! 42` | `cs[i] <- 42` |
| `cs[i] ? x` | `x = <-cs[i]` |
| `PROC f([]CHAN OF INT cs)` | `func f(cs []chan int)` |
+| `PROC f([]CHAN OF INT cs?)` | `func f(cs []chan int)` (direction dropped for array params) |
+| `PROC f([]CHAN OF INT cs!)` | `func f(cs []chan int)` (direction dropped for array params) |
| `PROC f(CHAN OF INT c?)` | `func f(c <-chan int)` (input/receive-only) |
| `PROC f(CHAN OF INT c!)` | `func f(c chan<- int)` (output/send-only) |
| `f(out!, in?)` (call-site dir) | `f(out, in)` (direction annotations ignored) |
diff --git a/codegen/codegen.go b/codegen/codegen.go
index 0d2a53b..21b3655 100644
--- a/codegen/codegen.go
+++ b/codegen/codegen.go
@@ -2036,7 +2036,7 @@ func (g *Generator) generateProcParams(params []ast.ProcParam) string {
for _, p := range params {
var goType string
if p.IsChanArray {
- goType = "[]" + chanDirPrefix(p.ChanDir) + g.occamTypeToGo(p.ChanElemType)
+ goType = "[]chan " + g.occamTypeToGo(p.ChanElemType)
} else if p.IsChan {
goType = chanDirPrefix(p.ChanDir) + g.occamTypeToGo(p.ChanElemType)
} else if p.IsOpenArray {
diff --git a/codegen/codegen_test.go b/codegen/codegen_test.go
index 44d85a1..c6cda3d 100644
--- a/codegen/codegen_test.go
+++ b/codegen/codegen_test.go
@@ -620,11 +620,11 @@ func TestChanArrayDirParamGen(t *testing.T) {
`
output := transpile(t, input)
- if !strings.Contains(output, "cs []<-chan int") {
- t.Errorf("expected '[]<-chan int' for input chan array, got:\n%s", output)
+ if !strings.Contains(output, "cs []chan int") {
+ t.Errorf("expected '[]chan int' for input chan array, got:\n%s", output)
}
- if !strings.Contains(output, "out []chan<- int") {
- t.Errorf("expected '[]chan<- int' for output chan array, got:\n%s", output)
+ if !strings.Contains(output, "out []chan int") {
+ t.Errorf("expected '[]chan int' for output chan array, got:\n%s", output)
}
}
diff --git a/codegen/e2e_array_test.go b/codegen/e2e_array_test.go
index b4deac8..65dc3b4 100644
--- a/codegen/e2e_array_test.go
+++ b/codegen/e2e_array_test.go
@@ -306,3 +306,30 @@ func TestE2E_MultiAssignmentValues(t *testing.T) {
t.Errorf("expected %q, got %q", expected, output)
}
}
+
+func TestE2E_ChanArrayDirParam(t *testing.T) {
+ // Channel arrays passed to direction-annotated params must compile
+ // (Go slices are not covariant, so direction is dropped for array params)
+ occam := `PROC sender([]CHAN OF INT out!)
+ SEQ i = 0 FOR SIZE out
+ out[i] ! i
+:
+PROC receiver([]CHAN OF INT in?)
+ SEQ i = 0 FOR SIZE in
+ INT v:
+ SEQ
+ in[i] ? v
+ print.int(v)
+:
+SEQ
+ [3]CHAN OF INT cs:
+ PAR
+ sender(cs)
+ receiver(cs)
+`
+ output := transpileCompileRun(t, occam)
+ expected := "0\n1\n2\n"
+ if output != expected {
+ t.Errorf("expected %q, got %q", expected, output)
+ }
+}5a513cd Merge pull request #58 from codeassociates/fix/scope-boolvars-per-proc 347f176 Scope boolVars per proc/function body to prevent cross-proc leakage 36a462d Merge pull request #55 from codeassociates/fix/typed-abbreviation-type-loss 74b192a Fix typed abbreviations losing declared type in local scope c1adc5f Merge pull request #54 from codeassociates/feat/round-trunc-qualifiers
git add CLAUDE.md codegen/codegen.go codegen/codegen_test.go codegen/e2e_array_test.go && git commit -m "$(cat <<'EOF' Drop direction from channel array params to fix Go slice covariance issue Go slices are not covariant, so []chan byte cannot be assigned to []<-chan byte. Generate []chan T for channel array params instead, while keeping direction prefixes for single-channel params. Fixes #57 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> EOF )"
4 files changed, 34 insertions(+), 5 deletions(-)
remote: remote: Create a pull request for 'fix/chan-array-dir-params' on GitHub by visiting: remote: https://github.com/codeassociates/occam2go/pull/new/fix/chan-array-dir-params remote: To associate-1.github.com:codeassociates/occam2go.git * [new branch] fix/chan-array-dir-params -> fix/chan-array-dir-params branch 'fix/chan-array-dir-params' set up to track 'origin/fix/chan-array-dir-params'.
gh pr create --title "Fix channel array slices incompatible with direction-restricted params" --body "$(cat <<'EOF' ## Summary - Drop direction prefixes (`<-chan`, `chan<-`) from channel array params, generating `[]chan T` instead of `[]<-chan T` / `[]chan<- T` - Go slices are not covariant, so `[]chan byte` cannot be assigned to `[]<-chan byte`, causing compilation failures when passing channel arrays to direction-annotated proc params - Single-channel params (`CHAN OF INT c?`) continue to use direction prefixes since individual channels are covariant in Go ## Test plan - [x] Updated `TestChanArrayDirParamGen` unit test to expect `[]chan int` - [x] Added `TestE2E_ChanArrayDirParam` e2e test (sender/receiver with direction-annotated `[]CHAN OF INT` params) - [x] Full test suite passes (`go test ./...`) - [x] Course module regression check passes - [x] `sort_pump.occ` transpiles and passes `go vet` Fixes #57 🤖 Generated with [Claude Code](https://claude.com/claude-code) EOF )"
gh pr view 59 --json state,reviewDecision,statusCheckRollup --jq '{state: .state, reviewDecision: .reviewDecision, checks: [.statusCheckRollup[] | {name: .name, status: .status, conclusion: .conclusion}]}'{"checks":[{"conclusion":"SUCCESS","name":"test","status":"COMPLETED"}],"reviewDecision":"APPROVED","state":"OPEN"}