From 8a1f9ef44e51e17d601cb62b3c4c39d78e111861 Mon Sep 17 00:00:00 2001 From: Marc Vertes Date: Thu, 14 Jan 2021 15:46:04 +0100 Subject: [PATCH] interp: parse circular interface definitions An undefined type detection function has been added to better diagnose incomplete type definitions. Implicit type names in interface or struct declarations are now better handled. The incomplete status is not fowarded to aliased type declarations to handle circular definitions. Fixes #999 and #995. Improves #260 (goes farther, but still fails). --- _test/interface48.go | 17 +++++++++++++++++ _test/interface49.go | 45 ++++++++++++++++++++++++++++++++++++++++++++ interp/gta.go | 45 +++++++++++++++++++++++++++++++++++++++++++- interp/type.go | 17 ++++++++++++++--- 4 files changed, 120 insertions(+), 4 deletions(-) create mode 100644 _test/interface48.go create mode 100644 _test/interface49.go diff --git a/_test/interface48.go b/_test/interface48.go new file mode 100644 index 000000000..302bed41f --- /dev/null +++ b/_test/interface48.go @@ -0,0 +1,17 @@ +package main + +import "fmt" + +type I1 interface{ A } + +type A = I2 + +type I2 interface{ F() I1 } + +func main() { + var i I1 + fmt.Println(i) +} + +// Output: +// diff --git a/_test/interface49.go b/_test/interface49.go new file mode 100644 index 000000000..b9fb9fc9b --- /dev/null +++ b/_test/interface49.go @@ -0,0 +1,45 @@ +package main + +type Descriptor interface { + ParentFile() FileDescriptor +} + +type FileDescriptor interface { + Enums() EnumDescriptors + Services() ServiceDescriptors +} + +type EnumDescriptors interface { + Get(i int) EnumDescriptor +} + +type EnumDescriptor interface { + Values() EnumValueDescriptors +} + +type EnumValueDescriptors interface { + Get(i int) EnumValueDescriptor +} + +type EnumValueDescriptor interface { + Descriptor +} + +type ServiceDescriptors interface { + Get(i int) ServiceDescriptor +} + +type ServiceDescriptor interface { + Descriptor + isServiceDescriptor +} + +type isServiceDescriptor interface{ ProtoType(ServiceDescriptor) } + +func main() { + var d Descriptor + println(d == nil) +} + +// Output: +// true diff --git a/interp/gta.go b/interp/gta.go index 71c602b4a..19cef8091 100644 --- a/interp/gta.go +++ b/interp/gta.go @@ -319,7 +319,50 @@ func (interp *Interpreter) gtaRetry(nodes []*node, importPath string) error { } if len(revisit) > 0 { - return revisit[0].cfgErrorf("constant definition loop") + n := revisit[0] + if n.kind == typeSpec { + if err := definedType(n.typ); err != nil { + 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 aliasT, 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 } diff --git a/interp/type.go b/interp/type.go index d470e97b3..b92ef6bac 100644 --- a/interp/type.go +++ b/interp/type.go @@ -109,7 +109,7 @@ type itype struct { cat tcat // Type category field []structField // Array of struct fields if structT or interfaceT key *itype // Type of key element if MapT or nil - val *itype // Type of value element if chanT,chanSendT, chanRecvT, mapT, ptrT, aliasT, arrayT or variadicT + val *itype // Type of value element if chanT, chanSendT, chanRecvT, mapT, ptrT, aliasT, arrayT or variadicT recv *itype // Receiver type for funcT or nil arg []*itype // Argument types if funcT or nil ret []*itype // Return types if funcT or nil @@ -449,6 +449,9 @@ func nodeType(interp *Interpreter, sc *scope, n *node) (*itype, error) { } } t = sym.typ + if t.incomplete && t.cat == aliasT && t.val != nil && t.val.cat != nilT { + t.incomplete = false + } if t.incomplete && t.node != n { m := t.method if t, err = nodeType(interp, sc, t.node); err != nil { @@ -879,13 +882,19 @@ func isComplete(t *itype, visited map[string]bool) bool { } name := t.path + "/" + t.name if visited[name] { - return !t.incomplete + return true } if t.name != "" { visited[name] = true } switch t.cat { - case aliasT, arrayT, chanT, chanRecvT, chanSendT, ptrT: + case aliasT: + if t.val != nil && t.val.cat != nilT { + // A type aliased to a partially defined type is considered complete, to allow recursivity. + return true + } + fallthrough + case arrayT, chanT, chanRecvT, chanSendT, ptrT: return isComplete(t.val, visited) case funcT: complete := true @@ -899,6 +908,8 @@ func isComplete(t *itype, visited map[string]bool) bool { case interfaceT, structT: complete := true for _, f := range t.field { + // Field implicit type names must be marked as visited, to break false circles. + visited[f.typ.path+"/"+f.typ.name] = true complete = complete && isComplete(f.typ, visited) } return complete