From d494f9e4209d37343d6ecbfa79bb0c53304e4ad5 Mon Sep 17 00:00:00 2001 From: Marc Vertes Date: Thu, 19 Nov 2020 12:48:03 +0100 Subject: [PATCH] interp: support calling goto from sub-scope As opposed to other symbols, goto labels must be searched in included scopes, not upper ones. Implement scope.lookdown to perform this, to allow calls to goto to be embedded in included scopes where label is defined. Fixes #953. --- _test/goto1.go | 12 ++++++++++++ interp/cfg.go | 18 ++++++++---------- interp/scope.go | 34 +++++++++++++++++++++++++--------- 3 files changed, 45 insertions(+), 19 deletions(-) create mode 100644 _test/goto1.go diff --git a/_test/goto1.go b/_test/goto1.go new file mode 100644 index 00000000..517cb5d6 --- /dev/null +++ b/_test/goto1.go @@ -0,0 +1,12 @@ +package main + +func main() { + if true { + goto here + } +here: + println("ok") +} + +// Output: +// ok diff --git a/interp/cfg.go b/interp/cfg.go index a4f9e1a6..de1c4849 100644 --- a/interp/cfg.go +++ b/interp/cfg.go @@ -194,7 +194,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) { case breakStmt, continueStmt, gotoStmt: if len(n.child) > 0 { - // Handle labeled statements + // Handle labeled statements. label := n.child[0].ident if sym, _, ok := sc.lookup(label); ok { if sym.kind != labelSym { @@ -211,25 +211,23 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) { case labeledStmt: label := n.child[0].ident - if sym, _, ok := sc.lookup(label); ok { - if sym.kind != labelSym { - err = n.child[0].cfgErrorf("label %s not defined", label) - break - } + // TODO(marc): labels must be stored outside of symbols to avoid collisions + // Used labels are searched in current and sub scopes, not upper ones. + if sym, ok := sc.lookdown(label); ok { sym.node = n n.sym = sym } else { n.sym = &symbol{kind: labelSym, node: n, index: -1} - sc.sym[label] = n.sym } + sc.sym[label] = n.sym case caseClause: sc = sc.pushBloc() if sn := n.anc.anc; sn.kind == typeSwitch && sn.child[1].action == aAssign { - // Type switch clause with a var defined in switch guard + // Type switch clause with a var defined in switch guard. var typ *itype if len(n.child) == 2 { - // 1 type in clause: define the var with this type in the case clause scope + // 1 type in clause: define the var with this type in the case clause scope. switch { case n.child[0].ident == nilIdent: typ = sc.getType("interface{}") @@ -239,7 +237,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) { typ, err = nodeType(interp, sc, n.child[0]) } } else { - // define the var with the type in the switch guard expression + // Define the var with the type in the switch guard expression. typ = sn.child[1].child[1].child[0].typ } if err != nil { diff --git a/interp/scope.go b/interp/scope.go index 7dcbe60a..cae613f6 100644 --- a/interp/scope.go +++ b/interp/scope.go @@ -72,26 +72,28 @@ type symbol struct { // execution to the index in frame, created exactly from the types layout. // type scope struct { - anc *scope // Ancestor upper scope + anc *scope // ancestor upper scope + child []*scope // included scopes def *node // function definition node this scope belongs to, or nil loop *node // loop exit node for break statement loopRestart *node // loop restart node for continue statement pkgID string // unique id of package in which scope is defined - types []reflect.Type // Frame layout, may be shared by same level scopes - level int // Frame level: number of frame indirections to access var during execution - sym map[string]*symbol // Map of symbols defined in this current scope + types []reflect.Type // frame layout, may be shared by same level scopes + level int // frame level: number of frame indirections to access var during execution + sym map[string]*symbol // map of symbols defined in this current scope global bool // true if scope refers to global space (single frame for universe and package level scopes) iota int // iota value in this scope } -// push creates a new scope and chain it to the current one. +// push creates a new child scope and chain it to the current one. func (s *scope) push(indirect bool) *scope { - sc := scope{anc: s, level: s.level, sym: map[string]*symbol{}} + sc := &scope{anc: s, level: s.level, sym: map[string]*symbol{}} + s.child = append(s.child, sc) if indirect { sc.types = []reflect.Type{} sc.level = s.level + 1 } else { - // propagate size, types, def and global as scopes at same level share the same frame + // Propagate size, types, def and global as scopes at same level share the same frame. sc.types = s.types sc.def = s.def sc.global = s.global @@ -99,7 +101,7 @@ func (s *scope) push(indirect bool) *scope { } // inherit loop state and pkgID from ancestor sc.loop, sc.loopRestart, sc.pkgID = s.loop, s.loopRestart, s.pkgID - return &sc + return sc } func (s *scope) pushBloc() *scope { return s.push(false) } @@ -107,7 +109,7 @@ func (s *scope) pushFunc() *scope { return s.push(true) } func (s *scope) pop() *scope { if s.level == s.anc.level { - // propagate size and types, as scopes at same level share the same frame + // Propagate size and types, as scopes at same level share the same frame. s.anc.types = s.types } return s.anc @@ -138,6 +140,20 @@ func (s *scope) lookup(ident string) (*symbol, int, bool) { return nil, 0, false } +// lookdown searches for a symbol in the current scope and included ones, recursively. +// It returns the first found symbol and true, or nil and false. +func (s *scope) lookdown(ident string) (*symbol, bool) { + if sym, ok := s.sym[ident]; ok { + return sym, true + } + for _, c := range s.child { + if sym, ok := c.lookdown(ident); ok { + return sym, true + } + } + return nil, false +} + func (s *scope) rangeChanType(n *node) *itype { if sym, _, found := s.lookup(n.child[1].ident); found { if t := sym.typ; len(n.child) == 3 && t != nil && (t.cat == chanT || t.cat == chanRecvT) {