diff --git a/src/go/types/api.go b/src/go/types/api.go index 1abcd9d951..fcb7d025dc 100644 --- a/src/go/types/api.go +++ b/src/go/types/api.go @@ -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 diff --git a/src/go/types/check.go b/src/go/types/check.go index badb54aca1..08adce7349 100644 --- a/src/go/types/check.go +++ b/src/go/types/check.go @@ -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) } } diff --git a/src/go/types/check_test.go b/src/go/types/check_test.go index 090582bec2..f5aea185ef 100644 --- a/src/go/types/check_test.go +++ b/src/go/types/check_test.go @@ -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 { diff --git a/src/go/types/decl.go b/src/go/types/decl.go index d2361e1842..d64c3ecf5d 100644 --- a/src/go/types/decl.go +++ b/src/go/types/decl.go @@ -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() { diff --git a/src/go/types/errors.go b/src/go/types/errors.go index 91b077163c..2bb92cc7a0 100644 --- a/src/go/types/errors.go +++ b/src/go/types/errors.go @@ -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) } diff --git a/src/go/types/expr.go b/src/go/types/expr.go index 5cfab6ad42..82af084b78 100644 --- a/src/go/types/expr.go +++ b/src/go/types/expr.go @@ -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() { diff --git a/src/go/types/predicates.go b/src/go/types/predicates.go index 28ce556f23..fd164a9099 100644 --- a/src/go/types/predicates.go +++ b/src/go/types/predicates.go @@ -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 } } diff --git a/src/go/types/resolver.go b/src/go/types/resolver.go index eb315c6dcf..d699848aec 100644 --- a/src/go/types/resolver.go +++ b/src/go/types/resolver.go @@ -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) diff --git a/src/go/types/stmt.go b/src/go/types/stmt.go index 1eb7a62c4f..34a04acec9 100644 --- a/src/go/types/stmt.go +++ b/src/go/types/stmt.go @@ -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(), "--- ") diff --git a/src/go/types/subst.go b/src/go/types/subst.go index 155619f6fa..19c657fc64 100644 --- a/src/go/types/subst.go +++ b/src/go/types/subst.go @@ -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() { diff --git a/src/go/types/testdata/tmp.go2 b/src/go/types/testdata/tmp.go2 index f43cdc68c6..bca1410297 100644 --- a/src/go/types/testdata/tmp.go2 +++ b/src/go/types/testdata/tmp.go2 @@ -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] } -*/ \ No newline at end of file + +// var _ string = List(string){"foo"}.First() \ No newline at end of file diff --git a/src/go/types/typexpr.go b/src/go/types/typexpr.go index 15f342d3c4..d5015c5b94 100644 --- a/src/go/types/typexpr.go +++ b/src/go/types/typexpr.go @@ -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() {