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.
This commit is contained in:
Marc Vertes
2020-11-19 12:48:03 +01:00
committed by GitHub
parent 6da1107c39
commit d494f9e420
3 changed files with 45 additions and 19 deletions

12
_test/goto1.go Normal file
View File

@@ -0,0 +1,12 @@
package main
func main() {
if true {
goto here
}
here:
println("ok")
}
// Output:
// ok

View File

@@ -194,7 +194,7 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
case breakStmt, continueStmt, gotoStmt: case breakStmt, continueStmt, gotoStmt:
if len(n.child) > 0 { if len(n.child) > 0 {
// Handle labeled statements // Handle labeled statements.
label := n.child[0].ident label := n.child[0].ident
if sym, _, ok := sc.lookup(label); ok { if sym, _, ok := sc.lookup(label); ok {
if sym.kind != labelSym { if sym.kind != labelSym {
@@ -211,25 +211,23 @@ func (interp *Interpreter) cfg(root *node, importPath string) ([]*node, error) {
case labeledStmt: case labeledStmt:
label := n.child[0].ident label := n.child[0].ident
if sym, _, ok := sc.lookup(label); ok { // TODO(marc): labels must be stored outside of symbols to avoid collisions
if sym.kind != labelSym { // Used labels are searched in current and sub scopes, not upper ones.
err = n.child[0].cfgErrorf("label %s not defined", label) if sym, ok := sc.lookdown(label); ok {
break
}
sym.node = n sym.node = n
n.sym = sym n.sym = sym
} else { } else {
n.sym = &symbol{kind: labelSym, node: n, index: -1} n.sym = &symbol{kind: labelSym, node: n, index: -1}
sc.sym[label] = n.sym
} }
sc.sym[label] = n.sym
case caseClause: case caseClause:
sc = sc.pushBloc() sc = sc.pushBloc()
if sn := n.anc.anc; sn.kind == typeSwitch && sn.child[1].action == aAssign { 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 var typ *itype
if len(n.child) == 2 { 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 { switch {
case n.child[0].ident == nilIdent: case n.child[0].ident == nilIdent:
typ = sc.getType("interface{}") 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]) typ, err = nodeType(interp, sc, n.child[0])
} }
} else { } 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 typ = sn.child[1].child[1].child[0].typ
} }
if err != nil { if err != nil {

View File

@@ -72,26 +72,28 @@ type symbol struct {
// execution to the index in frame, created exactly from the types layout. // execution to the index in frame, created exactly from the types layout.
// //
type scope struct { 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 def *node // function definition node this scope belongs to, or nil
loop *node // loop exit node for break statement loop *node // loop exit node for break statement
loopRestart *node // loop restart node for continue statement loopRestart *node // loop restart node for continue statement
pkgID string // unique id of package in which scope is defined pkgID string // unique id of package in which scope is defined
types []reflect.Type // Frame layout, may be shared by same level scopes 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 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 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) global bool // true if scope refers to global space (single frame for universe and package level scopes)
iota int // iota value in this scope 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 { 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 { if indirect {
sc.types = []reflect.Type{} sc.types = []reflect.Type{}
sc.level = s.level + 1 sc.level = s.level + 1
} else { } 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.types = s.types
sc.def = s.def sc.def = s.def
sc.global = s.global sc.global = s.global
@@ -99,7 +101,7 @@ func (s *scope) push(indirect bool) *scope {
} }
// inherit loop state and pkgID from ancestor // inherit loop state and pkgID from ancestor
sc.loop, sc.loopRestart, sc.pkgID = s.loop, s.loopRestart, s.pkgID 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) } 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 { func (s *scope) pop() *scope {
if s.level == s.anc.level { 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 s.anc.types = s.types
} }
return s.anc return s.anc
@@ -138,6 +140,20 @@ func (s *scope) lookup(ident string) (*symbol, int, bool) {
return nil, 0, false 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 { func (s *scope) rangeChanType(n *node) *itype {
if sym, _, found := s.lookup(n.child[1].ident); found { 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) { if t := sym.typ; len(n.child) == 3 && t != nil && (t.cat == chanT || t.cat == chanRecvT) {