go/types: unpack parameterized method receivers

Also: Print debug trace when running tests and -v is set.

Change-Id: I741377f82845d4713eebeb26706cb47b55afebd3
This commit is contained in:
Robert Griesemer 2019-07-19 14:11:16 -07:00
parent be88be6e7a
commit 3663c91f3a
12 changed files with 119 additions and 44 deletions

View File

@ -114,6 +114,9 @@ type Config struct {
// It is an error to set both FakeImportC and go115UsesCgo.
go115UsesCgo bool
// If Trace is set, a debug trace is printed to stdout.
Trace bool
// If Error != nil, it is called with each error found
// during type checking; err has dynamic type Error.
// Secondary errors (for instance, to enumerate all types

View File

@ -15,10 +15,7 @@ import (
)
// debugging/development support
const (
debug = true // leave on during development
trace = false // turn on for detailed type resolution traces
)
const debug = true // leave on during development
// If Strict is set, the type-checker enforces additional
// rules not specified by the Go 1 spec, but which will
@ -261,7 +258,7 @@ func (check *Checker) checkFiles(files []*ast.File) (err error) {
defer check.handleBailout(&err)
print := func(msg string) {
if trace {
if check.conf.Trace {
fmt.Println(msg)
}
}

View File

@ -269,6 +269,7 @@ func checkFiles(t *testing.T, testfiles []string) {
if len(testfiles) == 1 && testfiles[0] == "testdata/importC.src" {
conf.FakeImportC = true
}
conf.Trace = testing.Verbose()
conf.Importer = importer.Default()
conf.Error = func(err error) {
if *haltOnError {

View File

@ -52,7 +52,7 @@ func pathString(path []Object) string {
// objDecl type-checks the declaration of obj in its respective (file) context.
// For the meaning of def, see Checker.definedType, in typexpr.go.
func (check *Checker) objDecl(obj Object, def *Named) {
if trace {
if check.conf.Trace {
check.trace(obj.Pos(), "-- checking %s (%s, objPath = %s)", obj, obj.color(), pathString(check.objPath))
check.indent++
defer func() {
@ -248,7 +248,7 @@ func (check *Checker) cycle(obj Object) (isCycle bool) {
}
}
if trace {
if check.conf.Trace {
check.trace(obj.Pos(), "## cycle detected: objPath = %s->%s (len = %d)", pathString(cycle), obj.Name(), len(cycle))
check.trace(obj.Pos(), "## cycle contains: %d values, %d type definitions", nval, ndef)
defer func() {

View File

@ -87,7 +87,7 @@ func (check *Checker) err(pos token.Pos, msg string, soft bool) {
check.firstErr = err
}
if trace {
if check.conf.Trace {
check.trace(pos, "ERROR: %s", msg)
}

View File

@ -975,7 +975,7 @@ const (
// If hint != nil, it is the type of a composite literal element.
//
func (check *Checker) rawExpr(x *operand, e ast.Expr, hint Type) exprKind {
if trace {
if check.conf.Trace {
check.trace(e.Pos(), "%s", e)
check.indent++
defer func() {

View File

@ -318,7 +318,7 @@ func (check *Checker) identical0(x, y Type, cmpTags bool, p *ifacePair, tparams
assert(len(x.targs) == len(y.targs)) // since x, y have identical tname
for i, x := range x.targs {
y := y.targs[i]
if !identical(x, y, cmpTags, p, tparams) {
if !check.identical0(x, y, cmpTags, p, tparams) {
return false
}
}

View File

@ -214,7 +214,12 @@ func (check *Checker) collectObjects() {
pkgImports[imp] = true
}
var methods []*Func // list of methods with non-blank _ names
type methodInfo struct {
obj *Func // method
ptr bool // true if pointer receiver
recv *ast.Ident // receiver type name
}
var methods []methodInfo // collected methods with valid receivers and non-blank _ names
var fileScopes []*Scope
for fileNo, file := range check.files {
// The package identifier denotes the current package,
@ -401,8 +406,12 @@ func (check *Checker) collectObjects() {
case *ast.FuncDecl:
name := d.Name.Name
obj := NewFunc(d.Name.Pos(), pkg, name, nil)
if d.Recv == nil {
if d.Recv == nil || len(d.Recv.List) == 0 {
// regular function
if d.Recv != nil {
check.errorf(d.Recv.Pos(), "method is missing receiver")
// treat as function
}
if name == "init" {
if d.TParams != nil {
check.softErrorf(d.TParams.Pos(), "func init must have no type parameters")
@ -426,15 +435,29 @@ func (check *Checker) collectObjects() {
}
} else {
// method
// d.Recv != nil && len(d.Recv.List) > 0
if d.TParams != nil {
// TODO(gri) should this be done in the parser (and this an invalidAST error)?
check.softErrorf(d.TParams.Pos(), "method must have no type parameters")
}
// (Methods with blank _ names are never found; no need to collect
// them for later type association. They will still be type-checked
// with all the other functions.)
if name != "_" {
methods = append(methods, obj)
// collect parameterized receiver type parameters, if any
// - a receiver type parameter is like any other type parameter, except that it is passed implicitly (via the receiver)
// - the receiver specification is effectively the declaration of that type parameter
// - if the receiver type is parameterized but we don't need the parameters, we permit leaving them away
// - this is a effectively a declaration, and thus a receiver type parameter may be the blank identifier (_)
// - since methods cannot have other type parameters, we store receiver type parameters where function type parameters would be
ptr, recv, tparams := check.unpackRecv(d.Recv.List[0].Type)
if tparams != nil {
obj.scope = NewScope(pkg.scope, d.Pos(), d.End(), "receiver type parameters")
check.recordScope(d, obj.scope)
obj.tparams = check.declareTypeParams(obj.scope, tparams)
}
// (Methods with invalid receiver cannot be associated to a type, and
// methods with blank _ names are never found; no need to collect any
// of them. They will still be type-checked with all the other functions.)
if recv != nil && name != "_" {
methods = append(methods, methodInfo{obj, ptr, recv})
}
check.recordDef(d.Name, obj)
}
@ -472,19 +495,15 @@ func (check *Checker) collectObjects() {
// associate methods with receiver base type name where possible.
// Ignore methods that have an invalid receiver. They will be
// type-checked later, with regular functions.
if methods == nil {
return // nothing to do
}
check.methods = make(map[*TypeName][]*Func)
for _, f := range methods {
fdecl := check.objMap[f].fdecl
if list := fdecl.Recv.List; len(list) > 0 {
// f is a method.
// Determine the receiver base type and associate f with it.
ptr, base := check.resolveBaseTypeName(list[0].Type)
if methods != nil {
check.methods = make(map[*TypeName][]*Func)
for i := range methods {
m := &methods[i]
// Determine the receiver base type and associate m with it.
ptr, base := check.resolveBaseTypeName(m.ptr, m.recv)
if base != nil {
f.hasPtrRecv = ptr
check.methods[base] = append(check.methods[base], f)
m.obj.hasPtrRecv = ptr
check.methods[base] = append(check.methods[base], m.obj)
}
}
}
@ -494,17 +513,67 @@ func (check *Checker) collectTypeParams(parent *Scope, node ast.Node, list *ast.
scope = NewScope(parent, node.Pos(), node.End(), "type parameters")
check.recordScope(node, scope)
index := 0
var names []*ast.Ident
for _, f := range list.List {
for _, name := range f.Names {
tpar := NewTypeName(name.Pos(), check.pkg, name.Name, nil)
NewTypeParam(tpar, index) // assigns type to tpar as a side-effect
check.declare(scope, name, tpar, scope.pos)
tparams = append(tparams, tpar)
index++
names = append(names, name)
}
}
return scope, check.declareTypeParams(scope, names)
}
func (check *Checker) declareTypeParams(scope *Scope, list []*ast.Ident) []*TypeName {
tparams := make([]*TypeName, len(list))
for i, name := range list {
tpar := NewTypeName(name.Pos(), check.pkg, name.Name, nil)
NewTypeParam(tpar, i) // assigns type to tpar as a side-effect
check.declare(scope, name, tpar, scope.pos)
tparams[i] = tpar
}
return tparams
}
// unpackRecv unpacks a receiver type and returns its components: ptr indicates whether
// rtyp is a pointer receiver, rname is the receiver type name, and tparams are its
// type parameters, if any. If rname is nil, the receiver is unusable (i.e., the source
// has a bug which we cannot easily work aound with).
func (check *Checker) unpackRecv(rtyp ast.Expr) (ptr bool, rname *ast.Ident, tparams []*ast.Ident) {
// unparen and dereference
rtyp = unparen(rtyp)
if ptyp, _ := rtyp.(*ast.StarExpr); ptyp != nil {
ptr = true
rtyp = unparen(ptyp.X)
}
// extract type parameters, if any
if ptyp, _ := rtyp.(*ast.CallExpr); ptyp != nil {
rtyp = ptyp.Fun
tparams = make([]*ast.Ident, len(ptyp.Args))
for i, arg := range ptyp.Args {
var par *ast.Ident
switch arg := arg.(type) {
case *ast.Ident:
par = arg
case *ast.BadExpr:
// ignore - error already reported by parser
case nil:
check.invalidAST(ptyp.Pos(), "parameterized reveiver contains nil parameters")
default:
check.errorf(arg.Pos(), "%s is not a valid receiver type parameter declaration", arg)
}
if par == nil {
par = &ast.Ident{NamePos: arg.Pos(), Name: "_"}
}
tparams[i] = par
}
}
// extract receiver name
if name, _ := rtyp.(*ast.Ident); name != nil {
rname = name
}
return
}
@ -512,12 +581,13 @@ func (check *Checker) collectTypeParams(parent *Scope, node ast.Node, list *ast.
// there was a pointer indirection to get to it. The base type name must be declared
// in package scope, and there can be at most one pointer indirection. If no such type
// name exists, the returned base is nil.
func (check *Checker) resolveBaseTypeName(typ ast.Expr) (ptr bool, base *TypeName) {
func (check *Checker) resolveBaseTypeName(seenPtr bool, typ ast.Expr) (ptr bool, base *TypeName) {
// Algorithm: Starting from a type expression, which may be a name,
// we follow that type through alias declarations until we reach a
// non-alias type name. If we encounter anything but pointer types or
// parentheses we're done. If we encounter more than one pointer type
// we're done.
ptr = seenPtr
var seen map[*TypeName]bool
for {
typ = unparen(typ)

View File

@ -14,7 +14,7 @@ import (
)
func (check *Checker) funcBody(decl *declInfo, name string, sig *Signature, body *ast.BlockStmt, iota constant.Value) {
if trace {
if check.conf.Trace {
check.trace(body.Pos(), "--- %s: %s", name, sig)
defer func() {
check.trace(body.End(), "--- <end>")

View File

@ -14,7 +14,7 @@ import (
// inst returns the instantiated type of tname.
func (check *Checker) inst(tname *TypeName, targs []Type) (res Type) {
if trace {
if check.conf.Trace {
check.trace(tname.pos, "-- instantiating %s", tname)
check.indent++
defer func() {

View File

@ -6,11 +6,11 @@ package p
type List(type E) []E
/*
func (l List(E)) First() E {
if len(l) == 0 {
panic("no first")
}
return l[0]
}
*/
// var _ string = List(string){"foo"}.First()

View File

@ -126,7 +126,7 @@ func (check *Checker) typ(e ast.Expr) Type {
// any components of e are type-checked.
//
func (check *Checker) definedType(e ast.Expr, def *Named) (T Type) {
if trace {
if check.conf.Trace {
check.trace(e.Pos(), "%s", e)
check.indent++
defer func() {
@ -159,7 +159,6 @@ func (check *Checker) instantiatedType(e ast.Expr) Type {
// funcType type-checks a function or method type.
func (check *Checker) funcType(sig *Signature, recvPar *ast.FieldList, ftyp *ast.FuncType) {
scope := NewScope(check.scope, token.NoPos, token.NoPos, "function")
// TODO(gri) should we close this scope?
scope.isFunc = true
check.recordScope(ftyp, scope)
@ -174,7 +173,7 @@ func (check *Checker) funcType(sig *Signature, recvPar *ast.FieldList, ftyp *ast
var recv *Var
switch len(recvList) {
case 0:
check.error(recvPar.Pos(), "method is missing receiver")
// error reported by resolver
recv = NewParam(0, nil, "", Typ[Invalid]) // ignore recv below
default:
// more than one receiver
@ -187,6 +186,11 @@ func (check *Checker) funcType(sig *Signature, recvPar *ast.FieldList, ftyp *ast
// (ignore invalid types - error was reported before)
if t, _ := deref(recv.typ); t != Typ[Invalid] {
var err string
// TODO(gri) Unpacking a parameterized receiver here is a bit of a party trick
// and probably not very robust. Rethink this code.
if p, _ := t.(*Parameterized); p != nil {
t = p.tname.typ
}
if T, _ := t.(*Named); T != nil {
// spec: "The type denoted by T is called the receiver base type; it must not
// be a pointer or interface type and it must be declared in the same package
@ -626,7 +630,7 @@ func (check *Checker) completeInterface(ityp *Interface) {
panic("internal error: incomplete interface")
}
if trace {
if check.conf.Trace {
check.trace(token.NoPos, "complete %s", ityp)
check.indent++
defer func() {