From 6a5f7e8498b7cd53bb5461fbf777aa83aea067a8 Mon Sep 17 00:00:00 2001 From: Keith Randall Date: Tue, 8 Jun 2021 15:58:16 -0700 Subject: [PATCH] [dev.typeparams] cmd/compile: use dictionary entries for more conversion cases This CL handles I(x) where I is an interface type and x has typeparam type. Change-Id: Ib99de2b741d588947f5e0164255f6365e98acd8a Reviewed-on: https://go-review.googlesource.com/c/go/+/326189 Trust: Keith Randall Trust: Dan Scales Run-TryBot: Keith Randall TryBot-Result: Go Bot Reviewed-by: Dan Scales --- src/cmd/compile/internal/noder/stencil.go | 79 ++++++++++++++++------- test/typeparam/ifaceconv.go | 9 ++- 2 files changed, 62 insertions(+), 26 deletions(-) diff --git a/src/cmd/compile/internal/noder/stencil.go b/src/cmd/compile/internal/noder/stencil.go index 49781ddc074..29ee863a71f 100644 --- a/src/cmd/compile/internal/noder/stencil.go +++ b/src/cmd/compile/internal/noder/stencil.go @@ -838,7 +838,15 @@ func (subst *subster) node(n ir.Node) ir.Node { case ir.OTYPE: // Transform the conversion, now that we know the // type argument. - m = transformConvCall(m.(*ir.CallExpr)) + m = transformConvCall(call) + if m.Op() == ir.OCONVIFACE { + if srcType := x.(*ir.CallExpr).Args[0].Type(); srcType.IsTypeParam() { // TODO: or derived type + // Note: srcType uses x.Args[0], not m.X or call.Args[0], because + // we need the type before the type parameter -> type argument substitution. + c := m.(*ir.ConvExpr) + m = subst.convertUsingDictionary(c.Pos(), c.X, c.Type(), srcType) + } + } case ir.OMETHVALUE: // Redo the transformation of OXDOT, now that we @@ -919,30 +927,10 @@ func (subst *subster) node(n ir.Node) ir.Node { case ir.OCONVIFACE: x := x.(*ir.ConvExpr) - // TODO: handle converting from derived types. For now, just from naked - // type parameters. - if x.X.Type().IsTypeParam() { - // Load the actual runtime._type of the type parameter from the dictionary. - rt := subst.getDictionaryType(m.Pos(), x.X.Type()) - - // At this point, m is an interface type with a data word we want. - // But the type word represents a gcshape type, which we don't want. - // Replace with the instantiated type loaded from the dictionary. - m = ir.NewUnaryExpr(m.Pos(), ir.OIDATA, m) - typed(types.Types[types.TUNSAFEPTR], m) - m = ir.NewBinaryExpr(m.Pos(), ir.OEFACE, rt, m) - if !x.Type().IsEmptyInterface() { - // We just built an empty interface{}. Type it as such, - // then assert it to the required non-empty interface. - typed(types.NewInterface(types.LocalPkg, nil), m) - m = ir.NewTypeAssertExpr(m.Pos(), m, nil) - } - typed(x.Type(), m) - // TODO: we're throwing away the type word of the original version - // of m here (it would be OITAB(m)), which probably took some - // work to generate. Can we avoid generating it at all? - // (The linker will throw them away if not needed, so it would just - // save toolchain work, not binary size.) + // Note: x's argument is still typed as a type parameter. + // m's argument now has an instantiated type. + if t := x.X.Type(); t.IsTypeParam() { + m = subst.convertUsingDictionary(x.Pos(), m.(*ir.ConvExpr).X, m.Type(), t) } } return m @@ -951,6 +939,47 @@ func (subst *subster) node(n ir.Node) ir.Node { return edit(n) } +// convertUsingDictionary converts value v from generic type src to an interface type dst. +func (subst *subster) convertUsingDictionary(pos src.XPos, v ir.Node, dst, src *types.Type) ir.Node { + // TODO: handle converting from derived types. For now, just from naked + // type parameters. + if !src.IsTypeParam() { + base.Fatalf("source must be a type parameter %+v", src) + } + if !dst.IsInterface() { + base.Fatalf("can only convert type parameters to interfaces %+v -> %+v", src, dst) + } + // Load the actual runtime._type of the type parameter from the dictionary. + rt := subst.getDictionaryType(pos, src) + + // Convert value to an interface type, so the data field is what we want. + if !v.Type().IsInterface() { + v = ir.NewConvExpr(v.Pos(), ir.OCONVIFACE, nil, v) + typed(types.NewInterface(types.LocalPkg, nil), v) + } + + // At this point, v is an interface type with a data word we want. + // But the type word represents a gcshape type, which we don't want. + // Replace with the instantiated type loaded from the dictionary. + data := ir.NewUnaryExpr(pos, ir.OIDATA, v) + typed(types.Types[types.TUNSAFEPTR], data) + var i ir.Node = ir.NewBinaryExpr(pos, ir.OEFACE, rt, data) + if !dst.IsEmptyInterface() { + // We just built an empty interface{}. Type it as such, + // then assert it to the required non-empty interface. + typed(types.NewInterface(types.LocalPkg, nil), i) + i = ir.NewTypeAssertExpr(pos, i, nil) + } + typed(dst, i) + // TODO: we're throwing away the type word of the original version + // of m here (it would be OITAB(m)), which probably took some + // work to generate. Can we avoid generating it at all? + // (The linker will throw them away if not needed, so it would just + // save toolchain work, not binary size.) + return i + +} + func (subst *subster) namelist(l []*ir.Name) []*ir.Name { s := make([]*ir.Name, len(l)) for i, n := range l { diff --git a/test/typeparam/ifaceconv.go b/test/typeparam/ifaceconv.go index 0b0776815c9..32c2dbe7c28 100644 --- a/test/typeparam/ifaceconv.go +++ b/test/typeparam/ifaceconv.go @@ -38,10 +38,14 @@ func h[T C](x T) interface{foo() int} { return i } func i[T C](x T) C { - var i C = x + var i C = x // conversion in assignment return i } +func j[T C](t T) C { + return C(t) // explicit conversion +} + func main() { if got, want := f[int](7), 7; got != want { panic(fmt.Sprintf("got %d want %d", got, want)) @@ -55,4 +59,7 @@ func main() { if got, want := i[myInt](7).foo(), 8; got != want { panic(fmt.Sprintf("got %d want %d", got, want)) } + if got, want := j[myInt](7).foo(), 8; got != want { + panic(fmt.Sprintf("got %d want %d", got, want)) + } }