mirror of https://github.com/golang/go.git
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:
parent
be88be6e7a
commit
3663c91f3a
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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>")
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
@ -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() {
|
||||
|
|
|
|||
Loading…
Reference in New Issue