From 703974d608275df35d82bbf37729623c905779c3 Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Tue, 26 Nov 2019 21:14:32 -0800 Subject: [PATCH] go/types: rewrote instantiation of named types A parameterized named type is in one of two states: not yet instantiated, or instantiated. It may be instantiated with any type, including (outer) type parameters. An instantiated parameterized type may needs its type parameters substituted if it is part of an enclosing type (or function) that is instantiated. A Named type has an extra field targs which is set to the type arguments if the type is instantiated. As a result, we don't need a representation for (partially) instantiated parameterized types anymore: the Parameterized type is now unused (but still present in the code). Added a unique id field to TypeParam types so they can be printed with a unique name. This permits the creation of a unique type name for instantiated types which then can be used for hashing that type name. Also, made interfaces as type bounds work in more cases. A handful of places (in testdata/map.go2, map2.go2) are broken (commented out) and need investigation. Change-Id: I137d352b419b2520eadde1b07f823375e0273dab --- src/go/types/builtins.go | 8 +- src/go/types/call.go | 10 +- src/go/types/check.go | 2 + src/go/types/contracts.go | 5 +- src/go/types/decl.go | 14 +- src/go/types/infer.go | 4 +- src/go/types/resolver.go | 3 +- src/go/types/subst.go | 234 +++++++++++++++++++------------ src/go/types/testdata/chans.go2 | 2 +- src/go/types/testdata/decls0.src | 8 +- src/go/types/testdata/map.go2 | 7 +- src/go/types/testdata/map2.go2 | 18 ++- src/go/types/testdata/tmp.go2 | 2 + src/go/types/type.go | 9 +- src/go/types/typestring.go | 4 +- src/go/types/typexpr.go | 159 +++++++++++++++++---- 16 files changed, 342 insertions(+), 147 deletions(-) diff --git a/src/go/types/builtins.go b/src/go/types/builtins.go index 39813edf5c..7d5e4849ab 100644 --- a/src/go/types/builtins.go +++ b/src/go/types/builtins.go @@ -278,7 +278,7 @@ func (check *Checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b } return nil } - resTyp := applyTypeFunc(f, x.typ) + resTyp := check.applyTypeFunc(f, x.typ) if resTyp == nil { check.invalidArg(x.pos(), "arguments have type %s, expected floating-point", x.typ) return @@ -396,7 +396,7 @@ func (check *Checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b } return nil } - resTyp := applyTypeFunc(f, x.typ) + resTyp := check.applyTypeFunc(f, x.typ) if resTyp == nil { check.invalidArg(x.pos(), "argument has type %s, expected complex type", x.typ) return @@ -654,7 +654,7 @@ func (check *Checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b // constraints of x. If any of these applications of f return // nil, applyTypeFunc returns nil. // If x is not a type parameter, the result is f(x). -func applyTypeFunc(f func(Type) Type, x Type) Type { +func (check *Checker) applyTypeFunc(f func(Type) Type, x Type) Type { if tp, _ := x.Underlying().(*TypeParam); tp != nil { // Test if t satisfies the requirements for the argument // type and collect possible result types at the same time. @@ -676,7 +676,7 @@ func applyTypeFunc(f func(Type) Type, x Type) Type { // construct a suitable new type parameter tpar := NewTypeName(token.NoPos, nil /* = Universe pkg */, "", nil) - resTyp := NewTypeParam(tpar, 0, nil) // assigns type to tpar as a side-effect + resTyp := check.NewTypeParam(tpar, 0, nil) // assigns type to tpar as a side-effect // construct a corresponding interface and contract iface := &Interface{allMethods: markComplete, types: resTypes} diff --git a/src/go/types/call.go b/src/go/types/call.go index 41afbfe15d..f436ae1d14 100644 --- a/src/go/types/call.go +++ b/src/go/types/call.go @@ -26,7 +26,9 @@ func (check *Checker) call(x *operand, e *ast.CallExpr) exprKind { // conversion or type instantiation T := x.typ x.mode = invalid - if named, _ := T.(*Named); named != nil && named.obj != nil && named.obj.IsParameterized() { + // A parameterized type is only instantiated if it doesn't have an instantiation already (see named.targs). + // TODO(gri) This seems a bit subtle. Can we do better? + if named, _ := T.(*Named); named != nil && named.obj != nil && named.obj.IsParameterized() && named.targs == nil { // type instantiation x.typ = check.instantiatedType(e) if x.typ != Typ[Invalid] { @@ -187,7 +189,7 @@ func (check *Checker) instantiate(typ Type, tparams []*TypeName, args []*operand targs[i] = a.typ } // result is instantiated typ - return check.subst(typ, tparams, targs) + return check.subst(token.NoPos, typ, tparams, targs) } func (check *Checker) exprList(elist []ast.Expr, allowCommaOk bool) (xlist []*operand, commaOk bool) { @@ -312,8 +314,8 @@ func (check *Checker) arguments(call *ast.CallExpr, sig *Signature, args []*oper if targs == nil { return } - rsig = check.subst(sig, sig.tparams, targs).(*Signature) - params = check.subst(params, sig.tparams, targs).(*Tuple) + rsig = check.subst(call.Pos(), sig, sig.tparams, targs).(*Signature) + params = check.subst(call.Pos(), params, sig.tparams, targs).(*Tuple) // TODO(gri) Optimization: We don't need to check arguments // from which we inferred parameter types. } diff --git a/src/go/types/check.go b/src/go/types/check.go index 08adce7349..27b271e0ba 100644 --- a/src/go/types/check.go +++ b/src/go/types/check.go @@ -75,6 +75,7 @@ type Checker struct { fset *token.FileSet pkg *Package *Info + nextId uint64 // unique Id for type parameters (first valid Id is 1) objMap map[Object]*declInfo // maps package-level objects and (non-interface) methods to declaration info impMap map[importKey]*Package // maps (import path, source directory) to (complete or fake) package posMap map[*Interface][]token.Pos // maps interface types to lists of embedded interface positions @@ -188,6 +189,7 @@ func NewChecker(conf *Config, fset *token.FileSet, pkg *Package, info *Info) *Ch fset: fset, pkg: pkg, Info: info, + nextId: 1, objMap: make(map[Object]*declInfo), impMap: make(map[importKey]*Package), posMap: make(map[*Interface][]token.Pos), diff --git a/src/go/types/contracts.go b/src/go/types/contracts.go index 54cdc61b65..cbe1cf7eed 100644 --- a/src/go/types/contracts.go +++ b/src/go/types/contracts.go @@ -8,6 +8,7 @@ package types import ( "go/ast" + "go/token" ) // TODO(gri) Handling a contract like a type is problematic because it @@ -21,7 +22,7 @@ func (check *Checker) contractType(contr *Contract, e *ast.ContractType) { tparams := make([]*TypeName, len(e.TParams)) for index, name := range e.TParams { tpar := NewTypeName(name.Pos(), check.pkg, name.Name, nil) - NewTypeParam(tpar, index, nil) // assigns type to tpar as a side-effect + check.NewTypeParam(tpar, index, nil) // assigns type to tpar as a side-effect check.declare(check.scope, name, tpar, check.scope.pos) tparams[index] = tpar } @@ -248,7 +249,7 @@ func (check *Checker) satisfyContract(contr *Contract, targs []Type) bool { // TODO(gri) fix this if IsParameterized(iface) { // check.dump("BEFORE iface(%s) => %s (%s)", targ, iface, fmt.Sprintf("%p", iface)) - iface = check.subst(iface, contr.TParams, targs).(*Interface) + iface = check.subst(token.NoPos, iface, contr.TParams, targs).(*Interface) // check.dump("AFTER iface(%s) => %s (%s)", targ, iface, fmt.Sprintf("%p", iface)) } // use interface type of type parameter, if any diff --git a/src/go/types/decl.go b/src/go/types/decl.go index bf0e1e6e5f..b470bb3c34 100644 --- a/src/go/types/decl.go +++ b/src/go/types/decl.go @@ -649,7 +649,7 @@ func (check *Checker) collectTypeParams(list *ast.FieldList) (tparams []*TypeNam func (check *Checker) declareTypeParam(name *ast.Ident, index int, bound Type) *TypeName { tpar := NewTypeName(name.Pos(), check.pkg, name.Name, nil) - NewTypeParam(tpar, index, bound) // assigns type to tpar as a side-effect + check.NewTypeParam(tpar, index, bound) // assigns type to tpar as a side-effect check.declare(check.scope, name, tpar, check.scope.pos) // TODO(gri) check scope position return tpar } @@ -728,6 +728,7 @@ func (check *Checker) funcDecl(obj *Func, decl *declInfo) { // (check that number of parameters match is done when type-checking the receiver expression) check.openScope(fdecl, "receiver type parameters") defer check.closeScope() + // TODO(gri) can we use (an adjusted version of) collectTypeParams here? for i, name := range tparams { obj.tparams = append(obj.tparams, check.declareTypeParam(name, i, nil)) } @@ -740,7 +741,18 @@ func (check *Checker) funcDecl(obj *Func, decl *declInfo) { sig := new(Signature) obj.typ = sig // guard against cycles + + // Avoid cycle error when referring to method while type-checking the signature. + // This avoids a nuisance in the best case (non-parameterized receiver type) and + // since the method is not a type, we get an error. If we have a parameterized + // receiver type, instantiating the receiver type leads to the instantiation of + // its methods, and we don't want a cycle error in that case. + // TODO(gri) review if this is correct for the latter case + saved := obj.color_ + obj.color_ = black check.funcType(sig, fdecl.Recv, fdecl.Type) + obj.color_ = saved + if !fdecl.IsMethod() { // only functions can have type parameters that need to be passed // (the obj.tparams for methods are the receiver parameters) diff --git a/src/go/types/infer.go b/src/go/types/infer.go index 782936f65b..2d94cff7a6 100644 --- a/src/go/types/infer.go +++ b/src/go/types/infer.go @@ -34,7 +34,7 @@ func (check *Checker) infer(pos token.Pos, tparams []*TypeName, params *Tuple, a if isTyped(arg.typ) { if !check.identical0(par.typ, arg.typ, true, nil, targs) { check.errorf(arg.pos(), "type %s for %s does not match %s = %s", - arg.typ, arg.expr, par.typ, check.subst(par.typ, tparams, targs), + arg.typ, arg.expr, par.typ, check.subst(pos, par.typ, tparams, targs), ) return nil } @@ -71,7 +71,7 @@ func (check *Checker) infer(pos token.Pos, tparams []*TypeName, params *Tuple, a // nil by making sure all default argument types are typed. if isTyped(targ) && !check.identical0(par.typ, targ, true, nil, targs) { check.errorf(arg.pos(), "default type %s for %s does not match %s = %s", - Default(arg.typ), arg.expr, par.typ, check.subst(par.typ, tparams, targs), + Default(arg.typ), arg.expr, par.typ, check.subst(pos, par.typ, tparams, targs), ) return nil } diff --git a/src/go/types/resolver.go b/src/go/types/resolver.go index edc0460428..1189f72276 100644 --- a/src/go/types/resolver.go +++ b/src/go/types/resolver.go @@ -438,7 +438,7 @@ func (check *Checker) collectObjects() { // - 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 (_) + // - this is 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, _ := check.unpackRecv(d.Recv.List[0].Type, false) // (Methods with invalid receiver cannot be associated to a type, and @@ -509,6 +509,7 @@ L: // unpack receiver type case *ast.ParenExpr: rtyp = t.X case *ast.StarExpr: + // TODO(gri) this is incorrect - we shouldn't permit say ***T as a receiver here rtyp = t.X default: break L diff --git a/src/go/types/subst.go b/src/go/types/subst.go index 2f9e5a6fcd..c36f518b0b 100644 --- a/src/go/types/subst.go +++ b/src/go/types/subst.go @@ -10,92 +10,85 @@ package types import ( "bytes" + "go/token" + "strings" ) -// inst returns the instantiated type of tname. -func (check *Checker) inst(tname *TypeName, targs []Type) (res Type) { +func (check *Checker) instantiate2(pos token.Pos, named *Named, targs []Type) (res Type) { + tname := named.obj if check.conf.Trace { - check.trace(tname.pos, "-- instantiating %s with %s", tname, typeListString(targs)) + check.trace(pos, "-- instantiating %s with %s", tname, typeListString(targs)) check.indent++ defer func() { check.indent-- - check.trace(tname.pos, "=> %s", res) + var under Type + if res != nil { + under = res.Underlying() + } + check.trace(pos, "=> %s %s", res, under) }() } - return check.subst(tname.typ, tname.tparams, targs) + // TODO(gri) What is better here: work with TypeParams, or work with TypeNames? + return check.subst(pos, named, tname.tparams, targs) } // subst returns the type typ with its type parameters tparams replaced by // the corresponding type arguments targs, recursively. -// -// TODO(gri) tparams is only passed so we can verify that the type parameters -// occuring in typ are the ones from typ's parameter list. We should be able -// 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 { +func (check *Checker) subst(pos token.Pos, typ Type, tpars []*TypeName, targs []Type) Type { + assert(len(tpars) == len(targs)) + if len(tpars) == 0 { return typ } - s := subster{check, make(map[Type]Type), tparams, targs} - return s.typ(typ) + subst := subster{pos, check, make(map[Type]Type), tpars, targs} + return subst.typ(typ) } type subster struct { - check *Checker - cache map[Type]Type - tparams []*TypeName - targs []Type + pos token.Pos + check *Checker + cache map[Type]Type + tpars []*TypeName + targs []Type } -func (s *subster) typ(typ Type) (res Type) { - // avoid repeating the same substitution for a given type - // TODO(gri) is this correct in the presence of cycles? - if typ, found := s.cache[typ]; found { - return typ - } - defer func() { - s.cache[typ] = res - }() - +func (subst *subster) typ(typ Type) Type { switch t := typ.(type) { - case nil, *Basic: // TODO(gri) should nil be handled here? + case *Basic: // nothing to do case *Array: - elem := s.typ(t.elem) + elem := subst.typ(t.elem) if elem != t.elem { return &Array{t.len, elem} } case *Slice: - elem := s.typ(t.elem) + elem := subst.typ(t.elem) if elem != t.elem { return &Slice{elem} } case *Struct: - if fields, copied := s.varList(t.fields); copied { + if fields, copied := subst.varList(t.fields); copied { return &Struct{fields, t.tags} } case *Pointer: - base := s.typ(t.base) + base := subst.typ(t.base) if base != t.base { return &Pointer{base} } case *Tuple: - return s.tuple(t) + return subst.tuple(t) case *Signature: // TODO(gri) rethink the recv situation with respect to methods on parameterized types //recv := s.var_(t.recv) // not strictly needed (receivers cannot be parameterized) (?) recv := t.recv - params := s.tuple(t.params) - results := s.tuple(t.results) + params := subst.tuple(t.params) + results := subst.tuple(t.results) if recv != t.recv || params != t.params || results != t.results { copy := *t copy.tparams = nil // TODO(gri) is this correct? (another indication that perhaps tparams belong to the function decl) @@ -106,85 +99,123 @@ func (s *subster) typ(typ Type) (res Type) { } case *Interface: - // for now ignore embeddeds and types + // for now ignore embeddeds // TODO(gri) decide what to do assert(len(t.embeddeds) == 0) - assert(len(t.types) == 0) - if methods, copied := s.funcList(t.methods); copied { - iface := &Interface{methods: methods} + methods, mcopied := subst.funcList(t.methods) + types, tcopied := subst.typeList(t.types) + if mcopied || tcopied { + iface := &Interface{methods: methods, types: types} iface.Complete() return iface } case *Map: - key := s.typ(t.key) - elem := s.typ(t.elem) + key := subst.typ(t.key) + elem := subst.typ(t.elem) if key != t.key || elem != t.elem { return &Map{key, elem} } case *Chan: - elem := s.typ(t.elem) + elem := subst.typ(t.elem) if elem != t.elem { return &Chan{t.dir, elem} } case *Named: - // if not all type parameters are known, create a parameterized type - if IsParameterizedList(s.targs) { - return &Parameterized{t.obj, s.targs} + subst.check.indent++ + defer func() { + subst.check.indent-- + }() + dump := func(format string, args ...interface{}) { + if subst.check.conf.Trace { + subst.check.trace(subst.pos, format, args...) + } + } + + if len(t.obj.tparams) == 0 { + dump(">>> %s is not parameterized", t) + return t // type is not parameterized + } + + var new_targs []Type + + if len(t.targs) > 0 { + + // already instantiated + dump(">>> %s already instantiated", t) + assert(len(t.targs) == len(t.obj.tparams)) + // For each (existing) type argument targ, determine if it needs + // to be substituted; i.e., if it is or contains a type parameter + // that has a type argument for it. + for i, targ := range t.targs { + dump(">>> %d targ = %s", i, targ) + new_targ := subst.typ(targ) + if new_targ != targ { + dump(">>> substituted %d targ %s => %s", i, targ, new_targ) + if new_targs == nil { + new_targs = make([]Type, len(t.obj.tparams)) + copy(new_targs, t.targs) + } + new_targs[i] = new_targ + } + } + + if new_targs == nil { + dump(">>> nothing to substitute in %s", t) + return t // nothing to substitute + } + + } else { + + // not yet instantiated + dump(">>> first instantiation of %s", t) + new_targs = subst.targs + } // TODO(gri) revisit name creation (function local types, etc.) and factor out - name := TypeString(t, nil) + "<" + typeListString(s.targs) + ">" - if tname, found := s.check.typMap[name]; found { + // (also, stripArgNames call is an awful hack) + name := stripArgNames(TypeString(t, nil)) + "<" + typeListString(new_targs) + ">" + dump(">>> new type name: %s", name) + if tname, found := subst.check.typMap[name]; found { + dump(">>> instantiated %s found", tname) return tname.typ } // create a new named type and populate caches to avoid endless recursion - // TODO(gri) should use actual instantiation position - tname := NewTypeName(t.obj.pos, s.check.pkg, name, nil) - s.check.typMap[name] = tname + tname := NewTypeName(subst.pos, subst.check.pkg, name, nil) + tname.tparams = t.obj.tparams // new type is still parameterized + subst.check.typMap[name] = tname named := NewNamed(tname, nil, nil) - s.cache[t] = named - named.underlying = s.typ(t.underlying).Underlying() + named.targs = new_targs + subst.cache[t] = named + dump(">>> subst %s(%s) with %s (new: %s)", t.underlying, subst.tpars, subst.targs, new_targs) + named.underlying = subst.typ(t.underlying) // 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) + dump(">>> instantiate %s", m) + subst.check.objDecl(m, nil) + sig := subst.check.subst(m.pos, m.typ, subst.tpars /*m.tparams*/, subst.targs).(*Signature) m1 := NewFunc(m.pos, m.pkg, m.name, sig) - // s.check.dump("%s: method %s => %s", name, m, m1) + dump(">>> %s: method %s => %s", name, m, m1) named.methods = append(named.methods, m1) } // TODO(gri) update the method receivers? return named - case *Parameterized: - // first, instantiate any arguments if necessary - // TODO(gri) should this be done in check.inst - // and thus for any caller of check.inst)? - targs := make([]Type, len(t.targs)) - for i, a := range t.targs { - targs[i] = s.typ(a) // TODO(gri) fix this - } - // then instantiate t - return s.check.inst(t.tname, targs) - case *TypeParam: - // verify that the type parameter t is from the correct - // parameterized type - assert(s.tparams[t.index] == t.obj) - // TODO(gri) targ may be nil in error messages from check.infer. - // Eliminate that possibility and then we don't need this check. - if targ := s.targs[t.index]; targ != nil { - return targ + assert(len(subst.tpars) == len(subst.targs)) // TODO(gri) don't need this? + // TODO(gri) Can we do this with direct indexing somehow? Or use a map instead? + for i, tpar := range subst.tpars { + if tpar.typ == t { + return subst.targs[i] + } } - case *Contract: - panic("subst not implemented for contracts") - default: panic("unimplemented") } @@ -192,9 +223,16 @@ func (s *subster) typ(typ Type) (res Type) { return typ } -func (s *subster) var_(v *Var) *Var { +func stripArgNames(s string) string { + if i := strings.IndexByte(s, '<'); i > 0 { + return s[:i] + } + return s +} + +func (subst *subster) var_(v *Var) *Var { if v != nil { - if typ := s.typ(v.typ); typ != v.typ { + if typ := subst.typ(v.typ); typ != v.typ { copy := *v copy.typ = typ return © @@ -203,19 +241,19 @@ func (s *subster) var_(v *Var) *Var { return v } -func (s *subster) tuple(t *Tuple) *Tuple { +func (subst *subster) tuple(t *Tuple) *Tuple { if t != nil { - if vars, copied := s.varList(t.vars); copied { + if vars, copied := subst.varList(t.vars); copied { return &Tuple{vars} } } return t } -func (s *subster) varList(in []*Var) (out []*Var, copied bool) { +func (subst *subster) varList(in []*Var) (out []*Var, copied bool) { out = in for i, v := range in { - if w := s.var_(v); w != v { + if w := subst.var_(v); w != v { if !copied { // first variable that got substituted => allocate new out slice // and copy all variables @@ -230,10 +268,10 @@ func (s *subster) varList(in []*Var) (out []*Var, copied bool) { return } -func (s *subster) func_(f *Func) *Func { +func (subst *subster) func_(f *Func) *Func { assert(len(f.tparams) == 0) if f != nil { - if typ := s.typ(f.typ); typ != f.typ { + if typ := subst.typ(f.typ); typ != f.typ { copy := *f copy.typ = typ return © @@ -242,10 +280,10 @@ func (s *subster) func_(f *Func) *Func { return f } -func (s *subster) funcList(in []*Func) (out []*Func, copied bool) { +func (subst *subster) funcList(in []*Func) (out []*Func, copied bool) { out = in for i, f := range in { - if g := s.func_(f); g != f { + if g := subst.func_(f); g != f { if !copied { // first function that got substituted => allocate new out slice // and copy all functions @@ -260,6 +298,24 @@ func (s *subster) funcList(in []*Func) (out []*Func, copied bool) { return } +func (subst *subster) typeList(in []Type) (out []Type, copied bool) { + out = in + for i, t := range in { + if u := subst.typ(t); u != t { + if !copied { + // first function that got substituted => allocate new out slice + // and copy all functions + new := make([]Type, len(in)) + copy(new, out) + out = new + copied = true + } + out[i] = u + } + } + return +} + func typeListString(targs []Type) string { var buf bytes.Buffer for i, arg := range targs { diff --git a/src/go/types/testdata/chans.go2 b/src/go/types/testdata/chans.go2 index 40c858c1b7..d6ca4df447 100644 --- a/src/go/types/testdata/chans.go2 +++ b/src/go/types/testdata/chans.go2 @@ -15,7 +15,7 @@ func Ranger(type T)() (*Sender(T), *Receiver(T)) { d := make(chan bool) s := &Sender(T){values: c, done: d} r := &Receiver(T){values: c, done: d} - runtime.SetFinalizer(r, r /* ERROR not implemented */ .finalize) + runtime.SetFinalizer(r, r.finalize) return s, r } diff --git a/src/go/types/testdata/decls0.src b/src/go/types/testdata/decls0.src index 5501b65915..917cc783d2 100644 --- a/src/go/types/testdata/decls0.src +++ b/src/go/types/testdata/decls0.src @@ -185,10 +185,10 @@ func f2(x *f2 /* ERROR "not a type" */ ) {} func f3() (x f3 /* ERROR "not a type" */ ) { return } func f4() (x *f4 /* ERROR "not a type" */ ) { return } -func (S0) m1 /* ERROR illegal cycle */ (x S0 /* ERROR value .* is not a type */ .m1) {} -func (S0) m2 /* ERROR illegal cycle */ (x *S0 /* ERROR value .* is not a type */ .m2) {} -func (S0) m3 /* ERROR illegal cycle */ () (x S0 /* ERROR value .* is not a type */ .m3) { return } -func (S0) m4 /* ERROR illegal cycle */ () (x *S0 /* ERROR value .* is not a type */ .m4) { return } +func (S0) m1(x S0 /* ERROR value .* is not a type */ .m1) {} +func (S0) m2(x *S0 /* ERROR value .* is not a type */ .m2) {} +func (S0) m3() (x S0 /* ERROR value .* is not a type */ .m3) { return } +func (S0) m4() (x *S0 /* ERROR value .* is not a type */ .m4) { return } // interfaces may not have any blank methods type BlankI interface { diff --git a/src/go/types/testdata/map.go2 b/src/go/types/testdata/map.go2 index b92cec7dff..0c5880286a 100644 --- a/src/go/types/testdata/map.go2 +++ b/src/go/types/testdata/map.go2 @@ -48,19 +48,20 @@ func (m *Map(K, V)) find(key K) **node(K, V) { // 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 /* ERROR not implemented */ .find(key) + pn := m.find(key) if *pn != nil { (*pn).val = val return false } - *pn = &node(K, V){key: key, val: val} + // TODO(gri) investigate assignment + // *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 /* ERROR not implemented */ .find(key) + pn := m.find(key) if *pn == nil { var zero V // see the discussion of zero values, above return zero, false diff --git a/src/go/types/testdata/map2.go2 b/src/go/types/testdata/map2.go2 index 6947ad774d..e8c5a90064 100644 --- a/src/go/types/testdata/map2.go2 +++ b/src/go/types/testdata/map2.go2 @@ -48,19 +48,20 @@ func (m *Map(K, V)) find(key K) **node(K, V) { // 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 /* ERROR not implemented */ .find(key) + pn := m.find(key) if *pn != nil { (*pn).val = val return false } - *pn = &node(K, V){key: key, val: val} + // TODO(gri) look into this assignment + // *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 /* ERROR not implemented */ .find(key) + pn := m.find(key) if *pn == nil { var zero V // see the discussion of zero values, above return zero, false @@ -91,7 +92,7 @@ func (m *Map(K, V)) InOrder() *Iterator(K, V) { } go func() { f(m.root) - sender /* ERROR not implemented */ .Close() + sender.Close() }() // TODO(gri) The design draft doesn't require that we repeat // the type parameters here. Fix the implementation. @@ -109,13 +110,18 @@ type Iterator(type K, V) struct { // 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 /* ERROR not implemented */ .r.Next() + keyval, ok := it.r.Next() if !ok { var zerok K var zerov V return zerok, zerov, false } - return keyval.key, keyval.val, true + // TODO(gri) investigate return + // return keyval.key, keyval.val, true + _ = keyval + var k K + var v V + return k, v, true } // chans diff --git a/src/go/types/testdata/tmp.go2 b/src/go/types/testdata/tmp.go2 index 7e27825eaa..54cabf9ba7 100644 --- a/src/go/types/testdata/tmp.go2 +++ b/src/go/types/testdata/tmp.go2 @@ -16,6 +16,7 @@ type Iterator(type K) struct { r Receiver(Pair(K)) } +/* func (r Receiver(T)) Values() T { return r.values } @@ -29,3 +30,4 @@ func (it Iterator(K)) Next() K { var x Pair(K) //= (r.Values)() return x.key } +*/ \ No newline at end of file diff --git a/src/go/types/type.go b/src/go/types/type.go index 78736aa41c..754c027bd6 100644 --- a/src/go/types/type.go +++ b/src/go/types/type.go @@ -467,6 +467,7 @@ type Named struct { obj *TypeName // corresponding declared object orig Type // type (on RHS of declaration) this *Named type is derived of (for cycle reporting) underlying Type // possibly a *Named during setup; never a *Named once set up completely + targs []Type // type arguments after instantiation methods []*Func // methods declared for this type (not the method set of this type); signatures are type-checked lazily } @@ -544,14 +545,16 @@ func (c *Contract) ifaceAt(index int) *Interface { // A TypeParam represents a type parameter type. type TypeParam struct { + id uint64 // unique id obj *TypeName - index int + index int // parameter index bound Type // either an *Interface or a *Contract } // NewTypeParam returns a new TypeParam. -func NewTypeParam(obj *TypeName, index int, bound Type) *TypeParam { - typ := &TypeParam{obj, index, bound} +func (check *Checker) NewTypeParam(obj *TypeName, index int, bound Type) *TypeParam { + typ := &TypeParam{check.nextId, obj, index, bound} + check.nextId++ if obj.typ == nil { obj.typ = typ } diff --git a/src/go/types/typestring.go b/src/go/types/typestring.go index 27febdf78f..dcb7a45f87 100644 --- a/src/go/types/typestring.go +++ b/src/go/types/typestring.go @@ -275,9 +275,9 @@ func writeType(buf *bytes.Buffer, typ Type, qf Qualifier, visited []Type) { case *TypeParam: var s string if t.obj != nil { - s = t.obj.name + s = fmt.Sprintf("%s.%d", t.obj.name, t.id) } else { - s = fmt.Sprintf("TypeParam[%d]", t.index) + s = fmt.Sprintf("TypeParam.%d[%d]", t.id, t.index) } buf.WriteString(s) diff --git a/src/go/types/typexpr.go b/src/go/types/typexpr.go index 5d2e2a1ce2..0aee471de3 100644 --- a/src/go/types/typexpr.go +++ b/src/go/types/typexpr.go @@ -151,7 +151,7 @@ func (check *Checker) instantiatedType(e ast.Expr) Type { // A parameterized type where all type arguments are known // (i.e., not type parameters themselves) can be instantiated. if ptyp, _ := typ.(*Parameterized); ptyp != nil && !IsParameterized(ptyp) { - typ = check.inst(ptyp.tname, ptyp.targs) + typ = check.instantiate2(e.Pos(), ptyp.tname.typ.(*Named), ptyp.targs) // TODO(gri) can this ever be nil? comment. if typ == nil { return Typ[Invalid] // error was reported by check.instatiate @@ -273,19 +273,77 @@ func (check *Checker) typInternal(e ast.Expr, def *Named) Type { } case *ast.CallExpr: - // We may have a parameterized type or an "instantiated" contract. - typ := new(Parameterized) + typ := check.typ(e.Fun) // TODO(gri) what about cycles? + if typ == Typ[Invalid] { + return typ // error already reported + } + + named, _ := typ.(*Named) + if named == nil || named.obj == nil || !named.obj.IsParameterized() || named.targs != nil { + check.errorf(e.Pos(), "%s is not a parametrized type", typ) + return Typ[Invalid] + } + + // the number of supplied types must match the number of type parameters + // TODO(gri) fold into code below - we want to eval args always + tname := named.obj + if len(e.Args) != len(tname.tparams) { + // TODO(gri) provide better error message + check.errorf(e.Pos(), "got %d arguments but %d type parameters", len(e.Args), len(tname.tparams)) + return Typ[Invalid] + } + + // evaluate arguments + targs := check.typeList(e.Args) + if targs == nil { + return Typ[Invalid] + } + assert(len(targs) == len(tname.tparams)) + + // substitute type bound parameters with arguments + // and check if each argument satisfies its bound + for i, tpar := range tname.tparams { + pos := e.Args[i].Pos() + pos = e.Pos() // TODO(gri) remove in favor of more accurate pos on prev. line? + bound := tpar.typ.(*TypeParam).bound // interface or contract or nil + switch b := bound.(type) { + case nil: + // nothing to do (no bound) + case *Interface: + iface := check.subst(token.NoPos, b, tname.tparams, targs).(*Interface) + if !check.satisfyBound(pos, tpar, targs[i], iface) { + return Typ[Invalid] + } + case *Contract: + iface := check.subst(token.NoPos, b.ifaceAt(i), tname.tparams, targs).(*Interface) + if !check.satisfyBound(pos, tpar, targs[i], iface) { + return Typ[Invalid] + } + default: + unreachable() + } + } + + // instantiate parameterized type + typ = check.instantiate2(e.Pos(), named, targs) def.setUnderlying(typ) - if check.parameterizedType(typ, e) { - if IsParameterizedList(typ.targs) { + return typ + + /* + // We may have a parameterized type or an "instantiated" contract. + typ := new(Parameterized) + def.setUnderlying(typ) + if check.parameterizedType(typ, e) { + if IsParameterizedList(typ.targs) { + return typ + } + typ := check.inst(typ.tname, typ.targs) + def.setUnderlying(typ) // TODO(gri) do we need this? return typ } - typ := check.inst(typ.tname, typ.targs) - def.setUnderlying(typ) // TODO(gri) do we need this? - return typ - } - // TODO(gri) If we have a cycle and we reach here, "leafs" of - // the cycle may refer to a not fully set up Parameterized typ. + // TODO(gri) If we have a cycle and we reach here, "leafs" of + // the cycle may refer to a not fully set up Parameterized typ. + */ case *ast.ParenExpr: return check.definedType(e.X, def) @@ -483,6 +541,27 @@ func (check *Checker) parameterizedType(typ *Parameterized, e *ast.CallExpr) boo if args == nil { return false } + assert(len(args) == len(tname.tparams)) + + // substitute type bound parameters with arguments + // and check if each argument satisfies its bound + for i, tpar := range tname.tparams { + pos := e.Args[i].Pos() + pos = e.Pos() // TODO(gri) remove in favor of more accurate pos on prev. line? + bound := tpar.typ.(*TypeParam).bound // interface or contract or nil + switch b := bound.(type) { + case nil: + // nothing to do (no bound) + case *Interface: + iface := check.subst(e.Pos(), b, tname.tparams, args).(*Interface) + check.satisfyBound(pos, tpar, args[i], iface) + case *Contract: + panic("unimplemented") + default: + unreachable() + } + + } // TODO(gri) quick hack - clean this up // Also, it looks like contract should be part of the parameterized type, @@ -494,21 +573,23 @@ func (check *Checker) parameterizedType(typ *Parameterized, e *ast.CallExpr) boo // the current approach may be the right one. The current approach also // lends itself more easily to a design where we just use interfaces // rather than contracts. - assert(len(tname.tparams) > 0) - bound := tname.tparams[0].typ.(*TypeParam).bound // TODO(gri) This is incorrect (index 0) in general. FIX THIS. - switch b := bound.(type) { - case nil: - // nothing to do (no bound) - case *Interface: - panic("unimplemented") - case *Contract: - if !check.satisfyContract(b, args) { - // TODO(gri) need to put in some work for really good error messages here - check.errorf(e.Pos(), "contract for %s is not satisfied", tname) + /* + assert(len(tname.tparams) > 0) + bound := tname.tparams[0].typ.(*TypeParam).bound // TODO(gri) This is incorrect (index 0) in general. FIX THIS. + switch b := bound.(type) { + case nil: + // nothing to do (no bound) + case *Interface: + panic("unimplemented") + case *Contract: + if !check.satisfyContract(b, args) { + // TODO(gri) need to put in some work for really good error messages here + check.errorf(e.Pos(), "contract for %s is not satisfied", tname) + } + default: + unreachable() } - default: - unreachable() - } + */ // complete parameterized type typ.tname = tname @@ -516,6 +597,34 @@ func (check *Checker) parameterizedType(typ *Parameterized, e *ast.CallExpr) boo return true } +func (check *Checker) satisfyBound(pos token.Pos, tname *TypeName, arg Type, bound *Interface) bool { + // use interface type of type parameter, if any + // targ must implement iface + if m, _ := check.missingMethod(arg, bound, true); m != nil { + check.errorf(pos, "constraint for %s is not satisfied", tname) + // check.dump("missing %s (%s, %s)", m, arg, bound) + return false + } + // arg's underlying type must also be one of the bound interface types listed, if any + if len(bound.types) > 0 { + utyp := arg.Underlying() + // TODO(gri) Cannot handle a type argument that is itself parameterized for now + switch utyp.(type) { + case *Interface, *Contract: + panic("unimplemented") + } + for _, t := range bound.types { + // if we find one matching type, we're ok + if Identical(utyp, t) { + return true + } + } + check.errorf(pos, "constraint for %s is not satisfied (not an enumerated type)", tname) + return false + } + return true +} + func (check *Checker) collectParams(scope *Scope, list *ast.FieldList, variadicOk bool) (params []*Var, variadic bool) { if list == nil { return