Files
moxa/interp/gta.go
Marc Vertes f3dbce93a4 interp: improve handling of generic types
When generating a new type, the parameter type was not correctly duplicated in the new AST. This is fixed by making copyNode recursive if needed. The out of order processing of generic types has also been fixed.

Fixes #1488
2023-02-08 11:48:05 +01:00

476 lines
13 KiB
Go

package interp
import (
"path"
"path/filepath"
)
// gta performs a global types analysis on the AST, registering types,
// variables and functions symbols at package level, prior to CFG.
// All function bodies are skipped. GTA is necessary to handle out of
// order declarations and multiple source files packages.
// rpath is the relative path to the directory containing the source for the package.
func (interp *Interpreter) gta(root *node, rpath, importPath, pkgName string) ([]*node, error) {
sc := interp.initScopePkg(importPath, pkgName)
var err error
var revisit []*node
baseName := filepath.Base(interp.fset.Position(root.pos).Filename)
root.Walk(func(n *node) bool {
if err != nil {
return false
}
if n.scope == nil {
n.scope = sc
}
switch n.kind {
case constDecl:
// Early parse of constDecl subtree, to compute all constant
// values which may be used in further declarations.
if _, err = interp.cfg(n, sc, importPath, pkgName); err != nil {
// No error processing here, to allow recovery in subtree nodes.
// TODO(marc): check for a non recoverable error and return it for better diagnostic.
err = nil
}
case blockStmt:
if n != root {
return false // skip statement block if not the entry point
}
case defineStmt:
var (
atyp *itype
err2 error
)
if n.nleft+n.nright < len(n.child) {
// Type is declared explicitly in the assign expression.
if atyp, err2 = nodeType(interp, sc, n.child[n.nleft]); err2 != nil {
// The type does not exist yet, stash the error and come back
// when the type is known.
n.meta = err2
revisit = append(revisit, n)
return false
}
}
var sbase int
if n.nright > 0 {
sbase = len(n.child) - n.nright
}
for i := 0; i < n.nleft; i++ {
dest, src := n.child[i], n.child[sbase+i]
if isBlank(src) {
err = n.cfgErrorf("cannot use _ as value")
}
val := src.rval
if n.anc.kind == constDecl {
if _, err2 := interp.cfg(n, sc, importPath, pkgName); err2 != nil {
// Constant value can not be computed yet.
// Come back when child dependencies are known.
revisit = append(revisit, n)
return false
}
}
typ := atyp
if typ == nil {
if typ, err2 = nodeType(interp, sc, src); err2 != nil || typ == nil {
// The type does is not known yet, stash the error and come back
// when the type is known.
n.meta = err2
revisit = append(revisit, n)
return false
}
val = src.rval
}
if !typ.isComplete() {
// Come back when type is known.
revisit = append(revisit, n)
return false
}
if typ.cat == nilT {
err = n.cfgErrorf("use of untyped nil")
return false
}
if typ.isBinMethod {
typ = valueTOf(typ.methodCallType(), isBinMethod(), withScope(sc))
}
sc.sym[dest.ident] = &symbol{kind: varSym, global: true, index: sc.add(typ), typ: typ, rval: val, node: n}
if n.anc.kind == constDecl {
sc.sym[dest.ident].kind = constSym
if childPos(n) == len(n.anc.child)-1 {
sc.iota = 0
} else {
sc.iota++
}
}
}
return false
case defineXStmt:
err = compDefineX(sc, n)
case valueSpec:
l := len(n.child) - 1
if n.typ = n.child[l].typ; n.typ == nil {
if n.typ, err = nodeType(interp, sc, n.child[l]); err != nil {
return false
}
if !n.typ.isComplete() {
// Come back when type is known.
revisit = append(revisit, n)
return false
}
}
for _, c := range n.child[:l] {
asImportName := filepath.Join(c.ident, baseName)
sym, exists := sc.sym[asImportName]
if !exists {
sc.sym[c.ident] = &symbol{index: sc.add(n.typ), kind: varSym, global: true, typ: n.typ, node: n}
continue
}
c.level = globalFrame
// redeclaration error
if sym.typ.node != nil && sym.typ.node.anc != nil {
prevDecl := n.interp.fset.Position(sym.typ.node.anc.pos)
err = n.cfgErrorf("%s redeclared in this block\n\tprevious declaration at %v", c.ident, prevDecl)
return false
}
err = n.cfgErrorf("%s redeclared in this block", c.ident)
return false
}
case funcDecl:
if n.typ, err = nodeType(interp, sc, n.child[2]); err != nil {
return false
}
genericMethod := false
ident := n.child[1].ident
switch {
case isMethod(n):
// Add a method symbol in the receiver type name space
var rcvrtype *itype
n.ident = ident
rcvr := n.child[0].child[0]
rtn := rcvr.lastChild()
typName, typPtr := rtn.ident, false
// Identifies the receiver type name. It could be an ident, a
// generic type (indexExpr), or a pointer on either lasts.
if typName == "" {
typName = rtn.child[0].ident
switch rtn.kind {
case starExpr:
typPtr = true
switch c := rtn.child[0]; c.kind {
case indexExpr, indexListExpr:
typName = c.child[0].ident
genericMethod = true
}
case indexExpr, indexListExpr:
genericMethod = true
}
}
sym, _, found := sc.lookup(typName)
if !found {
n.meta = n.cfgErrorf("undefined: %s", typName)
revisit = append(revisit, n)
return false
}
if sym.typ.path != pkgName {
err = n.cfgErrorf("cannot define new methods on non-local type %s", baseType(sym.typ).id())
return false
}
rcvrtype = sym.typ
if typPtr {
elementType := sym.typ
rcvrtype = ptrOf(elementType, withNode(rtn), withScope(sc))
rcvrtype.incomplete = elementType.incomplete
elementType.addMethod(n)
}
rcvrtype.addMethod(n)
rtn.typ = rcvrtype
if rcvrtype.cat == genericT {
// generate methods for already instantiated receivers
for _, it := range rcvrtype.instance {
if err = genMethod(interp, sc, it, n, it.node.anc.param); err != nil {
return false
}
}
}
case ident == "init":
// init functions do not get declared as per the Go spec.
default:
asImportName := filepath.Join(ident, baseName)
if _, exists := sc.sym[asImportName]; exists {
// redeclaration error
err = n.cfgErrorf("%s redeclared in this block", ident)
return false
}
// Add a function symbol in the package name space except for init
sc.sym[ident] = &symbol{kind: funcSym, typ: n.typ, node: n, index: -1}
}
if !n.typ.isComplete() && !genericMethod {
revisit = append(revisit, n)
}
return false
case importSpec:
var name, ipath string
if len(n.child) == 2 {
ipath = constToString(n.child[1].rval)
name = n.child[0].ident
} else {
ipath = constToString(n.child[0].rval)
}
// Try to import a binary package first, or a source package
var pkgName string
if packageName := path.Base(ipath); path.Dir(ipath) == packageName {
ipath = packageName
}
if pkg := interp.binPkg[ipath]; pkg != nil {
switch name {
case "_": // no import of symbols
case ".": // import symbols in current scope
for n, v := range pkg {
typ := v.Type()
kind := binSym
if isBinType(v) {
typ = typ.Elem()
kind = typeSym
}
sc.sym[n] = &symbol{kind: kind, typ: valueTOf(typ, withScope(sc)), rval: v}
}
default: // import symbols in package namespace
if name == "" {
name = interp.pkgNames[ipath]
}
// If an incomplete type exists, delete it
if sym, exists := sc.sym[name]; exists && sym.kind == typeSym && sym.typ.incomplete {
delete(sc.sym, name)
}
// Imports of a same package are all mapped in the same scope, so we cannot just
// map them by their names, otherwise we could have collisions from same-name
// imports in different source files of the same package. Therefore, we suffix
// the key with the basename of the source file.
name = filepath.Join(name, baseName)
if sym, exists := sc.sym[name]; !exists {
sc.sym[name] = &symbol{kind: pkgSym, typ: &itype{cat: binPkgT, path: ipath, scope: sc}}
break
} else if sym.kind == pkgSym && sym.typ.cat == srcPkgT && sym.typ.path == ipath {
// ignore re-import of identical package
break
}
// redeclaration error. Not caught by the parser.
err = n.cfgErrorf("%s redeclared in this block", name)
return false
}
} else if pkgName, err = interp.importSrc(rpath, ipath, NoTest); err == nil {
sc.types = interp.universe.types
switch name {
case "_": // no import of symbols
case ".": // import symbols in current namespace
for k, v := range interp.srcPkg[ipath] {
if canExport(k) {
sc.sym[k] = v
}
}
default: // import symbols in package namespace
if name == "" {
name = pkgName
}
name = filepath.Join(name, baseName)
if sym, exists := sc.sym[name]; !exists {
sc.sym[name] = &symbol{kind: pkgSym, typ: &itype{cat: srcPkgT, path: ipath, scope: sc}}
break
} else if sym.kind == pkgSym && sym.typ.cat == srcPkgT && sym.typ.path == ipath {
// ignore re-import of identical package
break
}
// redeclaration error
err = n.cfgErrorf("%s redeclared as imported package name", name)
return false
}
} else {
err = n.cfgErrorf("import %q error: %v", ipath, err)
}
case typeSpec, typeSpecAssign:
if isBlank(n.child[0]) {
err = n.cfgErrorf("cannot use _ as value")
return false
}
typeName := n.child[0].ident
if len(n.child) > 2 {
// Handle a generic type: skip definition as parameter is not instantiated yet.
n.typ = genericOf(nil, typeName, pkgName, withNode(n.child[0]), withScope(sc))
if _, exists := sc.sym[typeName]; !exists {
sc.sym[typeName] = &symbol{kind: typeSym, node: n}
}
sc.sym[typeName].typ = n.typ
return false
}
var typ *itype
if typ, err = nodeType(interp, sc, n.child[1]); err != nil {
err = nil
revisit = append(revisit, n)
return false
}
if n.kind == typeSpecAssign {
// Create an aliased type in the current scope
sc.sym[typeName] = &symbol{kind: typeSym, node: n, typ: typ}
n.typ = typ
break
}
// else we are not an alias (typeSpec)
switch n.child[1].kind {
case identExpr, selectorExpr:
n.typ = namedOf(typ, pkgName, typeName, withNode(n.child[0]), withScope(sc))
n.typ.incomplete = typ.incomplete
n.typ.field = typ.field
copy(n.typ.method, typ.method)
default:
n.typ = typ
n.typ.name = typeName
n.typ.path = pkgName
}
n.typ.str = n.typ.path + "." + n.typ.name
asImportName := filepath.Join(typeName, baseName)
if _, exists := sc.sym[asImportName]; exists {
// redeclaration error
err = n.cfgErrorf("%s redeclared in this block", typeName)
return false
}
sym, exists := sc.sym[typeName]
if !exists {
sym = &symbol{kind: typeSym, node: n}
sc.sym[typeName] = sym
} else if sym.typ != nil && (len(sym.typ.method) > 0) {
// Type has already been seen as a receiver in a method function
for _, m := range sym.typ.method {
n.typ.addMethod(m)
}
}
sym.typ = n.typ
if !n.typ.isComplete() {
revisit = append(revisit, n)
}
return false
}
return true
}, nil)
if sc != interp.universe {
sc.pop()
}
return revisit, err
}
func baseType(t *itype) *itype {
for {
switch t.cat {
case ptrT, linkedT:
t = t.val
default:
return t
}
}
}
// gtaRetry (re)applies gta until all global constants and types are defined.
func (interp *Interpreter) gtaRetry(nodes []*node, importPath, pkgName string) error {
revisit := []*node{}
for {
for _, n := range nodes {
list, err := interp.gta(n, importPath, importPath, pkgName)
if err != nil {
return err
}
revisit = append(revisit, list...)
}
if len(revisit) == 0 || equalNodes(nodes, revisit) {
break
}
nodes = revisit
revisit = []*node{}
}
if len(revisit) > 0 {
n := revisit[0]
switch n.kind {
case typeSpec, typeSpecAssign:
if err := definedType(n.typ); err != nil {
return err
}
case defineStmt, funcDecl:
if err, ok := n.meta.(error); ok {
return err
}
}
return n.cfgErrorf("constant definition loop")
}
return nil
}
func definedType(typ *itype) error {
if !typ.incomplete {
return nil
}
switch typ.cat {
case interfaceT, structT:
for _, f := range typ.field {
if err := definedType(f.typ); err != nil {
return err
}
}
case funcT:
for _, t := range typ.arg {
if err := definedType(t); err != nil {
return err
}
}
for _, t := range typ.ret {
if err := definedType(t); err != nil {
return err
}
}
case mapT:
if err := definedType(typ.key); err != nil {
return err
}
fallthrough
case linkedT, arrayT, chanT, chanSendT, chanRecvT, ptrT, variadicT:
if err := definedType(typ.val); err != nil {
return err
}
case nilT:
return typ.node.cfgErrorf("undefined: %s", typ.node.ident)
}
return nil
}
// equalNodes returns true if two slices of nodes are identical.
func equalNodes(a, b []*node) bool {
if len(a) != len(b) {
return false
}
for i, n := range a {
if n != b[i] {
return false
}
}
return true
}