Implement the following plan:
boolVars per proc/function body (Issue #56)The boolVars map in codegen.go is flat and global. A pre-pass (collectBoolVars) adds ALL BOOL-typed variables from the entire program (including #INCLUDEd modules) into a single map. When generating a type conversion like INT16 b where b is a BYTE, the codegen checks g.boolVars["b"] and incorrectly finds true because a different proc in an included file declared BOOL b:. This produces invalid Go: int16(_boolToInt(b)) instead of int16(b).
Follow the existing refParams scoping pattern (save/restore in generateProcDecl):
codegen/codegen.go1. Scope boolVars in generateProcDecl (~line 1927)
After the existing oldRefParams save (line 1929), add save/restore for boolVars:
oldBoolVars := g.boolVarsnestingLevel == 0), inherited copy for nested procs (closures can reference parent BOOL vars)proc.Params (where p.Type == "BOOL" and not a chan/chanArray)g.boolVars = oldBoolVars (next to existing g.refParams = oldRefParams at line 2016)generateVarDecl (line 1084-1088) already adds BOOL vars dynamically during body generation, so local BOOL declarations will correctly populate the scoped map.
2. Scope boolVars in generateFuncDecl (~line 2103)
Same pattern: save before body generation, create scoped map (inheriting if nested), register BOOL params, restore after body.
codegen/codegen.go — generateProcDecl, generateFuncDeclcollectBoolVars (lines 114-117) — still needed for containsBoolConversion to decide whether to emit the _boolToInt helper functiongenerateVarDecl BOOL tracking (lines 1084-1088) — already correct, adds to whatever the current scoped map isisBoolExpression (line 2675) — no changes needed, it already checks g.boolVars# Reproduce the bug (should fail with go vet error before fix)
./occam2go -I kroc/modules/course/libsrc -D TARGET.BITS.PER.WORD=32 -o /tmp/cast.go kroc/modules/course/examples/cast.occ
go vet /tmp/cast.go
# Full course module still compiles
./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
# All existing tests pass
go test ./...
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/f025241a-2f05-4a0f-bd8b-62efa9622376.jsonl
Let me implement the plan to scope boolVars per proc/function body. I need to read the relevant sections of codegen/codegen.go first.
{
"file_path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
"offset": 1920,
"limit": 110
} 1920→ g.generateStatement(s)
1921→ }
1922→
1923→ g.indent--
1924→ g.writeLine("}")
1925→}
1926→
1927→func (g *Generator) generateProcDecl(proc *ast.ProcDecl) {
1928→ // Track reference parameters for this procedure
1929→ oldRefParams := g.refParams
1930→ newRefParams := make(map[string]bool)
1931→ // Inherit parent's ref params for closure captures when nested
1932→ if g.nestingLevel > 0 {
1933→ for k, v := range oldRefParams {
1934→ newRefParams[k] = v
1935→ }
1936→ }
1937→ for _, p := range proc.Params {
1938→ if !p.IsVal && !p.IsChan && !p.IsChanArray && !p.IsOpenArray && p.ArraySize == "" {
1939→ newRefParams[p.Name] = true
1940→ } else {
1941→ // Own param shadows any inherited ref param with same name
1942→ delete(newRefParams, p.Name)
1943→ }
1944→ // Register chan params with protocol mappings
1945→ if p.IsChan || p.IsChanArray {
1946→ if _, ok := g.protocolDefs[p.ChanElemType]; ok {
1947→ g.chanProtocols[p.Name] = p.ChanElemType
1948→ }
1949→ }
1950→ // Register record-typed params
1951→ if !p.IsChan {
1952→ if _, ok := g.recordDefs[p.Type]; ok {
1953→ g.recordVars[p.Name] = p.Type
1954→ }
1955→ }
1956→ }
1957→ g.refParams = newRefParams
1958→
1959→ // Scan proc body for RETYPES declarations that shadow parameters.
1960→ // When VAL INT X RETYPES X :, Go can't redeclare X in the same scope,
1961→ // so we rename the parameter (e.g. X → _rp_X) and let RETYPES declare the original name.
1962→ oldRenames := g.retypesRenames
1963→ g.retypesRenames = nil
1964→ paramNames := make(map[string]bool)
1965→ for _, p := range proc.Params {
1966→ paramNames[p.Name] = true
1967→ }
1968→ for _, stmt := range proc.Body {
1969→ if rd, ok := stmt.(*ast.RetypesDecl); ok {
1970→ if paramNames[rd.Source] && rd.Name == rd.Source {
1971→ if g.retypesRenames == nil {
1972→ g.retypesRenames = make(map[string]string)
1973→ }
1974→ g.retypesRenames[rd.Name] = "_rp_" + goIdent(rd.Name)
1975→ }
1976→ }
1977→ }
1978→
1979→ // Generate function signature
1980→ params := g.generateProcParams(proc.Params)
1981→ gName := goIdent(proc.Name)
1982→ if g.nestingLevel > 0 {
1983→ // Nested PROC: generate as Go closure
1984→ g.writeLine(fmt.Sprintf("%s := func(%s) {", gName, params))
1985→ } else {
1986→ g.writeLine(fmt.Sprintf("func %s(%s) {", gName, params))
1987→ }
1988→ g.indent++
1989→ g.nestingLevel++
1990→
1991→ // Register nested proc/func signatures for this scope so that calls
1992→ // within this proc resolve to the correct (local) signature rather than
1993→ // a same-named proc from a different scope.
1994→ oldSigs := make(map[string][]ast.ProcParam)
1995→ g.collectNestedProcSigsScoped(proc.Body, oldSigs)
1996→
1997→ for _, stmt := range proc.Body {
1998→ g.generateStatement(stmt)
1999→ }
2000→
2001→ // Restore overwritten signatures
2002→ for name, params := range oldSigs {
2003→ if params == nil {
2004→ delete(g.procSigs, name)
2005→ } else {
2006→ g.procSigs[name] = params
2007→ }
2008→ }
2009→
2010→ g.nestingLevel--
2011→ g.indent--
2012→ g.writeLine("}")
2013→ g.writeLine("")
2014→
2015→ // Restore previous context
2016→ g.refParams = oldRefParams
2017→ g.retypesRenames = oldRenames
2018→}
2019→
2020→func (g *Generator) generateProcParams(params []ast.ProcParam) string {
2021→ var parts []string
2022→ for _, p := range params {
2023→ var goType string
2024→ if p.IsChanArray {
2025→ goType = "[]" + chanDirPrefix(p.ChanDir) + g.occamTypeToGo(p.ChanElemType)
2026→ } else if p.IsChan {
2027→ goType = chanDirPrefix(p.ChanDir) + g.occamTypeToGo(p.ChanElemType)
2028→ } else if p.IsOpenArray {
2029→ goType = "[]" + g.occamTypeToGo(p.Type){
"file_path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
"offset": 2090,
"limit": 100
} 2090→ // Wrap string literals with []byte() when passed to []BYTE parameters
2091→ if _, isStr := arg.(*ast.StringLiteral); isStr && i < len(params) && params[i].IsOpenArray && params[i].Type == "BYTE" {
2092→ g.write("[]byte(")
2093→ g.generateExpression(arg)
2094→ g.write(")")
2095→ } else {
2096→ g.generateExpression(arg)
2097→ }
2098→ }
2099→ g.write(")")
2100→ g.write("\n")
2101→}
2102→
2103→func (g *Generator) generateFuncDecl(fn *ast.FuncDecl) {
2104→ params := g.generateProcParams(fn.Params)
2105→
2106→ // Build return type string
2107→ var returnTypeStr string
2108→ if len(fn.ReturnTypes) == 1 {
2109→ returnTypeStr = g.occamTypeToGo(fn.ReturnTypes[0])
2110→ } else {
2111→ goTypes := make([]string, len(fn.ReturnTypes))
2112→ for i, rt := range fn.ReturnTypes {
2113→ goTypes[i] = g.occamTypeToGo(rt)
2114→ }
2115→ returnTypeStr = "(" + strings.Join(goTypes, ", ") + ")"
2116→ }
2117→
2118→ gName := goIdent(fn.Name)
2119→ if g.nestingLevel > 0 {
2120→ // Nested FUNCTION: generate as Go closure
2121→ g.writeLine(fmt.Sprintf("%s := func(%s) %s {", gName, params, returnTypeStr))
2122→ } else {
2123→ g.writeLine(fmt.Sprintf("func %s(%s) %s {", gName, params, returnTypeStr))
2124→ }
2125→ g.indent++
2126→ g.nestingLevel++
2127→
2128→ for _, stmt := range fn.Body {
2129→ g.generateStatement(stmt)
2130→ }
2131→
2132→ if len(fn.ResultExprs) > 0 {
2133→ g.builder.WriteString(strings.Repeat("\t", g.indent))
2134→ g.write("return ")
2135→ for i, expr := range fn.ResultExprs {
2136→ if i > 0 {
2137→ g.write(", ")
2138→ }
2139→ g.generateExpression(expr)
2140→ }
2141→ g.write("\n")
2142→ }
2143→
2144→ g.nestingLevel--
2145→ g.indent--
2146→ g.writeLine("}")
2147→ g.writeLine("")
2148→}
2149→
2150→func (g *Generator) generateFuncCallExpr(call *ast.FuncCall) {
2151→ if transpIntrinsics[call.Name] {
2152→ g.write("_" + call.Name)
2153→ } else {
2154→ g.write(goIdent(call.Name))
2155→ }
2156→ g.write("(")
2157→ params := g.procSigs[call.Name]
2158→ for i, arg := range call.Args {
2159→ if i > 0 {
2160→ g.write(", ")
2161→ }
2162→ // Wrap string literals with []byte() when passed to []BYTE parameters
2163→ if _, isStr := arg.(*ast.StringLiteral); isStr && i < len(params) && params[i].IsOpenArray && params[i].Type == "BYTE" {
2164→ g.write("[]byte(")
2165→ g.generateExpression(arg)
2166→ g.write(")")
2167→ } else {
2168→ g.generateExpression(arg)
2169→ }
2170→ }
2171→ g.write(")")
2172→}
2173→
2174→func (g *Generator) generateMultiAssignment(stmt *ast.MultiAssignment) {
2175→ g.builder.WriteString(strings.Repeat("\t", g.indent))
2176→ for i, target := range stmt.Targets {
2177→ if i > 0 {
2178→ g.write(", ")
2179→ }
2180→ if target.Index != nil {
2181→ // Check if this is a record field access
2182→ if _, ok := g.recordVars[target.Name]; ok {
2183→ if ident, ok := target.Index.(*ast.Identifier); ok {
2184→ g.write(goIdent(target.Name))
2185→ g.write(".")
2186→ g.write(goIdent(ident.Value))
2187→ continue
2188→ }
2189→ }{
"file_path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
"offset": 1080,
"limit": 15
} 1080→ for _, n := range goNames {
1081→ g.writeLine(fmt.Sprintf("_ = %s", n))
1082→ }
1083→ // Track BOOL variables for type conversion codegen
1084→ if decl.Type == "BOOL" {
1085→ for _, n := range decl.Names {
1086→ g.boolVars[n] = true
1087→ }
1088→ }
1089→}
1090→
1091→func (g *Generator) generateAbbreviation(abbr *ast.Abbreviation) {
1092→ g.builder.WriteString(strings.Repeat("\t", g.indent))
1093→ if abbr.Type != "" {
1094→ goType := g.occamTypeToGo(abbr.Type)Now I have a good understanding of the code. Let me also check how generateFuncDecl handles nested procs and scoping, and look at the boolVars field definition and collectBoolVars.
{
"pattern": "boolVars",
"path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
"output_mode": "content",
"-n": true
}39: boolVars map[string]bool 112: g.boolVars = make(map[string]bool) 1086: g.boolVars[n] = true 1402: g.boolVars[name] = true 2675: return g.boolVars[e.Value]
{
"pattern": "collectBoolVars",
"path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
"output_mode": "content",
"-n": true,
"-C": 5
}111- g.recordVars = make(map[string]string)
112- g.boolVars = make(map[string]bool)
113-
114- / Pre-pass: collect BOOL variable names (needed before containsBoolConversion)
115- for _, stmt := range program.Statements {
116: g.collectBoolVars(stmt)
117- }
118-
119- / First pass: collect procedure signatures, protocols, and check for PAR/print
120- for _, stmt := range program.Statements {
121- if g.containsPar(stmt) {
--
1392- }
1393- }
1394- }
1395-}
1396-
1397:func (g *Generator) collectBoolVars(stmt ast.Statement) {
1398- switch s := stmt.(type) {
1399- case *ast.VarDecl:
1400- if s.Type == "BOOL" {
1401- for _, name := range s.Names {
1402- g.boolVars[name] = true
1403- }
1404- }
1405- case *ast.SeqBlock:
1406- for _, inner := range s.Statements {
1407: g.collectBoolVars(inner)
1408- }
1409- case *ast.ParBlock:
1410- for _, inner := range s.Statements {
1411: g.collectBoolVars(inner)
1412- }
1413- case *ast.ProcDecl:
1414- for _, inner := range s.Body {
1415: g.collectBoolVars(inner)
1416- }
1417- case *ast.FuncDecl:
1418- for _, inner := range s.Body {
1419: g.collectBoolVars(inner)
1420- }
1421- case *ast.WhileLoop:
1422- for _, inner := range s.Body {
1423: g.collectBoolVars(inner)
1424- }
1425- case *ast.IfStatement:
1426- for _, choice := range s.Choices {
1427- if choice.NestedIf != nil {
1428: g.collectBoolVars(choice.NestedIf)
1429- }
1430- for _, inner := range choice.Body {
1431: g.collectBoolVars(inner)
1432- }
1433- }
1434- case *ast.CaseStatement:
1435- for _, choice := range s.Choices {
1436- for _, inner := range choice.Body {
1437: g.collectBoolVars(inner)
1438- }
1439- }
1440- }
1441-}
1442-{
"file_path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
"offset": 1091,
"limit": 30
} 1091→func (g *Generator) generateAbbreviation(abbr *ast.Abbreviation) {
1092→ g.builder.WriteString(strings.Repeat("\t", g.indent))
1093→ if abbr.Type != "" {
1094→ goType := g.occamTypeToGo(abbr.Type)
1095→ if abbr.IsOpenArray {
1096→ goType = "[]" + goType
1097→ }
1098→ g.write(fmt.Sprintf("var %s %s = ", goIdent(abbr.Name), goType))
1099→ } else {
1100→ g.write(fmt.Sprintf("%s := ", goIdent(abbr.Name)))
1101→ }
1102→ // Wrap string literals with []byte() when assigned to []byte variables
1103→ if _, isStr := abbr.Value.(*ast.StringLiteral); isStr && abbr.IsOpenArray && abbr.Type == "BYTE" {
1104→ g.write("[]byte(")
1105→ g.generateExpression(abbr.Value)
1106→ g.write(")")
1107→ } else {
1108→ g.generateExpression(abbr.Value)
1109→ }
1110→ g.write("\n")
1111→ // Suppress "declared and not used" for abbreviations inside function bodies
1112→ if g.nestingLevel > 0 {
1113→ g.writeLine(fmt.Sprintf("_ = %s", goIdent(abbr.Name)))
1114→ }
1115→}
1116→
1117→func (g *Generator) generateChanDecl(decl *ast.ChanDecl) {
1118→ goType := g.occamTypeToGo(decl.ElemType)
1119→ if decl.IsArray {
1120→ for _, name := range decl.Names {{
"subject": "Scope boolVars per proc/function body",
"activeForm": "Scoping boolVars per proc/function body"
}Now let me apply the changes. First, generateProcDecl — add boolVars scoping after the refParams save:
// Track reference parameters for this procedure
oldRefParams := g.refParams
newRefParams := make(map[string]bool)
// Inherit parent's ref params for closure captures when nested
if g.nestingLevel > 0 {
for k, v := range oldRefParams {
newRefParams[k] = v
}
}
for _, p := range proc.Params {
if !p.IsVal && !p.IsChan && !p.IsChanArray && !p.IsOpenArray && p.ArraySize == "" {
newRefParams[p.Name] = true
} else {
// Own param shadows any inherited ref param with same name
delete(newRefParams, p.Name)
} // Track reference parameters for this procedure
oldRefParams := g.refParams
newRefParams := make(map[string]bool)
// Scope boolVars per proc body
oldBoolVars := g.boolVars
newBoolVars := make(map[string]bool)
// Inherit parent's ref params and boolVars for closure captures when nested
if g.nestingLevel > 0 {
for k, v := range oldRefParams {
newRefParams[k] = v
}
for k, v := range oldBoolVars {
newBoolVars[k] = v
}
}
for _, p := range proc.Params {
if !p.IsVal && !p.IsChan && !p.IsChanArray && !p.IsOpenArray && p.ArraySize == "" {
newRefParams[p.Name] = true
} else {
// Own param shadows any inherited ref param with same name
delete(newRefParams, p.Name)
}
// Track BOOL params; delete non-BOOL params that shadow inherited names
if p.Type == "BOOL" && !p.IsChan && !p.IsChanArray {
newBoolVars[p.Name] = true
} else {
delete(newBoolVars, p.Name)
}The file /home/david/projects/code-associates/occam2go/codegen/codegen.go has been updated successfully.
{
"file_path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
"offset": 1962,
"limit": 10
} 1962→ // Register record-typed params
1963→ if !p.IsChan {
1964→ if _, ok := g.recordDefs[p.Type]; ok {
1965→ g.recordVars[p.Name] = p.Type
1966→ }
1967→ }
1968→ }
1969→ g.refParams = newRefParams
1970→
1971→ // Scan proc body for RETYPES declarations that shadow parameters.g.refParams = newRefParams // Scan proc body for RETYPES declarations that shadow parameters.
g.refParams = newRefParams g.boolVars = newBoolVars // Scan proc body for RETYPES declarations that shadow parameters.
The file /home/david/projects/code-associates/occam2go/codegen/codegen.go has been updated successfully.
{
"file_path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
"offset": 2025,
"limit": 10
} 2025→ g.writeLine("}")
2026→ g.writeLine("")
2027→
2028→ // Restore previous context
2029→ g.refParams = oldRefParams
2030→ g.retypesRenames = oldRenames
2031→}
2032→
2033→func (g *Generator) generateProcParams(params []ast.ProcParam) string {
2034→ var parts []string// Restore previous context g.refParams = oldRefParams g.retypesRenames = oldRenames }
// Restore previous context g.refParams = oldRefParams g.boolVars = oldBoolVars g.retypesRenames = oldRenames }
The file /home/david/projects/code-associates/occam2go/codegen/codegen.go has been updated successfully.
{
"file_path": "/home/david/projects/code-associates/occam2go/codegen/codegen.go",
"offset": 2116,
"limit": 50
} 2116→
2117→func (g *Generator) generateFuncDecl(fn *ast.FuncDecl) {
2118→ params := g.generateProcParams(fn.Params)
2119→
2120→ // Build return type string
2121→ var returnTypeStr string
2122→ if len(fn.ReturnTypes) == 1 {
2123→ returnTypeStr = g.occamTypeToGo(fn.ReturnTypes[0])
2124→ } else {
2125→ goTypes := make([]string, len(fn.ReturnTypes))
2126→ for i, rt := range fn.ReturnTypes {
2127→ goTypes[i] = g.occamTypeToGo(rt)
2128→ }
2129→ returnTypeStr = "(" + strings.Join(goTypes, ", ") + ")"
2130→ }
2131→
2132→ gName := goIdent(fn.Name)
2133→ if g.nestingLevel > 0 {
2134→ // Nested FUNCTION: generate as Go closure
2135→ g.writeLine(fmt.Sprintf("%s := func(%s) %s {", gName, params, returnTypeStr))
2136→ } else {
2137→ g.writeLine(fmt.Sprintf("func %s(%s) %s {", gName, params, returnTypeStr))
2138→ }
2139→ g.indent++
2140→ g.nestingLevel++
2141→
2142→ for _, stmt := range fn.Body {
2143→ g.generateStatement(stmt)
2144→ }
2145→
2146→ if len(fn.ResultExprs) > 0 {
2147→ g.builder.WriteString(strings.Repeat("\t", g.indent))
2148→ g.write("return ")
2149→ for i, expr := range fn.ResultExprs {
2150→ if i > 0 {
2151→ g.write(", ")
2152→ }
2153→ g.generateExpression(expr)
2154→ }
2155→ g.write("\n")
2156→ }
2157→
2158→ g.nestingLevel--
2159→ g.indent--
2160→ g.writeLine("}")
2161→ g.writeLine("")
2162→}
2163→
2164→func (g *Generator) generateFuncCallExpr(call *ast.FuncCall) {
2165→ if transpIntrinsics[call.Name] {func (g *Generator) generateFuncDecl(fn *ast.FuncDecl) {
params := g.generateProcParams(fn.Params)
// Build return type string
var returnTypeStr string
if len(fn.ReturnTypes) == 1 {
returnTypeStr = g.occamTypeToGo(fn.ReturnTypes[0])
} else {
goTypes := make([]string, len(fn.ReturnTypes))
for i, rt := range fn.ReturnTypes {
goTypes[i] = g.occamTypeToGo(rt)
}
returnTypeStr = "(" + strings.Join(goTypes, ", ") + ")"
}
gName := goIdent(fn.Name)
if g.nestingLevel > 0 {
// Nested FUNCTION: generate as Go closure
g.writeLine(fmt.Sprintf("%s := func(%s) %s {", gName, params, returnTypeStr))
} else {
g.writeLine(fmt.Sprintf("func %s(%s) %s {", gName, params, returnTypeStr))
}
g.indent++
g.nestingLevel++
for _, stmt := range fn.Body {
g.generateStatement(stmt)
}
if len(fn.ResultExprs) > 0 {
g.builder.WriteString(strings.Repeat("\t", g.indent))
g.write("return ")
for i, expr := range fn.ResultExprs {
if i > 0 {
g.write(", ")
}
g.generateExpression(expr)
}
g.write("\n")
}
g.nestingLevel--
g.indent--
g.writeLine("}")
g.writeLine("")
}func (g *Generator) generateFuncDecl(fn *ast.FuncDecl) {
params := g.generateProcParams(fn.Params)
// Build return type string
var returnTypeStr string
if len(fn.ReturnTypes) == 1 {
returnTypeStr = g.occamTypeToGo(fn.ReturnTypes[0])
} else {
goTypes := make([]string, len(fn.ReturnTypes))
for i, rt := range fn.ReturnTypes {
goTypes[i] = g.occamTypeToGo(rt)
}
returnTypeStr = "(" + strings.Join(goTypes, ", ") + ")"
}
// Scope boolVars per function body
oldBoolVars := g.boolVars
newBoolVars := make(map[string]bool)
if g.nestingLevel > 0 {
for k, v := range oldBoolVars {
newBoolVars[k] = v
}
}
for _, p := range fn.Params {
if p.Type == "BOOL" && !p.IsChan && !p.IsChanArray {
newBoolVars[p.Name] = true
} else {
delete(newBoolVars, p.Name)
}
}
g.boolVars = newBoolVars
gName := goIdent(fn.Name)
if g.nestingLevel > 0 {
// Nested FUNCTION: generate as Go closure
g.writeLine(fmt.Sprintf("%s := func(%s) %s {", gName, params, returnTypeStr))
} else {
g.writeLine(fmt.Sprintf("func %s(%s) %s {", gName, params, returnTypeStr))
}
g.indent++
g.nestingLevel++
for _, stmt := range fn.Body {
g.generateStatement(stmt)
}
if len(fn.ResultExprs) > 0 {
g.builder.WriteString(strings.Repeat("\t", g.indent))
g.write("return ")
for i, expr := range fn.ResultExprs {
if i > 0 {
g.write(", ")
}
g.generateExpression(expr)
}
g.write("\n")
}
g.nestingLevel--
g.indent--
g.writeLine("}")
g.writeLine("")
// Restore previous boolVars
g.boolVars = oldBoolVars
}The file /home/david/projects/code-associates/occam2go/codegen/codegen.go has been updated successfully.
./occam2go -I kroc/modules/course/libsrc -D TARGET.BITS.PER.WORD=32 -o /tmp/cast.go kroc/modules/course/examples/cast.occ && go vet /tmp/cast.go
./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
? github.com/codeassociates/occam2go [no test files] ? github.com/codeassociates/occam2go/ast [no test files] ok github.com/codeassociates/occam2go/codegen 17.805s 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)
Done. Here's a summary of the changes in codegen/codegen.go:
generateProcDecl — Added save/restore of g.boolVars alongside the existing refParams pattern:
- Save oldBoolVars before the proc body
- Create a fresh newBoolVars map (inheriting from parent if nested, for closure captures)
- Register BOOL-typed params; delete non-BOOL params that shadow inherited names
- Restore g.boolVars = oldBoolVars after body generation
generateFuncDecl — Same pattern: save before body, create scoped map with BOOL param registration, restore after body.
The pre-pass collectBoolVars is unchanged (still needed for containsBoolConversion to decide whether to emit the _boolToInt helper). The dynamic tracking in generateVarDecl (line 1084) naturally populates whichever scoped map is current.
On branch fix/typed-abbreviation-type-loss Your branch is up to date with 'origin/fix/typed-abbreviation-type-loss'. 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: codegen/codegen.go no changes added to commit (use "git add" and/or "git commit -a")
diff --git a/codegen/codegen.go b/codegen/codegen.go
index a60d2d5..0d2a53b 100644
--- a/codegen/codegen.go
+++ b/codegen/codegen.go
@@ -1928,11 +1928,17 @@ func (g *Generator) generateProcDecl(proc *ast.ProcDecl) {
// Track reference parameters for this procedure
oldRefParams := g.refParams
newRefParams := make(map[string]bool)
- // Inherit parent's ref params for closure captures when nested
+ // Scope boolVars per proc body
+ oldBoolVars := g.boolVars
+ newBoolVars := make(map[string]bool)
+ // Inherit parent's ref params and boolVars for closure captures when nested
if g.nestingLevel > 0 {
for k, v := range oldRefParams {
newRefParams[k] = v
}
+ for k, v := range oldBoolVars {
+ newBoolVars[k] = v
+ }
}
for _, p := range proc.Params {
if !p.IsVal && !p.IsChan && !p.IsChanArray && !p.IsOpenArray && p.ArraySize == "" {
@@ -1941,6 +1947,12 @@ func (g *Generator) generateProcDecl(proc *ast.ProcDecl) {
// Own param shadows any inherited ref param with same name
delete(newRefParams, p.Name)
}
+ // Track BOOL params; delete non-BOOL params that shadow inherited names
+ if p.Type == "BOOL" && !p.IsChan && !p.IsChanArray {
+ newBoolVars[p.Name] = true
+ } else {
+ delete(newBoolVars, p.Name)
+ }
// Register chan params with protocol mappings
if p.IsChan || p.IsChanArray {
if _, ok := g.protocolDefs[p.ChanElemType]; ok {
@@ -1955,6 +1967,7 @@ func (g *Generator) generateProcDecl(proc *ast.ProcDecl) {
}
}
g.refParams = newRefParams
+ g.boolVars = newBoolVars
// Scan proc body for RETYPES declarations that shadow parameters.
// When VAL INT X RETYPES X :, Go can't redeclare X in the same scope,
@@ -2014,6 +2027,7 @@ func (g *Generator) generateProcDecl(proc *ast.ProcDecl) {
// Restore previous context
g.refParams = oldRefParams
+ g.boolVars = oldBoolVars
g.retypesRenames = oldRenames
}
@@ -2115,6 +2129,23 @@ func (g *Generator) generateFuncDecl(fn *ast.FuncDecl) {
returnTypeStr = "(" + strings.Join(goTypes, ", ") + ")"
}
+ // Scope boolVars per function body
+ oldBoolVars := g.boolVars
+ newBoolVars := make(map[string]bool)
+ if g.nestingLevel > 0 {
+ for k, v := range oldBoolVars {
+ newBoolVars[k] = v
+ }
+ }
+ for _, p := range fn.Params {
+ if p.Type == "BOOL" && !p.IsChan && !p.IsChanArray {
+ newBoolVars[p.Name] = true
+ } else {
+ delete(newBoolVars, p.Name)
+ }
+ }
+ g.boolVars = newBoolVars
+
gName := goIdent(fn.Name)
if g.nestingLevel > 0 {
// Nested FUNCTION: generate as Go closure
@@ -2145,6 +2176,9 @@ func (g *Generator) generateFuncDecl(fn *ast.FuncDecl) {
g.indent--
g.writeLine("}")
g.writeLine("")
+
+ // Restore previous boolVars
+ g.boolVars = oldBoolVars
}
func (g *Generator) generateFuncCallExpr(call *ast.FuncCall) {74b192a Fix typed abbreviations losing declared type in local scope c1adc5f Merge pull request #54 from codeassociates/feat/round-trunc-qualifiers 1a6da77 Support ROUND/TRUNC qualifiers on type conversions babbe52 Merge pull request #53 from codeassociates/fix/bool-numeric-type-conversions 0df6ec8 Fix BOOL↔BYTE/numeric type conversions
git rev-parse --abbrev-ref --symbolic-full-name @{u} 2>/dev/null; echo "exit: $?"The branch is fix/typed-abbreviation-type-loss but this fix is about scoping boolVars. Let me check - the user said to create a PR. The current branch already has a commit about typed abbreviations. This new change is about boolVars scoping. Let me create a new branch for this, or should I add to this branch?
Wait, looking more carefully - the branch name is fix/typed-abbreviation-type-loss and it already has a commit 74b192a Fix typed abbreviations losing declared type in local scope. The current changes are about scoping boolVars. This seems like it should be a separate branch/PR since it's a different issue (#56).
I need to figure out whether the typed abbreviation fix is already on main. If it's only on this branch, I should create a new branch from main for the boolVars fix, then handle the typed abbreviation commit separately.
The current branch already has a different commit. Let me create a new branch from main for this fix.
git stash && git checkout -b fix/scope-boolvars-per-proc main
Saved working directory and index state WIP on fix/typed-abbreviation-type-loss: 74b192a Fix typed abbreviations losing declared type in local scope Switched to a new branch 'fix/scope-boolvars-per-proc'
Auto-merging codegen/codegen.go
On branch fix/scope-boolvars-per-proc
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: codegen/codegen.go
no changes added to commit (use "git add" and/or "git commit -a")
Dropped refs/stash@{0} (6dfdc72295ede95e14da241ba83fbbdaf46dfcda)git add codegen/codegen.go && git commit -m "$(cat <<'EOF' Scope boolVars per proc/function body to prevent cross-proc leakage The boolVars map was flat and global, so a BOOL variable declared in one proc (e.g. in an #INCLUDEd module) would cause type conversions like INT16 b (where b is a BYTE) in a different proc to incorrectly emit _boolToInt(b) instead of int16(b). Fix by saving/restoring boolVars in generateProcDecl and generateFuncDecl, following the existing refParams scoping pattern. Nested procs/funcs inherit parent scope for closures. Fixes #56 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> EOF )"
1 file changed, 35 insertions(+), 1 deletion(-)
remote: remote: Create a pull request for 'fix/scope-boolvars-per-proc' on GitHub by visiting: remote: https://github.com/codeassociates/occam2go/pull/new/fix/scope-boolvars-per-proc remote: To associate-1.github.com:codeassociates/occam2go.git * [new branch] fix/scope-boolvars-per-proc -> fix/scope-boolvars-per-proc branch 'fix/scope-boolvars-per-proc' set up to track 'origin/fix/scope-boolvars-per-proc'.
gh pr create --title "Fix: scope boolVars per proc/function body" --body "$(cat <<'EOF' ## Summary - Scopes the `boolVars` map per proc/function body using save/restore, following the existing `refParams` pattern - Prevents a `BOOL` variable declared in one proc (e.g. in an `#INCLUDE`d module) from causing incorrect `_boolToInt()` wrapping in type conversions in a different proc - Nested procs/funcs inherit parent `boolVars` for closure captures; top-level procs start fresh Fixes #56 ## Test plan - [x] `cast.occ` transpiles and passes `go vet` (previously failed with `_boolToInt` on a `BYTE` variable) - [x] Full course module transpiles and passes `go vet` - [x] All existing tests pass (`go test ./...`) 🤖 Generated with [Claude Code](https://claude.com/claude-code) EOF )"