Skip to content

Commit 12e15d4

Browse files
committed
[dev.typeparams] cmd/compile: handle calling a method on a type param in stenciling
- Have to delay the extra transformation on methods invoked on a type param, since the actual transformation (including path through embedded fields) will depend on the instantiated type. I am currently doing the transformation during the stencil substitution phase. We probably should have a separate pass after noder2 and stenciling, which drives the extra transformations that were in the old typechecker. - We handle method values (that are not called) and method calls. We don't currently handle method expressions. - Handle type substitution in function types, which is needed for function args in generic functions. - Added stringer.go and map.go tests, testing the above changes (including constraints with embedded interfaces). Change-Id: I3831a937d2b8814150f75bebf9f23ab10b93fa00 Reviewed-on: https://go-review.googlesource.com/c/go/+/290550 TryBot-Result: Go Bot <[email protected]> Trust: Dan Scales <[email protected]> Trust: Robert Griesemer <[email protected]> Run-TryBot: Dan Scales <[email protected]> Reviewed-by: Robert Griesemer <[email protected]>
1 parent ca18c42 commit 12e15d4

File tree

6 files changed

+219
-15
lines changed

6 files changed

+219
-15
lines changed

src/cmd/compile/internal/noder/expr.go

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,11 +87,13 @@ func (g *irgen) expr0(typ types2.Type, expr syntax.Expr) ir.Node {
8787

8888
case *syntax.CompositeLit:
8989
return g.compLit(typ, expr)
90+
9091
case *syntax.FuncLit:
9192
return g.funcLit(typ, expr)
9293

9394
case *syntax.AssertExpr:
9495
return Assert(pos, g.expr(expr.X), g.typeExpr(expr.Type))
96+
9597
case *syntax.CallExpr:
9698
fun := g.expr(expr.Fun)
9799
if inferred, ok := g.info.Inferred[expr]; ok && len(inferred.Targs) > 0 {
@@ -114,6 +116,7 @@ func (g *irgen) expr0(typ types2.Type, expr syntax.Expr) ir.Node {
114116

115117
}
116118
return Call(pos, g.typ(typ), fun, g.exprs(expr.ArgList), expr.HasDots)
119+
117120
case *syntax.IndexExpr:
118121
var targs []ir.Node
119122
if _, ok := expr.Index.(*syntax.ListExpr); ok {
@@ -139,6 +142,7 @@ func (g *irgen) expr0(typ types2.Type, expr syntax.Expr) ir.Node {
139142

140143
case *syntax.ParenExpr:
141144
return g.expr(expr.X) // skip parens; unneeded after parse+typecheck
145+
142146
case *syntax.SelectorExpr:
143147
// Qualified identifier.
144148
if name, ok := expr.X.(*syntax.Name); ok {
@@ -147,8 +151,8 @@ func (g *irgen) expr0(typ types2.Type, expr syntax.Expr) ir.Node {
147151
return typecheck.Expr(g.use(expr.Sel))
148152
}
149153
}
154+
return g.selectorExpr(pos, typ, expr)
150155

151-
return g.selectorExpr(pos, expr)
152156
case *syntax.SliceExpr:
153157
return Slice(pos, g.expr(expr.X), g.expr(expr.Index[0]), g.expr(expr.Index[1]), g.expr(expr.Index[2]))
154158

@@ -172,15 +176,22 @@ func (g *irgen) expr0(typ types2.Type, expr syntax.Expr) ir.Node {
172176
// selectorExpr resolves the choice of ODOT, ODOTPTR, OCALLPART (eventually
173177
// ODOTMETH & ODOTINTER), and OMETHEXPR and deals with embedded fields here rather
174178
// than in typecheck.go.
175-
func (g *irgen) selectorExpr(pos src.XPos, expr *syntax.SelectorExpr) ir.Node {
176-
selinfo := g.info.Selections[expr]
179+
func (g *irgen) selectorExpr(pos src.XPos, typ types2.Type, expr *syntax.SelectorExpr) ir.Node {
180+
x := g.expr(expr.X)
181+
if x.Type().Kind() == types.TTYPEPARAM {
182+
// Leave a method call on a type param as an OXDOT, since it can
183+
// only be fully transformed once it has an instantiated type.
184+
n := ir.NewSelectorExpr(pos, ir.OXDOT, x, typecheck.Lookup(expr.Sel.Value))
185+
typed(g.typ(typ), n)
186+
return n
187+
}
177188

189+
selinfo := g.info.Selections[expr]
178190
// Everything up to the last selection is an implicit embedded field access,
179191
// and the last selection is determined by selinfo.Kind().
180192
index := selinfo.Index()
181193
embeds, last := index[:len(index)-1], index[len(index)-1]
182194

183-
x := g.expr(expr.X)
184195
origx := x
185196
for _, ix := range embeds {
186197
x = Implicit(DotField(pos, x, ix))

src/cmd/compile/internal/noder/helpers.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,16 @@ func Call(pos src.XPos, typ *types.Type, fun ir.Node, args []ir.Node, dots bool)
119119
n := ir.NewCallExpr(pos, ir.OCALL, fun, args)
120120
n.IsDDD = dots
121121

122+
if fun.Op() == ir.OXDOT {
123+
if fun.(*ir.SelectorExpr).X.Type().Kind() != types.TTYPEPARAM {
124+
base.FatalfAt(pos, "Expecting type param receiver in %v", fun)
125+
}
126+
// For methods called in a generic function, don't do any extra
127+
// transformations. We will do those later when we create the
128+
// instantiated function and have the correct receiver type.
129+
typed(typ, n)
130+
return n
131+
}
122132
if fun.Op() != ir.OFUNCINST {
123133
// If no type params, still do normal typechecking, since we're
124134
// still missing some things done by tcCall below (mainly

src/cmd/compile/internal/noder/stencil.go

Lines changed: 66 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,33 @@ func (subst *subster) node(n ir.Node) ir.Node {
173173
m.SetType(subst.typ(x.Type()))
174174
}
175175
ir.EditChildren(m, edit)
176+
177+
// A method value/call via a type param will have been left as an
178+
// OXDOT. When we see this during stenciling, finish the
179+
// typechecking, now that we have the instantiated receiver type.
180+
// We need to do this now, since the access/selection to the
181+
// method for the real type is very different from the selection
182+
// for the type param.
183+
if x.Op() == ir.OXDOT {
184+
// Will transform to an OCALLPART
185+
m.SetTypecheck(0)
186+
typecheck.Expr(m)
187+
}
188+
if x.Op() == ir.OCALL {
189+
call := m.(*ir.CallExpr)
190+
if call.X.Op() != ir.OCALLPART {
191+
base.FatalfAt(call.Pos(), "Expecting OXDOT with CALL")
192+
}
193+
// Redo the typechecking, now that we know the method
194+
// value is being called
195+
call.X.(*ir.SelectorExpr).SetOp(ir.OXDOT)
196+
call.X.SetTypecheck(0)
197+
call.X.SetType(nil)
198+
typecheck.Callee(call.X)
199+
m.SetTypecheck(0)
200+
typecheck.Call(m.(*ir.CallExpr))
201+
}
202+
176203
if x.Op() == ir.OCLOSURE {
177204
x := x.(*ir.ClosureExpr)
178205
// Need to save/duplicate x.Func.Nname,
@@ -206,6 +233,31 @@ func (subst *subster) list(l []ir.Node) []ir.Node {
206233
return s
207234
}
208235

236+
// tstruct substitutes type params in a structure type
237+
func (subst *subster) tstruct(t *types.Type) *types.Type {
238+
if t.NumFields() == 0 {
239+
return t
240+
}
241+
var newfields []*types.Field
242+
for i, f := range t.Fields().Slice() {
243+
t2 := subst.typ(f.Type)
244+
if t2 != f.Type && newfields == nil {
245+
newfields = make([]*types.Field, t.NumFields())
246+
for j := 0; j < i; j++ {
247+
newfields[j] = t.Field(j)
248+
}
249+
}
250+
if newfields != nil {
251+
newfields[i] = types.NewField(f.Pos, f.Sym, t2)
252+
}
253+
}
254+
if newfields != nil {
255+
return types.NewStruct(t.Pkg(), newfields)
256+
}
257+
return t
258+
259+
}
260+
209261
// typ substitutes any type parameter found with the corresponding type argument.
210262
func (subst *subster) typ(t *types.Type) *types.Type {
211263
for i, tp := range subst.tparams.Slice() {
@@ -237,20 +289,23 @@ func (subst *subster) typ(t *types.Type) *types.Type {
237289
}
238290

239291
case types.TSTRUCT:
240-
newfields := make([]*types.Field, t.NumFields())
241-
change := false
242-
for i, f := range t.Fields().Slice() {
243-
t2 := subst.typ(f.Type)
244-
if t2 != f.Type {
245-
change = true
246-
}
247-
newfields[i] = types.NewField(f.Pos, f.Sym, t2)
292+
newt := subst.tstruct(t)
293+
if newt != t {
294+
return newt
248295
}
249-
if change {
250-
return types.NewStruct(t.Pkg(), newfields)
296+
297+
case types.TFUNC:
298+
newrecvs := subst.tstruct(t.Recvs())
299+
newparams := subst.tstruct(t.Params())
300+
newresults := subst.tstruct(t.Results())
301+
if newrecvs != t.Recvs() || newparams != t.Params() || newresults != t.Results() {
302+
var newrecv *types.Field
303+
if newrecvs.NumFields() > 0 {
304+
newrecv = newrecvs.Field(0)
305+
}
306+
return types.NewSignature(t.Pkg(), newrecv, nil, newparams.FieldSlice(), newresults.FieldSlice())
251307
}
252308

253-
// TODO: case TFUNC
254309
// TODO: case TCHAN
255310
// TODO: case TMAP
256311
// TODO: case TINTER

src/cmd/compile/internal/types/type.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1657,6 +1657,7 @@ func NewInterface(pkg *Pkg, methods []*Field) *Type {
16571657
// not really be needed except for the type checker).
16581658
func NewTypeParam(pkg *Pkg, constraint *Type) *Type {
16591659
t := New(TTYPEPARAM)
1660+
constraint.wantEtype(TINTER)
16601661
t.methods = constraint.methods
16611662
t.Extra.(*Interface).pkg = pkg
16621663
return t

test/typeparam/map.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// run -gcflags=-G=3
2+
3+
// Copyright 2021 The Go Authors. All rights reserved.
4+
// Use of this source code is governed by a BSD-style
5+
// license that can be found in the LICENSE file.
6+
7+
package main
8+
9+
import (
10+
"fmt"
11+
"reflect"
12+
"strconv"
13+
)
14+
15+
// Map calls the function f on every element of the slice s,
16+
// returning a new slice of the results.
17+
func mapper[F, T any](s []F, f func(F) T) []T {
18+
r := make([]T, len(s))
19+
for i, v := range s {
20+
r[i] = f(v)
21+
}
22+
return r
23+
}
24+
25+
func main() {
26+
got := mapper([]int{1, 2, 3}, strconv.Itoa)
27+
want := []string{"1", "2", "3"}
28+
if !reflect.DeepEqual(got, want) {
29+
panic(fmt.Sprintf("Got %s, want %s", got, want))
30+
}
31+
32+
fgot := mapper([]float64{2.5, 2.3, 3.5}, func(f float64) string {
33+
return strconv.FormatFloat(f, 'f', -1, 64)
34+
})
35+
fwant := []string{"2.5", "2.3", "3.5"}
36+
if !reflect.DeepEqual(fgot, fwant) {
37+
panic(fmt.Sprintf("Got %s, want %s", fgot, fwant))
38+
}
39+
}

test/typeparam/stringer.go

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
// run -gcflags=-G=3
2+
3+
// Copyright 2021 The Go Authors. All rights reserved.
4+
// Use of this source code is governed by a BSD-style
5+
// license that can be found in the LICENSE file.
6+
7+
// Test method calls on type parameters
8+
9+
package main
10+
11+
import (
12+
"fmt"
13+
"reflect"
14+
"strconv"
15+
)
16+
17+
// Simple constraint
18+
type Stringer interface {
19+
String() string
20+
}
21+
22+
func stringify[T Stringer](s []T) (ret []string) {
23+
for _, v := range s {
24+
ret = append(ret, v.String())
25+
}
26+
return ret
27+
}
28+
29+
type myint int
30+
31+
func (i myint) String() string {
32+
return strconv.Itoa(int(i))
33+
}
34+
35+
// Constraint with an embedded interface, but still only requires String()
36+
type Stringer2 interface {
37+
CanBeStringer2() int
38+
SubStringer2
39+
}
40+
41+
type SubStringer2 interface {
42+
CanBeSubStringer2() int
43+
String() string
44+
}
45+
46+
func stringify2[T Stringer2](s []T) (ret []string) {
47+
for _, v := range s {
48+
ret = append(ret, v.String())
49+
}
50+
return ret
51+
}
52+
53+
func (myint) CanBeStringer2() int {
54+
return 0
55+
}
56+
57+
func (myint) CanBeSubStringer2() int {
58+
return 0
59+
}
60+
61+
// Test use of method values that are not called
62+
func stringify3[T Stringer](s []T) (ret []string) {
63+
for _, v := range s {
64+
f := v.String
65+
ret = append(ret, f())
66+
}
67+
return ret
68+
}
69+
70+
func main() {
71+
x := []myint{myint(1), myint(2), myint(3)}
72+
73+
got := stringify(x)
74+
want := []string{"1", "2", "3"}
75+
if !reflect.DeepEqual(got, want) {
76+
panic(fmt.Sprintf("Got %s, want %s", got, want))
77+
}
78+
79+
got = stringify2(x)
80+
if !reflect.DeepEqual(got, want) {
81+
panic(fmt.Sprintf("Got %s, want %s", got, want))
82+
}
83+
84+
got = stringify3(x)
85+
if !reflect.DeepEqual(got, want) {
86+
panic(fmt.Sprintf("Got %s, want %s", got, want))
87+
}
88+
}

0 commit comments

Comments
 (0)