From fbff5cc915f08441b18adc94c6a19d9fd4541b45 Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Thu, 15 Aug 2019 16:09:31 -0700 Subject: [PATCH] go/types: fix collection of receiver type parameters ... and various related smaller fixes We can now type-check the slices, chans, and maps examples from the design doc (with the maps example prodcing an error because importing chans doesn't work yet). Progress! Change-Id: Ifc00359a9a1cdad3bde1659a7de2028ac2544469 --- src/go/ast/ast.go | 4 + src/go/types/call.go | 2 +- src/go/types/check_test.go | 5 ++ src/go/types/contracts.go | 13 --- src/go/types/decl.go | 37 ++++++--- src/go/types/lookup.go | 18 ++++- src/go/types/methodset.go | 4 +- src/go/types/object.go | 19 ++++- src/go/types/resolver.go | 91 ++++++++++----------- src/go/types/subst.go | 5 +- src/go/types/testdata/chans.go2 | 62 +++++++++++++++ src/go/types/testdata/map.go2 | 113 +++++++++++++++++++++++++++ src/go/types/testdata/slices.go2 | 2 +- src/go/types/testdata/tmp.go2 | 26 ++---- src/go/types/testdata/typeinst.go2 | 8 +- src/go/types/testdata/typeinst2.go2 | 6 ++ src/go/types/testdata/typeparams.go2 | 2 +- src/go/types/typestring.go | 11 --- 18 files changed, 309 insertions(+), 119 deletions(-) create mode 100644 src/go/types/testdata/chans.go2 create mode 100644 src/go/types/testdata/map.go2 diff --git a/src/go/ast/ast.go b/src/go/ast/ast.go index 2d2e0f9d35..d888785ed2 100644 --- a/src/go/ast/ast.go +++ b/src/go/ast/ast.go @@ -999,6 +999,10 @@ type ( } ) +func (f *FuncDecl) IsMethod() bool { + return f.Recv.NumFields() != 0 +} + // Pos and End implementations for declaration nodes. func (d *BadDecl) Pos() token.Pos { return d.From } diff --git a/src/go/types/call.go b/src/go/types/call.go index d8a95f31f7..44b0ddd3f2 100644 --- a/src/go/types/call.go +++ b/src/go/types/call.go @@ -522,7 +522,7 @@ func (check *Checker) selector(x *operand, e *ast.SelectorExpr) { // _before_ calling NewMethodSet: LookupFieldOrMethod completes // any incomplete interfaces so they are available to NewMethodSet // (which assumes that interfaces have been completed already). - typ := unpack(x.typ) + typ := x.typ if x.mode == variable { // If typ is not an (unnamed) pointer or an interface, // use *typ instead, because the method set of *typ diff --git a/src/go/types/check_test.go b/src/go/types/check_test.go index f5aea185ef..86f2000ede 100644 --- a/src/go/types/check_test.go +++ b/src/go/types/check_test.go @@ -105,6 +105,11 @@ var tests = [][]string{ {"testdata/typeinst.go2"}, {"testdata/typeinst2.go2"}, {"testdata/contracts.go2"}, + + // Go 2 examples from design doc + {"testdata/slices.go2"}, + {"testdata/chans.go2"}, + {"testdata/map.go2"}, } var fset = token.NewFileSet() diff --git a/src/go/types/contracts.go b/src/go/types/contracts.go index 11e8806d83..c25a098c3d 100644 --- a/src/go/types/contracts.go +++ b/src/go/types/contracts.go @@ -265,8 +265,6 @@ func (check *Checker) satisfyContract(contr *Contract, targs []Type) bool { panic("unimplemented") } // use interface type of type parameter, if any - // TODO(gri) is this the correct place for this? (why not in missinMethod?) - targ = unpack(targ) // targ must implement iface if m, _ := check.missingMethod(targ, iface, true); m != nil { // check.dump("missing %s (%s, %s)", m, targ, iface) @@ -276,14 +274,3 @@ func (check *Checker) satisfyContract(contr *Contract, targs []Type) bool { return true } - -// unpack returns the interface type of a type parameter, -// otherwise it just returns the argument type. -// TODO(gri) This function is currently uses if a few places. -// Need to determine if there's a better way to handle this. -func unpack(typ Type) Type { - if tpar, _ := typ.(*TypeParam); tpar != nil { - return tpar.Interface() - } - return typ -} diff --git a/src/go/types/decl.go b/src/go/types/decl.go index 82688b9a41..b27a88eeea 100644 --- a/src/go/types/decl.go +++ b/src/go/types/decl.go @@ -617,16 +617,20 @@ func (check *Checker) collectTypeParams(list *ast.FieldList) (tparams []*TypeNam } for _, name := range f.Names { - tpar := NewTypeName(name.Pos(), check.pkg, name.Name, nil) - NewTypeParam(tpar, len(tparams), contr) // assigns type to tpar as a side-effect - check.declare(check.scope, name, tpar, check.scope.pos) // TODO(gri) verify scope pos is correct - tparams = append(tparams, tpar) + tparams = append(tparams, check.declareTypeParam(name, len(tparams), contr)) } } return tparams } +func (check *Checker) declareTypeParam(name *ast.Ident, index int, contr *Contract) *TypeName { + tpar := NewTypeName(name.Pos(), check.pkg, name.Name, nil) + NewTypeParam(tpar, index, contr) // assigns type to tpar as a side-effect + check.declare(check.scope, name, tpar, check.scope.pos) // TODO(gri) check scope position + return tpar +} + func (check *Checker) addMethodDecls(obj *TypeName) { // get associated methods // (Checker.collectObjects only collects methods with non-blank names; @@ -694,18 +698,31 @@ func (check *Checker) funcDecl(obj *Func, decl *declInfo) { assert(check.iota == nil) fdecl := decl.fdecl - if fdecl.TParams != nil { - check.openScope(fdecl, "type parameters") + if fdecl.IsMethod() { + _, _, tparams := check.unpackRecv(fdecl.Recv.List[0].Type, true) + if len(tparams) > 0 { + // TODO(gri) need to provide contract + // (check that number of parameters match is done when type-checking the receiver expression) + check.openScope(fdecl, "receiver type parameters") + defer check.closeScope() + for i, name := range tparams { + obj.tparams = append(obj.tparams, check.declareTypeParam(name, i, nil)) + } + } + } else if fdecl.TParams != nil { + check.openScope(fdecl, "function type parameters") + defer check.closeScope() obj.tparams = check.collectTypeParams(fdecl.TParams) } sig := new(Signature) obj.typ = sig // guard against cycles check.funcType(sig, fdecl.Recv, fdecl.Type) - sig.tparams = obj.tparams - - if fdecl.TParams != nil { - check.closeScope() + if !fdecl.IsMethod() { + // only functions can have type parameters that need to be passed + // (the obj.tparams for methods are the receiver parameters) + // TODO(gri) remove the need for storing tparams in signatures + sig.tparams = obj.tparams } // function body must be type-checked after global declarations diff --git a/src/go/types/lookup.go b/src/go/types/lookup.go index 3c9ff182ec..bd1de7341f 100644 --- a/src/go/types/lookup.go +++ b/src/go/types/lookup.go @@ -80,7 +80,7 @@ func (check *Checker) rawLookupFieldOrMethod(T Type, addressable bool, pkg *Pack return // blank fields/methods are never found } - typ, isPtr := deref(T) + typ, isPtr := derefUnpack(T) // *typ where typ is an interface has no methods. if isPtr && IsInterface(typ) { @@ -164,7 +164,7 @@ func (check *Checker) rawLookupFieldOrMethod(T Type, addressable bool, pkg *Pack // this depth, f.typ appears multiple times at the next // depth. if obj == nil && f.embedded { - typ, isPtr := deref(f.typ) + typ, isPtr := derefUnpack(f.typ) // TODO(gri) optimization: ignore types that can't // have fields or methods (only Named, Struct, and // Interface types need to be considered). @@ -356,6 +356,20 @@ func deref(typ Type) (Type, bool) { return typ, false } +// derefUnpack is like deref but it also unpacks type parameters +// and parameterized types. +func derefUnpack(typ Type) (Type, bool) { + typ, ptr := deref(typ) + // TODO(gri) do we need to iterate/recurse here for unpacking? + switch t := typ.(type) { + case *Parameterized: + typ = t.tname.typ + case *TypeParam: + typ = t.Interface() + } + return typ, ptr +} + // derefStructPtr dereferences typ if it is a (named or unnamed) pointer to a // (named or unnamed) struct and returns its base. Otherwise it returns typ. func derefStructPtr(typ Type) Type { diff --git a/src/go/types/methodset.go b/src/go/types/methodset.go index c34d732b7a..38a7d57903 100644 --- a/src/go/types/methodset.go +++ b/src/go/types/methodset.go @@ -76,7 +76,7 @@ func NewMethodSet(T Type) *MethodSet { // method set up to the current depth, allocated lazily var base methodSet - typ, isPtr := deref(T) + typ, isPtr := derefUnpack(T) // *typ where typ is an interface has no methods. if isPtr && IsInterface(typ) { @@ -141,7 +141,7 @@ func NewMethodSet(T Type) *MethodSet { // this depth, f.Type appears multiple times at the next // depth. if f.embedded { - typ, isPtr := deref(f.typ) + typ, isPtr := derefUnpack(f.typ) // TODO(gri) optimization: ignore types that can't // have fields or methods (only Named, Struct, and // Interface types need to be considered). diff --git a/src/go/types/object.go b/src/go/types/object.go index 6206fde809..e873ecef82 100644 --- a/src/go/types/object.go +++ b/src/go/types/object.go @@ -311,7 +311,7 @@ func (*Var) isDependency() {} // a variable may be a dependency of an initializa type Func struct { object hasPtrRecv bool // only valid for methods that don't have a type yet - tparams []*TypeName // type parameters from left to right; or nil + tparams []*TypeName // type parameters from left to right (rcvr parameters for methods); or nil } // NewFunc returns a new function with the given signature, representing @@ -336,9 +336,6 @@ func (obj *Func) FullName() string { // Scope returns the scope of the function's body block. func (obj *Func) Scope() *Scope { return obj.typ.(*Signature).scope } -// IsParameterized reports whether obj is a parameterized function. -func (obj *Func) IsParameterized() bool { return len(obj.tparams) > 0 } - func (*Func) isDependency() {} // a function may be a dependency of an initialization expression // A Label represents a declared label. @@ -398,6 +395,20 @@ func writeObject(buf *bytes.Buffer, obj Object, qf Qualifier) { case *Func: buf.WriteString("func ") writeFuncName(buf, obj, qf) + // Func.tparams is used for functions and methods; but for methods + // these are the receiver parameters. Don't print them twice. + // TODO(gri) receiver and type parameters should be in the Func + // object, not the signature. That should simplify things throughout. + if len(obj.tparams) > 0 && (typ == nil || typ.(*Signature).recv == nil) { + buf.WriteString("(type ") + for i, tname := range obj.tparams { + if i > 0 { + buf.WriteString(", ") + } + buf.WriteString(tname.name) + } + buf.WriteByte(')') + } if typ != nil { WriteSignature(buf, typ.(*Signature), qf) } diff --git a/src/go/types/resolver.go b/src/go/types/resolver.go index 4f16448ee6..edc0460428 100644 --- a/src/go/types/resolver.go +++ b/src/go/types/resolver.go @@ -403,7 +403,7 @@ func (check *Checker) collectObjects() { case *ast.FuncDecl: name := d.Name.Name obj := NewFunc(d.Name.Pos(), pkg, name, nil) - if d.Recv == nil || len(d.Recv.List) == 0 { + if !d.IsMethod() { // regular function if d.Recv != nil { check.errorf(d.Recv.Pos(), "method is missing receiver") @@ -440,14 +440,7 @@ func (check *Checker) collectObjects() { // - 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 - // TODO(gri) move this into decl phase? (like we did for type and func type parameters?) - ptr, recv, tparams := check.unpackRecv(d.Recv.List[0].Type) - if tparams != nil { - scope := NewScope(pkg.scope, d.Pos(), d.End(), "receiver type parameters") - check.recordScope(d, scope) - obj.tparams = check.declareTypeParams(scope, tparams) - } - + ptr, recv, _ := check.unpackRecv(d.Recv.List[0].Type, false) // (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.) @@ -504,53 +497,49 @@ func (check *Checker) collectObjects() { } } -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, nil) // 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 +// type parameters, if any. The type parameters are only unpacked if unpackParams is +// set. 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, unpackParams bool) (ptr bool, rname *ast.Ident, tparams []*ast.Ident) { +L: // unpack receiver type + for { + switch t := rtyp.(type) { + case *ast.ParenExpr: + rtyp = t.X + case *ast.StarExpr: + rtyp = t.X + default: + break L } } - // extract receiver name + // unpack type parameters, if any + if ptyp, _ := rtyp.(*ast.CallExpr); ptyp != nil { + rtyp = ptyp.Fun + if unpackParams { + for _, 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 receiver contains nil parameters") + default: + check.errorf(arg.Pos(), "receiver type parameter %s must be an identifier", arg) + } + if par == nil { + par = &ast.Ident{NamePos: arg.Pos(), Name: "_"} + } + tparams = append(tparams, par) + } + } + } + + // unpack receiver name if name, _ := rtyp.(*ast.Ident); name != nil { rname = name } diff --git a/src/go/types/subst.go b/src/go/types/subst.go index a066edf9af..b8e732d04b 100644 --- a/src/go/types/subst.go +++ b/src/go/types/subst.go @@ -34,6 +34,7 @@ func (check *Checker) inst(tname *TypeName, targs []Type) (res Type) { // to prove that this is always the case and then we don't need this extra // argument anymore. func (check *Checker) subst(typ Type, tparams []*TypeName, targs []Type) Type { + // check.dump("%s: tparams %d, targs %d", typ, len(tparams), len(targs)) assert(len(tparams) == len(targs)) if len(tparams) == 0 { return typ @@ -139,9 +140,11 @@ func (s *subster) typ(typ Type) (res Type) { //s.check.dump("- finished %s", tname) // instantiate custom methods as necessary for _, m := range t.methods { + // methods may not have a fully set up signature yet + s.check.objDecl(m, nil) sig := s.check.subst(m.typ, m.tparams, s.targs).(*Signature) m1 := NewFunc(m.pos, m.pkg, m.name, sig) - //s.check.dump("%s: method %s => %s", name, m, m1) + // s.check.dump("%s: method %s => %s", name, m, m1) named.methods = append(named.methods, m1) } // TODO(gri) update the method receivers? diff --git a/src/go/types/testdata/chans.go2 b/src/go/types/testdata/chans.go2 new file mode 100644 index 0000000000..d6ca4df447 --- /dev/null +++ b/src/go/types/testdata/chans.go2 @@ -0,0 +1,62 @@ +package chans + +import "runtime" + +// Ranger returns a Sender and a Receiver. The Receiver provides a +// Next method to retrieve values. The Sender provides a Send method +// to send values and a Close method to stop sending values. The Next +// method indicates when the Sender has been closed, and the Send +// method indicates when the Receiver has been freed. +// +// This is a convenient way to exit a goroutine sending values when +// the receiver stops reading them. +func Ranger(type T)() (*Sender(T), *Receiver(T)) { + c := make(chan T) + d := make(chan bool) + s := &Sender(T){values: c, done: d} + r := &Receiver(T){values: c, done: d} + runtime.SetFinalizer(r, r.finalize) + return s, r +} + +// A sender is used to send values to a Receiver. +type Sender(type T) struct { + values chan<- T + done <-chan bool +} + +// Send sends a value to the receiver. It returns whether any more +// values may be sent; if it returns false the value was not sent. +func (s *Sender(T)) Send(v T) bool { + select { + case s.values <- v: + return true + case <-s.done: + return false + } +} + +// Close tells the receiver that no more values will arrive. +// After Close is called, the Sender may no longer be used. +func (s *Sender(T)) Close() { + close(s.values) +} + +// A Receiver receives values from a Sender. +type Receiver(type T) struct { + values <-chan T + done chan<- bool +} + +// Next returns the next value from the channel. The bool result +// indicates whether the value is valid, or whether the Sender has +// been closed and no more values will be received. +func (r *Receiver(T)) Next() (T, bool) { + v, ok := <-r.values + return v, ok +} + +// finalize is a finalizer for the receiver. +func (r *Receiver(T)) finalize() { + close(r.done) +} diff --git a/src/go/types/testdata/map.go2 b/src/go/types/testdata/map.go2 new file mode 100644 index 0000000000..64f0cba4a5 --- /dev/null +++ b/src/go/types/testdata/map.go2 @@ -0,0 +1,113 @@ +// Package orderedmap provides an ordered map, implemented as a binary tree. +package orderedmap + +// TODO(gri) fix imports for tests +import "chans" // ERROR could not import + +// Map is an ordered map. +type Map(type K, V) struct { + root *node(K, V) + compare func(K, K) int +} + +// node is the type of a node in the binary tree. +type node(type K, V) struct { + key K + val V + left, right *node(K, V) +} + +// New returns a new map. +func New(type K, V)(compare func(K, K) int) *Map(K, V) { + return &Map(K, V){compare: compare} +} + +// find looks up key in the map, and returns either a pointer +// to the node holding key, or a pointer to the location where +// such a node would go. +func (m *Map(K, V)) find(key K) **node(K, V) { + pn := &m.root + for *pn != nil { + switch cmp := m.compare(key, (*pn).key); { + case cmp < 0: + pn = &(*pn).left + case cmp > 0: + pn = &(*pn).right + default: + return pn + } + } + return pn +} + +// Insert inserts a new key/value into the map. +// If the key is already present, the value is replaced. +// Returns true if this is a new key, false if already present. +func (m *Map(K, V)) Insert(key K, val V) bool { + pn := m.find(key) + if *pn != nil { + (*pn).val = val + return false + } + *pn = &node(K, V){key: key, val: val} + return true +} + +// Find returns the value associated with a key, or zero if not present. +// The found result reports whether the key was found. +func (m *Map(K, V)) Find(key K) (V, bool) { + pn := m.find(key) + if *pn == nil { + var zero V // see the discussion of zero values, above + return zero, false + } + return (*pn).val, true +} + +// keyValue is a pair of key and value used when iterating. +type keyValue(type K, V) struct { + key K + val V +} + +// InOrder returns an iterator that does an in-order traversal of the map. +func (m *Map(K, V)) InOrder() *Iterator(K, V) { + sender, receiver := chans.Ranger(keyValue(K, V))() + var f func(*node(K, V)) bool + f = func(n *node(K, V)) bool { + if n == nil { + return true + } + // Stop sending values if sender.Send returns false, + // meaning that nothing is listening at the receiver end. + return f(n.left) && + // TODO + // sender.Send(keyValue(K, V){n.key, n.val}) && + f(n.right) + } + go func() { + f(m.root) + sender.Close() + }() + // TODO(gri) The design doc doensn't require that we repeat the + // type parameters here. Fix the implementation. + return &Iterator(K, V){receiver} + // return &Iterator{receiver} +} + +// Iterator is used to iterate over the map. +type Iterator(type K, V) struct { + r *chans.Receiver(keyValue(K, V)) +} + +// Next returns the next key and value pair, and a boolean indicating +// whether they are valid or whether we have reached the end. +func (it *Iterator(K, V)) Next() (K, V, bool) { + keyval, ok := it.r.Next() + if !ok { + var zerok K + var zerov V + return zerok, zerov, false + } + return keyval.key, keyval.val, true +} diff --git a/src/go/types/testdata/slices.go2 b/src/go/types/testdata/slices.go2 index 478289aa48..c8f8b9df0c 100644 --- a/src/go/types/testdata/slices.go2 +++ b/src/go/types/testdata/slices.go2 @@ -53,7 +53,7 @@ func reducer(x float64, y int) float64 { var reduced1 = Reduce(int, float64)(input, 0, reducer) var reduced2 = Reduce(input, 1i /* ERROR overflows */, reducer) // using type inference -var reduced2 = Reduce(input, 1, reducer) // using type inference +var reduced3 = Reduce(input, 1, reducer) // using type inference func filter(x int) bool { return x&1 != 0 diff --git a/src/go/types/testdata/tmp.go2 b/src/go/types/testdata/tmp.go2 index 75a2c34c2c..8472f85c44 100644 --- a/src/go/types/testdata/tmp.go2 +++ b/src/go/types/testdata/tmp.go2 @@ -1,23 +1,13 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package p -contract Stringer(T) { - T String() string +type S(type T) struct { + f T } -type List(type T Stringer) struct{ - data T - link *List(T) -} - -type MyData string - -func (s MyData) String() string { return string(s) } - -var _ List(MyData) - -func _(type T Stringer)(s []T) (r []string) { - for _, x := range s { - r = append(r, x.String()) - } - return +func (r S(T)) m() T { + return r.f } \ No newline at end of file diff --git a/src/go/types/testdata/typeinst.go2 b/src/go/types/testdata/typeinst.go2 index 6f5001ec1b..0b53241573 100644 --- a/src/go/types/testdata/typeinst.go2 +++ b/src/go/types/testdata/typeinst.go2 @@ -6,7 +6,7 @@ package p type myInt int -// Parametrized type declarations +// Parameterized type declarations type T1(type P) P @@ -17,10 +17,10 @@ type T2(type P) struct { type List(type P) []P -// Alias type declarations cannot have parameters. +// Alias type declarations cannot have type parameters. type A1( /* ERROR cannot be parameterized */ type P) = P /* ERROR undeclared */ -// Parametrized type instantiations +// Parameterized type instantiations var x int type _ x /* ERROR not a type */ (int) @@ -39,7 +39,7 @@ var _ List(int) = []int{1, 2, 3} var _ List([]int) = [][]int{{1, 2, 3}} var _ List(List(List(int))) -// Parametrized types containing parametrized types +// Parameterized types containing parameterized types type T3(type P) List(P) diff --git a/src/go/types/testdata/typeinst2.go2 b/src/go/types/testdata/typeinst2.go2 index 518a15d06d..80a18d0495 100644 --- a/src/go/types/testdata/typeinst2.go2 +++ b/src/go/types/testdata/typeinst2.go2 @@ -50,3 +50,9 @@ var ( // Parameterized types with methods +func (l List(E)) Head() (_ E, _ bool) { + if len(l) > 0 { + return l[0], true + } + return +} diff --git a/src/go/types/testdata/typeparams.go2 b/src/go/types/testdata/typeparams.go2 index 0db08eadc4..f5496ab1eb 100644 --- a/src/go/types/testdata/typeparams.go2 +++ b/src/go/types/testdata/typeparams.go2 @@ -118,7 +118,7 @@ var _ = f8(int, float64)(1, 2.3, 3.4, 4) var _ = f8(int, float64)(0, 0, nil...) // test case for #18268 -// init function and methods cannot have type parameters +// init functions and methods cannot have type parameters func init() {} func init(/* ERROR func init must have no type parameters */ type)() {} diff --git a/src/go/types/typestring.go b/src/go/types/typestring.go index 46e254d293..4a300dfc93 100644 --- a/src/go/types/typestring.go +++ b/src/go/types/typestring.go @@ -348,17 +348,6 @@ func WriteSignature(buf *bytes.Buffer, sig *Signature, qf Qualifier) { } func writeSignature(buf *bytes.Buffer, sig *Signature, qf Qualifier, visited []Type) { - if len(sig.tparams) > 0 { - buf.WriteString("(type ") - for i, tname := range sig.tparams { - if i > 0 { - buf.WriteString(", ") - } - buf.WriteString(tname.name) - } - buf.WriteByte(')') - } - writeTuple(buf, sig.params, sig.variadic, qf, visited) n := sig.results.Len()