Files
moxa/interp/scope.go
Marc Vertes 38a7331bf9 interp: fix type check on function signature
Perform function declaration type check from the upper level scope (the scope where the
function is declared), to avoid possible collisions of local variables with package names.

Fixes #957.
2020-11-13 18:02:04 +01:00

216 lines
6.1 KiB
Go

package interp
import (
"log"
"reflect"
"strconv"
)
// A sKind represents the kind of symbol.
type sKind uint
// Symbol kinds for the Go interpreter.
const (
undefSym sKind = iota
binSym // Binary from runtime
bltnSym // Builtin
constSym // Constant
funcSym // Function
labelSym // Label
pkgSym // Package
typeSym // Type
varSym // Variable
)
var symKinds = [...]string{
undefSym: "undefSym",
binSym: "binSym",
bltnSym: "bltnSym",
constSym: "constSym",
funcSym: "funcSym",
labelSym: "labelSym",
pkgSym: "pkgSym",
typeSym: "typeSym",
varSym: "varSym",
}
func (k sKind) String() string {
if k < sKind(len(symKinds)) {
return symKinds[k]
}
return "SymKind(" + strconv.Itoa(int(k)) + ")"
}
// A symbol represents an interpreter object such as type, constant, var, func,
// label, builtin or binary object. Symbols are defined within a scope.
type symbol struct {
kind sKind
typ *itype // Type of value
node *node // Node value if index is negative
from []*node // list of nodes jumping to node if kind is label, or nil
recv *receiver // receiver node value, if sym refers to a method
index int // index of value in frame or -1
rval reflect.Value // default value (used for constants)
builtin bltnGenerator // Builtin function or nil
global bool // true if symbol is defined in global space
}
// scope type stores symbols in maps, and frame layout as array of types
// The purposes of scopes are to manage the visibility of each symbol
// and to store the memory frame layout information (type and index in frame)
// at each level (global, package, functions)
//
// scopes are organized in a stack fashion: a first scope (universe) is created
// once at global level, and for each block (package, func, for, etc...), a new
// scope is pushed at entry, and poped at exit.
//
// Nested scopes with the same level value use the same frame: it allows to have
// exactly one frame per function, with a fixed position for each variable (named
// or not), no matter the inner complexity (number of nested blocks in the function)
//
// In symbols, the index value corresponds to the index in scope.types, and at
// execution to the index in frame, created exactly from the types layout.
//
type scope struct {
anc *scope // Ancestor upper scope
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
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.
func (s *scope) push(indirect bool) *scope {
sc := scope{anc: s, level: s.level, sym: map[string]*symbol{}}
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
sc.types = s.types
sc.def = s.def
sc.global = s.global
sc.level = s.level
}
// inherit loop state and pkgID from ancestor
sc.loop, sc.loopRestart, sc.pkgID = s.loop, s.loopRestart, s.pkgID
return &sc
}
func (s *scope) pushBloc() *scope { return s.push(false) }
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
s.anc.types = s.types
}
return s.anc
}
func (s *scope) upperLevel() *scope {
level := s.level
for s != nil && s.level == level {
s = s.anc
}
return s
}
// lookup searches for a symbol in the current scope, and upper ones if not found
// it returns the symbol, the number of indirections level from the current scope
// and status (false if no result).
func (s *scope) lookup(ident string) (*symbol, int, bool) {
level := s.level
for {
if sym, ok := s.sym[ident]; ok {
return sym, level - s.level, true
}
if s.anc == nil {
break
}
s = s.anc
}
return nil, 0, 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) {
return t
}
}
c := n.child[1]
if c.typ == nil {
return nil
}
switch {
case c.typ.cat == chanT, c.typ.cat == chanRecvT:
return c.typ
case c.typ.cat == valueT && c.typ.rtype.Kind() == reflect.Chan:
return &itype{cat: chanT, val: &itype{cat: valueT, rtype: c.typ.rtype.Elem()}}
}
return nil
}
// fixType returns the input type, or a valid default type for untyped constant.
func (s *scope) fixType(t *itype) *itype {
if !t.untyped || t.cat != valueT {
return t
}
switch typ := t.TypeOf(); typ.Kind() {
case reflect.Int64:
return s.getType("int")
case reflect.Uint64:
return s.getType("uint")
case reflect.Float64:
return s.getType("float64")
case reflect.Complex128:
return s.getType("complex128")
}
return t
}
func (s *scope) getType(ident string) *itype {
var t *itype
if sym, _, found := s.lookup(ident); found {
if sym.kind == typeSym {
t = sym.typ
}
}
return t
}
// add adds a type to the scope types array, and returns its index.
func (s *scope) add(typ *itype) (index int) {
if typ == nil {
log.Panic("nil type")
}
index = len(s.types)
t := typ.frameType()
if t == nil {
log.Panic("nil reflect type")
}
s.types = append(s.types, t)
return
}
func (interp *Interpreter) initScopePkg(pkgID string) *scope {
sc := interp.universe
interp.mutex.Lock()
if _, ok := interp.scopes[pkgID]; !ok {
interp.scopes[pkgID] = sc.pushBloc()
}
sc = interp.scopes[pkgID]
sc.pkgID = pkgID
interp.mutex.Unlock()
return sc
}