builder.go raw

   1  package mxssa
   2  
   3  import (
   4  	"go/constant"
   5  	"go/token"
   6  
   7  	"moxie/syntax"
   8  	"moxie/typecheck"
   9  )
  10  
  11  // ─── Package builder ──────────────────────────────────────────────────────────
  12  
  13  // CreatePackage builds the SSA Package for pkg from its type-checked syntax files.
  14  func (prog *Program) CreatePackage(pkg *typecheck.Package, files []*syntax.File, info *typecheck.Info) *Package {
  15  	p := &Package{
  16  		Prog:    prog,
  17  		Pkg:     pkg,
  18  		Members: map[string]Member{},
  19  	}
  20  	prog.packages[pkg] = p
  21  	prog.imported[pkg.Path()] = p
  22  
  23  	pb := &pkgBuilder{pkg: p, info: info, prog: prog}
  24  
  25  	// Pass 1: register top-level members (no bodies yet).
  26  	for _, f := range files {
  27  		pb.registerFile(f)
  28  	}
  29  
  30  	// Pass 2: build function bodies.
  31  	for _, f := range files {
  32  		pb.buildFile(f)
  33  	}
  34  
  35  	return p
  36  }
  37  
  38  // pkgBuilder builds one Package.
  39  type pkgBuilder struct {
  40  	pkg  *Package
  41  	info *typecheck.Info
  42  	prog *Program
  43  }
  44  
  45  func (pb *pkgBuilder) registerFile(f *syntax.File) {
  46  	for _, d := range f.DeclList {
  47  		switch d := d.(type) {
  48  		case *syntax.FuncDecl:
  49  			pb.registerFunc(d)
  50  		case *syntax.VarDecl:
  51  			pb.registerVar(d)
  52  		case *syntax.TypeDecl:
  53  			pb.registerType(d)
  54  		case *syntax.ConstDecl:
  55  			pb.registerConst(d)
  56  		}
  57  	}
  58  }
  59  
  60  func (pb *pkgBuilder) registerFunc(d *syntax.FuncDecl) {
  61  	if d.Name.Value == "init" {
  62  		return // init functions handled separately
  63  	}
  64  	var obj *typecheck.Func
  65  	if o := pb.pkg.Pkg.Scope().Lookup(d.Name.Value); o != nil {
  66  		obj, _ = o.(*typecheck.Func)
  67  	}
  68  	var sig *typecheck.Signature
  69  	if obj != nil {
  70  		sig, _ = obj.Type().(*typecheck.Signature)
  71  	}
  72  	fn := &Function{
  73  		name:      d.Name.Value,
  74  		object:    obj,
  75  		Signature: sig,
  76  		pos:       posFromSyntax(d.Name.Pos()),
  77  		Pkg:       pb.pkg,
  78  		Prog:      pb.prog,
  79  	}
  80  	pb.pkg.Members[fn.name] = fn
  81  }
  82  
  83  func (pb *pkgBuilder) registerVar(d *syntax.VarDecl) {
  84  	for _, name := range d.NameList {
  85  		var obj *typecheck.Var
  86  		if o := pb.pkg.Pkg.Scope().Lookup(name.Value); o != nil {
  87  			obj, _ = o.(*typecheck.Var)
  88  		}
  89  		var typ typecheck.Type
  90  		if obj != nil {
  91  			typ = obj.Type()
  92  		}
  93  		g := &Global{
  94  			name:   name.Value,
  95  			object: obj,
  96  			typ:    typecheck.NewPointer(typ), // globals are pointers in SSA
  97  			pos:    posFromSyntax(name.Pos()),
  98  			pkg:    pb.pkg,
  99  		}
 100  		pb.pkg.Members[name.Value] = g
 101  	}
 102  }
 103  
 104  func (pb *pkgBuilder) registerType(d *syntax.TypeDecl) {
 105  	var obj *typecheck.TypeName
 106  	if o := pb.pkg.Pkg.Scope().Lookup(d.Name.Value); o != nil {
 107  		obj, _ = o.(*typecheck.TypeName)
 108  	}
 109  	if obj == nil {
 110  		return
 111  	}
 112  	t := &Type_{object: obj, pkg: pb.pkg}
 113  	pb.pkg.Members[d.Name.Value] = t
 114  }
 115  
 116  func (pb *pkgBuilder) registerConst(d *syntax.ConstDecl) {
 117  	for _, name := range d.NameList {
 118  		var obj *typecheck.Const
 119  		if o := pb.pkg.Pkg.Scope().Lookup(name.Value); o != nil {
 120  			obj, _ = o.(*typecheck.Const)
 121  		}
 122  		if obj == nil {
 123  			continue
 124  		}
 125  		var cval constant.Value
 126  		if obj.Val() != nil {
 127  			cval, _ = obj.Val().(constant.Value)
 128  		}
 129  		c := &NamedConst{
 130  			object: obj,
 131  			Value:  &Const{typ: obj.Type(), val: cval},
 132  			pkg:    pb.pkg,
 133  		}
 134  		pb.pkg.Members[name.Value] = c
 135  	}
 136  }
 137  
 138  func (pb *pkgBuilder) buildFile(f *syntax.File) {
 139  	for _, d := range f.DeclList {
 140  		if fd, ok := d.(*syntax.FuncDecl); ok {
 141  			pb.buildFunc(fd)
 142  		}
 143  	}
 144  }
 145  
 146  func (pb *pkgBuilder) buildFunc(d *syntax.FuncDecl) {
 147  	if d.Body == nil {
 148  		return // external function
 149  	}
 150  	fn, _ := pb.pkg.Members[d.Name.Value].(*Function)
 151  	if fn == nil {
 152  		return
 153  	}
 154  	fb := newFuncBuilder(fn, pb.info)
 155  	fb.buildBody(d)
 156  }
 157  
 158  // ─── Function builder ─────────────────────────────────────────────────────────
 159  
 160  // funcBuilder builds one Function body.
 161  type funcBuilder struct {
 162  	fn           *Function
 163  	info         *typecheck.Info
 164  	currentBlock *BasicBlock
 165  	vars         map[typecheck.Object]*Alloc // local variable allocas
 166  	namedResults []*Alloc
 167  	counter      int // for generating unique SSA names
 168  
 169  	// Loop/defer state
 170  	loops    []loopState
 171  	deferred int // number of pending defer statements
 172  }
 173  
 174  type loopState struct {
 175  	body  *BasicBlock
 176  	post  *BasicBlock
 177  	done  *BasicBlock
 178  }
 179  
 180  func newFuncBuilder(fn *Function, info *typecheck.Info) *funcBuilder {
 181  	return &funcBuilder{
 182  		fn:   fn,
 183  		info: info,
 184  		vars: map[typecheck.Object]*Alloc{},
 185  	}
 186  }
 187  
 188  func (fb *funcBuilder) newBlock(comment string) *BasicBlock {
 189  	return NewBasicBlock(fb.fn, comment)
 190  }
 191  
 192  func (fb *funcBuilder) emit(instr Instruction) {
 193  	if fb.currentBlock == nil {
 194  		return
 195  	}
 196  	instr.setBlock(fb.currentBlock)
 197  	fb.currentBlock.Instrs = append(fb.currentBlock.Instrs, instr)
 198  }
 199  
 200  func (fb *funcBuilder) nextName() string {
 201  	fb.counter++
 202  	return "t" + itoa(fb.counter)
 203  }
 204  
 205  func (fb *funcBuilder) emitValue(instr interface {
 206  	Instruction
 207  	Value
 208  }) Value {
 209  	instr.setName(fb.nextName())
 210  	fb.emit(instr)
 211  	return instr
 212  }
 213  
 214  // buildBody initializes parameters and builds the function body.
 215  func (fb *funcBuilder) buildBody(d *syntax.FuncDecl) {
 216  	entry := fb.newBlock("entry")
 217  	fb.currentBlock = entry
 218  	fb.fn.currentBlock = entry
 219  
 220  	sig := fb.fn.Signature
 221  	if sig == nil {
 222  		return
 223  	}
 224  
 225  	// Allocate parameters.
 226  	params := sig.Params()
 227  	if d.Recv != nil {
 228  		// receiver as first param
 229  		recv := sig.Recv()
 230  		if recv != nil {
 231  			var recvObj *typecheck.Var
 232  			if d.Recv.Name != nil {
 233  				recvObj = fb.lookupVar(d.Recv.Name.Value)
 234  			}
 235  			p := &Parameter{
 236  				name:   recv.Name(),
 237  				object: recvObj,
 238  				typ:    recv.Type(),
 239  				pos:    posFromSyntax(d.Recv.Pos()),
 240  				parent: fb.fn,
 241  			}
 242  			fb.fn.Params = append(fb.fn.Params, p)
 243  			if recvObj != nil {
 244  				alloc := fb.emitAlloc(recv.Type(), p.pos)
 245  				fb.vars[recvObj] = alloc
 246  				fb.emitStore(alloc, p)
 247  			}
 248  		}
 249  	}
 250  	if params != nil {
 251  		for i := 0; i < params.Len(); i++ {
 252  			pvar := params.At(i)
 253  			p := &Parameter{
 254  				name:   pvar.Name(),
 255  				typ:    pvar.Type(),
 256  				pos:    fb.fn.pos,
 257  				parent: fb.fn,
 258  			}
 259  			fb.fn.Params = append(fb.fn.Params, p)
 260  			if pvar.Name() != "" && pvar.Name() != "_" {
 261  				if obj := fb.lookupVarByType(pvar.Name(), pvar.Type()); obj != nil {
 262  					alloc := fb.emitAlloc(pvar.Type(), p.pos)
 263  					fb.vars[obj] = alloc
 264  					fb.emitStore(alloc, p)
 265  				}
 266  			}
 267  		}
 268  	}
 269  
 270  	// Named results.
 271  	if sig.Results() != nil {
 272  		for i := 0; i < sig.Results().Len(); i++ {
 273  			r := sig.Results().At(i)
 274  			if r.Name() != "" && r.Name() != "_" {
 275  				alloc := fb.emitAlloc(r.Type(), fb.fn.pos)
 276  				fb.namedResults = append(fb.namedResults, alloc)
 277  				fb.fn.Locals = append(fb.fn.Locals, alloc)
 278  			}
 279  		}
 280  	}
 281  
 282  	// Build the body.
 283  	if d.Body != nil {
 284  		fb.buildBlock(d.Body)
 285  	}
 286  
 287  	// Implicit return at end.
 288  	if fb.currentBlock != nil && !fb.blockTerminated(fb.currentBlock) {
 289  		fb.emitReturn(nil, 0)
 290  	}
 291  }
 292  
 293  func (fb *funcBuilder) emitAlloc(typ typecheck.Type, pos token.Pos) *Alloc {
 294  	a := &Alloc{Heap: false}
 295  	a.typ = typecheck.NewPointer(typ)
 296  	a.pos = pos
 297  	a.name = fb.nextName()
 298  	fb.emit(a)
 299  	fb.fn.Locals = append(fb.fn.Locals, a)
 300  	return a
 301  }
 302  
 303  func (fb *funcBuilder) emitStore(addr Value, val Value) {
 304  	fb.emit(&Store{Addr: addr, Val: val})
 305  }
 306  
 307  func (fb *funcBuilder) emitLoad(addr Value, typ typecheck.Type) Value {
 308  	u := &UnOp{Op: token.MUL, X: addr}
 309  	u.typ = typ
 310  	u.name = fb.nextName()
 311  	fb.emit(u)
 312  	return u
 313  }
 314  
 315  func (fb *funcBuilder) emitReturn(vals []Value, pos token.Pos) {
 316  	fb.emit(&Return{Results: vals, pos: pos})
 317  	fb.currentBlock = nil
 318  }
 319  
 320  func (fb *funcBuilder) blockTerminated(b *BasicBlock) bool {
 321  	if len(b.Instrs) == 0 {
 322  		return false
 323  	}
 324  	switch b.Instrs[len(b.Instrs)-1].(type) {
 325  	case *Return, *Jump, *If, *Panic:
 326  		return true
 327  	}
 328  	return false
 329  }
 330  
 331  // ─── Statement builder ────────────────────────────────────────────────────────
 332  
 333  func (fb *funcBuilder) buildBlock(b *syntax.BlockStmt) {
 334  	if b == nil {
 335  		return
 336  	}
 337  	for _, s := range b.List {
 338  		fb.buildStmt(s)
 339  		if fb.currentBlock == nil {
 340  			return // terminated
 341  		}
 342  	}
 343  }
 344  
 345  func (fb *funcBuilder) buildStmt(s syntax.Stmt) {
 346  	if s == nil || fb.currentBlock == nil {
 347  		return
 348  	}
 349  	switch s := s.(type) {
 350  	case *syntax.EmptyStmt:
 351  		// nothing
 352  	case *syntax.ExprStmt:
 353  		// push/resize are statement-level mutating builtins.
 354  		if call, ok := s.X.(*syntax.CallExpr); ok {
 355  			fmt.Fprintf(os.Stderr, "EXPRSTMT: call fun type=%T\n", call.Fun)
 356  			if name, ok2 := call.Fun.(*syntax.Name); ok2 {
 357  				fmt.Fprintf(os.Stderr, "EXPRSTMT: name=%s\n", name.Value)
 358  				if name.Value == "push" || name.Value == "resize" {
 359  					result := fb.buildExpr(s.X)
 360  					if result != nil && len(call.ArgList) > 0 {
 361  						fb.buildStore(call.ArgList[0], result)
 362  						fmt.Fprintf(os.Stderr, "PUSH STORE-BACK: fn=%s argType=%T\n", fb.fn.Name(), call.ArgList[0])
 363  					}
 364  				} else if name.Value == "pop" {
 365  					// pop as statement: build the pop (discards element),
 366  					// then build a resize(s, len(s)-1) for the store-back.
 367  					fb.buildExpr(s.X) // discard element
 368  					if len(call.ArgList) > 0 {
 369  						sv := fb.buildExpr(call.ArgList[0])
 370  						if sv != nil {
 371  							// Build len(s) - 1
 372  							lenB := &Builtin{name: "len", id: BuiltinLen}
 373  							lenCall := &Call{Call: CallCommon{Value: lenB, Args: []Value{sv}}}
 374  							lenCall.typ = typecheck.Typ[typecheck.Int32]
 375  							lenCall.name = fb.nextName()
 376  							fb.emit(lenCall)
 377  							one := &Const{typ: typecheck.Typ[typecheck.Int32], val: constant.MakeInt64(1)}
 378  							sub := &BinOp{Op: token.SUB, X: lenCall, Y: one}
 379  							sub.typ = typecheck.Typ[typecheck.Int32]
 380  							sub.name = fb.nextName()
 381  							fb.emit(sub)
 382  							// Build resize(s, newLen)
 383  							resizeB := &Builtin{name: "resize", id: BuiltinResize}
 384  							resizeCall := &Call{Call: CallCommon{Value: resizeB, Args: []Value{sv, sub}}}
 385  							resizeCall.typ = sv.Type()
 386  							resizeCall.name = fb.nextName()
 387  							fb.emit(resizeCall)
 388  							fb.buildStore(call.ArgList[0], resizeCall)
 389  						}
 390  					}
 391  				} else {
 392  					fb.buildExpr(s.X)
 393  				}
 394  			} else {
 395  				fb.buildExpr(s.X)
 396  			}
 397  		} else {
 398  			fb.buildExpr(s.X)
 399  		}
 400  	case *syntax.AssignStmt:
 401  		fb.buildAssign(s)
 402  	case *syntax.BlockStmt:
 403  		fb.buildBlock(s)
 404  	case *syntax.DeclStmt:
 405  		for _, d := range s.DeclList {
 406  			fb.buildLocalDecl(d)
 407  		}
 408  	case *syntax.IfStmt:
 409  		fb.buildIf(s)
 410  	case *syntax.ForStmt:
 411  		fb.buildFor(s)
 412  	case *syntax.SwitchStmt:
 413  		fb.buildSwitch(s)
 414  	case *syntax.SelectStmt:
 415  		fb.buildSelect(s)
 416  	case *syntax.ReturnStmt:
 417  		fb.buildReturn(s)
 418  	case *syntax.BranchStmt:
 419  		fb.buildBranch(s)
 420  	case *syntax.LabeledStmt:
 421  		fb.buildStmt(s.Stmt)
 422  	case *syntax.SendStmt:
 423  		ch := fb.buildExpr(s.Chan)
 424  		val := fb.buildExpr(s.Value)
 425  		if ch != nil && val != nil {
 426  			fb.emit(&Send{Chan: ch, X: val})
 427  		}
 428  	case *syntax.CallStmt:
 429  		call, _ := s.Call.(*syntax.CallExpr)
 430  		switch s.Tok {
 431  		case syntax.Go:
 432  			if call != nil {
 433  				fb.buildGoStmt(call)
 434  			}
 435  		case syntax.Defer:
 436  			if call != nil {
 437  				fb.buildDeferStmt(call)
 438  			}
 439  		default:
 440  			fb.buildExpr(s.Call)
 441  		}
 442  	}
 443  }
 444  
 445  func (fb *funcBuilder) buildAssign(s *syntax.AssignStmt) {
 446  	if s.Rhs == nil {
 447  		// x++ or x--
 448  		lv := fb.buildExpr(s.Lhs)
 449  		if lv == nil {
 450  			return
 451  		}
 452  		one := &Const{typ: lv.Type(), val: constant.MakeInt64(1)}
 453  		op := token.ADD
 454  		if s.Op == syntax.Sub {
 455  			op = token.SUB
 456  		}
 457  		bin := &BinOp{Op: op, X: lv, Y: one}
 458  		bin.typ = lv.Type()
 459  		bin.name = fb.nextName()
 460  		fb.emit(bin)
 461  		fb.buildStore(s.Lhs, bin)
 462  		return
 463  	}
 464  	if s.Op == syntax.Def {
 465  		fb.buildShortVarDecl(s)
 466  		return
 467  	}
 468  	// compound assignment (+=, -=, etc.): Op != 0 means an operator is applied before =
 469  	if s.Op != 0 {
 470  		lv := fb.buildExpr(s.Lhs)
 471  		rv := fb.buildExpr(s.Rhs)
 472  		if lv == nil || rv == nil {
 473  			return
 474  		}
 475  		op := compoundOp(s.Op)
 476  		bin := &BinOp{Op: op, X: lv, Y: rv}
 477  		bin.typ = lv.Type()
 478  		bin.name = fb.nextName()
 479  		fb.emit(bin)
 480  		fb.buildStore(s.Lhs, bin)
 481  		return
 482  	}
 483  	// plain assignment
 484  	rhs := fb.buildExpr(s.Rhs)
 485  	if rhs == nil {
 486  		return
 487  	}
 488  	fb.buildStore(s.Lhs, rhs)
 489  }
 490  
 491  // compoundOp translates a syntax.Operator (from a compound assignment like +=)
 492  // to the corresponding token.Token for BinOp. The parser stores the base op
 493  // (syntax.Add for +=) in AssignStmt.Op; there are no AddAssign etc. constants.
 494  func compoundOp(op syntax.Operator) token.Token {
 495  	switch op {
 496  	case syntax.Add, syntax.Or: // += and |= both map to ADD (| on strings is concat)
 497  		return token.ADD
 498  	case syntax.Sub:
 499  		return token.SUB
 500  	case syntax.Mul:
 501  		return token.MUL
 502  	case syntax.Div:
 503  		return token.QUO
 504  	case syntax.Rem:
 505  		return token.REM
 506  	case syntax.And:
 507  		return token.AND
 508  	case syntax.Xor:
 509  		return token.XOR
 510  	case syntax.Shl:
 511  		return token.SHL
 512  	case syntax.Shr:
 513  		return token.SHR
 514  	case syntax.AndNot:
 515  		return token.AND_NOT
 516  	}
 517  	return token.ILLEGAL
 518  }
 519  
 520  func (fb *funcBuilder) buildStore(lhs syntax.Expr, val Value) {
 521  	switch lhs := lhs.(type) {
 522  	case *syntax.Name:
 523  		if lhs.Value == "_" {
 524  			return
 525  		}
 526  		obj := fb.lookupObject(lhs.Value)
 527  		if obj == nil {
 528  			return
 529  		}
 530  		if alloc, ok := fb.vars[obj]; ok {
 531  			fb.emitStore(alloc, val)
 532  		}
 533  	case *syntax.Operation:
 534  		if lhs.Y == nil && lhs.Op == syntax.Mul {
 535  			// *ptr = val
 536  			ptr := fb.buildExpr(lhs.X)
 537  			if ptr != nil {
 538  				fb.emitStore(ptr, val)
 539  			}
 540  		}
 541  	case *syntax.SelectorExpr:
 542  		base := fb.buildExpr(lhs.X)
 543  		if base == nil {
 544  			return
 545  		}
 546  		idx := fb.fieldIndex(base.Type(), lhs.Sel.Value)
 547  		fa := &FieldAddr{X: base, Field: idx}
 548  		fa.typ = typecheck.NewPointer(fb.fieldType(base.Type(), idx))
 549  		fa.name = fb.nextName()
 550  		fb.emit(fa)
 551  		fb.emitStore(fa, val)
 552  	case *syntax.IndexExpr:
 553  		base := fb.buildExpr(lhs.X)
 554  		idx := fb.buildExpr(lhs.Index)
 555  		if base == nil || idx == nil {
 556  			return
 557  		}
 558  		ia := &IndexAddr{X: base, Index: idx}
 559  		ia.typ = typecheck.NewPointer(elemType(base.Type()))
 560  		ia.name = fb.nextName()
 561  		fb.emit(ia)
 562  		fb.emitStore(ia, val)
 563  	case *syntax.ListExpr:
 564  		// multi-assign: a, b = ...
 565  		// val is a tuple; extract each component
 566  		for i, e := range lhs.ElemList {
 567  			ext := &Extract{Tuple: val, Index: i}
 568  			ext.typ = tupleElemType(val.Type(), i)
 569  			ext.name = fb.nextName()
 570  			fb.emit(ext)
 571  			fb.buildStore(e, ext)
 572  		}
 573  	}
 574  }
 575  
 576  func (fb *funcBuilder) buildShortVarDecl(s *syntax.AssignStmt) {
 577  	rhs := fb.buildExpr(s.Rhs)
 578  	names := exprNames(s.Lhs)
 579  
 580  	for i, name := range names {
 581  		if name.Value == "_" {
 582  			continue
 583  		}
 584  		var typ typecheck.Type
 585  		if rhs != nil {
 586  			if i == 0 {
 587  				typ = rhs.Type()
 588  			} else if tup, ok := rhs.Type().(*typecheck.Tuple); ok && i < tup.Len() {
 589  				typ = tup.At(i).Type()
 590  			}
 591  		}
 592  		// Look up existing object from the info.Defs map.
 593  		var obj typecheck.Object
 594  		if fb.info != nil {
 595  			obj = fb.info.Defs[name]
 596  		}
 597  		if obj == nil {
 598  			// fall back to a synthetic var
 599  			obj = typecheck.NewVar(fb.fn.Pkg.Pkg, name.Value, typ)
 600  		}
 601  		alloc := fb.emitAlloc(typ, posFromSyntax(name.Pos()))
 602  		fb.vars[obj] = alloc
 603  		// Store the initial value.
 604  		var initVal Value
 605  		if rhs != nil {
 606  			if i == 0 {
 607  				initVal = rhs
 608  			} else {
 609  				ext := &Extract{Tuple: rhs, Index: i}
 610  				ext.typ = typ
 611  				ext.name = fb.nextName()
 612  				fb.emit(ext)
 613  				initVal = ext
 614  			}
 615  		}
 616  		if initVal != nil {
 617  			fb.emitStore(alloc, initVal)
 618  		}
 619  	}
 620  }
 621  
 622  func (fb *funcBuilder) buildLocalDecl(d syntax.Decl) {
 623  	switch d := d.(type) {
 624  	case *syntax.VarDecl:
 625  		var typ typecheck.Type
 626  		if fb.info != nil && len(d.NameList) > 0 {
 627  			if obj := fb.info.Defs[d.NameList[0]]; obj != nil {
 628  				typ = obj.Type()
 629  			}
 630  		}
 631  		initVal := fb.buildExpr(d.Values)
 632  		for _, name := range d.NameList {
 633  			if name.Value == "_" {
 634  				continue
 635  			}
 636  			var obj typecheck.Object
 637  			if fb.info != nil {
 638  				obj = fb.info.Defs[name]
 639  			}
 640  			if obj == nil {
 641  				obj = typecheck.NewVar(fb.fn.Pkg.Pkg, name.Value, typ)
 642  			}
 643  			alloc := fb.emitAlloc(obj.Type(), posFromSyntax(name.Pos()))
 644  			fb.vars[obj] = alloc
 645  			fb.fn.Locals = append(fb.fn.Locals, alloc)
 646  			if initVal != nil {
 647  				fb.emitStore(alloc, initVal)
 648  			}
 649  		}
 650  	case *syntax.ConstDecl:
 651  		// Constants don't generate alloca; they're inlined as Const values.
 652  	case *syntax.TypeDecl:
 653  		// Local type declarations don't generate code.
 654  	}
 655  }
 656  
 657  // buildIf builds an if statement.
 658  func (fb *funcBuilder) buildIf(s *syntax.IfStmt) {
 659  	// Optional init.
 660  	if s.Init != nil {
 661  		fb.buildStmt(s.Init)
 662  	}
 663  	if fb.currentBlock == nil {
 664  		return
 665  	}
 666  	cond := fb.buildExpr(s.Cond)
 667  	if cond == nil {
 668  		return
 669  	}
 670  
 671  	thenBlock := fb.newBlock("if.then")
 672  	var elseBlock *BasicBlock
 673  	doneBlock := fb.newBlock("if.done")
 674  
 675  	if s.Else != nil {
 676  		elseBlock = fb.newBlock("if.else")
 677  	} else {
 678  		elseBlock = doneBlock
 679  	}
 680  
 681  	fb.emit(&If{Cond: cond})
 682  	fb.currentBlock.Succs = append(fb.currentBlock.Succs, thenBlock, elseBlock)
 683  	thenBlock.Preds = append(thenBlock.Preds, fb.currentBlock)
 684  	elseBlock.Preds = append(elseBlock.Preds, fb.currentBlock)
 685  
 686  	// then branch
 687  	fb.currentBlock = thenBlock
 688  	fb.buildBlock(s.Then)
 689  	if fb.currentBlock != nil && !fb.blockTerminated(fb.currentBlock) {
 690  		fb.emit(&Jump{Comment: "if.done"})
 691  		fb.currentBlock.Succs = append(fb.currentBlock.Succs, doneBlock)
 692  		doneBlock.Preds = append(doneBlock.Preds, fb.currentBlock)
 693  	}
 694  
 695  	// else branch
 696  	if s.Else != nil {
 697  		fb.currentBlock = elseBlock
 698  		fb.buildStmt(s.Else)
 699  		if fb.currentBlock != nil && !fb.blockTerminated(fb.currentBlock) {
 700  			fb.emit(&Jump{Comment: "if.done"})
 701  			fb.currentBlock.Succs = append(fb.currentBlock.Succs, doneBlock)
 702  			doneBlock.Preds = append(doneBlock.Preds, fb.currentBlock)
 703  		}
 704  	}
 705  
 706  	fb.currentBlock = doneBlock
 707  }
 708  
 709  // buildFor builds a for/range loop.
 710  func (fb *funcBuilder) buildFor(s *syntax.ForStmt) {
 711  	if s.Init != nil {
 712  		if rc, ok := s.Init.(*syntax.RangeClause); ok {
 713  			fb.buildRangeLoop(s, rc)
 714  			return
 715  		}
 716  		fb.buildStmt(s.Init)
 717  	}
 718  
 719  	condBlock := fb.newBlock("for.cond")
 720  	bodyBlock := fb.newBlock("for.body")
 721  	postBlock := fb.newBlock("for.post")
 722  	doneBlock := fb.newBlock("for.done")
 723  
 724  	// Jump to cond.
 725  	fb.emit(&Jump{Comment: "for.cond"})
 726  	fb.currentBlock.Succs = append(fb.currentBlock.Succs, condBlock)
 727  	condBlock.Preds = append(condBlock.Preds, fb.currentBlock)
 728  
 729  	fb.loops = append(fb.loops, loopState{body: bodyBlock, post: postBlock, done: doneBlock})
 730  
 731  	// Condition.
 732  	fb.currentBlock = condBlock
 733  	if s.Cond != nil {
 734  		cond := fb.buildExpr(s.Cond)
 735  		if cond != nil {
 736  			fb.emit(&If{Cond: cond})
 737  			fb.currentBlock.Succs = append(fb.currentBlock.Succs, bodyBlock, doneBlock)
 738  			bodyBlock.Preds = append(bodyBlock.Preds, condBlock)
 739  			doneBlock.Preds = append(doneBlock.Preds, condBlock)
 740  		}
 741  	} else {
 742  		fb.emit(&Jump{Comment: "for.body"})
 743  		fb.currentBlock.Succs = append(fb.currentBlock.Succs, bodyBlock)
 744  		bodyBlock.Preds = append(bodyBlock.Preds, condBlock)
 745  	}
 746  
 747  	// Body.
 748  	fb.currentBlock = bodyBlock
 749  	fb.buildBlock(s.Body)
 750  	if fb.currentBlock != nil && !fb.blockTerminated(fb.currentBlock) {
 751  		fb.emit(&Jump{Comment: "for.post"})
 752  		fb.currentBlock.Succs = append(fb.currentBlock.Succs, postBlock)
 753  		postBlock.Preds = append(postBlock.Preds, fb.currentBlock)
 754  	}
 755  
 756  	// Post.
 757  	fb.currentBlock = postBlock
 758  	if s.Post != nil {
 759  		fb.buildStmt(s.Post)
 760  	}
 761  	if fb.currentBlock != nil {
 762  		fb.emit(&Jump{Comment: "for.cond"})
 763  		fb.currentBlock.Succs = append(fb.currentBlock.Succs, condBlock)
 764  		condBlock.Preds = append(condBlock.Preds, fb.currentBlock)
 765  	}
 766  
 767  	fb.loops = fb.loops[:len(fb.loops)-1]
 768  	fb.currentBlock = doneBlock
 769  }
 770  
 771  func (fb *funcBuilder) buildRangeLoop(s *syntax.ForStmt, rc *syntax.RangeClause) {
 772  	iterExpr := fb.buildExpr(rc.X)
 773  	if iterExpr == nil {
 774  		return
 775  	}
 776  
 777  	r := &Range{X: iterExpr}
 778  	r.typ = iterType(iterExpr.Type())
 779  	r.name = fb.nextName()
 780  	fb.emit(r)
 781  
 782  	bodyBlock := fb.newBlock("range.body")
 783  	doneBlock := fb.newBlock("range.done")
 784  
 785  	fb.loops = append(fb.loops, loopState{body: bodyBlock, done: doneBlock})
 786  
 787  	// Check done.
 788  	condBlock := fb.newBlock("range.cond")
 789  	fb.emit(&Jump{Comment: "range.cond"})
 790  	fb.currentBlock.Succs = append(fb.currentBlock.Succs, condBlock)
 791  	condBlock.Preds = append(condBlock.Preds, fb.currentBlock)
 792  
 793  	fb.currentBlock = condBlock
 794  	nxt := &Next{Iter: r, IsString: isStringType(iterExpr.Type())}
 795  	nxt.typ = nextType(r.typ)
 796  	nxt.name = fb.nextName()
 797  	fb.emit(nxt)
 798  
 799  	// Extract ok (index 0).
 800  	okExt := &Extract{Tuple: nxt, Index: 0}
 801  	okExt.typ = typecheck.Typ[typecheck.Bool]
 802  	okExt.name = fb.nextName()
 803  	fb.emit(okExt)
 804  
 805  	fb.emit(&If{Cond: okExt})
 806  	fb.currentBlock.Succs = append(fb.currentBlock.Succs, bodyBlock, doneBlock)
 807  	bodyBlock.Preds = append(bodyBlock.Preds, condBlock)
 808  	doneBlock.Preds = append(doneBlock.Preds, condBlock)
 809  
 810  	// Body: bind key/value.
 811  	fb.currentBlock = bodyBlock
 812  	if rc.Lhs != nil && rc.Def {
 813  		names := exprNames(rc.Lhs)
 814  		// index 1 = key, index 2 = value
 815  		for i, name := range names {
 816  			if name.Value == "_" {
 817  				continue
 818  			}
 819  			ext := &Extract{Tuple: nxt, Index: i + 1}
 820  			ext.typ = extractNthType(nxt.typ, i+1)
 821  			ext.name = fb.nextName()
 822  			fb.emit(ext)
 823  			var obj typecheck.Object
 824  			if fb.info != nil {
 825  				obj = fb.info.Defs[name]
 826  			}
 827  			if obj == nil {
 828  				obj = typecheck.NewVar(fb.fn.Pkg.Pkg, name.Value, ext.typ)
 829  			}
 830  			alloc := fb.emitAlloc(ext.typ, posFromSyntax(name.Pos()))
 831  			fb.vars[obj] = alloc
 832  			fb.emitStore(alloc, ext)
 833  		}
 834  	}
 835  	fb.buildBlock(s.Body)
 836  	if fb.currentBlock != nil && !fb.blockTerminated(fb.currentBlock) {
 837  		fb.emit(&Jump{Comment: "range.cond"})
 838  		fb.currentBlock.Succs = append(fb.currentBlock.Succs, condBlock)
 839  		condBlock.Preds = append(condBlock.Preds, fb.currentBlock)
 840  	}
 841  
 842  	fb.loops = fb.loops[:len(fb.loops)-1]
 843  	fb.currentBlock = doneBlock
 844  }
 845  
 846  func (fb *funcBuilder) buildSwitch(s *syntax.SwitchStmt) {
 847  	// Optional init.
 848  	if s.Init != nil {
 849  		fb.buildStmt(s.Init)
 850  	}
 851  	if fb.currentBlock == nil {
 852  		return
 853  	}
 854  
 855  	doneBlock := fb.newBlock("switch.done")
 856  	savedLoops := fb.loops
 857  	fb.loops = append(fb.loops, loopState{done: doneBlock})
 858  
 859  	// Type switch vs expression switch.
 860  	if s.Tag != nil {
 861  		if tsg, ok := s.Tag.(*syntax.TypeSwitchGuard); ok {
 862  			fb.buildTypeSwitch(s, tsg, doneBlock)
 863  			fb.loops = savedLoops
 864  			fb.currentBlock = doneBlock
 865  			return
 866  		}
 867  	}
 868  
 869  	var tag Value
 870  	if s.Tag != nil {
 871  		tag = fb.buildExpr(s.Tag)
 872  	}
 873  
 874  	for _, clause := range s.Body {
 875  		caseBlock := fb.newBlock("switch.case")
 876  		nextBlock := fb.newBlock("switch.next")
 877  
 878  		if clause.Cases != nil && tag != nil {
 879  			caseVal := fb.buildExpr(clause.Cases)
 880  			cmp := &BinOp{Op: token.EQL, X: tag, Y: caseVal}
 881  			cmp.typ = typecheck.Typ[typecheck.Bool]
 882  			cmp.name = fb.nextName()
 883  			fb.emit(cmp)
 884  			fb.emit(&If{Cond: cmp})
 885  			fb.currentBlock.Succs = append(fb.currentBlock.Succs, caseBlock, nextBlock)
 886  			caseBlock.Preds = append(caseBlock.Preds, fb.currentBlock)
 887  			nextBlock.Preds = append(nextBlock.Preds, fb.currentBlock)
 888  		} else {
 889  			// default or no tag
 890  			fb.emit(&Jump{Comment: "switch.case"})
 891  			fb.currentBlock.Succs = append(fb.currentBlock.Succs, caseBlock)
 892  			caseBlock.Preds = append(caseBlock.Preds, fb.currentBlock)
 893  		}
 894  
 895  		fb.currentBlock = caseBlock
 896  		for _, stmt := range clause.Body {
 897  			fb.buildStmt(stmt)
 898  			if fb.currentBlock == nil {
 899  				break
 900  			}
 901  		}
 902  		if fb.currentBlock != nil && !fb.blockTerminated(fb.currentBlock) {
 903  			fb.emit(&Jump{Comment: "switch.done"})
 904  			fb.currentBlock.Succs = append(fb.currentBlock.Succs, doneBlock)
 905  			doneBlock.Preds = append(doneBlock.Preds, fb.currentBlock)
 906  		}
 907  		fb.currentBlock = nextBlock
 908  	}
 909  
 910  	// Fall through last nextBlock to done.
 911  	if fb.currentBlock != nil {
 912  		fb.emit(&Jump{Comment: "switch.done"})
 913  		fb.currentBlock.Succs = append(fb.currentBlock.Succs, doneBlock)
 914  		doneBlock.Preds = append(doneBlock.Preds, fb.currentBlock)
 915  	}
 916  
 917  	fb.loops = savedLoops
 918  	fb.currentBlock = doneBlock
 919  }
 920  
 921  func (fb *funcBuilder) buildTypeSwitch(s *syntax.SwitchStmt, tsg *syntax.TypeSwitchGuard, doneBlock *BasicBlock) {
 922  	x := fb.buildExpr(tsg.X)
 923  	for _, clause := range s.Body {
 924  		caseBlock := fb.newBlock("typeswitch.case")
 925  		nextBlock := fb.newBlock("typeswitch.next")
 926  
 927  		if clause.Cases != nil {
 928  			var assertedType typecheck.Type
 929  			if fb.info != nil {
 930  				tv := fb.info.Types[clause.Cases]
 931  				assertedType = tv.Type
 932  			}
 933  			if assertedType == nil {
 934  				assertedType = typecheck.Typ[typecheck.Invalid]
 935  			}
 936  			ta := &TypeAssert{X: x, AssertedType: assertedType, CommaOk: true}
 937  			ta.typ = typecheck.NewTuple(
 938  				typecheck.NewVar(nil, "val", assertedType),
 939  				typecheck.NewVar(nil, "ok", typecheck.Typ[typecheck.Bool]),
 940  			)
 941  			ta.name = fb.nextName()
 942  			fb.emit(ta)
 943  			okExt := &Extract{Tuple: ta, Index: 1}
 944  			okExt.typ = typecheck.Typ[typecheck.Bool]
 945  			okExt.name = fb.nextName()
 946  			fb.emit(okExt)
 947  			fb.emit(&If{Cond: okExt})
 948  			fb.currentBlock.Succs = append(fb.currentBlock.Succs, caseBlock, nextBlock)
 949  			caseBlock.Preds = append(caseBlock.Preds, fb.currentBlock)
 950  			nextBlock.Preds = append(nextBlock.Preds, fb.currentBlock)
 951  
 952  			// Bind guard variable in case block.
 953  			fb.currentBlock = caseBlock
 954  			if tsg.Lhs != nil {
 955  				guardVal := &Extract{Tuple: ta, Index: 0}
 956  				guardVal.typ = assertedType
 957  				guardVal.name = fb.nextName()
 958  				fb.emit(guardVal)
 959  				obj := typecheck.NewVar(fb.fn.Pkg.Pkg, tsg.Lhs.Value, assertedType)
 960  				alloc := fb.emitAlloc(assertedType, posFromSyntax(tsg.Lhs.Pos()))
 961  				fb.vars[obj] = alloc
 962  				fb.emitStore(alloc, guardVal)
 963  			}
 964  		} else {
 965  			fb.emit(&Jump{Comment: "typeswitch.case"})
 966  			fb.currentBlock.Succs = append(fb.currentBlock.Succs, caseBlock)
 967  			caseBlock.Preds = append(caseBlock.Preds, fb.currentBlock)
 968  			fb.currentBlock = caseBlock
 969  		}
 970  
 971  		for _, stmt := range clause.Body {
 972  			fb.buildStmt(stmt)
 973  			if fb.currentBlock == nil {
 974  				break
 975  			}
 976  		}
 977  		if fb.currentBlock != nil && !fb.blockTerminated(fb.currentBlock) {
 978  			fb.emit(&Jump{Comment: "switch.done"})
 979  			fb.currentBlock.Succs = append(fb.currentBlock.Succs, doneBlock)
 980  			doneBlock.Preds = append(doneBlock.Preds, fb.currentBlock)
 981  		}
 982  		fb.currentBlock = nextBlock
 983  	}
 984  	if fb.currentBlock != nil {
 985  		fb.emit(&Jump{Comment: "switch.done"})
 986  		fb.currentBlock.Succs = append(fb.currentBlock.Succs, doneBlock)
 987  		doneBlock.Preds = append(doneBlock.Preds, fb.currentBlock)
 988  	}
 989  }
 990  
 991  func (fb *funcBuilder) buildSelect(s *syntax.SelectStmt) {
 992  	doneBlock := fb.newBlock("select.done")
 993  
 994  	var states []*SelectState
 995  	caseBlocks := make([]*BasicBlock, len(s.Body))
 996  	for i, clause := range s.Body {
 997  		cb := fb.newBlock("select.case")
 998  		caseBlocks[i] = cb
 999  		state := &SelectState{}
1000  		if clause.Comm != nil {
1001  			switch comm := clause.Comm.(type) {
1002  			case *syntax.SendStmt:
1003  				state.Dir = token.ARROW // SEND direction marker
1004  				state.Chan = fb.buildExpr(comm.Chan)
1005  				state.Send = fb.buildExpr(comm.Value)
1006  			case *syntax.AssignStmt:
1007  				// recv: v, ok := <-ch  or  v := <-ch
1008  				var chanExpr syntax.Expr
1009  				if op, ok2 := comm.Rhs.(*syntax.Operation); ok2 && op.Y == nil && op.Op == syntax.Recv {
1010  					chanExpr = op.X
1011  				}
1012  				if chanExpr != nil {
1013  					state.Dir = token.ILLEGAL // RECV direction marker
1014  					state.Chan = fb.buildExpr(chanExpr)
1015  				}
1016  			case *syntax.ExprStmt:
1017  				if op, ok2 := comm.X.(*syntax.Operation); ok2 && op.Y == nil && op.Op == syntax.Recv {
1018  					state.Dir = token.ILLEGAL // RECV direction marker
1019  					state.Chan = fb.buildExpr(op.X)
1020  				}
1021  			}
1022  		}
1023  		states = append(states, state)
1024  	}
1025  
1026  	sel := &Select{States: states, Blocking: true}
1027  	sel.typ = selectResultType(states)
1028  	sel.name = fb.nextName()
1029  	fb.emit(sel)
1030  
1031  	for i, clause := range s.Body {
1032  		fb.currentBlock = caseBlocks[i]
1033  		for _, stmt := range clause.Body {
1034  			fb.buildStmt(stmt)
1035  			if fb.currentBlock == nil {
1036  				break
1037  			}
1038  		}
1039  		if fb.currentBlock != nil && !fb.blockTerminated(fb.currentBlock) {
1040  			fb.emit(&Jump{Comment: "select.done"})
1041  			fb.currentBlock.Succs = append(fb.currentBlock.Succs, doneBlock)
1042  			doneBlock.Preds = append(doneBlock.Preds, fb.currentBlock)
1043  		}
1044  	}
1045  
1046  	fb.currentBlock = doneBlock
1047  }
1048  
1049  func (fb *funcBuilder) buildReturn(s *syntax.ReturnStmt) {
1050  	var vals []Value
1051  	if s.Results != nil {
1052  		v := fb.buildExpr(s.Results)
1053  		if v != nil {
1054  			vals = append(vals, v)
1055  		}
1056  	}
1057  	fb.emitReturn(vals, posFromSyntax(s.Pos()))
1058  }
1059  
1060  func (fb *funcBuilder) buildBranch(s *syntax.BranchStmt) {
1061  	if fb.currentBlock == nil {
1062  		return
1063  	}
1064  	switch s.Tok {
1065  	case syntax.Break:
1066  		if len(fb.loops) > 0 {
1067  			doneBlock := fb.loops[len(fb.loops)-1].done
1068  			fb.emit(&Jump{Comment: "break"})
1069  			fb.currentBlock.Succs = append(fb.currentBlock.Succs, doneBlock)
1070  			doneBlock.Preds = append(doneBlock.Preds, fb.currentBlock)
1071  			fb.currentBlock = nil
1072  		}
1073  	case syntax.Continue:
1074  		for i := len(fb.loops) - 1; i >= 0; i-- {
1075  			postBlock := fb.loops[i].post
1076  			if postBlock == nil {
1077  				postBlock = fb.loops[i].body
1078  			}
1079  			if postBlock == nil {
1080  				continue // select-loop state has no post/body; skip upward
1081  			}
1082  			fb.emit(&Jump{Comment: "continue"})
1083  			fb.currentBlock.Succs = append(fb.currentBlock.Succs, postBlock)
1084  			postBlock.Preds = append(postBlock.Preds, fb.currentBlock)
1085  			fb.currentBlock = nil
1086  			break
1087  		}
1088  	case syntax.Return:
1089  		fb.emitReturn(nil, 0)
1090  	}
1091  }
1092  
1093  func (fb *funcBuilder) buildGoStmt(call *syntax.CallExpr) {
1094  	fn := fb.buildExpr(call.Fun)
1095  	if fn == nil {
1096  		return
1097  	}
1098  	args := fb.buildArgs(call.ArgList)
1099  	g := &Go{Call: CallCommon{Value: fn, Args: args}}
1100  	fb.emit(g)
1101  }
1102  
1103  func (fb *funcBuilder) buildDeferStmt(call *syntax.CallExpr) {
1104  	fn := fb.buildExpr(call.Fun)
1105  	if fn == nil {
1106  		return
1107  	}
1108  	args := fb.buildArgs(call.ArgList)
1109  	d := &Defer{Call: CallCommon{Value: fn, Args: args}}
1110  	fb.emit(d)
1111  	fb.deferred++
1112  }
1113  
1114  // ─── Expression builder ───────────────────────────────────────────────────────
1115  
1116  // buildExpr compiles an expression and returns the resulting Value.
1117  // Returns nil for void expressions or errors.
1118  func (fb *funcBuilder) buildExpr(e syntax.Expr) Value {
1119  	if e == nil || fb.currentBlock == nil {
1120  		return nil
1121  	}
1122  	switch e := e.(type) {
1123  	case *syntax.Name:
1124  		return fb.buildIdent(e)
1125  	case *syntax.BasicLit:
1126  		return fb.buildLit(e)
1127  	case *syntax.Operation:
1128  		return fb.buildOperation(e)
1129  	case *syntax.CallExpr:
1130  		return fb.buildCall(e)
1131  	case *syntax.SelectorExpr:
1132  		return fb.buildSelector(e)
1133  	case *syntax.IndexExpr:
1134  		return fb.buildIndex(e)
1135  	case *syntax.SliceExpr:
1136  		return fb.buildSlice(e)
1137  	case *syntax.AssertExpr:
1138  		return fb.buildAssert(e)
1139  	case *syntax.CompositeLit:
1140  		return fb.buildCompositeLit(e)
1141  	case *syntax.FuncLit:
1142  		return fb.buildFuncLit(e)
1143  	case *syntax.ParenExpr:
1144  		return fb.buildExpr(e.X)
1145  	case *syntax.ListExpr:
1146  		// Multiple return values — return the last (callers handle multi-return differently)
1147  		var last Value
1148  		for _, el := range e.ElemList {
1149  			last = fb.buildExpr(el)
1150  		}
1151  		return last
1152  	case *syntax.KeyValueExpr:
1153  		return fb.buildExpr(e.Value)
1154  	}
1155  	return nil
1156  }
1157  
1158  func (fb *funcBuilder) buildIdent(e *syntax.Name) Value {
1159  	if e.Value == "_" || e.Value == "nil" {
1160  		return &Const{typ: nil, val: nil}
1161  	}
1162  	obj := fb.lookupObject(e.Value)
1163  	if obj == nil {
1164  		// Could be a builtin.
1165  		return fb.builtinValue(e.Value)
1166  	}
1167  	switch obj := obj.(type) {
1168  	case *typecheck.Var:
1169  		if alloc, ok := fb.vars[obj]; ok {
1170  			return fb.emitLoad(alloc, obj.Type())
1171  		}
1172  		// Package-level global.
1173  		if g, ok := fb.fn.Pkg.Members[e.Value].(*Global); ok {
1174  			return fb.emitLoad(g, obj.Type())
1175  		}
1176  		return &Const{typ: obj.Type(), val: nil}
1177  	case *typecheck.Const:
1178  		var cval constant.Value
1179  		if obj.Val() != nil {
1180  			cval, _ = obj.Val().(constant.Value)
1181  		}
1182  		return &Const{typ: obj.Type(), val: cval}
1183  	case *typecheck.Func:
1184  		fn, _ := fb.fn.Pkg.Members[e.Value].(*Function)
1185  		if fn != nil {
1186  			return fn
1187  		}
1188  		return &Const{typ: obj.Type(), val: nil}
1189  	case *typecheck.TypeName:
1190  		return nil // type name used as expression (e.g., conversion)
1191  	}
1192  	return nil
1193  }
1194  
1195  func (fb *funcBuilder) builtinValue(name string) Value {
1196  	id, ok := builtinID(name)
1197  	if !ok {
1198  		return nil
1199  	}
1200  	return &Builtin{id: id, name: name}
1201  }
1202  
1203  func (fb *funcBuilder) buildLit(e *syntax.BasicLit) Value {
1204  	switch e.Kind {
1205  	case syntax.IntLit:
1206  		n, _ := constant.Val(constant.MakeFromLiteral(e.Value, token.INT, 0)).(int64)
1207  		return &Const{typ: typecheck.Typ[typecheck.UntypedInt], val: constant.MakeInt64(n)}
1208  	case syntax.FloatLit:
1209  		return &Const{
1210  			typ: typecheck.Typ[typecheck.UntypedFloat],
1211  			val: constant.MakeFromLiteral(e.Value, token.FLOAT, 0),
1212  		}
1213  	case syntax.StringLit:
1214  		s := e.Value
1215  		if len(s) >= 2 {
1216  			s = s[1 : len(s)-1] // strip quotes (simplified)
1217  		}
1218  		return &Const{typ: typecheck.Typ[typecheck.UntypedString], val: constant.MakeString(s)}
1219  	case syntax.RuneLit:
1220  		return &Const{
1221  			typ: typecheck.Typ[typecheck.UntypedRune],
1222  			val: constant.MakeFromLiteral(e.Value, token.CHAR, 0),
1223  		}
1224  	}
1225  	return nil
1226  }
1227  
1228  func (fb *funcBuilder) buildOperation(e *syntax.Operation) Value {
1229  	if e.Y == nil {
1230  		// unary
1231  		x := fb.buildExpr(e.X)
1232  		if x == nil {
1233  			return nil
1234  		}
1235  		op := syntaxOpToToken(e.Op, true)
1236  		if op == token.ARROW {
1237  			// receive
1238  			u := &UnOp{Op: token.ARROW, X: x}
1239  			u.typ = chanElemType(x.Type())
1240  			u.name = fb.nextName()
1241  			fb.emit(u)
1242  			return u
1243  		}
1244  		if op == token.AND {
1245  			// address-of
1246  			if name, ok := e.X.(*syntax.Name); ok {
1247  				obj := fb.lookupObject(name.Value)
1248  				if obj != nil {
1249  					if alloc, ok2 := fb.vars[obj]; ok2 {
1250  						return alloc
1251  					}
1252  					if g, ok2 := fb.fn.Pkg.Members[name.Value].(*Global); ok2 {
1253  						return g
1254  					}
1255  				}
1256  			}
1257  			// heap alloc
1258  			inner := x
1259  			a := &Alloc{Heap: true}
1260  			a.typ = typecheck.NewPointer(inner.Type())
1261  			a.name = fb.nextName()
1262  			fb.emit(a)
1263  			fb.emitStore(a, inner)
1264  			return a
1265  		}
1266  		u := &UnOp{Op: op, X: x}
1267  		u.typ = x.Type()
1268  		u.name = fb.nextName()
1269  		fb.emit(u)
1270  		return u
1271  	}
1272  	// binary
1273  	x := fb.buildExpr(e.X)
1274  	y := fb.buildExpr(e.Y)
1275  	if x == nil || y == nil {
1276  		return nil
1277  	}
1278  	op := syntaxOpToToken(e.Op, false)
1279  	b := &BinOp{Op: op, X: x, Y: y}
1280  	b.name = fb.nextName()
1281  	switch op {
1282  	case token.EQL, token.NEQ, token.LSS, token.LEQ, token.GTR, token.GEQ,
1283  		token.LAND, token.LOR:
1284  		b.typ = typecheck.Typ[typecheck.Bool]
1285  	default:
1286  		if x.Type() != nil {
1287  			b.typ = x.Type()
1288  		} else if y.Type() != nil {
1289  			b.typ = y.Type()
1290  		}
1291  	}
1292  	fb.emit(b)
1293  	return b
1294  }
1295  
1296  func (fb *funcBuilder) buildCall(e *syntax.CallExpr) Value {
1297  	fn := fb.buildExpr(e.Fun)
1298  	args := fb.buildArgs(e.ArgList)
1299  
1300  	if fn == nil {
1301  		return nil
1302  	}
1303  	if _, ok := fn.(*Builtin); ok {
1304  		return fb.buildBuiltinCall(e, fn.(*Builtin), args)
1305  	}
1306  
1307  	var retType typecheck.Type
1308  	if fn.Type() != nil {
1309  		if sig, ok := fn.Type().Underlying().(*typecheck.Signature); ok {
1310  			if sig.Results() != nil && sig.Results().Len() == 1 {
1311  				retType = sig.Results().At(0).Type()
1312  			} else if sig.Results() != nil && sig.Results().Len() > 1 {
1313  				retType = sig.Results()
1314  			}
1315  		}
1316  	}
1317  
1318  	call := &Call{Call: CallCommon{Value: fn, Args: args}}
1319  	call.typ = retType
1320  	call.name = fb.nextName()
1321  	fb.emit(call)
1322  
1323  	if retType == nil {
1324  		return nil // void call
1325  	}
1326  	return call
1327  }
1328  
1329  func (fb *funcBuilder) buildArgs(argList []syntax.Expr) []Value {
1330  	args := make([]Value, 0, len(argList))
1331  	for _, a := range argList {
1332  		v := fb.buildExpr(a)
1333  		if v != nil {
1334  			args = append(args, v)
1335  		}
1336  	}
1337  	return args
1338  }
1339  
1340  func (fb *funcBuilder) buildBuiltinCall(e *syntax.CallExpr, b *Builtin, args []Value) Value {
1341  	switch b.id {
1342  	case BuiltinLen, BuiltinCap:
1343  		call := &Call{Call: CallCommon{Value: b, Args: args}}
1344  		call.typ = typecheck.Typ[typecheck.Int32]
1345  		call.name = fb.nextName()
1346  		fb.emit(call)
1347  		return call
1348  	case BuiltinAppend:
1349  		call := &Call{Call: CallCommon{Value: b, Args: args}}
1350  		if len(args) > 0 {
1351  			call.typ = args[0].Type()
1352  		}
1353  		call.name = fb.nextName()
1354  		fb.emit(call)
1355  		return call
1356  	case BuiltinMake:
1357  		if len(e.ArgList) == 0 {
1358  			return nil
1359  		}
1360  		typ := fb.resolveType(e.ArgList[0])
1361  		// args already has type expr filtered (buildExpr returns nil for types),
1362  		// so args == the size arguments.
1363  		return fb.emitMake(typ, args)
1364  	case BuiltinNew:
1365  		if len(e.ArgList) == 0 {
1366  			return nil
1367  		}
1368  		typ := fb.resolveType(e.ArgList[0])
1369  		a := &Alloc{Heap: true}
1370  		a.typ = typecheck.NewPointer(typ)
1371  		a.name = fb.nextName()
1372  		fb.emit(a)
1373  		return a
1374  	case BuiltinPanic:
1375  		if len(args) > 0 {
1376  			fb.emit(&Panic{X: args[0]})
1377  			fb.currentBlock = nil
1378  		}
1379  		return nil
1380  	case BuiltinClose, BuiltinDelete, BuiltinClear, BuiltinPrint, BuiltinPrintln:
1381  		call := &Call{Call: CallCommon{Value: b, Args: args}}
1382  		call.name = fb.nextName()
1383  		fb.emit(call)
1384  		return nil
1385  	case BuiltinCopy:
1386  		call := &Call{Call: CallCommon{Value: b, Args: args}}
1387  		call.typ = typecheck.Typ[typecheck.Int32]
1388  		call.name = fb.nextName()
1389  		fb.emit(call)
1390  		return call
1391  	case BuiltinRecover:
1392  		call := &Call{Call: CallCommon{Value: b, Args: args}}
1393  		call.typ = typecheck.Typ[typecheck.String] // any
1394  		call.name = fb.nextName()
1395  		fb.emit(call)
1396  		return call
1397  	case BuiltinPush, BuiltinResize:
1398  		// push(s, v...) and resize(s, n) return modified slice for store-back.
1399  		// The ExprStmt handler desugars push(s,v) to s = push(s,v).
1400  		call := &Call{Call: CallCommon{Value: b, Args: args}}
1401  		if len(args) > 0 {
1402  			call.typ = args[0].Type()
1403  		}
1404  		call.name = fb.nextName()
1405  		fb.emit(call)
1406  		return call
1407  	case BuiltinPop:
1408  		// pop(s) returns the element. The modified slice store-back is
1409  		// handled by the ExprStmt desugaring (which calls resize(s, len-1)
1410  		// semantics via the store-back path).
1411  		call := &Call{Call: CallCommon{Value: b, Args: args}}
1412  		if len(args) > 0 {
1413  			st := args[0].Type()
1414  			if sl, ok := st.Underlying().(*typecheck.Slice); ok {
1415  				call.typ = sl.Elem()
1416  			} else {
1417  				call.typ = typecheck.Typ[typecheck.Uint8]
1418  			}
1419  		}
1420  		call.name = fb.nextName()
1421  		fb.emit(call)
1422  		return call
1423  	}
1424  	call := &Call{Call: CallCommon{Value: b, Args: args}}
1425  	call.name = fb.nextName()
1426  	fb.emit(call)
1427  	return call
1428  }
1429  
1430  func (fb *funcBuilder) emitMake(typ typecheck.Type, sizeArgs []Value) Value {
1431  	if typ == nil {
1432  		return nil
1433  	}
1434  	switch typ.Underlying().(type) {
1435  	case *typecheck.Slice:
1436  		ms := &MakeSlice{}
1437  		ms.typ = typ
1438  		ms.name = fb.nextName()
1439  		if len(sizeArgs) > 0 {
1440  			ms.Len = sizeArgs[0]
1441  		}
1442  		if len(sizeArgs) > 1 {
1443  			ms.Cap = sizeArgs[1]
1444  		}
1445  		fb.emit(ms)
1446  		return ms
1447  	case *typecheck.Map:
1448  		mm := &MakeMap{}
1449  		mm.typ = typ
1450  		mm.name = fb.nextName()
1451  		if len(sizeArgs) > 0 {
1452  			mm.Reserve = sizeArgs[0]
1453  		}
1454  		fb.emit(mm)
1455  		return mm
1456  	case *typecheck.Chan:
1457  		mc := &MakeChan{}
1458  		mc.typ = typ
1459  		mc.name = fb.nextName()
1460  		if len(sizeArgs) > 0 {
1461  			mc.Size = sizeArgs[0]
1462  		}
1463  		fb.emit(mc)
1464  		return mc
1465  	}
1466  	return nil
1467  }
1468  
1469  func (fb *funcBuilder) buildSelector(e *syntax.SelectorExpr) Value {
1470  	x := fb.buildExpr(e.X)
1471  	if x == nil || x.Type() == nil {
1472  		// Package-qualified name.
1473  		if name, ok := e.X.(*syntax.Name); ok {
1474  			obj := fb.lookupObject(name.Value)
1475  			if _, ok := obj.(*typecheck.PkgName); ok {
1476  				// imported name — look up in that package's members
1477  				// (simplified: return nil for now; proper resolution in B3)
1478  				return nil
1479  			}
1480  		}
1481  		return nil
1482  	}
1483  	// field or method
1484  	fieldIdx := fb.fieldIndex(x.Type(), e.Sel.Value)
1485  	if fieldIdx >= 0 {
1486  		fa := &FieldAddr{X: x, Field: fieldIdx}
1487  		fa.typ = typecheck.NewPointer(fb.fieldType(x.Type(), fieldIdx))
1488  		fa.name = fb.nextName()
1489  		fb.emit(fa)
1490  		return fb.emitLoad(fa, fb.fieldType(x.Type(), fieldIdx))
1491  	}
1492  	// method value
1493  	return nil
1494  }
1495  
1496  func (fb *funcBuilder) buildIndex(e *syntax.IndexExpr) Value {
1497  	x := fb.buildExpr(e.X)
1498  	idx := fb.buildExpr(e.Index)
1499  	if x == nil || idx == nil {
1500  		return nil
1501  	}
1502  	if x.Type() == nil {
1503  		return nil
1504  	}
1505  	switch t := x.Type().Underlying().(type) {
1506  	case *typecheck.Map:
1507  		l := &Lookup{X: x, Index: idx}
1508  		l.typ = t.Elem()
1509  		l.name = fb.nextName()
1510  		fb.emit(l)
1511  		return l
1512  	default:
1513  		ia := &IndexAddr{X: x, Index: idx}
1514  		ia.typ = typecheck.NewPointer(elemType(x.Type()))
1515  		ia.name = fb.nextName()
1516  		fb.emit(ia)
1517  		return fb.emitLoad(ia, elemType(x.Type()))
1518  	}
1519  }
1520  
1521  func (fb *funcBuilder) buildSlice(e *syntax.SliceExpr) Value {
1522  	x := fb.buildExpr(e.X)
1523  	if x == nil {
1524  		return nil
1525  	}
1526  	sl := &Slice{X: x}
1527  	if len(e.Index) > 0 && e.Index[0] != nil {
1528  		sl.Low = fb.buildExpr(e.Index[0])
1529  	}
1530  	if len(e.Index) > 1 && e.Index[1] != nil {
1531  		sl.High = fb.buildExpr(e.Index[1])
1532  	}
1533  	if len(e.Index) > 2 && e.Index[2] != nil {
1534  		sl.Max = fb.buildExpr(e.Index[2])
1535  	}
1536  	sl.typ = sliceOf(x.Type())
1537  	sl.name = fb.nextName()
1538  	fb.emit(sl)
1539  	return sl
1540  }
1541  
1542  func (fb *funcBuilder) buildAssert(e *syntax.AssertExpr) Value {
1543  	x := fb.buildExpr(e.X)
1544  	assertedType := fb.resolveType(e.Type)
1545  	if x == nil {
1546  		return nil
1547  	}
1548  	ta := &TypeAssert{X: x, AssertedType: assertedType, CommaOk: false}
1549  	ta.typ = assertedType
1550  	ta.name = fb.nextName()
1551  	fb.emit(ta)
1552  	return ta
1553  }
1554  
1555  func (fb *funcBuilder) buildCompositeLit(e *syntax.CompositeLit) Value {
1556  	var typ typecheck.Type
1557  	if e.Type != nil {
1558  		typ = fb.resolveType(e.Type)
1559  	}
1560  	if typ == nil {
1561  		return nil
1562  	}
1563  	alloc := fb.emitAlloc(typ, posFromSyntax(e.Pos()))
1564  	for _, el := range e.ElemList {
1565  		if kv, ok := el.(*syntax.KeyValueExpr); ok {
1566  			// struct field or map key
1567  			if _, ok2 := typ.Underlying().(*typecheck.Struct); ok2 {
1568  				idx := fb.fieldIndex(typ, kv.Key.(*syntax.Name).Value)
1569  				if idx >= 0 {
1570  					fa := &FieldAddr{X: alloc, Field: idx}
1571  					fa.typ = typecheck.NewPointer(fb.fieldType(typ, idx))
1572  					fa.name = fb.nextName()
1573  					fb.emit(fa)
1574  					v := fb.buildExpr(kv.Value)
1575  					if v != nil {
1576  						fb.emitStore(fa, v)
1577  					}
1578  				}
1579  			} else if _, ok2 := typ.Underlying().(*typecheck.Map); ok2 {
1580  				k := fb.buildExpr(kv.Key)
1581  				v := fb.buildExpr(kv.Value)
1582  				if k != nil && v != nil {
1583  					fb.emit(&MapUpdate{Map: alloc, Key: k, Value: v})
1584  				}
1585  			}
1586  		} else {
1587  			v := fb.buildExpr(el)
1588  			_ = v
1589  		}
1590  	}
1591  	return fb.emitLoad(alloc, typ)
1592  }
1593  
1594  func (fb *funcBuilder) buildFuncLit(e *syntax.FuncLit) Value {
1595  	// Create an anonymous function member.
1596  	var sig *typecheck.Signature
1597  	if fb.info != nil {
1598  		if tv, ok := fb.info.Types[e]; ok {
1599  			sig, _ = tv.Type.(*typecheck.Signature)
1600  		}
1601  	}
1602  	name := fb.fn.name + "$" + itoa(len(fb.fn.AnonFuncs)+1)
1603  	anon := &Function{
1604  		name:      name,
1605  		Signature: sig,
1606  		pos:       posFromSyntax(e.Pos()),
1607  		Pkg:       fb.fn.Pkg,
1608  		Prog:      fb.fn.Prog,
1609  		parent:    fb.fn,
1610  	}
1611  	fb.fn.AnonFuncs = append(fb.fn.AnonFuncs, anon)
1612  
1613  	// Build anon's body.
1614  	ab := newFuncBuilder(anon, fb.info)
1615  	d := &syntax.FuncDecl{
1616  		Name: &syntax.Name{Value: name},
1617  		Type: e.Type,
1618  		Body: e.Body,
1619  	}
1620  	ab.buildBody(d)
1621  
1622  	// Create closure if there are free variables.
1623  	if len(anon.FreeVars) == 0 {
1624  		return anon
1625  	}
1626  	bindings := make([]Value, len(anon.FreeVars))
1627  	for i, fv := range anon.FreeVars {
1628  		obj := fb.lookupObject(fv.name)
1629  		if obj != nil {
1630  			if alloc, ok := fb.vars[obj]; ok {
1631  				alloc.Heap = true
1632  				bindings[i] = alloc
1633  			}
1634  		}
1635  	}
1636  	mc := &MakeClosure{Fn: anon, Bindings: bindings}
1637  	mc.typ = sig
1638  	mc.name = fb.nextName()
1639  	fb.emit(mc)
1640  	return mc
1641  }
1642  
1643  // ─── Helpers ──────────────────────────────────────────────────────────────────
1644  
1645  func (fb *funcBuilder) lookupObject(name string) typecheck.Object {
1646  	// Look in local vars first.
1647  	for obj := range fb.vars {
1648  		if obj.Name() == name {
1649  			return obj
1650  		}
1651  	}
1652  	// Package scope.
1653  	if fb.fn.Pkg != nil {
1654  		if _, obj := fb.fn.Pkg.Pkg.Scope().LookupParent(name); obj != nil {
1655  			return obj
1656  		}
1657  	}
1658  	return nil
1659  }
1660  
1661  func (fb *funcBuilder) lookupVar(name string) *typecheck.Var {
1662  	obj := fb.lookupObject(name)
1663  	v, _ := obj.(*typecheck.Var)
1664  	return v
1665  }
1666  
1667  func (fb *funcBuilder) lookupVarByType(name string, typ typecheck.Type) typecheck.Object {
1668  	// For parameter binding: find the Var in info.Defs or synthesize one.
1669  	return typecheck.NewVar(fb.fn.Pkg.Pkg, name, typ)
1670  }
1671  
1672  func (fb *funcBuilder) resolveType(e syntax.Expr) typecheck.Type {
1673  	if fb.info != nil {
1674  		if tv, ok := fb.info.Types[e]; ok && tv.Type != nil {
1675  			return tv.Type
1676  		}
1677  	}
1678  	return nil
1679  }
1680  
1681  func (fb *funcBuilder) fieldIndex(t typecheck.Type, name string) int {
1682  	if t == nil {
1683  		return -1
1684  	}
1685  	// dereference pointer
1686  	if pt, ok := t.Underlying().(*typecheck.Pointer); ok {
1687  		t = pt.Elem()
1688  	}
1689  	if st, ok := t.Underlying().(*typecheck.Struct); ok {
1690  		for i := 0; i < st.NumFields(); i++ {
1691  			if st.Field(i).Name() == name {
1692  				return i
1693  			}
1694  		}
1695  	}
1696  	return -1
1697  }
1698  
1699  func (fb *funcBuilder) fieldType(t typecheck.Type, idx int) typecheck.Type {
1700  	if t == nil {
1701  		return nil
1702  	}
1703  	if pt, ok := t.Underlying().(*typecheck.Pointer); ok {
1704  		t = pt.Elem()
1705  	}
1706  	if st, ok := t.Underlying().(*typecheck.Struct); ok {
1707  		if idx >= 0 && idx < st.NumFields() {
1708  			return st.Field(idx).Type()
1709  		}
1710  	}
1711  	return nil
1712  }
1713  
1714  // ─── Type helpers ─────────────────────────────────────────────────────────────
1715  
1716  func elemType(t typecheck.Type) typecheck.Type {
1717  	if t == nil {
1718  		return nil
1719  	}
1720  	switch t := t.Underlying().(type) {
1721  	case *typecheck.Slice:
1722  		return t.Elem()
1723  	case *typecheck.Array:
1724  		return t.Elem()
1725  	case *typecheck.Map:
1726  		return t.Elem()
1727  	case *typecheck.Pointer:
1728  		return t.Elem()
1729  	}
1730  	return nil
1731  }
1732  
1733  func chanElemType(t typecheck.Type) typecheck.Type {
1734  	if t == nil {
1735  		return nil
1736  	}
1737  	if ch, ok := t.Underlying().(*typecheck.Chan); ok {
1738  		return ch.Elem()
1739  	}
1740  	return nil
1741  }
1742  
1743  func isStringType(t typecheck.Type) bool {
1744  	if t == nil {
1745  		return false
1746  	}
1747  	if b, ok := t.Underlying().(*typecheck.Basic); ok {
1748  		return b.Info()&typecheck.IsString != 0
1749  	}
1750  	return false
1751  }
1752  
1753  func sliceOf(t typecheck.Type) typecheck.Type {
1754  	if t == nil {
1755  		return nil
1756  	}
1757  	switch t := t.Underlying().(type) {
1758  	case *typecheck.Slice:
1759  		return t
1760  	case *typecheck.Array:
1761  		return typecheck.NewSlice(t.Elem())
1762  	}
1763  	return typecheck.NewSlice(typecheck.Typ[typecheck.Uint8])
1764  }
1765  
1766  func tupleElemType(t typecheck.Type, i int) typecheck.Type {
1767  	if t == nil {
1768  		return nil
1769  	}
1770  	if tup, ok := t.(*typecheck.Tuple); ok && i < tup.Len() {
1771  		return tup.At(i).Type()
1772  	}
1773  	return nil
1774  }
1775  
1776  func extractNthType(t typecheck.Type, n int) typecheck.Type {
1777  	return tupleElemType(t, n)
1778  }
1779  
1780  func iterType(t typecheck.Type) typecheck.Type {
1781  	// Range iterator is an opaque type; approximate with a struct.
1782  	return typecheck.Typ[typecheck.Invalid]
1783  }
1784  
1785  func nextType(iterT typecheck.Type) typecheck.Type {
1786  	// next(iter) returns a tuple (bool, key, val) — approximate.
1787  	return typecheck.NewTuple(
1788  		typecheck.NewVar(nil, "ok", typecheck.Typ[typecheck.Bool]),
1789  		typecheck.NewVar(nil, "k", typecheck.Typ[typecheck.Int32]),
1790  		typecheck.NewVar(nil, "v", typecheck.Typ[typecheck.Invalid]),
1791  	)
1792  }
1793  
1794  func selectResultType(states []*SelectState) typecheck.Type {
1795  	return typecheck.NewTuple(
1796  		typecheck.NewVar(nil, "index", typecheck.Typ[typecheck.Int32]),
1797  		typecheck.NewVar(nil, "recvOk", typecheck.Typ[typecheck.Bool]),
1798  	)
1799  }
1800  
1801  // ─── Operator mapping ─────────────────────────────────────────────────────────
1802  
1803  func syntaxOpToToken(op syntax.Operator, unary bool) token.Token {
1804  	if unary {
1805  		switch op {
1806  		case syntax.Not:
1807  			return token.NOT
1808  		case syntax.Recv:
1809  			return token.ARROW
1810  		case syntax.And:
1811  			return token.AND
1812  		case syntax.Mul:
1813  			return token.MUL
1814  		case syntax.Sub:
1815  			return token.SUB
1816  		case syntax.Xor:
1817  			return token.XOR
1818  		}
1819  	}
1820  	switch op {
1821  	case syntax.Add, syntax.Or:
1822  		return token.ADD // Moxie: | on strings is concat
1823  	case syntax.Sub:
1824  		return token.SUB
1825  	case syntax.Mul:
1826  		return token.MUL
1827  	case syntax.Div:
1828  		return token.QUO
1829  	case syntax.Rem:
1830  		return token.REM
1831  	case syntax.And:
1832  		return token.AND
1833  	case syntax.Xor:
1834  		return token.XOR
1835  	case syntax.Shl:
1836  		return token.SHL
1837  	case syntax.Shr:
1838  		return token.SHR
1839  	case syntax.AndNot:
1840  		return token.AND_NOT
1841  	case syntax.OrOr:
1842  		return token.LOR
1843  	case syntax.AndAnd:
1844  		return token.LAND
1845  	case syntax.Eql:
1846  		return token.EQL
1847  	case syntax.Neq:
1848  		return token.NEQ
1849  	case syntax.Lss:
1850  		return token.LSS
1851  	case syntax.Leq:
1852  		return token.LEQ
1853  	case syntax.Gtr:
1854  		return token.GTR
1855  	case syntax.Geq:
1856  		return token.GEQ
1857  	}
1858  	return token.ILLEGAL
1859  }
1860  
1861  // isSliceType reports whether t is a slice or string (string = []byte in Moxie).
1862  func isSliceType(t typecheck.Type) bool {
1863  	if t == nil {
1864  		return false
1865  	}
1866  	switch t.Underlying().(type) {
1867  	case *typecheck.Slice:
1868  		return true
1869  	case *typecheck.Basic:
1870  		b := t.Underlying().(*typecheck.Basic)
1871  		return b.Kind() == typecheck.String || b.Kind() == typecheck.UntypedString
1872  	}
1873  	return false
1874  }
1875  
1876  // ─── Builtin ID lookup ────────────────────────────────────────────────────────
1877  
1878  func builtinID(name string) (BuiltinID, bool) {
1879  	switch name {
1880  	case "append":
1881  		return BuiltinAppend, true
1882  	case "cap":
1883  		return BuiltinCap, true
1884  	case "clear":
1885  		return BuiltinClear, true
1886  	case "close":
1887  		return BuiltinClose, true
1888  	case "copy":
1889  		return BuiltinCopy, true
1890  	case "delete":
1891  		return BuiltinDelete, true
1892  	case "len":
1893  		return BuiltinLen, true
1894  	case "make":
1895  		return BuiltinMake, true
1896  	case "max":
1897  		return BuiltinMax, true
1898  	case "min":
1899  		return BuiltinMin, true
1900  	case "new":
1901  		return BuiltinNew, true
1902  	case "panic":
1903  		return BuiltinPanic, true
1904  	case "print":
1905  		return BuiltinPrint, true
1906  	case "println":
1907  		return BuiltinPrintln, true
1908  	case "recover":
1909  		return BuiltinRecover, true
1910  	case "push":
1911  		return BuiltinPush, true
1912  	case "pop":
1913  		return BuiltinPop, true
1914  	case "resize":
1915  		return BuiltinResize, true
1916  	}
1917  	return 0, false
1918  }
1919  
1920  // ─── Misc utilities ───────────────────────────────────────────────────────────
1921  
1922  func posFromSyntax(p syntax.Pos) token.Pos {
1923  	// syntax.Pos is a struct (base,line,col); full mapping via fset requires
1924  	// the adapter's file. For B2, positions are debug-only; return NoPos.
1925  	// B3 will thread fset through the builder for accurate positions.
1926  	return token.NoPos
1927  }
1928  
1929  func itoa(n int) string {
1930  	if n == 0 {
1931  		return "0"
1932  	}
1933  	buf := [20]byte{}
1934  	pos := len(buf)
1935  	for n > 0 {
1936  		pos--
1937  		buf[pos] = byte('0' + n%10)
1938  		n /= 10
1939  	}
1940  	return string(buf[pos:])
1941  }
1942  
1943  func exprNames(e syntax.Expr) []*syntax.Name {
1944  	if e == nil {
1945  		return nil
1946  	}
1947  	if n, ok := e.(*syntax.Name); ok {
1948  		return []*syntax.Name{n}
1949  	}
1950  	if l, ok := e.(*syntax.ListExpr); ok {
1951  		var names []*syntax.Name
1952  		for _, el := range l.ElemList {
1953  			if n, ok := el.(*syntax.Name); ok {
1954  				names = append(names, n)
1955  			}
1956  		}
1957  		return names
1958  	}
1959  	return nil
1960  }
1961