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