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).
This commit is contained in:
Marc Vertes
2021-01-14 15:46:04 +01:00
committed by GitHub
parent 5cd1e11379
commit 8a1f9ef44e
4 changed files with 120 additions and 4 deletions

17
_test/interface48.go Normal file
View File

@@ -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
_test/interface49.go Normal file
View File

@@ -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

View File

@@ -319,7 +319,50 @@ func (interp *Interpreter) gtaRetry(nodes []*node, importPath string) error {
} }
if len(revisit) > 0 { 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 return nil
} }

View File

@@ -109,7 +109,7 @@ type itype struct {
cat tcat // Type category cat tcat // Type category
field []structField // Array of struct fields if structT or interfaceT field []structField // Array of struct fields if structT or interfaceT
key *itype // Type of key element if MapT or nil 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 recv *itype // Receiver type for funcT or nil
arg []*itype // Argument types if funcT or nil arg []*itype // Argument types if funcT or nil
ret []*itype // Return 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 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 { if t.incomplete && t.node != n {
m := t.method m := t.method
if t, err = nodeType(interp, sc, t.node); err != nil { 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 name := t.path + "/" + t.name
if visited[name] { if visited[name] {
return !t.incomplete return true
} }
if t.name != "" { if t.name != "" {
visited[name] = true visited[name] = true
} }
switch t.cat { 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) return isComplete(t.val, visited)
case funcT: case funcT:
complete := true complete := true
@@ -899,6 +908,8 @@ func isComplete(t *itype, visited map[string]bool) bool {
case interfaceT, structT: case interfaceT, structT:
complete := true complete := true
for _, f := range t.field { 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) complete = complete && isComplete(f.typ, visited)
} }
return complete return complete