1
0
mirror of https://github.com/golang/go synced 2024-11-26 06:27:58 -07:00

cmd/compile: get instantiated generic types working with interfaces

Get instantiatiated generic types working with interfaces, including
typechecking assignments to interfaces and instantiating all the methods
properly. To get it all working, this change includes:

 - Add support for substituting in interfaces in subster.typ()

 - Fill in the info for the methods for all instantiated generic types,
   so those methods will be available for later typechecking (by the old
   typechecker) when assigning an instantiated generic type to an
   interface. We also want those methods available so we have the list
   when we want to instantiate all methods of an instantiated type. We
   have both for instantiated types encountered during the initial noder
   phase, and for instantiated types created during stenciling of a
   function/method.

 - When we first create a fully-instantiated generic type (whether
   during initial noder2 pass or while instantiating a method/function),
   add it to a list so that all of its methods will also be
   instantiated. This is needed so that an instantiated type can be
   assigned to an interface.

 - Properly substitute type names in the names of instantiated methods.

 - New accessor methods for types.Type.RParam.

 - To deal with generic types which are empty structs (or just don't use
   their type params anywhere), we want to set HasTParam if a named type
   has any type params that are not fully instantiated, even if the
   type param is not used in the type.

 - In subst.typ() and elsewhere, always set sym.Def for a new forwarding
   type we are creating, so we always create a single unique type for
   each generic type instantiation. This handles recursion within a
   type, and also recursive relationships across many types or methods.
   We remove the seen[] hashtable, which was serving the same purpose,
   but for subst.typ() only. We now handle all kinds of recursive types.

 - We don't seem to need to force types.CheckSize() on
   created/substituted generic types anymore, so commented out for now.

 - Add an RParams accessor to types2.Signature, and also a new
   exported types2.AsSignature() function.

Change-Id: If6c5dd98427b20bfe9de3379cc16f83df9c9b632
Reviewed-on: https://go-review.googlesource.com/c/go/+/298449
Run-TryBot: Dan Scales <danscales@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Trust: Dan Scales <danscales@google.com>
Reviewed-by: Robert Griesemer <gri@golang.org>
This commit is contained in:
Dan Scales 2021-03-03 13:33:27 -08:00
parent 034fffdb49
commit a70eb2c9f2
10 changed files with 623 additions and 142 deletions

View File

@ -134,14 +134,14 @@ func (g *irgen) typeDecl(out *ir.Nodes, decl *syntax.TypeDecl) {
}
// We need to use g.typeExpr(decl.Type) here to ensure that for
// chained, defined-type declarations like
// chained, defined-type declarations like:
//
// type T U
//
// //go:notinheap
// type U struct { … }
//
// that we mark both T and U as NotInHeap. If we instead used just
// we mark both T and U as NotInHeap. If we instead used just
// g.typ(otyp.Underlying()), then we'd instead set T's underlying
// type directly to the struct type (which is not marked NotInHeap)
// and fail to mark T as NotInHeap.
@ -154,6 +154,12 @@ func (g *irgen) typeDecl(out *ir.Nodes, decl *syntax.TypeDecl) {
// [mdempsky: Subtleties like these are why I always vehemently
// object to new type pragmas.]
ntyp.SetUnderlying(g.typeExpr(decl.Type))
if len(decl.TParamList) > 0 {
// Set HasTParam if there are any tparams, even if no tparams are
// used in the type itself (e.g., if it is an empty struct, or no
// fields in the struct use the tparam).
ntyp.SetHasTParam(true)
}
types.ResumeCheckSize()
if otyp, ok := otyp.(*types2.Named); ok && otyp.NumMethods() != 0 {

View File

@ -100,6 +100,9 @@ type irgen struct {
objs map[types2.Object]*ir.Name
typs map[types2.Type]*types.Type
marker dwarfgen.ScopeMarker
// Fully-instantiated generic types whose methods should be instantiated
instTypeList []*types.Type
}
func (g *irgen) generate(noders []*noder) {

View File

@ -18,10 +18,25 @@ import (
"strings"
)
// stencil scans functions for instantiated generic function calls and
// creates the required stencils for simple generic functions.
// For catching problems as we add more features
// TODO(danscales): remove assertions or replace with base.FatalfAt()
func assert(p bool) {
if !p {
panic("assertion failed")
}
}
// stencil scans functions for instantiated generic function calls and creates the
// required instantiations for simple generic functions. It also creates
// instantiated methods for all fully-instantiated generic types that have been
// encountered already or new ones that are encountered during the stenciling
// process.
func (g *irgen) stencil() {
g.target.Stencils = make(map[*types.Sym]*ir.Func)
// Instantiate the methods of instantiated generic types that we have seen so far.
g.instantiateMethods()
// Don't use range(g.target.Decls) - we also want to process any new instantiated
// functions that are created during this loop, in order to handle generic
// functions calling other generic functions.
@ -65,7 +80,7 @@ func (g *irgen) stencil() {
// instantiation.
call := n.(*ir.CallExpr)
inst := call.X.(*ir.InstExpr)
st := g.getInstantiation(inst)
st := g.getInstantiationForNode(inst)
// Replace the OFUNCINST with a direct reference to the
// new stenciled function
call.X = st.Nname
@ -94,7 +109,7 @@ func (g *irgen) stencil() {
var edit func(ir.Node) ir.Node
edit = func(x ir.Node) ir.Node {
if x.Op() == ir.OFUNCINST {
st := g.getInstantiation(x.(*ir.InstExpr))
st := g.getInstantiationForNode(x.(*ir.InstExpr))
return st.Nname
}
ir.EditChildren(x, edit)
@ -105,27 +120,65 @@ func (g *irgen) stencil() {
if base.Flag.W > 1 && modified {
ir.Dump(fmt.Sprintf("\nmodified %v", decl), decl)
}
// We may have seen new fully-instantiated generic types while
// instantiating any needed functions/methods in the above
// function. If so, instantiate all the methods of those types
// (which will then lead to more function/methods to scan in the loop).
g.instantiateMethods()
}
}
// getInstantiation gets the instantiated function corresponding to inst. If the
// instantiated function is not already cached, then it calls genericStub to
// create the new instantiation.
func (g *irgen) getInstantiation(inst *ir.InstExpr) *ir.Func {
var sym *types.Sym
if meth, ok := inst.X.(*ir.SelectorExpr); ok {
// Write the name of the generic method, including receiver type
sym = makeInstName(meth.Selection.Nname.Sym(), inst.Targs)
} else {
sym = makeInstName(inst.X.(*ir.Name).Name().Sym(), inst.Targs)
// instantiateMethods instantiates all the methods of all fully-instantiated
// generic types that have been added to g.instTypeList.
func (g *irgen) instantiateMethods() {
for i := 0; i < len(g.instTypeList); i++ {
typ := g.instTypeList[i]
// Get the base generic type by looking up the symbol of the
// generic (uninstantiated) name.
baseSym := typ.Sym().Pkg.Lookup(genericTypeName(typ.Sym()))
baseType := baseSym.Def.(*ir.Name).Type()
for j, m := range typ.Methods().Slice() {
name := m.Nname.(*ir.Name)
targs := make([]ir.Node, len(typ.RParams()))
for k, targ := range typ.RParams() {
targs[k] = ir.TypeNode(targ)
}
baseNname := baseType.Methods().Slice()[j].Nname.(*ir.Name)
name.Func = g.getInstantiation(baseNname, targs, true)
}
}
//fmt.Printf("Found generic func call in %v to %v\n", f, s)
g.instTypeList = nil
}
// genericSym returns the name of the base generic type for the type named by
// sym. It simply returns the name obtained by removing everything after the
// first bracket ("[").
func genericTypeName(sym *types.Sym) string {
return sym.Name[0:strings.Index(sym.Name, "[")]
}
// getInstantiationForNode returns the function/method instantiation for a
// InstExpr node inst.
func (g *irgen) getInstantiationForNode(inst *ir.InstExpr) *ir.Func {
if meth, ok := inst.X.(*ir.SelectorExpr); ok {
return g.getInstantiation(meth.Selection.Nname.(*ir.Name), inst.Targs, true)
} else {
return g.getInstantiation(inst.X.(*ir.Name), inst.Targs, false)
}
}
// getInstantiation gets the instantiantion of the function or method nameNode
// with the type arguments targs. If the instantiated function is not already
// cached, then it calls genericSubst to create the new instantiation.
func (g *irgen) getInstantiation(nameNode *ir.Name, targs []ir.Node, isMeth bool) *ir.Func {
sym := makeInstName(nameNode.Sym(), targs, isMeth)
st := g.target.Stencils[sym]
if st == nil {
// If instantiation doesn't exist yet, create it and add
// to the list of decls.
st = g.genericSubst(sym, inst)
st = g.genericSubst(sym, nameNode, targs, isMeth)
g.target.Stencils[sym] = st
g.target.Decls = append(g.target.Decls, st)
if base.Flag.W > 1 {
@ -135,11 +188,29 @@ func (g *irgen) getInstantiation(inst *ir.InstExpr) *ir.Func {
return st
}
// makeInstName makes the unique name for a stenciled generic function, based on
// the name of the function and the targs.
func makeInstName(fnsym *types.Sym, targs []ir.Node) *types.Sym {
b := bytes.NewBufferString("#")
b.WriteString(fnsym.Name)
// makeInstName makes the unique name for a stenciled generic function or method,
// based on the name of the function fy=nsym and the targs. It replaces any
// existing bracket type list in the name. makeInstName asserts that fnsym has
// brackets in its name if and only if hasBrackets is true.
// TODO(danscales): remove the assertions and the hasBrackets argument later.
//
// Names of declared generic functions have no brackets originally, so hasBrackets
// should be false. Names of generic methods already have brackets, since the new
// type parameter is specified in the generic type of the receiver (e.g. func
// (func (v *value[T]).set(...) { ... } has the original name (*value[T]).set.
//
// The standard naming is something like: 'genFn[int,bool]' for functions and
// '(*genType[int,bool]).methodName' for methods
func makeInstName(fnsym *types.Sym, targs []ir.Node, hasBrackets bool) *types.Sym {
b := bytes.NewBufferString("")
name := fnsym.Name
i := strings.Index(name, "[")
assert(hasBrackets == (i >= 0))
if i >= 0 {
b.WriteString(name[0:i])
} else {
b.WriteString(name)
}
b.WriteString("[")
for i, targ := range targs {
if i > 0 {
@ -148,60 +219,64 @@ func makeInstName(fnsym *types.Sym, targs []ir.Node) *types.Sym {
b.WriteString(targ.Type().String())
}
b.WriteString("]")
if i >= 0 {
i2 := strings.Index(name[i:], "]")
assert(i2 >= 0)
b.WriteString(name[i+i2+1:])
}
return typecheck.Lookup(b.String())
}
// Struct containing info needed for doing the substitution as we create the
// instantiation of a generic function with specified type arguments.
type subster struct {
g *irgen
newf *ir.Func // Func node for the new stenciled function
tparams []*types.Field
targs []ir.Node
g *irgen
isMethod bool // If a method is being instantiated
newf *ir.Func // Func node for the new stenciled function
tparams []*types.Field
targs []ir.Node
// The substitution map from name nodes in the generic function to the
// name nodes in the new stenciled function.
vars map[*ir.Name]*ir.Name
seen map[*types.Type]*types.Type
}
// genericSubst returns a new function with the specified name. The function is an
// instantiation of a generic function or method with type params, as specified by
// inst. For a method with a generic receiver, it returns an instantiated function
// type where the receiver becomes the first parameter. Otherwise the instantiated
// method would still need to be transformed by later compiler phases.
func (g *irgen) genericSubst(name *types.Sym, inst *ir.InstExpr) *ir.Func {
var nameNode *ir.Name
// genericSubst returns a new function with name newsym. The function is an
// instantiation of a generic function or method specified by namedNode with type
// args targs. For a method with a generic receiver, it returns an instantiated
// function type where the receiver becomes the first parameter. Otherwise the
// instantiated method would still need to be transformed by later compiler
// phases.
func (g *irgen) genericSubst(newsym *types.Sym, nameNode *ir.Name, targs []ir.Node, isMethod bool) *ir.Func {
var tparams []*types.Field
if selExpr, ok := inst.X.(*ir.SelectorExpr); ok {
if isMethod {
// Get the type params from the method receiver (after skipping
// over any pointer)
nameNode = ir.AsNode(selExpr.Selection.Nname).(*ir.Name)
recvType := selExpr.Type().Recv().Type
if recvType.IsPtr() {
recvType = recvType.Elem()
}
tparams = make([]*types.Field, len(recvType.RParams))
for i, rparam := range recvType.RParams {
recvType := nameNode.Type().Recv().Type
recvType = deref(recvType)
tparams = make([]*types.Field, len(recvType.RParams()))
for i, rparam := range recvType.RParams() {
tparams[i] = types.NewField(src.NoXPos, nil, rparam)
}
} else {
nameNode = inst.X.(*ir.Name)
tparams = nameNode.Type().TParams().Fields().Slice()
}
gf := nameNode.Func
newf := ir.NewFunc(inst.Pos())
newf.Nname = ir.NewNameAt(inst.Pos(), name)
// Pos of the instantiated function is same as the generic function
newf := ir.NewFunc(gf.Pos())
newf.Nname = ir.NewNameAt(gf.Pos(), newsym)
newf.Nname.Func = newf
newf.Nname.Defn = newf
name.Def = newf.Nname
newsym.Def = newf.Nname
assert(len(tparams) == len(targs))
subst := &subster{
g: g,
newf: newf,
tparams: tparams,
targs: inst.Targs,
vars: make(map[*ir.Name]*ir.Name),
seen: make(map[*types.Type]*types.Type),
g: g,
isMethod: isMethod,
newf: newf,
tparams: tparams,
targs: targs,
vars: make(map[*ir.Name]*ir.Name),
}
newf.Dcl = make([]*ir.Name, len(gf.Dcl))
@ -213,7 +288,7 @@ func (g *irgen) genericSubst(name *types.Sym, inst *ir.InstExpr) *ir.Func {
// Ugly: we have to insert the Name nodes of the parameters/results into
// the function type. The current function type has no Nname fields set,
// because it came via conversion from the types2 type.
oldt := inst.X.Type()
oldt := nameNode.Type()
// We also transform a generic method type to the corresponding
// instantiated function type where the receiver is the first parameter.
newt := types.NewSignature(oldt.Pkg(), nil, nil,
@ -326,7 +401,9 @@ func (subst *subster) node(n ir.Node) ir.Node {
}
newfn.SetIsHiddenClosure(true)
m.(*ir.ClosureExpr).Func = newfn
newsym := makeInstName(oldfn.Nname.Sym(), subst.targs)
// Closure name can already have brackets, if it derives
// from a generic method
newsym := makeInstName(oldfn.Nname.Sym(), subst.targs, subst.isMethod)
newfn.Nname = ir.NewNameAt(oldfn.Nname.Pos(), newsym)
newfn.Nname.Func = newfn
newfn.Nname.Defn = newfn
@ -379,6 +456,12 @@ func (subst *subster) list(l []ir.Node) []ir.Node {
// Nname is in subst.vars.
func (subst *subster) tstruct(t *types.Type) *types.Type {
if t.NumFields() == 0 {
if t.HasTParam() {
// For an empty struct, we need to return a new type,
// since it may now be fully instantiated (HasTParam
// becomes false).
return types.NewStruct(t.Pkg(), nil)
}
return t
}
var newfields []*types.Field
@ -391,12 +474,21 @@ func (subst *subster) tstruct(t *types.Type) *types.Type {
}
}
if newfields != nil {
// TODO(danscales): make sure this works for the field
// names of embedded types (which should keep the name of
// the type param, not the instantiated type).
newfields[i] = types.NewField(f.Pos, f.Sym, t2)
if f.Nname != nil {
// f.Nname may not be in subst.vars[] if this is
// a function name or a function instantiation type
// that we are translating
newfields[i].Nname = subst.vars[f.Nname.(*ir.Name)]
v := subst.vars[f.Nname.(*ir.Name)]
// Be careful not to put a nil var into Nname,
// since Nname is an interface, so it would be a
// non-nil interface.
if v != nil {
newfields[i].Nname = v
}
}
}
}
@ -407,7 +499,32 @@ func (subst *subster) tstruct(t *types.Type) *types.Type {
}
// instTypeName creates a name for an instantiated type, based on the type args
// tinter substitutes type params in types of the methods of an interface type.
func (subst *subster) tinter(t *types.Type) *types.Type {
if t.Methods().Len() == 0 {
return t
}
var newfields []*types.Field
for i, f := range t.Methods().Slice() {
t2 := subst.typ(f.Type)
if (t2 != f.Type || f.Nname != nil) && newfields == nil {
newfields = make([]*types.Field, t.NumFields())
for j := 0; j < i; j++ {
newfields[j] = t.Methods().Slice()[j]
}
}
if newfields != nil {
newfields[i] = types.NewField(f.Pos, f.Sym, t2)
}
}
if newfields != nil {
return types.NewInterface(t.Pkg(), newfields)
}
return t
}
// instTypeName creates a name for an instantiated type, based on the name of the
// generic type and the type args
func instTypeName(name string, targs []*types.Type) string {
b := bytes.NewBufferString(name)
b.WriteByte('[')
@ -423,27 +540,57 @@ func instTypeName(name string, targs []*types.Type) string {
// typ computes the type obtained by substituting any type parameter in t with the
// corresponding type argument in subst. If t contains no type parameters, the
// result is t; otherwise the result is a new type.
// It deals with recursive types by using a map and TFORW types.
// TODO(danscales) deal with recursion besides ptr/struct cases.
// result is t; otherwise the result is a new type. It deals with recursive types
// by using TFORW types and finding partially or fully created types via sym.Def.
func (subst *subster) typ(t *types.Type) *types.Type {
if !t.HasTParam() {
return t
}
if subst.seen[t] != nil {
// We've hit a recursive type
return subst.seen[t]
}
var newt *types.Type
switch t.Kind() {
case types.TTYPEPARAM:
if t.Kind() == types.TTYPEPARAM {
for i, tp := range subst.tparams {
if tp.Type == t {
return subst.targs[i].Type()
}
}
return t
}
var newsym *types.Sym
var neededTargs []*types.Type
var forw *types.Type
if t.Sym() != nil {
// Translate the type params for this type according to
// the tparam/targs mapping from subst.
neededTargs = make([]*types.Type, len(t.RParams()))
for i, rparam := range t.RParams() {
neededTargs[i] = subst.typ(rparam)
}
// For a named (defined) type, we have to change the name of the
// type as well. We do this first, so we can look up if we've
// already seen this type during this substitution or other
// definitions/substitutions.
genName := genericTypeName(t.Sym())
newsym = t.Sym().Pkg.Lookup(instTypeName(genName, neededTargs))
if newsym.Def != nil {
// We've already created this instantiated defined type.
return newsym.Def.Type()
}
// In order to deal with recursive generic types, create a TFORW type
// initially and set its Def field, so it can be found if this type
// appears recursively within the type.
forw = types.New(types.TFORW)
forw.SetSym(newsym)
newsym.Def = ir.TypeNode(forw)
//println("Creating new type by sub", newsym.Name, forw.HasTParam())
forw.SetRParams(neededTargs)
}
var newt *types.Type
switch t.Kind() {
case types.TARRAY:
elem := t.Elem()
@ -454,17 +601,10 @@ func (subst *subster) typ(t *types.Type) *types.Type {
case types.TPTR:
elem := t.Elem()
// In order to deal with recursive generic types, create a TFORW
// type initially and store it in the seen map, so it can be
// accessed if this type appears recursively within the type.
forw := types.New(types.TFORW)
subst.seen[t] = forw
newelem := subst.typ(elem)
if newelem != elem {
forw.SetUnderlying(types.NewPtr(newelem))
newt = forw
newt = types.NewPtr(newelem)
}
delete(subst.seen, t)
case types.TSLICE:
elem := t.Elem()
@ -474,14 +614,10 @@ func (subst *subster) typ(t *types.Type) *types.Type {
}
case types.TSTRUCT:
forw := types.New(types.TFORW)
subst.seen[t] = forw
newt = subst.tstruct(t)
if newt != t {
forw.SetUnderlying(newt)
newt = forw
if newt == t {
newt = nil
}
delete(subst.seen, t)
case types.TFUNC:
newrecvs := subst.tstruct(t.Recvs())
@ -492,40 +628,61 @@ func (subst *subster) typ(t *types.Type) *types.Type {
if newrecvs.NumFields() > 0 {
newrecv = newrecvs.Field(0)
}
newt = types.NewSignature(t.Pkg(), newrecv, nil, newparams.FieldSlice(), newresults.FieldSlice())
newt = types.NewSignature(t.Pkg(), newrecv, t.TParams().FieldSlice(), newparams.FieldSlice(), newresults.FieldSlice())
}
case types.TINTER:
newt = subst.tinter(t)
if newt == t {
newt = nil
}
// TODO: case TCHAN
// TODO: case TMAP
// TODO: case TINTER
}
if newt != nil {
if t.Sym() != nil {
// Since we've substituted types, we also need to change
// the defined name of the type, by removing the old types
// (in brackets) from the name, and adding the new types.
if newt == nil {
// Even though there were typeparams in the type, there may be no
// change if this is a function type for a function call (which will
// have its own tparams/targs in the function instantiation).
return t
}
// Translate the type params for this type according to
// the tparam/targs mapping of the function.
neededTargs := make([]*types.Type, len(t.RParams))
for i, rparam := range t.RParams {
neededTargs[i] = subst.typ(rparam)
}
oldname := t.Sym().Name
i := strings.Index(oldname, "[")
oldname = oldname[:i]
sym := t.Sym().Pkg.Lookup(instTypeName(oldname, neededTargs))
if sym.Def != nil {
// We've already created this instantiated defined type.
return sym.Def.Type()
}
newt.SetSym(sym)
sym.Def = ir.TypeNode(newt)
}
if t.Sym() == nil {
// Not a named type, so there was no forwarding type and there are
// no methods to substitute.
assert(t.Methods().Len() == 0)
return newt
}
return t
forw.SetUnderlying(newt)
newt = forw
if t.Kind() != types.TINTER && t.Methods().Len() > 0 {
// Fill in the method info for the new type.
var newfields []*types.Field
newfields = make([]*types.Field, t.Methods().Len())
for i, f := range t.Methods().Slice() {
t2 := subst.typ(f.Type)
oldsym := f.Nname.Sym()
newsym := makeInstName(oldsym, subst.targs, true)
var nname *ir.Name
if newsym.Def != nil {
nname = newsym.Def.(*ir.Name)
} else {
nname = ir.NewNameAt(f.Pos, newsym)
nname.SetType(t2)
newsym.Def = nname
}
newfields[i] = types.NewField(f.Pos, f.Sym, t2)
newfields[i].Nname = nname
}
newt.Methods().Set(newfields)
if !newt.HasTParam() {
// Generate all the methods for a new fully-instantiated type.
subst.g.instTypeList = append(subst.g.instTypeList, newt)
}
}
return newt
}
// fields sets the Nname field for the Field nodes inside a type signature, based
@ -554,3 +711,11 @@ func (subst *subster) fields(class ir.Class, oldfields []*types.Field, dcl []*ir
}
return newfields
}
// defer does a single defer of type t, if it is a pointer type.
func deref(t *types.Type) *types.Type {
if t.IsPtr() {
return t.Elem()
}
return t
}

View File

@ -12,6 +12,7 @@ import (
"cmd/compile/internal/types"
"cmd/compile/internal/types2"
"cmd/internal/src"
"strings"
)
func (g *irgen) pkg(pkg *types2.Package) *types.Pkg {
@ -29,11 +30,10 @@ func (g *irgen) pkg(pkg *types2.Package) *types.Pkg {
// typ converts a types2.Type to a types.Type, including caching of previously
// translated types.
func (g *irgen) typ(typ types2.Type) *types.Type {
// Caching type mappings isn't strictly needed, because typ0 preserves
// type identity; but caching minimizes memory blow-up from mapping the
// same composite type multiple times, and also plays better with the
// current state of cmd/compile (e.g., haphazard calculation of type
// sizes).
// Cache type2-to-type mappings. Important so that each defined generic
// type (instantiated or not) has a single types.Type representation.
// Also saves a lot of computation and memory by avoiding re-translating
// types2 types repeatedly.
res, ok := g.typs[typ]
if !ok {
res = g.typ0(typ)
@ -42,9 +42,9 @@ func (g *irgen) typ(typ types2.Type) *types.Type {
// Ensure we calculate the size for all concrete types seen by
// the frontend. This is another heavy hammer for something that
// should really be the backend's responsibility instead.
if res != nil && !res.IsUntyped() && !res.IsFuncArgStruct() {
types.CheckSize(res)
}
//if res != nil && !res.IsUntyped() && !res.IsFuncArgStruct() {
// types.CheckSize(res)
//}
}
return res
}
@ -99,27 +99,35 @@ func (g *irgen) typ0(typ types2.Type) *types.Type {
// Create a forwarding type first and put it in the g.typs
// map, in order to deal with recursive generic types.
// Fully set up the extra ntyp information (Def, RParams,
// which may set HasTParam) before translating the
// underlying type itself, so we handle recursion
// correctly, including via method signatures.
ntyp := types.New(types.TFORW)
g.typs[typ] = ntyp
ntyp.SetUnderlying(g.typ(typ.Underlying()))
ntyp.SetSym(s)
if ntyp.HasTParam() {
// If ntyp still has type params, then we must be
// referencing something like 'value[T2]', as when
// specifying the generic receiver of a method,
// where value was defined as "type value[T any]
// ...". Save the type args, which will now be the
// new type params of the current type.
ntyp.RParams = make([]*types.Type, len(typ.TArgs()))
for i, targ := range typ.TArgs() {
ntyp.RParams[i] = g.typ(targ)
}
}
// Make sure instantiated type can be uniquely found from
// the sym
s.Def = ir.TypeNode(ntyp)
// If ntyp still has type params, then we must be
// referencing something like 'value[T2]', as when
// specifying the generic receiver of a method,
// where value was defined as "type value[T any]
// ...". Save the type args, which will now be the
// new type of the current type.
//
// If ntyp does not have type params, we are saving the
// concrete types used to instantiate this type. We'll use
// these when instantiating the methods of the
// instantiated type.
rparams := make([]*types.Type, len(typ.TArgs()))
for i, targ := range typ.TArgs() {
rparams[i] = g.typ(targ)
}
ntyp.SetRParams(rparams)
//fmt.Printf("Saw new type %v %v\n", instName, ntyp.HasTParam())
ntyp.SetUnderlying(g.typ(typ.Underlying()))
g.fillinMethods(typ, ntyp)
return ntyp
}
obj := g.obj(typ.Obj())
@ -174,7 +182,9 @@ func (g *irgen) typ0(typ types2.Type) *types.Type {
case *types2.TypeParam:
tp := types.NewTypeParam(g.tpkg(typ), g.typ(typ.Bound()))
// Save the name of the type parameter in the sym of the type.
tp.SetSym(g.sym(typ.Obj()))
// Include the types2 subscript in the sym name
sym := g.pkg(typ.Obj().Pkg()).Lookup(types2.TypeString(typ, func(*types2.Package) string { return "" }))
tp.SetSym(sym)
return tp
case *types2.Tuple:
@ -188,7 +198,7 @@ func (g *irgen) typ0(typ types2.Type) *types.Type {
fields[i] = g.param(typ.At(i))
}
t := types.NewStruct(types.LocalPkg, fields)
types.CheckSize(t)
//types.CheckSize(t)
// Can only set after doing the types.CheckSize()
t.StructType().Funarg = types.FunargResults
return t
@ -199,6 +209,71 @@ func (g *irgen) typ0(typ types2.Type) *types.Type {
}
}
// fillinMethods fills in the method name nodes and types for a defined type. This
// is needed for later typechecking when looking up methods of instantiated types,
// and for actually generating the methods for instantiated types.
func (g *irgen) fillinMethods(typ *types2.Named, ntyp *types.Type) {
if typ.NumMethods() != 0 {
targs := make([]ir.Node, len(typ.TArgs()))
for i, targ := range typ.TArgs() {
targs[i] = ir.TypeNode(g.typ(targ))
}
methods := make([]*types.Field, typ.NumMethods())
for i := range methods {
m := typ.Method(i)
meth := g.obj(m)
recvType := types2.AsSignature(m.Type()).Recv().Type()
ptr := types2.AsPointer(recvType)
if ptr != nil {
recvType = ptr.Elem()
}
if recvType != types2.Type(typ) {
// Unfortunately, meth is the type of the method of the
// generic type, so we have to do a substitution to get
// the name/type of the method of the instantiated type,
// using m.Type().RParams() and typ.TArgs()
inst2 := instTypeName2("", typ.TArgs())
name := meth.Sym().Name
i1 := strings.Index(name, "[")
i2 := strings.Index(name[i1:], "]")
assert(i1 >= 0 && i2 >= 0)
// Generate the name of the instantiated method.
name = name[0:i1] + inst2 + name[i1+i2+1:]
newsym := meth.Sym().Pkg.Lookup(name)
var meth2 *ir.Name
if newsym.Def != nil {
meth2 = newsym.Def.(*ir.Name)
} else {
meth2 = ir.NewNameAt(meth.Pos(), newsym)
rparams := types2.AsSignature(m.Type()).RParams()
tparams := make([]*types.Field, len(rparams))
for i, rparam := range rparams {
tparams[i] = types.NewField(src.NoXPos, nil, g.typ(rparam.Type()))
}
assert(len(tparams) == len(targs))
subst := &subster{
g: g,
tparams: tparams,
targs: targs,
}
// Do the substitution of the type
meth2.SetType(subst.typ(meth.Type()))
newsym.Def = meth2
}
meth = meth2
}
methods[i] = types.NewField(meth.Pos(), g.selector(m), meth.Type())
methods[i].Nname = meth
}
ntyp.Methods().Set(methods)
if !ntyp.HasTParam() {
// Generate all the methods for a new fully-instantiated type.
g.instTypeList = append(g.instTypeList, ntyp)
}
}
}
func (g *irgen) signature(recv *types.Field, sig *types2.Signature) *types.Type {
tparams2 := sig.TParams()
tparams := make([]*types.Field, len(tparams2))

View File

@ -96,9 +96,7 @@ func (g *irgen) unsafeExpr(name string, arg syntax.Expr) int64 {
selection := g.info.Selections[sel]
typ := g.typ(g.info.Types[sel.X].Type)
if typ.IsPtr() {
typ = typ.Elem()
}
typ = deref(typ)
var offset int64
for _, i := range selection.Index() {

View File

@ -177,10 +177,16 @@ type Type struct {
flags bitset8
// Type params (in order) of this named type that need to be instantiated.
// For defined (named) generic types, the list of type params (in order)
// of this type that need to be instantiated. For fully-instantiated
// generic types, this is the targs used to instantiate them (which are
// used when generating the corresponding instantiated methods). rparams
// is only set for named types that are generic or are fully-instantiated
// from a generic type.
// TODO(danscales): for space reasons, should probably be a pointer to a
// slice, possibly change the name of this field.
RParams []*Type
rparams []*Type
}
func (*Type) CanBeAnSSAAux() {}
@ -236,6 +242,26 @@ func (t *Type) Pos() src.XPos {
return src.NoXPos
}
func (t *Type) RParams() []*Type {
return t.rparams
}
func (t *Type) SetRParams(rparams []*Type) {
t.rparams = rparams
if t.HasTParam() {
return
}
// HasTParam should be set if any rparam is or has a type param. This is
// to handle the case of a generic type which doesn't reference any of its
// type params (e.g. most commonly, an empty struct).
for _, rparam := range rparams {
if rparam.HasTParam() {
t.SetHasTParam(true)
break
}
}
}
// NoPkg is a nil *Pkg value for clarity.
// It's intended for use when constructing types that aren't exported
// and thus don't need to be associated with any package.
@ -1702,6 +1728,13 @@ func NewBasic(kind Kind, obj Object) *Type {
func NewInterface(pkg *Pkg, methods []*Field) *Type {
t := New(TINTER)
t.SetInterface(methods)
for _, f := range methods {
// f.Type could be nil for a broken interface declaration
if f.Type != nil && f.Type.HasTParam() {
t.SetHasTParam(true)
break
}
}
if anyBroke(methods) {
t.SetBroke(true)
}
@ -1754,6 +1787,7 @@ func NewSignature(pkg *Pkg, recv *Field, tparams, params, results []*Field) *Typ
unzeroFieldOffsets(params)
unzeroFieldOffsets(results)
ft.Receiver = funargs(recvs, FunargRcvr)
// TODO(danscales): just use nil here (save memory) if no tparams
ft.TParams = funargs(tparams, FunargTparams)
ft.Params = funargs(params, FunargParams)
ft.Results = funargs(results, FunargResults)

View File

@ -243,6 +243,9 @@ func (s *Signature) Recv() *Var { return s.recv }
// TParams returns the type parameters of signature s, or nil.
func (s *Signature) TParams() []*TypeName { return s.tparams }
// RParams returns the receiver type params of signature s, or nil.
func (s *Signature) RParams() []*TypeName { return s.rparams }
// SetTParams sets the type parameters of signature s.
func (s *Signature) SetTParams(tparams []*TypeName) { s.tparams = tparams }
@ -965,5 +968,6 @@ func asTypeParam(t Type) *TypeParam {
// Exported for the compiler.
func AsPointer(t Type) *Pointer { return asPointer(t) }
func AsNamed(t Type) *Named { return asNamed(t) }
func AsPointer(t Type) *Pointer { return asPointer(t) }
func AsNamed(t Type) *Named { return asNamed(t) }
func AsSignature(t Type) *Signature { return asSignature(t) }

101
test/typeparam/cons.go Normal file
View File

@ -0,0 +1,101 @@
// run -gcflags=-G=3
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// lice
package main
import "fmt"
// Overriding the predeclare "any", so it can be used as a type constraint or a type
// argument
type any interface{}
type _Function[a, b any] interface {
Apply(x a) b
}
type incr struct{ n int }
func (this incr) Apply(x int) int {
return x + this.n
}
type pos struct{}
func (this pos) Apply(x int) bool {
return x > 0
}
type compose[a, b, c any] struct {
f _Function[a, b]
g _Function[b, c]
}
func (this compose[a, b, c]) Apply(x a) c {
return this.g.Apply(this.f.Apply(x))
}
type _Eq[a any] interface {
Equal(a) bool
}
type Int int
func (this Int) Equal(that int) bool {
return int(this) == that
}
type _List[a any] interface {
Match(casenil _Function[_Nil[a], any], casecons _Function[_Cons[a], any]) any
}
type _Nil[a any] struct{
}
func (xs _Nil[a]) Match(casenil _Function[_Nil[a], any], casecons _Function[_Cons[a], any]) any {
return casenil.Apply(xs)
}
type _Cons[a any] struct {
Head a
Tail _List[a]
}
func (xs _Cons[a]) Match(casenil _Function[_Nil[a], any], casecons _Function[_Cons[a], any]) any {
return casecons.Apply(xs)
}
type mapNil[a, b any] struct{
}
func (m mapNil[a, b]) Apply(_ _Nil[a]) any {
return _Nil[b]{}
}
type mapCons[a, b any] struct {
f _Function[a, b]
}
func (m mapCons[a, b]) Apply(xs _Cons[a]) any {
return _Cons[b]{m.f.Apply(xs.Head), _Map[a, b](m.f, xs.Tail)}
}
func _Map[a, b any](f _Function[a, b], xs _List[a]) _List[b] {
return xs.Match(mapNil[a, b]{}, mapCons[a, b]{f}).(_List[b])
}
func main() {
var xs _List[int] = _Cons[int]{3, _Cons[int]{6, _Nil[int]{}}}
// TODO(danscales): Remove conversion calls in next two, needed for now.
var ys _List[int] = _Map[int, int](_Function[int, int](incr{-5}), xs)
var xz _List[bool] = _Map[int, bool](_Function[int, bool](pos{}), ys)
cs1 := xz.(_Cons[bool])
cs2 := cs1.Tail.(_Cons[bool])
_, ok := cs2.Tail.(_Nil[bool])
if cs1.Head != false || cs2.Head != true || !ok {
panic(fmt.Sprintf("got %v, %v, %v, expected false, true, true",
cs1.Head, cs2.Head, ok))
}
}

95
test/typeparam/ordered.go Normal file
View File

@ -0,0 +1,95 @@
// run -gcflags=-G=3
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"fmt"
"math"
"sort"
)
type Ordered interface {
type int, int8, int16, int32, int64,
uint, uint8, uint16, uint32, uint64, uintptr,
float32, float64,
string
}
type orderedSlice[Elem Ordered] []Elem
func (s orderedSlice[Elem]) Len() int { return len(s) }
func (s orderedSlice[Elem]) Less(i, j int) bool {
if s[i] < s[j] {
return true
}
isNaN := func(f Elem) bool { return f != f }
if isNaN(s[i]) && !isNaN(s[j]) {
return true
}
return false
}
func (s orderedSlice[Elem]) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func _OrderedSlice[Elem Ordered](s []Elem) {
sort.Sort(orderedSlice[Elem](s))
}
var ints = []int{74, 59, 238, -784, 9845, 959, 905, 0, 0, 42, 7586, -5467984, 7586}
var float64s = []float64{74.3, 59.0, math.Inf(1), 238.2, -784.0, 2.3, math.NaN(), math.NaN(), math.Inf(-1), 9845.768, -959.7485, 905, 7.8, 7.8}
var strings = []string{"", "Hello", "foo", "bar", "foo", "f00", "%*&^*&^&", "***"}
func TestSortOrderedInts() bool {
return testOrdered("ints", ints, sort.Ints)
}
func TestSortOrderedFloat64s() bool {
return testOrdered("float64s", float64s, sort.Float64s)
}
func TestSortOrderedStrings() bool {
return testOrdered("strings", strings, sort.Strings)
}
func testOrdered[Elem Ordered](name string, s []Elem, sorter func([]Elem)) bool {
s1 := make([]Elem, len(s))
copy(s1, s)
s2 := make([]Elem, len(s))
copy(s2, s)
_OrderedSlice(s1)
sorter(s2)
ok := true
if !sliceEq(s1, s2) {
fmt.Printf("%s: got %v, want %v", name, s1, s2)
ok = false
}
for i := len(s1) - 1; i > 0; i-- {
if s1[i] < s1[i-1] {
fmt.Printf("%s: element %d (%v) < element %d (%v)", name, i, s1[i], i - 1, s1[i - 1])
ok = false
}
}
return ok
}
func sliceEq[Elem Ordered](s1, s2 []Elem) bool {
for i, v1 := range s1 {
v2 := s2[i]
if v1 != v2 {
isNaN := func(f Elem) bool { return f != f }
if !isNaN(v1) || !isNaN(v2) {
return false
}
}
}
return true
}
func main() {
if !TestSortOrderedInts() || !TestSortOrderedFloat64s() || !TestSortOrderedStrings() {
panic("failure")
}
}

View File

@ -11,12 +11,12 @@ import (
"strconv"
)
type Setter[B any] interface {
type _Setter[B any] interface {
Set(string)
type *B
}
func fromStrings1[T any, PT Setter[T]](s []string) []T {
func fromStrings1[T any, PT _Setter[T]](s []string) []T {
result := make([]T, len(s))
for i, v := range s {
// The type of &result[i] is *T which is in the type list