mirror of https://github.com/golang/go.git
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
This commit is contained in:
parent
46442bd4a9
commit
703974d608
|
|
@ -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 */, "<type parameter literal>", 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}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
*/
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue