diff --git a/src/cmd/compile/internal/gc/export.go b/src/cmd/compile/internal/gc/export.go index 356fcfa671..4d8221f53b 100644 --- a/src/cmd/compile/internal/gc/export.go +++ b/src/cmd/compile/internal/gc/export.go @@ -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) } diff --git a/src/cmd/compile/internal/noder/expr.go b/src/cmd/compile/internal/noder/expr.go index 3d6fba2dfe..2819c8252d 100644 --- a/src/cmd/compile/internal/noder/expr.go +++ b/src/cmd/compile/internal/noder/expr.go @@ -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 diff --git a/src/cmd/compile/internal/noder/irgen.go b/src/cmd/compile/internal/noder/irgen.go index d4f1b7461a..28536cc1f7 100644 --- a/src/cmd/compile/internal/noder/irgen.go +++ b/src/cmd/compile/internal/noder/irgen.go @@ -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++ } diff --git a/src/cmd/compile/internal/noder/stencil.go b/src/cmd/compile/internal/noder/stencil.go index 74ea2e0927..69461a8190 100644 --- a/src/cmd/compile/internal/noder/stencil.go +++ b/src/cmd/compile/internal/noder/stencil.go @@ -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 diff --git a/src/cmd/compile/internal/noder/types.go b/src/cmd/compile/internal/noder/types.go index 1e71969858..c23295c3a1 100644 --- a/src/cmd/compile/internal/noder/types.go +++ b/src/cmd/compile/internal/noder/types.go @@ -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) diff --git a/src/cmd/compile/internal/reflectdata/reflect.go b/src/cmd/compile/internal/reflectdata/reflect.go index 632e0f48d4..06a7f91c52 100644 --- a/src/cmd/compile/internal/reflectdata/reflect.go +++ b/src/cmd/compile/internal/reflectdata/reflect.go @@ -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) diff --git a/src/cmd/compile/internal/types/sizeof_test.go b/src/cmd/compile/internal/types/sizeof_test.go index 6937283d69..f80de937be 100644 --- a/src/cmd/compile/internal/types/sizeof_test.go +++ b/src/cmd/compile/internal/types/sizeof_test.go @@ -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}, diff --git a/src/cmd/compile/internal/types/type.go b/src/cmd/compile/internal/types/type.go index 987aa11454..b6374e49a5 100644 --- a/src/cmd/compile/internal/types/type.go +++ b/src/cmd/compile/internal/types/type.go @@ -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 } diff --git a/src/cmd/compile/internal/types2/selection.go b/src/cmd/compile/internal/types2/selection.go index 8128aeee2e..4358458b88 100644 --- a/src/cmd/compile/internal/types2/selection.go +++ b/src/cmd/compile/internal/types2/selection.go @@ -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 } diff --git a/test/typeparam/list.go b/test/typeparam/list.go new file mode 100644 index 0000000000..64230060de --- /dev/null +++ b/test/typeparam/list.go @@ -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)) + } +} diff --git a/test/typeparam/pair.go b/test/typeparam/pair.go new file mode 100644 index 0000000000..7faf083c89 --- /dev/null +++ b/test/typeparam/pair.go @@ -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})) + } +} diff --git a/test/typeparam/stringable.go b/test/typeparam/stringable.go new file mode 100644 index 0000000000..9340a3b10a --- /dev/null +++ b/test/typeparam/stringable.go @@ -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)) + } +} diff --git a/test/typeparam/struct.go b/test/typeparam/struct.go new file mode 100644 index 0000000000..98f0fcd888 --- /dev/null +++ b/test/typeparam/struct.go @@ -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)) + } +} diff --git a/test/typeparam/value.go b/test/typeparam/value.go new file mode 100644 index 0000000000..5dd7449d9c --- /dev/null +++ b/test/typeparam/value.go @@ -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)) + } +}