1
0
mirror of https://github.com/golang/go synced 2024-11-22 20:50:05 -07: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:
Dan Scales 2021-02-11 10:50:20 -08:00
parent 2ff1e05a4c
commit 20050a15fe
14 changed files with 582 additions and 32 deletions

View File

@ -25,6 +25,11 @@ func exportf(bout *bio.Writer, format string, args ...interface{}) {
func dumpexport(bout *bio.Writer) {
p := &exporter{marked: make(map[*types.Type]bool)}
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)
}

View File

@ -209,7 +209,7 @@ func (g *irgen) selectorExpr(pos src.XPos, typ types2.Type, expr *syntax.Selecto
// interface embedding).
var n ir.Node
method := selinfo.Obj().(*types2.Func)
method2 := selinfo.Obj().(*types2.Func)
if kind == types2.MethodExpr {
// 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)
} else {
// Add implicit addr/deref for method values, if needed.
if !x.Type().IsInterface() {
recvTyp := method.Type().(*types2.Signature).Recv().Type()
_, wantPtr := recvTyp.(*types2.Pointer)
if x.Type().IsInterface() {
n = DotMethod(pos, x, last)
} else {
recvType2 := method2.Type().(*types2.Signature).Recv().Type()
_, wantPtr := recvType2.(*types2.Pointer)
havePtr := x.Type().IsPtr()
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))
}
}
if !g.match(x.Type(), recvTyp, false) {
base.FatalfAt(pos, "expected %L to have type %v", x, recvTyp)
recvType2Base := recvType2
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)
}
return n

View File

@ -195,7 +195,7 @@ Outer:
// eventually export any exportable generic functions.
j := 0
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]
j++
}

View File

@ -13,7 +13,9 @@ import (
"cmd/compile/internal/ir"
"cmd/compile/internal/typecheck"
"cmd/compile/internal/types"
"cmd/internal/src"
"fmt"
"strings"
)
// 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
// new stenciled function
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
})
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.
func makeInstName(inst *ir.InstExpr) *types.Sym {
b := bytes.NewBufferString("#")
b.WriteString(inst.X.(*ir.Name).Name().Sym().Name)
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("[")
for i, targ := range inst.Targs {
if i > 0 {
@ -90,18 +108,38 @@ func makeInstName(inst *ir.InstExpr) *types.Sym {
// instantiation of a generic function with specified type arguments.
type subster struct {
newf *ir.Func // Func node for the new stenciled function
tparams *types.Fields
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 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 {
// Similar to noder.go: funcDecl
nameNode := inst.X.(*ir.Name)
var nameNode *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
newf := ir.NewFunc(inst.Pos())
newf.Nname = ir.NewNameAt(inst.Pos(), name)
@ -111,9 +149,10 @@ func genericSubst(name *types.Sym, inst *ir.InstExpr) *ir.Func {
subst := &subster{
newf: newf,
tparams: nameNode.Type().TParams().Fields(),
tparams: tparams,
targs: inst.Targs,
vars: make(map[*ir.Name]*ir.Name),
seen: make(map[*types.Type]*types.Type),
}
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
// the function type. The current function type has no Nname fields set,
// because it came via conversion from the types2 type.
oldt := inst.Type()
newt := types.NewSignature(oldt.Pkg(), nil, nil, subst.fields(ir.PPARAM, oldt.Params(), newf.Dcl),
subst.fields(ir.PPARAMOUT, oldt.Results(), newf.Dcl))
oldt := inst.X.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,
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.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.
func (subst *subster) typ(t *types.Type) *types.Type {
for i, tp := range subst.tparams.Slice() {
if tp.Type == t {
return subst.targs[i].Type()
// 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 {
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 {
return subst.targs[i].Type()
}
}
return t
case types.TARRAY:
elem := t.Elem()
newelem := subst.typ(elem)
if newelem != elem {
return types.NewArray(newelem, t.NumElem())
newt = types.NewArray(newelem, t.NumElem())
}
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 {
return types.NewPtr(newelem)
forw.SetUnderlying(types.NewPtr(newelem))
newt = forw
}
delete(subst.seen, t)
case types.TSLICE:
elem := t.Elem()
newelem := subst.typ(elem)
if newelem != elem {
return types.NewSlice(newelem)
newt = types.NewSlice(newelem)
}
case types.TSTRUCT:
newt := subst.tstruct(t)
forw := types.New(types.TFORW)
subst.seen[t] = forw
newt = subst.tstruct(t)
if newt != t {
return newt
forw.SetUnderlying(newt)
newt = forw
}
delete(subst.seen, t)
case types.TFUNC:
newrecvs := subst.tstruct(t.Recvs())
@ -321,21 +403,39 @@ func (subst *subster) typ(t *types.Type) *types.Type {
if newrecvs.NumFields() > 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 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.
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
}
// 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
// parameters being in order in dcl.
func (subst *subster) fields(class ir.Class, oldt *types.Type, dcl []*ir.Name) []*types.Field {
oldfields := oldt.FieldSlice()
func (subst *subster) fields(class ir.Class, oldfields []*types.Field, dcl []*ir.Name) []*types.Field {
newfields := make([]*types.Field, len(oldfields))
var i int

View File

@ -5,6 +5,7 @@
package noder
import (
"bytes"
"cmd/compile/internal/base"
"cmd/compile/internal/ir"
"cmd/compile/internal/typecheck"
@ -25,6 +26,8 @@ func (g *irgen) pkg(pkg *types2.Package) *types.Pkg {
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 {
// Caching type mappings isn't strictly needed, because typ0 preserves
// 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
}
// 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 {
switch typ := typ.(type) {
case *types2.Basic:
return g.basic(typ)
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())
if obj.Op() != ir.OTYPE {
base.FatalfAt(obj.Pos(), "expected type: %L", obj)

View File

@ -1288,6 +1288,11 @@ func ITabSym(it *obj.LSym, offset int64) *obj.LSym {
// NeedRuntimeType ensures that a runtime type descriptor is emitted for t.
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 {
signatset[t] = struct{}{}
signatslice = append(signatslice, t)

View File

@ -21,7 +21,7 @@ func TestSizeof(t *testing.T) {
_64bit uintptr // size on 64bit platforms
}{
{Sym{}, 44, 72},
{Type{}, 56, 96},
{Type{}, 68, 120},
{Map{}, 20, 40},
{Forward{}, 20, 32},
{Func{}, 28, 48},

View File

@ -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)
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() {}
@ -186,6 +191,7 @@ const (
typeNoalg // suppress hash and eq algorithm generation
typeDeferwidth // width computation has been deferred and type is on deferredTypeStack
typeRecur
typeHasTParam // there is a typeparam somewhere in the type (generic function or type)
)
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) Deferwidth() bool { return t.flags&typeDeferwidth != 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) SetBroke(b bool) { t.flags.set(typeBroke, 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) 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.
func (t *Type) Kind() Kind { return t.kind }
@ -527,6 +535,9 @@ func NewArray(elem *Type, bound int64) *Type {
t := New(TARRAY)
t.Extra = &Array{Elem: elem, Bound: bound}
t.SetNotInHeap(elem.NotInHeap())
if elem.HasTParam() {
t.SetHasTParam(true)
}
return t
}
@ -542,6 +553,9 @@ func NewSlice(elem *Type) *Type {
t := New(TSLICE)
t.Extra = Slice{Elem: elem}
elem.cache.slice = t
if elem.HasTParam() {
t.SetHasTParam(true)
}
return t
}
@ -551,6 +565,9 @@ func NewChan(elem *Type, dir ChanDir) *Type {
ct := t.ChanType()
ct.Elem = elem
ct.Dir = dir
if elem.HasTParam() {
t.SetHasTParam(true)
}
return t
}
@ -558,6 +575,9 @@ func NewTuple(t1, t2 *Type) *Type {
t := New(TTUPLE)
t.Extra.(*Tuple).first = t1
t.Extra.(*Tuple).second = t2
if t1.HasTParam() || t2.HasTParam() {
t.SetHasTParam(true)
}
return t
}
@ -579,6 +599,9 @@ func NewMap(k, v *Type) *Type {
mt := t.MapType()
mt.Key = k
mt.Elem = v
if k.HasTParam() || v.HasTParam() {
t.SetHasTParam(true)
}
return t
}
@ -597,6 +620,12 @@ func NewPtr(elem *Type) *Type {
if t.Elem() != elem {
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
}
@ -607,6 +636,9 @@ func NewPtr(elem *Type) *Type {
if NewPtrCacheEnabled {
elem.cache.ptr = t
}
if elem.HasTParam() {
t.SetHasTParam(true)
}
return t
}
@ -1611,6 +1643,9 @@ func (t *Type) SetUnderlying(underlying *Type) {
if underlying.Broke() {
t.SetBroke(true)
}
if underlying.HasTParam() {
t.SetHasTParam(true)
}
// spec: "The declared type does not inherit any methods bound
// 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.
func NewBasic(kind Kind, obj Object) *Type {
t := New(kind)
@ -1660,6 +1704,7 @@ func NewTypeParam(pkg *Pkg, constraint *Type) *Type {
constraint.wantEtype(TINTER)
t.methods = constraint.methods
t.Extra.(*Interface).pkg = pkg
t.SetHasTParam(true)
return t
}
@ -1688,6 +1733,10 @@ func NewSignature(pkg *Pkg, recv *Field, tparams, params, results []*Field) *Typ
ft.Params = funargs(params, FunargParams)
ft.Results = funargs(results, FunargResults)
ft.pkg = pkg
if len(tparams) > 0 || fieldsHasTParam(recvs) || fieldsHasTParam(params) ||
fieldsHasTParam(results) {
t.SetHasTParam(true)
}
return t
}
@ -1700,6 +1749,9 @@ func NewStruct(pkg *Pkg, fields []*Field) *Type {
t.SetBroke(true)
}
t.Extra.(*Struct).pkg = pkg
if fieldsHasTParam(fields) {
t.SetHasTParam(true)
}
return t
}

View File

@ -51,6 +51,22 @@ func (s *Selection) Kind() SelectionKind { return s.kind }
// Recv returns the type of x in x.f.
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
// a field selection, and a *Func in all other cases.
func (s *Selection) Obj() Object { return s.obj }

65
test/typeparam/list.go Normal file
View 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
View 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}))
}
}

View 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
View 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
View 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))
}
}