1 package compiler
2 3 // This file implements Moxie language restrictions.
4 // Moxie removes features that are incompatible with domain-isolated
5 // cooperative concurrency or that obstruct the programming model.
6 //
7 // Restrictions apply to user code and converted stdlib packages.
8 // Runtime/internal packages are permanently exempt.
9 10 import (
11 "go/ast"
12 "go/token"
13 "go/types"
14 "strings"
15 16 "moxie/loader"
17 18 "golang.org/x/tools/go/ssa"
19 )
20 21 // restrictedImports maps package paths that should not be used in Moxie code.
22 var restrictedImports = map[string]string{
23 "strings": `use "bytes" instead of "strings" (string=[]byte)`,
24 }
25 26 // restrictedBuiltins maps Go builtins to the Moxie alternative.
27 // The loader rewrites Moxie literal syntax to (make)(...) — parenthesized
28 // form that the AST check skips. User-written make(...) has a bare Ident
29 // which this check catches.
30 var restrictedBuiltins = map[string]string{
31 "make": "use literal syntax: []T{:n}, chan T{}, chan T{n}",
32 "new": "use composite literal with & or var declaration",
33 "complex": "complex numbers not supported in moxie",
34 "real": "complex numbers not supported in moxie",
35 "imag": "complex numbers not supported in moxie",
36 "append": "use push(s, v...) instead: no allocation, panics on capacity overflow",
37 }
38 39 // restrictedTypes maps type names to rejection reasons.
40 // Note: string is NOT restricted — with string=[]byte type unification,
41 // the types are interchangeable. The + operator restriction on strings
42 // still enforces Moxie's | concatenation syntax.
43 var restrictedTypes = map[string]string{
44 "int": "use explicit width: int8, int16, int32, int64",
45 "uint": "use explicit width: uint8, uint16, uint32, uint64",
46 "complex64": "complex numbers not supported in moxie",
47 "complex128": "complex numbers not supported in moxie",
48 "uintptr": "use explicit pointer types",
49 }
50 51 // isUserPackage returns true if a package should be subject to Moxie
52 // language restrictions. Stdlib and stdlib-vendored packages are exempt.
53 // User packages (identified by dots in the import path from the module
54 // path) get full restrictions. "internal/" has no special semantics.
55 func isUserPackage(pkg *ssa.Package) bool {
56 if pkg == nil {
57 return false
58 }
59 path := pkg.Pkg.Path()
60 if path == "main" || path == "command-line-arguments" {
61 return true
62 }
63 if strings.HasPrefix(path, "golang.org/x/") {
64 return false
65 }
66 // Compiler infrastructure packages (pkg/types, pkg/syntax, pkg/mxutil, etc.)
67 // are exempt from user restrictions.
68 if strings.Contains(path, "/pkg/") {
69 return false
70 }
71 return strings.Contains(path, ".")
72 }
73 74 // packageImportsUnsafe returns true if the package imports "unsafe".
75 // Packages using unsafe.Pointer legitimately need uintptr for pointer arithmetic.
76 func packageImportsUnsafe(pkg *ssa.Package) bool {
77 for _, imp := range pkg.Pkg.Imports() {
78 if imp.Path() == "unsafe" {
79 return true
80 }
81 }
82 return false
83 }
84 85 // isInitFunc returns true if fn is an init function (package init, synthetic
86 // init, or the init$N variants created by the Go SSA builder).
87 func isInitFunc(fn *ssa.Function) bool {
88 name := fn.Name()
89 if name == "init" {
90 return true
91 }
92 // Init-like functions: init$guard, init$N, initFoo, etc.
93 // Functions prefixed with "init" are treated as initialization
94 // code that may set up package globals.
95 if len(name) > 4 && name[:4] == "init" {
96 return true
97 }
98 // Package-level var initializer (synthetic).
99 if fn.Synthetic != "" && fn.Name() == "init" {
100 return true
101 }
102 return false
103 }
104 105 // isGlobalStore returns true if addr is a direct global variable.
106 // Only flags `global = value`, not `global.field = value` or
107 // `(*global).field = value`. Field mutations through a global pointer
108 // are fine - the pointer itself is immutable after init(), and the
109 // pointed-to struct is heap-local state.
110 func isGlobalStore(addr ssa.Value) bool {
111 _, isGlobal := addr.(*ssa.Global)
112 return isGlobal
113 }
114 115 // globalName extracts the global variable name from an address, if any.
116 func globalName(addr ssa.Value) string {
117 switch v := addr.(type) {
118 case *ssa.Global:
119 return v.Name()
120 case *ssa.FieldAddr:
121 return globalName(v.X)
122 case *ssa.IndexAddr:
123 return globalName(v.X)
124 case *ssa.UnOp:
125 if v.Op == token.MUL {
126 return globalName(v.X)
127 }
128 }
129 return ""
130 }
131 132 // checkMoxieRestrictions scans a function for uses of restricted features.
133 // Only applies to user code — runtime packages are exempt.
134 func (b *builder) checkMoxieRestrictions() {
135 if !isUserPackage(b.fn.Pkg) {
136 return
137 }
138 // Check for restricted imports (only on the init function to avoid per-function spam).
139 if b.fn.Name() == "init" || (b.fn.Name() == "main" && b.fn.Pkg.Pkg.Name() == "main") {
140 for _, imp := range b.fn.Pkg.Pkg.Imports() {
141 if reason, restricted := restrictedImports[imp.Path()]; restricted {
142 b.addError(b.fn.Pos(), "moxie: import \""+imp.Path()+"\" is not allowed: "+reason)
143 }
144 }
145 }
146 // Ban functions named after restricted builtins.
147 if _, restricted := restrictedBuiltins[b.fn.Name()]; restricted {
148 b.addError(b.fn.Pos(), "moxie: function name '"+b.fn.Name()+"' shadows a restricted builtin")
149 }
150 for _, block := range b.fn.Blocks {
151 for _, instr := range block.Instrs {
152 b.checkInstrRestrictions(instr)
153 }
154 }
155 // Check function signature for restricted types.
156 b.checkSignatureRestrictions(b.fn)
157 // Check for fallthrough (lowered away by SSA, must check AST).
158 b.checkFallthroughRestriction()
159 // Check for restricted types and builtins in AST (SSA may optimize them away).
160 b.checkASTRestrictions()
161 // Check that non-constant spawn args aren't used after the spawn call.
162 b.checkSpawnMoveRestriction()
163 // Check that every channel has both a sender and a listener.
164 b.checkChannelCompleteness()
165 }
166 167 func (b *builder) checkInstrRestrictions(instr ssa.Instruction) {
168 switch v := instr.(type) {
169 case *ssa.Call:
170 if builtin, ok := v.Call.Value.(*ssa.Builtin); ok {
171 if reason, restricted := restrictedBuiltins[builtin.Name()]; restricted {
172 b.addError(v.Pos(), "moxie: '"+builtin.Name()+"' is not allowed: "+reason)
173 }
174 }
175 176 // MakeChan, MakeMap, MakeSlice are NOT checked at SSA level — the loader
177 // rewrites Moxie literal syntax to (make)() calls. User-written make() is
178 // caught at AST level (restrictedBuiltins) where the bare Ident is visible.
179 180 case *ssa.BinOp:
181 // Reject + on non-numeric types (strings use | for concatenation).
182 if v.Op == token.ADD {
183 if basic, ok := v.X.Type().Underlying().(*types.Basic); ok {
184 if basic.Info()&types.IsString != 0 {
185 b.addError(v.Pos(), "moxie: '+' is not allowed for text concatenation: use | operator")
186 }
187 }
188 }
189 190 case *ssa.Store:
191 // Package globals are immutable after init(). Flag any Store
192 // to a global (or into a global's fields) outside init functions.
193 // Currently a warning (printed to stderr) while stage4 is being
194 // refactored. Will become a compile error once all mutable
195 // globals are moved to function-local state.
196 if isUserPackage(b.fn.Pkg) && !isInitFunc(b.fn) {
197 if isGlobalStore(v.Addr) {
198 msg := "moxie: assignment to package-level var outside init() is not allowed"
199 if g := globalName(v.Addr); g != "" {
200 msg = "moxie: assignment to package-level var '" + g + "' outside init() is not allowed"
201 }
202 b.addError(v.Pos(), msg)
203 }
204 }
205 206 case *ssa.MakeClosure:
207 // Moxie closures cannot capture variables from enclosing scopes.
208 // All inputs must be explicit parameters. Package-level globals
209 // are accessed directly (not via FreeVars), so they're fine.
210 if len(v.Bindings) > 0 {
211 fn := v.Fn.(*ssa.Function)
212 var names []string
213 for _, fv := range fn.FreeVars {
214 names = append(names, fv.Name())
215 }
216 b.addError(v.Pos(), "moxie: closure captures outer variable(s) ["+strings.Join(names, ", ")+"]; pass as explicit parameter(s)")
217 }
218 219 // Note: new(T) and make() are caught by AST check (restrictedBuiltins),
220 // not SSA. Restricted types (uintptr, complex) are also AST-checked.
221 }
222 }
223 224 func (b *builder) checkTypeAtPos(t types.Type, pos token.Pos) {
225 if t == nil {
226 return
227 }
228 // Unwrap pointer types — SSA wraps local var types in pointers.
229 if ptr, ok := t.(*types.Pointer); ok {
230 t = ptr.Elem()
231 }
232 basic, ok := t.(*types.Basic)
233 if !ok {
234 return
235 }
236 if reason, restricted := restrictedTypes[basic.Name()]; restricted {
237 // uintptr is allowed in packages that import unsafe — needed for
238 // pointer arithmetic with unsafe.Pointer.
239 if basic.Name() == "uintptr" && packageImportsUnsafe(b.fn.Pkg) {
240 return
241 }
242 b.addError(pos, "moxie: type '"+basic.Name()+"' is not allowed: "+reason)
243 }
244 }
245 246 func (b *builder) checkSignatureRestrictions(fn *ssa.Function) {
247 sig := fn.Signature
248 for i := 0; i < sig.Params().Len(); i++ {
249 b.checkTypeAtPos(sig.Params().At(i).Type(), fn.Pos())
250 }
251 if sig.Results() != nil {
252 for i := 0; i < sig.Results().Len(); i++ {
253 b.checkTypeAtPos(sig.Results().At(i).Type(), fn.Pos())
254 }
255 }
256 }
257 258 // checkFallthroughRestriction walks the function's AST looking for
259 // fallthrough statements. SSA eliminates these, so we must check the
260 // raw syntax tree.
261 func (b *builder) checkFallthroughRestriction() {
262 syntax := b.fn.Syntax()
263 if syntax == nil {
264 return
265 }
266 ast.Inspect(syntax, func(n ast.Node) bool {
267 if br, ok := n.(*ast.BranchStmt); ok && br.Tok == token.FALLTHROUGH {
268 b.addError(br.Pos(), "moxie: 'fallthrough' is not allowed: each case must be self-contained")
269 return false
270 }
271 return true
272 })
273 }
274 275 // checkASTRestrictions walks the function's AST to catch restricted types
276 // and builtins that SSA may optimize away (dead code elimination removes
277 // unused complex64 vars, unused complex() calls, etc).
278 func (b *builder) checkASTRestrictions() {
279 syntax := b.fn.Syntax()
280 if syntax == nil {
281 return
282 }
283 ast.Inspect(syntax, func(n ast.Node) bool {
284 switch node := n.(type) {
285 case *ast.Ident:
286 if reason, restricted := restrictedTypes[node.Name]; restricted {
287 if node.Name == "uintptr" && packageImportsUnsafe(b.fn.Pkg) {
288 return true
289 }
290 b.addError(node.Pos(), "moxie: type '"+node.Name+"' is not allowed: "+reason)
291 }
292 // Step 4: reject bare "any" keyword (alias for interface{}).
293 if node.Name == "any" {
294 b.addError(node.Pos(), "moxie: 'any' (empty interface) is not allowed: use a concrete type or named interface")
295 }
296 case *ast.GoStmt:
297 // Step 3: reject go keyword at AST level (redundant with SSA check, fires earlier).
298 b.addError(node.Pos(), "moxie: 'go' keyword is not allowed: use channels+select or spawn")
299 case *ast.InterfaceType:
300 // Step 4: reject empty interface literals.
301 if node.Methods == nil || len(node.Methods.List) == 0 {
302 b.addError(node.Pos(), "moxie: empty interface (interface{}) is not allowed: use a concrete type or named interface")
303 }
304 case *ast.CallExpr:
305 // Ban parenthesized call targets: (f)(args) is not valid Moxie.
306 // Exempt: type conversions like (*byte)(expr) where the paren wraps a type.
307 if paren, ok := node.Fun.(*ast.ParenExpr); ok {
308 if !isTypeExpr(paren.X) {
309 b.addError(paren.Pos(), "moxie: parenthesized call target is not allowed: write f(x), not (f)(x)")
310 }
311 }
312 if ident, ok := node.Fun.(*ast.Ident); ok {
313 if reason, restricted := restrictedBuiltins[ident.Name]; restricted {
314 if ident.Name == "make" {
315 if b.isMakeExempt(ident.Pos()) {
316 return true
317 }
318 }
319 b.addError(ident.Pos(), "moxie: '"+ident.Name+"' is not allowed: "+reason)
320 }
321 }
322 case *ast.FuncDecl:
323 // Exempt compiler-generated builtins (__moxie_*).
324 if node.Name != nil && strings.HasPrefix(node.Name.Name, "__moxie_") {
325 return true
326 }
327 // Step 6: reject value receivers (must use pointer receiver).
328 if node.Recv != nil && len(node.Recv.List) > 0 {
329 recvType := node.Recv.List[0].Type
330 if _, isStar := recvType.(*ast.StarExpr); !isStar {
331 b.addError(node.Recv.Pos(), "moxie: value receiver is not allowed: use pointer receiver (*T)")
332 }
333 }
334 // Step 7: reject unnamed return values.
335 if node.Type.Results != nil {
336 for _, field := range node.Type.Results.List {
337 if len(field.Names) == 0 {
338 b.addError(field.Pos(), "moxie: unnamed return values are not allowed: name all return values")
339 }
340 }
341 }
342 }
343 return true
344 })
345 }
346 347 // isTypeExpr returns true if the AST expression is syntactically a type
348 // (pointer type, selector, array type, etc.) rather than a value expression.
349 func isTypeExpr(expr ast.Expr) bool {
350 switch expr.(type) {
351 case *ast.StarExpr:
352 return true
353 case *ast.ArrayType:
354 return true
355 case *ast.MapType:
356 return true
357 case *ast.ChanType:
358 return true
359 case *ast.FuncType:
360 return true
361 case *ast.StructType:
362 return true
363 case *ast.InterfaceType:
364 return true
365 }
366 return false
367 }
368 369 // isMakeExempt returns true if a make() call at the given position was
370 // generated by the loader's literal-syntax rewriter. Also exempts .go
371 // files (stdlib) and stdlib .mx overlays.
372 func (b *builder) isMakeExempt(pos token.Pos) bool {
373 position := b.program.Fset.Position(pos)
374 if !strings.HasSuffix(position.Filename, ".mx") {
375 return true
376 }
377 path := b.fn.Pkg.Pkg.Path()
378 if path == "main" || path == "command-line-arguments" {
379 return b.isLoaderGeneratedMake(position)
380 }
381 if !strings.Contains(path, ".") {
382 return true
383 }
384 if strings.HasPrefix(path, "golang.org/x/") {
385 return true
386 }
387 return b.isLoaderGeneratedMake(position)
388 }
389 390 func (b *builder) isLoaderGeneratedMake(position token.Position) bool {
391 offsets := b.makeExemptOffsets[position.Filename]
392 for _, off := range offsets {
393 if position.Offset == off {
394 return true
395 }
396 }
397 return false
398 }
399 400 // checkSpawnMoveRestriction enforces move semantics for spawn arguments.
401 // Non-constant values passed to spawn must not be used after the call —
402 // ownership moves to the child domain. No shared memory.
403 func (b *builder) checkSpawnMoveRestriction() {
404 for _, block := range b.fn.Blocks {
405 for spawnIdx, instr := range block.Instrs {
406 call, ok := instr.(*ssa.Call)
407 if !ok {
408 continue
409 }
410 // Detect spawn builtin calls.
411 isSpawn := false
412 var spawnArgs []ssa.Value
413 if builtin, ok := call.Call.Value.(*ssa.Builtin); ok && builtin.Name() == "spawn" {
414 // Builtin spawn: args are direct in call.Call.Args.
415 // Skip the function arg (index 0, or 1 if transport string).
416 args := call.Call.Args
417 start := 0
418 if len(args) > 0 {
419 if _, ok := args[0].(*ssa.Const); ok {
420 // Could be transport string — skip it + fn.
421 start = 2
422 } else {
423 // fn is args[0], data starts at 1.
424 start = 1
425 }
426 }
427 if start < len(args) {
428 spawnArgs = args[start:]
429 }
430 isSpawn = true
431 }
432 if !isSpawn {
433 continue
434 }
435 436 // Collect every block reachable from spawn's block via successor
437 // edges. A referrer in any such block is "after" spawn in the
438 // control-flow sense, even if it's in a different basic block
439 // (branch, loop body, later sequential block). Same-block refs
440 // are handled separately by instruction-index comparison.
441 reachableAfter := make(map[*ssa.BasicBlock]bool)
442 var work []*ssa.BasicBlock
443 for _, succ := range block.Succs {
444 if !reachableAfter[succ] {
445 reachableAfter[succ] = true
446 work = append(work, succ)
447 }
448 }
449 for wi := 0; wi < len(work); wi++ {
450 bb := work[wi]
451 for _, succ := range bb.Succs {
452 if !reachableAfter[succ] {
453 reachableAfter[succ] = true
454 work = append(work, succ)
455 }
456 }
457 }
458 work = work[:0]
459 460 for _, arg := range spawnArgs {
461 if _, isConst := arg.(*ssa.Const); isConst {
462 continue
463 }
464 // Channels are exempt — both parent and child hold
465 // endpoints for IPC communication.
466 if _, isChan := arg.Type().Underlying().(*types.Chan); isChan {
467 continue
468 }
469 refs := arg.Referrers()
470 if refs == nil {
471 continue
472 }
473 for _, ref := range *refs {
474 ri, ok := ref.(ssa.Instruction)
475 if !ok {
476 continue
477 }
478 refBlock := ri.Block()
479 if refBlock == block {
480 // Same block: referrer must come after spawn.
481 // If the block has a back-edge to itself
482 // (reachableAfter[block] == true), any referrer
483 // in this block is reachable-after on the next
484 // iteration — flag it regardless of instruction
485 // position.
486 if reachableAfter[block] {
487 b.addError(ri.Pos(), "moxie: variable used after spawn — ownership moved to child domain")
488 break
489 }
490 for j := spawnIdx + 1; j < len(block.Instrs); j++ {
491 if block.Instrs[j] == ri {
492 b.addError(ri.Pos(), "moxie: variable used after spawn — ownership moved to child domain")
493 break
494 }
495 }
496 } else if reachableAfter[refBlock] {
497 b.addError(ri.Pos(), "moxie: variable used after spawn — ownership moved to child domain")
498 break
499 }
500 }
501 }
502 }
503 }
504 }
505 506 // checkChannelCompleteness ensures every channel created in a function has
507 // both a sender and a listener (select case or receive). Channels that escape
508 // the function (passed to calls, stored, returned) are exempt — the other end
509 // is elsewhere.
510 func (b *builder) checkChannelCompleteness() {
511 for _, block := range b.fn.Blocks {
512 for _, instr := range block.Instrs {
513 mc, ok := instr.(*ssa.MakeChan)
514 if !ok {
515 continue
516 }
517 refs := mc.Referrers()
518 if refs == nil || len(*refs) == 0 {
519 b.addError(mc.Pos(), "moxie: channel created but never used")
520 continue
521 }
522 523 hasSend := false
524 hasRecv := false
525 escapes := false
526 527 for _, ref := range *refs {
528 switch r := ref.(type) {
529 case *ssa.Send:
530 if r.Chan == mc {
531 hasSend = true
532 } else {
533 escapes = true // channel sent as a value
534 }
535 case *ssa.Select:
536 found := false
537 for _, state := range r.States {
538 if state.Chan == mc {
539 found = true
540 if state.Dir == types.SendOnly {
541 hasSend = true
542 } else {
543 hasRecv = true
544 }
545 }
546 }
547 if !found {
548 escapes = true // mc used as send value in select
549 }
550 case *ssa.UnOp:
551 if r.Op == token.ARROW {
552 hasRecv = true
553 }
554 case *ssa.Call:
555 if bi, ok := r.Call.Value.(*ssa.Builtin); ok {
556 if bi.Name() == "spawn" {
557 // A channel passed to spawn escapes to the spawned
558 // domain, which provides the other end.
559 for _, arg := range r.Call.Args {
560 if arg == mc {
561 escapes = true
562 break
563 }
564 }
565 }
566 // close, len, cap are fine — not an escape
567 } else {
568 escapes = true
569 }
570 case *ssa.DebugRef:
571 // Debug info, not real usage.
572 default:
573 escapes = true
574 }
575 }
576 577 if escapes {
578 continue
579 }
580 if !hasSend && !hasRecv {
581 b.addError(mc.Pos(), "moxie: channel created but has no send or select/receive")
582 } else if !hasSend {
583 b.addError(mc.Pos(), "moxie: channel has no sender — every channel needs both a sender and a listener")
584 } else if !hasRecv {
585 b.addError(mc.Pos(), "moxie: channel has no listener — every channel needs both a sender and a select/receive")
586 }
587 }
588 }
589 }
590 591 // isUserPackageByPath returns true if the import path looks like a user
592 // package (contains a dot, or is "main"/"command-line-arguments").
593 func isUserPackageByPath(path string) bool {
594 if path == "main" || path == "command-line-arguments" {
595 return true
596 }
597 if strings.HasPrefix(path, "golang.org/x/") {
598 return false
599 }
600 return strings.Contains(path, ".")
601 }
602 603 // checkPackageRestrictions runs package-scope checks that aren't per-function:
604 // - reject var X = expr at package scope (step 8)
605 // - reject named slice/map types (step 9)
606 // - reject scope shadowing including builtins (step 10)
607 // - enforce init() rules: main pkg can't have init, library max one, no select/spawn
608 func (c *compilerContext) checkPackageRestrictions(pkg *loader.Package) {
609 if !isUserPackageByPath(pkg.Pkg.Path()) {
610 return
611 }
612 isMainPkg := pkg.Pkg.Name() == "main"
613 initCount := 0
614 for _, f := range pkg.Files {
615 for _, decl := range f.Decls {
616 switch d := decl.(type) {
617 case *ast.FuncDecl:
618 if d.Name.Name == "init" && d.Recv == nil {
619 initCount++
620 if isMainPkg {
621 c.addError(d.Pos(), "moxie: init() is not allowed in main package: use main() for program entry")
622 }
623 if initCount > 1 {
624 c.addError(d.Pos(), "moxie: at most one init() per package")
625 }
626 // Body restrictions: no select or spawn.
627 if d.Body != nil {
628 checkInitBodyRestrictions(c, d.Body)
629 }
630 }
631 case *ast.GenDecl:
632 switch d.Tok {
633 case token.VAR:
634 for _, spec := range d.Specs {
635 vs := spec.(*ast.ValueSpec)
636 if len(vs.Values) > 0 && !isDemotedStringConst(vs) {
637 c.addError(vs.Pos(), "moxie: package-level var with initializer is not allowed: use zero-value declaration and init()")
638 }
639 }
640 case token.TYPE:
641 for _, spec := range d.Specs {
642 ts := spec.(*ast.TypeSpec)
643 switch ts.Type.(type) {
644 case *ast.ArrayType:
645 at := ts.Type.(*ast.ArrayType)
646 if at.Len == nil {
647 c.addError(ts.Pos(), "moxie: named slice type '"+ts.Name.Name+"' is not allowed: use []T directly")
648 }
649 case *ast.MapType:
650 c.addError(ts.Pos(), "moxie: named map type '"+ts.Name.Name+"' is not allowed: use map[K]V directly")
651 }
652 }
653 }
654 }
655 }
656 }
657 // Step 10: reject scope shadowing (including builtins from Universe).
658 c.checkScopeShadowing(pkg)
659 }
660 661 // checkScopeShadowing walks all scopes in the package and rejects any
662 // declaration that shadows a name in an enclosing scope, including Universe.
663 func (c *compilerContext) checkScopeShadowing(pkg *loader.Package) {
664 info := pkg.TypeInfo()
665 if info == nil || info.Scopes == nil {
666 return
667 }
668 pkgScope := pkg.Pkg.Scope()
669 for _, scope := range info.Scopes {
670 if scope == nil || scope == pkgScope {
671 continue
672 }
673 for _, name := range scope.Names() {
674 if name == "_" {
675 continue
676 }
677 obj := scope.Lookup(name)
678 if obj == nil {
679 continue
680 }
681 // Walk parent scopes up to (and including) Universe.
682 for parent := scope.Parent(); parent != nil; parent = parent.Parent() {
683 if shadowedObj := parent.Lookup(name); shadowedObj != nil {
684 c.addError(obj.Pos(), "moxie: '"+name+"' shadows declaration in enclosing scope")
685 break
686 }
687 }
688 }
689 }
690 }
691 692 // checkInitBodyRestrictions walks an init() function body for banned constructs:
693 // select statements and spawn() calls.
694 // isDemotedStringConst returns true if a var spec was originally a string
695 // constant that was demoted to var by splitConstBlocks (string->[]byte).
696 // These have initializers like []byte("literal") and should not be flagged
697 // as "package-level var with initializer".
698 func isDemotedStringConst(vs *ast.ValueSpec) bool {
699 for _, val := range vs.Values {
700 call, ok := val.(*ast.CallExpr)
701 if !ok {
702 return false
703 }
704 // Check for []byte(...) call
705 if _, ok := call.Fun.(*ast.ArrayType); !ok {
706 return false
707 }
708 if len(call.Args) != 1 {
709 return false
710 }
711 if lit, ok := call.Args[0].(*ast.BasicLit); !ok || lit.Kind != token.STRING {
712 return false
713 }
714 }
715 return true
716 }
717 718 func checkInitBodyRestrictions(c *compilerContext, body *ast.BlockStmt) {
719 ast.Inspect(body, func(n ast.Node) bool {
720 switch node := n.(type) {
721 case *ast.SelectStmt:
722 c.addError(node.Pos(), "moxie: select is not allowed in init(): event dispatch begins in main()")
723 case *ast.CallExpr:
724 if ident, ok := node.Fun.(*ast.Ident); ok && ident.Name == "spawn" {
725 c.addError(ident.Pos(), "moxie: spawn is not allowed in init(): domains can only be created after main() starts")
726 }
727 }
728 return true
729 })
730 }
731 732