mirror of
https://github.com/golang/go
synced 2024-09-04 15:34:21 +00:00
[dev.typeparams] cmd/compile: support generic types (with stenciling of method calls)
A type may now have a type param in it, either because it has been composed from a function type param, or it has been declared as or derived from a reference to a generic type. No objects or types with type params can be exported yet. No generic type has a runtime descriptor (but will likely eventually be associated with a dictionary). types.Type now has an RParam field, which for a Named type can specify the type params (in order) that must be supplied to fully instantiate the type. Also, there is a new flag HasTParam to indicate if there is a type param (TTYPEPARAM) anywhere in the type. An instantiated generic type (whether fully instantiated or re-instantiated to new type params) is a defined type, even though there was no explicit declaration. This allows us to handle recursive instantiated types (and improves printing of types). To avoid the need to transform later in the compiler, an instantiation of a method of a generic type is immediately represented as a function with the method as the first argument. Added 5 tests on generic types to test/typeparams, including list.go, which tests recursive generic types. Change-Id: Ib7ff27abd369a06d1c8ea84edc6ca1fd74bbb7c2 Reviewed-on: https://go-review.googlesource.com/c/go/+/292652 Trust: Dan Scales <danscales@google.com> Trust: Robert Griesemer <gri@golang.org> Run-TryBot: Dan Scales <danscales@google.com> Reviewed-by: Robert Griesemer <gri@golang.org>
This commit is contained in:
parent
2ff1e05a4c
commit
20050a15fe
|
@ -25,6 +25,11 @@ func exportf(bout *bio.Writer, format string, args ...interface{}) {
|
||||||
func dumpexport(bout *bio.Writer) {
|
func dumpexport(bout *bio.Writer) {
|
||||||
p := &exporter{marked: make(map[*types.Type]bool)}
|
p := &exporter{marked: make(map[*types.Type]bool)}
|
||||||
for _, n := range typecheck.Target.Exports {
|
for _, n := range typecheck.Target.Exports {
|
||||||
|
// Must catch it here rather than Export(), because the type can be
|
||||||
|
// not fully set (still TFORW) when Export() is called.
|
||||||
|
if n.Type() != nil && n.Type().HasTParam() {
|
||||||
|
base.Fatalf("Cannot (yet) export a generic type: %v", n)
|
||||||
|
}
|
||||||
p.markObject(n)
|
p.markObject(n)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -209,7 +209,7 @@ func (g *irgen) selectorExpr(pos src.XPos, typ types2.Type, expr *syntax.Selecto
|
||||||
// interface embedding).
|
// interface embedding).
|
||||||
|
|
||||||
var n ir.Node
|
var n ir.Node
|
||||||
method := selinfo.Obj().(*types2.Func)
|
method2 := selinfo.Obj().(*types2.Func)
|
||||||
|
|
||||||
if kind == types2.MethodExpr {
|
if kind == types2.MethodExpr {
|
||||||
// OMETHEXPR is unusual in using directly the node and type of the
|
// OMETHEXPR is unusual in using directly the node and type of the
|
||||||
|
@ -221,9 +221,11 @@ func (g *irgen) selectorExpr(pos src.XPos, typ types2.Type, expr *syntax.Selecto
|
||||||
n = MethodExpr(pos, origx, x.Type(), last)
|
n = MethodExpr(pos, origx, x.Type(), last)
|
||||||
} else {
|
} else {
|
||||||
// Add implicit addr/deref for method values, if needed.
|
// Add implicit addr/deref for method values, if needed.
|
||||||
if !x.Type().IsInterface() {
|
if x.Type().IsInterface() {
|
||||||
recvTyp := method.Type().(*types2.Signature).Recv().Type()
|
n = DotMethod(pos, x, last)
|
||||||
_, wantPtr := recvTyp.(*types2.Pointer)
|
} else {
|
||||||
|
recvType2 := method2.Type().(*types2.Signature).Recv().Type()
|
||||||
|
_, wantPtr := recvType2.(*types2.Pointer)
|
||||||
havePtr := x.Type().IsPtr()
|
havePtr := x.Type().IsPtr()
|
||||||
|
|
||||||
if havePtr != wantPtr {
|
if havePtr != wantPtr {
|
||||||
|
@ -233,13 +235,45 @@ func (g *irgen) selectorExpr(pos src.XPos, typ types2.Type, expr *syntax.Selecto
|
||||||
x = Implicit(Addr(pos, x))
|
x = Implicit(Addr(pos, x))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !g.match(x.Type(), recvTyp, false) {
|
recvType2Base := recvType2
|
||||||
base.FatalfAt(pos, "expected %L to have type %v", x, recvTyp)
|
if wantPtr {
|
||||||
|
recvType2Base = recvType2.Pointer().Elem()
|
||||||
}
|
}
|
||||||
|
if len(recvType2Base.Named().TParams()) > 0 {
|
||||||
|
// recvType2 is the original generic type that is
|
||||||
|
// instantiated for this method call.
|
||||||
|
// selinfo.Recv() is the instantiated type
|
||||||
|
recvType2 = recvType2Base
|
||||||
|
// method is the generic method associated with the gen type
|
||||||
|
method := g.obj(recvType2.Named().Method(last))
|
||||||
|
n = ir.NewSelectorExpr(pos, ir.OCALLPART, x, method.Sym())
|
||||||
|
n.(*ir.SelectorExpr).Selection = types.NewField(pos, method.Sym(), method.Type())
|
||||||
|
n.(*ir.SelectorExpr).Selection.Nname = method
|
||||||
|
typed(method.Type(), n)
|
||||||
|
|
||||||
|
// selinfo.Targs() are the types used to
|
||||||
|
// instantiate the type of receiver
|
||||||
|
targs2 := selinfo.TArgs()
|
||||||
|
targs := make([]ir.Node, len(targs2))
|
||||||
|
for i, targ2 := range targs2 {
|
||||||
|
targs[i] = ir.TypeNode(g.typ(targ2))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create function instantiation with the type
|
||||||
|
// args for the receiver type for the method call.
|
||||||
|
n = ir.NewInstExpr(pos, ir.OFUNCINST, n, targs)
|
||||||
|
typed(g.typ(typ), n)
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
if !g.match(x.Type(), recvType2, false) {
|
||||||
|
base.FatalfAt(pos, "expected %L to have type %v", x, recvType2)
|
||||||
|
} else {
|
||||||
n = DotMethod(pos, x, last)
|
n = DotMethod(pos, x, last)
|
||||||
}
|
}
|
||||||
if have, want := n.Sym(), g.selector(method); have != want {
|
}
|
||||||
|
}
|
||||||
|
if have, want := n.Sym(), g.selector(method2); have != want {
|
||||||
base.FatalfAt(pos, "bad Sym: have %v, want %v", have, want)
|
base.FatalfAt(pos, "bad Sym: have %v, want %v", have, want)
|
||||||
}
|
}
|
||||||
return n
|
return n
|
||||||
|
|
|
@ -195,7 +195,7 @@ Outer:
|
||||||
// eventually export any exportable generic functions.
|
// eventually export any exportable generic functions.
|
||||||
j := 0
|
j := 0
|
||||||
for i, decl := range g.target.Decls {
|
for i, decl := range g.target.Decls {
|
||||||
if decl.Op() != ir.ODCLFUNC || decl.Type().NumTParams() == 0 {
|
if decl.Op() != ir.ODCLFUNC || !decl.Type().HasTParam() {
|
||||||
g.target.Decls[j] = g.target.Decls[i]
|
g.target.Decls[j] = g.target.Decls[i]
|
||||||
j++
|
j++
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,9 @@ import (
|
||||||
"cmd/compile/internal/ir"
|
"cmd/compile/internal/ir"
|
||||||
"cmd/compile/internal/typecheck"
|
"cmd/compile/internal/typecheck"
|
||||||
"cmd/compile/internal/types"
|
"cmd/compile/internal/types"
|
||||||
|
"cmd/internal/src"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// stencil scans functions for instantiated generic function calls and
|
// stencil scans functions for instantiated generic function calls and
|
||||||
|
@ -61,6 +63,17 @@ func (g *irgen) stencil() {
|
||||||
// Replace the OFUNCINST with a direct reference to the
|
// Replace the OFUNCINST with a direct reference to the
|
||||||
// new stenciled function
|
// new stenciled function
|
||||||
call.X = st.Nname
|
call.X = st.Nname
|
||||||
|
if inst.X.Op() == ir.OCALLPART {
|
||||||
|
// When we create an instantiation of a method
|
||||||
|
// call, we make it a function. So, move the
|
||||||
|
// receiver to be the first arg of the function
|
||||||
|
// call.
|
||||||
|
withRecv := make([]ir.Node, len(call.Args)+1)
|
||||||
|
dot := inst.X.(*ir.SelectorExpr)
|
||||||
|
withRecv[0] = dot.X
|
||||||
|
copy(withRecv[1:], call.Args)
|
||||||
|
call.Args = withRecv
|
||||||
|
}
|
||||||
modified = true
|
modified = true
|
||||||
})
|
})
|
||||||
if base.Flag.W > 1 && modified {
|
if base.Flag.W > 1 && modified {
|
||||||
|
@ -74,7 +87,12 @@ func (g *irgen) stencil() {
|
||||||
// the name of the function and the types of the type params.
|
// the name of the function and the types of the type params.
|
||||||
func makeInstName(inst *ir.InstExpr) *types.Sym {
|
func makeInstName(inst *ir.InstExpr) *types.Sym {
|
||||||
b := bytes.NewBufferString("#")
|
b := bytes.NewBufferString("#")
|
||||||
|
if meth, ok := inst.X.(*ir.SelectorExpr); ok {
|
||||||
|
// Write the name of the generic method, including receiver type
|
||||||
|
b.WriteString(meth.Selection.Nname.Sym().Name)
|
||||||
|
} else {
|
||||||
b.WriteString(inst.X.(*ir.Name).Name().Sym().Name)
|
b.WriteString(inst.X.(*ir.Name).Name().Sym().Name)
|
||||||
|
}
|
||||||
b.WriteString("[")
|
b.WriteString("[")
|
||||||
for i, targ := range inst.Targs {
|
for i, targ := range inst.Targs {
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
|
@ -90,18 +108,38 @@ func makeInstName(inst *ir.InstExpr) *types.Sym {
|
||||||
// instantiation of a generic function with specified type arguments.
|
// instantiation of a generic function with specified type arguments.
|
||||||
type subster struct {
|
type subster struct {
|
||||||
newf *ir.Func // Func node for the new stenciled function
|
newf *ir.Func // Func node for the new stenciled function
|
||||||
tparams *types.Fields
|
tparams []*types.Field
|
||||||
targs []ir.Node
|
targs []ir.Node
|
||||||
// The substitution map from name nodes in the generic function to the
|
// The substitution map from name nodes in the generic function to the
|
||||||
// name nodes in the new stenciled function.
|
// name nodes in the new stenciled function.
|
||||||
vars map[*ir.Name]*ir.Name
|
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
|
// genericSubst returns a new function with the specified name. The function is an
|
||||||
// instantiation of a generic function with type params, as specified by inst.
|
// 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 genericSubst(name *types.Sym, inst *ir.InstExpr) *ir.Func {
|
func genericSubst(name *types.Sym, inst *ir.InstExpr) *ir.Func {
|
||||||
// Similar to noder.go: funcDecl
|
var nameNode *ir.Name
|
||||||
nameNode := inst.X.(*ir.Name)
|
var tparams []*types.Field
|
||||||
|
if selExpr, ok := inst.X.(*ir.SelectorExpr); ok {
|
||||||
|
// 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 {
|
||||||
|
tparams[i] = types.NewField(src.NoXPos, nil, rparam)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
nameNode = inst.X.(*ir.Name)
|
||||||
|
tparams = nameNode.Type().TParams().Fields().Slice()
|
||||||
|
}
|
||||||
gf := nameNode.Func
|
gf := nameNode.Func
|
||||||
newf := ir.NewFunc(inst.Pos())
|
newf := ir.NewFunc(inst.Pos())
|
||||||
newf.Nname = ir.NewNameAt(inst.Pos(), name)
|
newf.Nname = ir.NewNameAt(inst.Pos(), name)
|
||||||
|
@ -111,9 +149,10 @@ func genericSubst(name *types.Sym, inst *ir.InstExpr) *ir.Func {
|
||||||
|
|
||||||
subst := &subster{
|
subst := &subster{
|
||||||
newf: newf,
|
newf: newf,
|
||||||
tparams: nameNode.Type().TParams().Fields(),
|
tparams: tparams,
|
||||||
targs: inst.Targs,
|
targs: inst.Targs,
|
||||||
vars: make(map[*ir.Name]*ir.Name),
|
vars: make(map[*ir.Name]*ir.Name),
|
||||||
|
seen: make(map[*types.Type]*types.Type),
|
||||||
}
|
}
|
||||||
|
|
||||||
newf.Dcl = make([]*ir.Name, len(gf.Dcl))
|
newf.Dcl = make([]*ir.Name, len(gf.Dcl))
|
||||||
|
@ -125,9 +164,12 @@ func genericSubst(name *types.Sym, inst *ir.InstExpr) *ir.Func {
|
||||||
// Ugly: we have to insert the Name nodes of the parameters/results into
|
// 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,
|
// the function type. The current function type has no Nname fields set,
|
||||||
// because it came via conversion from the types2 type.
|
// because it came via conversion from the types2 type.
|
||||||
oldt := inst.Type()
|
oldt := inst.X.Type()
|
||||||
newt := types.NewSignature(oldt.Pkg(), nil, nil, subst.fields(ir.PPARAM, oldt.Params(), newf.Dcl),
|
// We also transform a generic method type to the corresponding
|
||||||
subst.fields(ir.PPARAMOUT, oldt.Results(), newf.Dcl))
|
// instantiated function type where the receiver is the first parameter.
|
||||||
|
newt := types.NewSignature(oldt.Pkg(), nil, nil,
|
||||||
|
subst.fields(ir.PPARAM, append(oldt.Recvs().FieldSlice(), oldt.Params().FieldSlice()...), newf.Dcl),
|
||||||
|
subst.fields(ir.PPARAMOUT, oldt.Results().FieldSlice(), newf.Dcl))
|
||||||
|
|
||||||
newf.Nname.Ntype = ir.TypeNode(newt)
|
newf.Nname.Ntype = ir.TypeNode(newt)
|
||||||
newf.Nname.SetType(newt)
|
newf.Nname.SetType(newt)
|
||||||
|
@ -276,41 +318,81 @@ func (subst *subster) tstruct(t *types.Type) *types.Type {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// typ substitutes any type parameter found with the corresponding type argument.
|
// instTypeName creates a name for an instantiated type, based on the type args
|
||||||
|
func instTypeName(name string, targs []ir.Node) string {
|
||||||
|
b := bytes.NewBufferString(name)
|
||||||
|
b.WriteByte('[')
|
||||||
|
for i, targ := range targs {
|
||||||
|
if i > 0 {
|
||||||
|
b.WriteByte(',')
|
||||||
|
}
|
||||||
|
b.WriteString(targ.Type().String())
|
||||||
|
}
|
||||||
|
b.WriteByte(']')
|
||||||
|
return b.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.
|
||||||
func (subst *subster) typ(t *types.Type) *types.Type {
|
func (subst *subster) typ(t *types.Type) *types.Type {
|
||||||
for i, tp := range subst.tparams.Slice() {
|
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:
|
||||||
|
for i, tp := range subst.tparams {
|
||||||
if tp.Type == t {
|
if tp.Type == t {
|
||||||
return subst.targs[i].Type()
|
return subst.targs[i].Type()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return t
|
||||||
|
|
||||||
switch t.Kind() {
|
|
||||||
case types.TARRAY:
|
case types.TARRAY:
|
||||||
elem := t.Elem()
|
elem := t.Elem()
|
||||||
newelem := subst.typ(elem)
|
newelem := subst.typ(elem)
|
||||||
if newelem != elem {
|
if newelem != elem {
|
||||||
return types.NewArray(newelem, t.NumElem())
|
newt = types.NewArray(newelem, t.NumElem())
|
||||||
}
|
}
|
||||||
|
|
||||||
case types.TPTR:
|
case types.TPTR:
|
||||||
elem := t.Elem()
|
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)
|
newelem := subst.typ(elem)
|
||||||
if newelem != elem {
|
if newelem != elem {
|
||||||
return types.NewPtr(newelem)
|
forw.SetUnderlying(types.NewPtr(newelem))
|
||||||
|
newt = forw
|
||||||
}
|
}
|
||||||
|
delete(subst.seen, t)
|
||||||
|
|
||||||
case types.TSLICE:
|
case types.TSLICE:
|
||||||
elem := t.Elem()
|
elem := t.Elem()
|
||||||
newelem := subst.typ(elem)
|
newelem := subst.typ(elem)
|
||||||
if newelem != elem {
|
if newelem != elem {
|
||||||
return types.NewSlice(newelem)
|
newt = types.NewSlice(newelem)
|
||||||
}
|
}
|
||||||
|
|
||||||
case types.TSTRUCT:
|
case types.TSTRUCT:
|
||||||
newt := subst.tstruct(t)
|
forw := types.New(types.TFORW)
|
||||||
|
subst.seen[t] = forw
|
||||||
|
newt = subst.tstruct(t)
|
||||||
if newt != t {
|
if newt != t {
|
||||||
return newt
|
forw.SetUnderlying(newt)
|
||||||
|
newt = forw
|
||||||
}
|
}
|
||||||
|
delete(subst.seen, t)
|
||||||
|
|
||||||
case types.TFUNC:
|
case types.TFUNC:
|
||||||
newrecvs := subst.tstruct(t.Recvs())
|
newrecvs := subst.tstruct(t.Recvs())
|
||||||
|
@ -321,21 +403,39 @@ func (subst *subster) typ(t *types.Type) *types.Type {
|
||||||
if newrecvs.NumFields() > 0 {
|
if newrecvs.NumFields() > 0 {
|
||||||
newrecv = newrecvs.Field(0)
|
newrecv = newrecvs.Field(0)
|
||||||
}
|
}
|
||||||
return types.NewSignature(t.Pkg(), newrecv, nil, newparams.FieldSlice(), newresults.FieldSlice())
|
newt = types.NewSignature(t.Pkg(), newrecv, nil, newparams.FieldSlice(), newresults.FieldSlice())
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: case TCHAN
|
// TODO: case TCHAN
|
||||||
// TODO: case TMAP
|
// TODO: case TMAP
|
||||||
// TODO: case TINTER
|
// 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.
|
||||||
|
oldname := t.Sym().Name
|
||||||
|
i := strings.Index(oldname, "[")
|
||||||
|
oldname = oldname[:i]
|
||||||
|
sym := t.Sym().Pkg.Lookup(instTypeName(oldname, subst.targs))
|
||||||
|
if sym.Def != nil {
|
||||||
|
// We've already created this instantiated defined type.
|
||||||
|
return sym.Def.Type()
|
||||||
|
}
|
||||||
|
newt.SetSym(sym)
|
||||||
|
sym.Def = ir.TypeNode(newt)
|
||||||
|
}
|
||||||
|
return newt
|
||||||
|
}
|
||||||
|
|
||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
// fields sets the Nname field for the Field nodes inside a type signature, based
|
// fields sets the Nname field for the Field nodes inside a type signature, based
|
||||||
// on the corresponding in/out parameters in dcl. It depends on the in and out
|
// on the corresponding in/out parameters in dcl. It depends on the in and out
|
||||||
// parameters being in order in dcl.
|
// parameters being in order in dcl.
|
||||||
func (subst *subster) fields(class ir.Class, oldt *types.Type, dcl []*ir.Name) []*types.Field {
|
func (subst *subster) fields(class ir.Class, oldfields []*types.Field, dcl []*ir.Name) []*types.Field {
|
||||||
oldfields := oldt.FieldSlice()
|
|
||||||
newfields := make([]*types.Field, len(oldfields))
|
newfields := make([]*types.Field, len(oldfields))
|
||||||
var i int
|
var i int
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
package noder
|
package noder
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"cmd/compile/internal/base"
|
"cmd/compile/internal/base"
|
||||||
"cmd/compile/internal/ir"
|
"cmd/compile/internal/ir"
|
||||||
"cmd/compile/internal/typecheck"
|
"cmd/compile/internal/typecheck"
|
||||||
|
@ -25,6 +26,8 @@ func (g *irgen) pkg(pkg *types2.Package) *types.Pkg {
|
||||||
return types.NewPkg(pkg.Path(), pkg.Name())
|
return types.NewPkg(pkg.Path(), pkg.Name())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// typ converts a types2.Type to a types.Type, including caching of previously
|
||||||
|
// translated types.
|
||||||
func (g *irgen) typ(typ types2.Type) *types.Type {
|
func (g *irgen) typ(typ types2.Type) *types.Type {
|
||||||
// Caching type mappings isn't strictly needed, because typ0 preserves
|
// Caching type mappings isn't strictly needed, because typ0 preserves
|
||||||
// type identity; but caching minimizes memory blow-up from mapping the
|
// type identity; but caching minimizes memory blow-up from mapping the
|
||||||
|
@ -46,11 +49,79 @@ func (g *irgen) typ(typ types2.Type) *types.Type {
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// instTypeName2 creates a name for an instantiated type, base on the type args
|
||||||
|
// (given as types2 types).
|
||||||
|
func instTypeName2(name string, targs []types2.Type) string {
|
||||||
|
b := bytes.NewBufferString(name)
|
||||||
|
b.WriteByte('[')
|
||||||
|
for i, targ := range targs {
|
||||||
|
if i > 0 {
|
||||||
|
b.WriteByte(',')
|
||||||
|
}
|
||||||
|
b.WriteString(types2.TypeString(targ,
|
||||||
|
func(*types2.Package) string { return "" }))
|
||||||
|
}
|
||||||
|
b.WriteByte(']')
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// typ0 converts a types2.Type to a types.Type, but doesn't do the caching check
|
||||||
|
// at the top level.
|
||||||
func (g *irgen) typ0(typ types2.Type) *types.Type {
|
func (g *irgen) typ0(typ types2.Type) *types.Type {
|
||||||
switch typ := typ.(type) {
|
switch typ := typ.(type) {
|
||||||
case *types2.Basic:
|
case *types2.Basic:
|
||||||
return g.basic(typ)
|
return g.basic(typ)
|
||||||
case *types2.Named:
|
case *types2.Named:
|
||||||
|
if typ.TParams() != nil {
|
||||||
|
// typ is an instantiation of a defined (named) generic type.
|
||||||
|
// This instantiation should also be a defined (named) type.
|
||||||
|
// types2 gives us the substituted type in t.Underlying()
|
||||||
|
// The substituted type may or may not still have type
|
||||||
|
// params. We might, for example, be substituting one type
|
||||||
|
// param for another type param.
|
||||||
|
|
||||||
|
if typ.TArgs() == nil {
|
||||||
|
base.Fatalf("In typ0, Targs should be set if TParams is set")
|
||||||
|
}
|
||||||
|
|
||||||
|
// When converted to types.Type, typ must have a name,
|
||||||
|
// based on the names of the type arguments. We need a
|
||||||
|
// name to deal with recursive generic types (and it also
|
||||||
|
// looks better when printing types).
|
||||||
|
instName := instTypeName2(typ.Obj().Name(), typ.TArgs())
|
||||||
|
s := g.pkg(typ.Obj().Pkg()).Lookup(instName)
|
||||||
|
if s.Def != nil {
|
||||||
|
// We have already encountered this instantiation,
|
||||||
|
// so use the type we previously created, since there
|
||||||
|
// must be exactly one instance of a defined type.
|
||||||
|
return s.Def.Type()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a forwarding type first and put it in the g.typs
|
||||||
|
// map, in order to deal with recursive generic types.
|
||||||
|
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)
|
||||||
|
return ntyp
|
||||||
|
}
|
||||||
obj := g.obj(typ.Obj())
|
obj := g.obj(typ.Obj())
|
||||||
if obj.Op() != ir.OTYPE {
|
if obj.Op() != ir.OTYPE {
|
||||||
base.FatalfAt(obj.Pos(), "expected type: %L", obj)
|
base.FatalfAt(obj.Pos(), "expected type: %L", obj)
|
||||||
|
|
|
@ -1288,6 +1288,11 @@ func ITabSym(it *obj.LSym, offset int64) *obj.LSym {
|
||||||
|
|
||||||
// NeedRuntimeType ensures that a runtime type descriptor is emitted for t.
|
// NeedRuntimeType ensures that a runtime type descriptor is emitted for t.
|
||||||
func NeedRuntimeType(t *types.Type) {
|
func NeedRuntimeType(t *types.Type) {
|
||||||
|
if t.HasTParam() {
|
||||||
|
// Generic types don't have a runtime type descriptor (but will
|
||||||
|
// have a dictionary)
|
||||||
|
return
|
||||||
|
}
|
||||||
if _, ok := signatset[t]; !ok {
|
if _, ok := signatset[t]; !ok {
|
||||||
signatset[t] = struct{}{}
|
signatset[t] = struct{}{}
|
||||||
signatslice = append(signatslice, t)
|
signatslice = append(signatslice, t)
|
||||||
|
|
|
@ -21,7 +21,7 @@ func TestSizeof(t *testing.T) {
|
||||||
_64bit uintptr // size on 64bit platforms
|
_64bit uintptr // size on 64bit platforms
|
||||||
}{
|
}{
|
||||||
{Sym{}, 44, 72},
|
{Sym{}, 44, 72},
|
||||||
{Type{}, 56, 96},
|
{Type{}, 68, 120},
|
||||||
{Map{}, 20, 40},
|
{Map{}, 20, 40},
|
||||||
{Forward{}, 20, 32},
|
{Forward{}, 20, 32},
|
||||||
{Func{}, 28, 48},
|
{Func{}, 28, 48},
|
||||||
|
|
|
@ -176,6 +176,11 @@ type Type struct {
|
||||||
Align uint8 // the required alignment of this type, in bytes (0 means Width and Align have not yet been computed)
|
Align uint8 // the required alignment of this type, in bytes (0 means Width and Align have not yet been computed)
|
||||||
|
|
||||||
flags bitset8
|
flags bitset8
|
||||||
|
|
||||||
|
// Type params (in order) of this named type that need to be instantiated.
|
||||||
|
// TODO(danscales): for space reasons, should probably be a pointer to a
|
||||||
|
// slice, possibly change the name of this field.
|
||||||
|
RParams []*Type
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*Type) CanBeAnSSAAux() {}
|
func (*Type) CanBeAnSSAAux() {}
|
||||||
|
@ -186,6 +191,7 @@ const (
|
||||||
typeNoalg // suppress hash and eq algorithm generation
|
typeNoalg // suppress hash and eq algorithm generation
|
||||||
typeDeferwidth // width computation has been deferred and type is on deferredTypeStack
|
typeDeferwidth // width computation has been deferred and type is on deferredTypeStack
|
||||||
typeRecur
|
typeRecur
|
||||||
|
typeHasTParam // there is a typeparam somewhere in the type (generic function or type)
|
||||||
)
|
)
|
||||||
|
|
||||||
func (t *Type) NotInHeap() bool { return t.flags&typeNotInHeap != 0 }
|
func (t *Type) NotInHeap() bool { return t.flags&typeNotInHeap != 0 }
|
||||||
|
@ -193,12 +199,14 @@ func (t *Type) Broke() bool { return t.flags&typeBroke != 0 }
|
||||||
func (t *Type) Noalg() bool { return t.flags&typeNoalg != 0 }
|
func (t *Type) Noalg() bool { return t.flags&typeNoalg != 0 }
|
||||||
func (t *Type) Deferwidth() bool { return t.flags&typeDeferwidth != 0 }
|
func (t *Type) Deferwidth() bool { return t.flags&typeDeferwidth != 0 }
|
||||||
func (t *Type) Recur() bool { return t.flags&typeRecur != 0 }
|
func (t *Type) Recur() bool { return t.flags&typeRecur != 0 }
|
||||||
|
func (t *Type) HasTParam() bool { return t.flags&typeHasTParam != 0 }
|
||||||
|
|
||||||
func (t *Type) SetNotInHeap(b bool) { t.flags.set(typeNotInHeap, b) }
|
func (t *Type) SetNotInHeap(b bool) { t.flags.set(typeNotInHeap, b) }
|
||||||
func (t *Type) SetBroke(b bool) { t.flags.set(typeBroke, b) }
|
func (t *Type) SetBroke(b bool) { t.flags.set(typeBroke, b) }
|
||||||
func (t *Type) SetNoalg(b bool) { t.flags.set(typeNoalg, b) }
|
func (t *Type) SetNoalg(b bool) { t.flags.set(typeNoalg, b) }
|
||||||
func (t *Type) SetDeferwidth(b bool) { t.flags.set(typeDeferwidth, b) }
|
func (t *Type) SetDeferwidth(b bool) { t.flags.set(typeDeferwidth, b) }
|
||||||
func (t *Type) SetRecur(b bool) { t.flags.set(typeRecur, b) }
|
func (t *Type) SetRecur(b bool) { t.flags.set(typeRecur, b) }
|
||||||
|
func (t *Type) SetHasTParam(b bool) { t.flags.set(typeHasTParam, b) }
|
||||||
|
|
||||||
// Kind returns the kind of type t.
|
// Kind returns the kind of type t.
|
||||||
func (t *Type) Kind() Kind { return t.kind }
|
func (t *Type) Kind() Kind { return t.kind }
|
||||||
|
@ -527,6 +535,9 @@ func NewArray(elem *Type, bound int64) *Type {
|
||||||
t := New(TARRAY)
|
t := New(TARRAY)
|
||||||
t.Extra = &Array{Elem: elem, Bound: bound}
|
t.Extra = &Array{Elem: elem, Bound: bound}
|
||||||
t.SetNotInHeap(elem.NotInHeap())
|
t.SetNotInHeap(elem.NotInHeap())
|
||||||
|
if elem.HasTParam() {
|
||||||
|
t.SetHasTParam(true)
|
||||||
|
}
|
||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -542,6 +553,9 @@ func NewSlice(elem *Type) *Type {
|
||||||
t := New(TSLICE)
|
t := New(TSLICE)
|
||||||
t.Extra = Slice{Elem: elem}
|
t.Extra = Slice{Elem: elem}
|
||||||
elem.cache.slice = t
|
elem.cache.slice = t
|
||||||
|
if elem.HasTParam() {
|
||||||
|
t.SetHasTParam(true)
|
||||||
|
}
|
||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -551,6 +565,9 @@ func NewChan(elem *Type, dir ChanDir) *Type {
|
||||||
ct := t.ChanType()
|
ct := t.ChanType()
|
||||||
ct.Elem = elem
|
ct.Elem = elem
|
||||||
ct.Dir = dir
|
ct.Dir = dir
|
||||||
|
if elem.HasTParam() {
|
||||||
|
t.SetHasTParam(true)
|
||||||
|
}
|
||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -558,6 +575,9 @@ func NewTuple(t1, t2 *Type) *Type {
|
||||||
t := New(TTUPLE)
|
t := New(TTUPLE)
|
||||||
t.Extra.(*Tuple).first = t1
|
t.Extra.(*Tuple).first = t1
|
||||||
t.Extra.(*Tuple).second = t2
|
t.Extra.(*Tuple).second = t2
|
||||||
|
if t1.HasTParam() || t2.HasTParam() {
|
||||||
|
t.SetHasTParam(true)
|
||||||
|
}
|
||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -579,6 +599,9 @@ func NewMap(k, v *Type) *Type {
|
||||||
mt := t.MapType()
|
mt := t.MapType()
|
||||||
mt.Key = k
|
mt.Key = k
|
||||||
mt.Elem = v
|
mt.Elem = v
|
||||||
|
if k.HasTParam() || v.HasTParam() {
|
||||||
|
t.SetHasTParam(true)
|
||||||
|
}
|
||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -597,6 +620,12 @@ func NewPtr(elem *Type) *Type {
|
||||||
if t.Elem() != elem {
|
if t.Elem() != elem {
|
||||||
base.Fatalf("NewPtr: elem mismatch")
|
base.Fatalf("NewPtr: elem mismatch")
|
||||||
}
|
}
|
||||||
|
if elem.HasTParam() {
|
||||||
|
// Extra check when reusing the cache, since the elem
|
||||||
|
// might have still been undetermined (i.e. a TFORW type)
|
||||||
|
// when this entry was cached.
|
||||||
|
t.SetHasTParam(true)
|
||||||
|
}
|
||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -607,6 +636,9 @@ func NewPtr(elem *Type) *Type {
|
||||||
if NewPtrCacheEnabled {
|
if NewPtrCacheEnabled {
|
||||||
elem.cache.ptr = t
|
elem.cache.ptr = t
|
||||||
}
|
}
|
||||||
|
if elem.HasTParam() {
|
||||||
|
t.SetHasTParam(true)
|
||||||
|
}
|
||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1611,6 +1643,9 @@ func (t *Type) SetUnderlying(underlying *Type) {
|
||||||
if underlying.Broke() {
|
if underlying.Broke() {
|
||||||
t.SetBroke(true)
|
t.SetBroke(true)
|
||||||
}
|
}
|
||||||
|
if underlying.HasTParam() {
|
||||||
|
t.SetHasTParam(true)
|
||||||
|
}
|
||||||
|
|
||||||
// spec: "The declared type does not inherit any methods bound
|
// spec: "The declared type does not inherit any methods bound
|
||||||
// to the existing type, but the method set of an interface
|
// to the existing type, but the method set of an interface
|
||||||
|
@ -1633,6 +1668,15 @@ func (t *Type) SetUnderlying(underlying *Type) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func fieldsHasTParam(fields []*Field) bool {
|
||||||
|
for _, f := range fields {
|
||||||
|
if f.Type != nil && f.Type.HasTParam() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// NewBasic returns a new basic type of the given kind.
|
// NewBasic returns a new basic type of the given kind.
|
||||||
func NewBasic(kind Kind, obj Object) *Type {
|
func NewBasic(kind Kind, obj Object) *Type {
|
||||||
t := New(kind)
|
t := New(kind)
|
||||||
|
@ -1660,6 +1704,7 @@ func NewTypeParam(pkg *Pkg, constraint *Type) *Type {
|
||||||
constraint.wantEtype(TINTER)
|
constraint.wantEtype(TINTER)
|
||||||
t.methods = constraint.methods
|
t.methods = constraint.methods
|
||||||
t.Extra.(*Interface).pkg = pkg
|
t.Extra.(*Interface).pkg = pkg
|
||||||
|
t.SetHasTParam(true)
|
||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1688,6 +1733,10 @@ func NewSignature(pkg *Pkg, recv *Field, tparams, params, results []*Field) *Typ
|
||||||
ft.Params = funargs(params, FunargParams)
|
ft.Params = funargs(params, FunargParams)
|
||||||
ft.Results = funargs(results, FunargResults)
|
ft.Results = funargs(results, FunargResults)
|
||||||
ft.pkg = pkg
|
ft.pkg = pkg
|
||||||
|
if len(tparams) > 0 || fieldsHasTParam(recvs) || fieldsHasTParam(params) ||
|
||||||
|
fieldsHasTParam(results) {
|
||||||
|
t.SetHasTParam(true)
|
||||||
|
}
|
||||||
|
|
||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
|
@ -1700,6 +1749,9 @@ func NewStruct(pkg *Pkg, fields []*Field) *Type {
|
||||||
t.SetBroke(true)
|
t.SetBroke(true)
|
||||||
}
|
}
|
||||||
t.Extra.(*Struct).pkg = pkg
|
t.Extra.(*Struct).pkg = pkg
|
||||||
|
if fieldsHasTParam(fields) {
|
||||||
|
t.SetHasTParam(true)
|
||||||
|
}
|
||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -51,6 +51,22 @@ func (s *Selection) Kind() SelectionKind { return s.kind }
|
||||||
// Recv returns the type of x in x.f.
|
// Recv returns the type of x in x.f.
|
||||||
func (s *Selection) Recv() Type { return s.recv }
|
func (s *Selection) Recv() Type { return s.recv }
|
||||||
|
|
||||||
|
// Work-around for bug where a (*instance) shows up in a final type.
|
||||||
|
// TODO(gri): fix this bug.
|
||||||
|
func (s *Selection) TArgs() []Type {
|
||||||
|
r := s.recv
|
||||||
|
if r.Pointer() != nil {
|
||||||
|
r = r.Pointer().Elem()
|
||||||
|
}
|
||||||
|
if r.Named() != nil {
|
||||||
|
return r.Named().TArgs()
|
||||||
|
}
|
||||||
|
// The base type (after skipping any pointer) must be a Named type. The
|
||||||
|
// bug is that sometimes it can be an instance type (which is supposed to
|
||||||
|
// be an internal type only).
|
||||||
|
return r.(*instance).targs
|
||||||
|
}
|
||||||
|
|
||||||
// Obj returns the object denoted by x.f; a *Var for
|
// Obj returns the object denoted by x.f; a *Var for
|
||||||
// a field selection, and a *Func in all other cases.
|
// a field selection, and a *Func in all other cases.
|
||||||
func (s *Selection) Obj() Object { return s.obj }
|
func (s *Selection) Obj() Object { return s.obj }
|
||||||
|
|
65
test/typeparam/list.go
Normal file
65
test/typeparam/list.go
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
// 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"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Ordered interface {
|
||||||
|
type int, int8, int16, int32, int64,
|
||||||
|
uint, uint8, uint16, uint32, uint64, uintptr,
|
||||||
|
float32, float64,
|
||||||
|
string
|
||||||
|
}
|
||||||
|
|
||||||
|
// List is a linked list of ordered values of type T.
|
||||||
|
type list[T Ordered] struct {
|
||||||
|
next *list[T]
|
||||||
|
val T
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *list[T]) largest() T {
|
||||||
|
var max T
|
||||||
|
for p := l; p != nil; p = p.next {
|
||||||
|
if p.val > max {
|
||||||
|
max = p.val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return max
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
i3 := &list[int]{nil, 1}
|
||||||
|
i2 := &list[int]{i3, 3}
|
||||||
|
i1 := &list[int]{i2, 2}
|
||||||
|
if got, want := i1.largest(), 3; got != want {
|
||||||
|
panic(fmt.Sprintf("got %d, want %d", got, want))
|
||||||
|
}
|
||||||
|
|
||||||
|
b3 := &list[byte]{nil, byte(1)}
|
||||||
|
b2 := &list[byte]{b3, byte(3)}
|
||||||
|
b1 := &list[byte]{b2, byte(2)}
|
||||||
|
if got, want := b1.largest(), byte(3); got != want {
|
||||||
|
panic(fmt.Sprintf("got %d, want %d", got, want))
|
||||||
|
}
|
||||||
|
|
||||||
|
f3 := &list[float64]{nil, 13.5}
|
||||||
|
f2 := &list[float64]{f3, 1.2}
|
||||||
|
f1 := &list[float64]{f2, 4.5}
|
||||||
|
if got, want := f1.largest(), 13.5; got != want {
|
||||||
|
panic(fmt.Sprintf("got %f, want %f", got, want))
|
||||||
|
}
|
||||||
|
|
||||||
|
s3 := &list[string]{nil, "dd"}
|
||||||
|
s2 := &list[string]{s3, "aa"}
|
||||||
|
s1 := &list[string]{s2, "bb"}
|
||||||
|
if got, want := s1.largest(), "dd"; got != want {
|
||||||
|
panic(fmt.Sprintf("got %s, want %s", got, want))
|
||||||
|
}
|
||||||
|
}
|
32
test/typeparam/pair.go
Normal file
32
test/typeparam/pair.go
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
// 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"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
type pair[F1, F2 any] struct {
|
||||||
|
f1 F1
|
||||||
|
f2 F2
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
p := pair[int32, int64]{1, 2}
|
||||||
|
if got, want := unsafe.Sizeof(p.f1), uintptr(4); got != want {
|
||||||
|
panic(fmt.Sprintf("unexpected f1 size == %d, want %d", got, want))
|
||||||
|
}
|
||||||
|
if got, want := unsafe.Sizeof(p.f2), uintptr(8); got != want {
|
||||||
|
panic(fmt.Sprintf("unexpected f2 size == %d, want %d", got, want))
|
||||||
|
}
|
||||||
|
type mypair struct { f1 int32; f2 int64 }
|
||||||
|
mp := mypair(p)
|
||||||
|
if mp.f1 != 1 || mp.f2 != 2 {
|
||||||
|
panic(fmt.Sprintf("mp == %#v, want %#v", mp, mypair{1, 2}))
|
||||||
|
}
|
||||||
|
}
|
46
test/typeparam/stringable.go
Normal file
46
test/typeparam/stringable.go
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
// 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"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Stringer interface {
|
||||||
|
String() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// stringableList is a slice of some type, where the type
|
||||||
|
// must have a String method.
|
||||||
|
type stringableList[T Stringer] []T
|
||||||
|
|
||||||
|
func (s stringableList[T]) String() string {
|
||||||
|
var sb strings.Builder
|
||||||
|
for i, v := range s {
|
||||||
|
if i > 0 {
|
||||||
|
sb.WriteString(", ")
|
||||||
|
}
|
||||||
|
sb.WriteString(v.String())
|
||||||
|
}
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
type myint int
|
||||||
|
|
||||||
|
func (a myint) String() string {
|
||||||
|
return strconv.Itoa(int(a))
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
v := stringableList[myint]{ myint(1), myint(2) }
|
||||||
|
|
||||||
|
if got, want := v.String(), "1, 2"; got != want {
|
||||||
|
panic(fmt.Sprintf("got %s, want %s", got, want))
|
||||||
|
}
|
||||||
|
}
|
49
test/typeparam/struct.go
Normal file
49
test/typeparam/struct.go
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
// 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"
|
||||||
|
)
|
||||||
|
|
||||||
|
type _E[T any] struct {
|
||||||
|
v T
|
||||||
|
}
|
||||||
|
|
||||||
|
type _S1 struct {
|
||||||
|
_E[int]
|
||||||
|
v string
|
||||||
|
}
|
||||||
|
|
||||||
|
type _Eint = _E[int]
|
||||||
|
type _Ebool = _E[bool]
|
||||||
|
|
||||||
|
type _S2 struct {
|
||||||
|
_Eint
|
||||||
|
_Ebool
|
||||||
|
v string
|
||||||
|
}
|
||||||
|
|
||||||
|
type _S3 struct {
|
||||||
|
*_E[int]
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
s1 := _S1{_Eint{2}, "foo"}
|
||||||
|
if got, want := s1._E.v, 2; got != want {
|
||||||
|
panic(fmt.Sprintf("got %d, want %d", got, want))
|
||||||
|
}
|
||||||
|
s2 := _S2{_Eint{3}, _Ebool{true}, "foo"}
|
||||||
|
if got, want := s2._Eint.v, 3; got != want {
|
||||||
|
panic(fmt.Sprintf("got %d, want %d", got, want))
|
||||||
|
}
|
||||||
|
var s3 _S3
|
||||||
|
s3._E = &_Eint{4}
|
||||||
|
if got, want := s3._E.v, 4; got != want {
|
||||||
|
panic(fmt.Sprintf("got %d, want %d", got, want))
|
||||||
|
}
|
||||||
|
}
|
75
test/typeparam/value.go
Normal file
75
test/typeparam/value.go
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
// 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"
|
||||||
|
|
||||||
|
type value[T any] struct {
|
||||||
|
val T
|
||||||
|
}
|
||||||
|
|
||||||
|
func get[T2 any](v *value[T2]) T2 {
|
||||||
|
return v.val
|
||||||
|
}
|
||||||
|
|
||||||
|
func set[T any](v *value[T], val T) {
|
||||||
|
v.val = val
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *value[T2]) set(val T2) {
|
||||||
|
v.val = val
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *value[T2]) get() T2 {
|
||||||
|
return v.val
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var v1 value[int]
|
||||||
|
set(&v1, 1)
|
||||||
|
if got, want := get(&v1), 1; got != want {
|
||||||
|
panic(fmt.Sprintf("get() == %d, want %d", got, want))
|
||||||
|
}
|
||||||
|
|
||||||
|
v1.set(2)
|
||||||
|
if got, want := v1.get(), 2; got != want {
|
||||||
|
panic(fmt.Sprintf("get() == %d, want %d", got, want))
|
||||||
|
}
|
||||||
|
|
||||||
|
v1p := new(value[int])
|
||||||
|
set(v1p, 3)
|
||||||
|
if got, want := get(v1p), 3; got != want {
|
||||||
|
panic(fmt.Sprintf("get() == %d, want %d", got, want))
|
||||||
|
}
|
||||||
|
|
||||||
|
v1p.set(4)
|
||||||
|
if got, want := v1p.get(), 4; got != want {
|
||||||
|
panic(fmt.Sprintf("get() == %d, want %d", got, want))
|
||||||
|
}
|
||||||
|
|
||||||
|
var v2 value[string]
|
||||||
|
set(&v2, "a")
|
||||||
|
if got, want := get(&v2), "a"; got != want {
|
||||||
|
panic(fmt.Sprintf("get() == %q, want %q", got, want))
|
||||||
|
}
|
||||||
|
|
||||||
|
v2.set("b")
|
||||||
|
if got, want := get(&v2), "b"; got != want {
|
||||||
|
panic(fmt.Sprintf("get() == %q, want %q", got, want))
|
||||||
|
}
|
||||||
|
|
||||||
|
v2p := new(value[string])
|
||||||
|
set(v2p, "c")
|
||||||
|
if got, want := get(v2p), "c"; got != want {
|
||||||
|
panic(fmt.Sprintf("get() == %d, want %d", got, want))
|
||||||
|
}
|
||||||
|
|
||||||
|
v2p.set("d")
|
||||||
|
if got, want := v2p.get(), "d"; got != want {
|
||||||
|
panic(fmt.Sprintf("get() == %d, want %d", got, want))
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue