Skip to content

Commit

Permalink
interp: parse circular interface definitions
Browse files Browse the repository at this point in the history
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).
  • Loading branch information
mvertes authored Jan 14, 2021
1 parent 5cd1e11 commit 8a1f9ef
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 4 deletions.
17 changes: 17 additions & 0 deletions _test/interface48.go
Original file line number Diff line number Diff line change
@@ -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:
// <nil>
45 changes: 45 additions & 0 deletions _test/interface49.go
Original file line number Diff line number Diff line change
@@ -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
45 changes: 44 additions & 1 deletion interp/gta.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
17 changes: 14 additions & 3 deletions interp/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down

0 comments on commit 8a1f9ef

Please sign in to comment.