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
This commit is contained in:
Robert Griesemer 2019-08-15 16:09:31 -07:00
parent 3c6943daac
commit fbff5cc915
18 changed files with 309 additions and 119 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

62
src/go/types/testdata/chans.go2 vendored Normal file
View File

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

113
src/go/types/testdata/map.go2 vendored Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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)() {}

View File

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