diff --git a/src/cmd/compile/internal/noder/stencil.go b/src/cmd/compile/internal/noder/stencil.go index 66c73a9427..50b6c0efcd 100644 --- a/src/cmd/compile/internal/noder/stencil.go +++ b/src/cmd/compile/internal/noder/stencil.go @@ -1036,13 +1036,13 @@ func (subst *subster) node(n ir.Node) ir.Node { } case ir.OXDOT: - // Finish the transformation of an OXDOT, unless this was a - // bound call (a direct call on a type param). A bound call - // will be transformed during the dictPass. Otherwise, m - // will be transformed to an OMETHVALUE node. It will be - // transformed to an ODOTMETH or ODOTINTER node if we find in - // the OCALL case below that the method value is actually - // called. + // Finish the transformation of an OXDOT, unless this is + // bound call or field access on a type param. A bound call + // or field access on a type param will be transformed during + // the dictPass. Otherwise, m will be transformed to an + // OMETHVALUE node. It will be transformed to an ODOTMETH or + // ODOTINTER node if we find in the OCALL case below that the + // method value is actually called. mse := m.(*ir.SelectorExpr) if src := mse.X.Type(); !src.IsShape() { transformDot(mse, false) @@ -1101,10 +1101,11 @@ func (subst *subster) node(n ir.Node) ir.Node { transformEarlyCall(call) case ir.OXDOT: - // This is the case of a bound call on a typeparam, - // which will be handled in the dictPass. - // As with OFUNCINST, we must transform the arguments of the call now, - // so any needed CONVIFACE nodes are exposed. + // This is the case of a bound call or a field access + // on a typeparam, which will be handled in the + // dictPass. As with OFUNCINST, we must transform the + // arguments of the call now, so any needed CONVIFACE + // nodes are exposed. transformEarlyCall(call) case ir.ODOTTYPE, ir.ODOTTYPE2: @@ -1228,13 +1229,13 @@ func (g *genInst) dictPass(info *instInfo) { // No need for transformDot - buildClosure2 has already // transformed to OCALLINTER/ODOTINTER. } else { - dst := info.dictInfo.shapeToBound[m.(*ir.SelectorExpr).X.Type()] // If we can't find the selected method in the // AllMethods of the bound, then this must be an access // to a field of a structural type. If so, we skip the // dictionary lookups - transformDot() will convert to // the desired direct field access. - if typecheck.Lookdot1(mse, mse.Sel, dst, dst.AllMethods(), 1) != nil { + if isBoundMethod(info.dictInfo, mse) { + dst := info.dictInfo.shapeToBound[mse.X.Type()] // Implement x.M as a conversion-to-bound-interface // 1) convert x to the bound interface // 2) call M on that interface @@ -1873,11 +1874,15 @@ func (g *genInst) getInstInfo(st *ir.Func, shapes []*types.Type, instInfo *instI info.subDictCalls = append(info.subDictCalls, subDictInfo{callNode: n, savedXNode: ce.X}) } } - if ce.X.Op() == ir.OXDOT && - isShapeDeref(ce.X.(*ir.SelectorExpr).X.Type()) { + // Note: this XDOT code is not actually needed as long as we + // continue to disable type parameters on RHS of type + // declarations (#45639). + if ce.X.Op() == ir.OXDOT { callMap[ce.X] = true - infoPrint(" Optional subdictionary at generic bound call: %v\n", n) - info.subDictCalls = append(info.subDictCalls, subDictInfo{callNode: n, savedXNode: nil}) + if isBoundMethod(info, ce.X.(*ir.SelectorExpr)) { + infoPrint(" Optional subdictionary at generic bound call: %v\n", n) + info.subDictCalls = append(info.subDictCalls, subDictInfo{callNode: n, savedXNode: nil}) + } } case ir.OCALLMETH: ce := n.(*ir.CallExpr) @@ -1900,7 +1905,8 @@ func (g *genInst) getInstInfo(st *ir.Func, shapes []*types.Type, instInfo *instI info.itabConvs = append(info.itabConvs, n) } case ir.OXDOT: - if n.(*ir.SelectorExpr).X.Type().IsShape() { + se := n.(*ir.SelectorExpr) + if isBoundMethod(info, se) { infoPrint(" Itab for bound call: %v\n", n) info.itabConvs = append(info.itabConvs, n) } @@ -1956,11 +1962,13 @@ func (g *genInst) getInstInfo(st *ir.Func, shapes []*types.Type, instInfo *instI info.dictLen = len(info.shapeParams) + len(info.derivedTypes) + len(info.subDictCalls) + len(info.itabConvs) } -// isShapeDeref returns true if t is either a shape or a pointer to a shape. (We -// can't just use deref(t).IsShape(), since a shape type is a complex type and may -// have a pointer as part of its shape.) -func isShapeDeref(t *types.Type) bool { - return t.IsShape() || t.IsPtr() && t.Elem().IsShape() +// isBoundMethod returns true if the selection indicated by se is a bound method of +// se.X. se.X must be a shape type (i.e. substituted directly from a type param). If +// isBoundMethod returns false, then the selection must be a field access of a +// structural type. +func isBoundMethod(info *dictInfo, se *ir.SelectorExpr) bool { + bound := info.shapeToBound[se.X.Type()] + return typecheck.Lookdot1(se, se.Sel, bound, bound.AllMethods(), 1) != nil } // addType adds t to info.derivedTypes if it is parameterized type (which is not diff --git a/test/typeparam/issue50690a.go b/test/typeparam/issue50690a.go new file mode 100644 index 0000000000..5af3c9ead8 --- /dev/null +++ b/test/typeparam/issue50690a.go @@ -0,0 +1,62 @@ +// run -gcflags=-G=3 + +// Copyright 2022 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" +) + +// Numeric expresses a type constraint satisfied by any numeric type. +type Numeric interface { + ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | + ~int | ~int8 | ~int16 | ~int32 | ~int64 | + ~float32 | ~float64 | + ~complex64 | ~complex128 +} + +// Sum returns the sum of the provided arguments. +func Sum[T Numeric](args ...T) T { + var sum T + for i := 0; i < len(args); i++ { + sum += args[i] + } + return sum +} + +// Ledger is an identifiable, financial record. +type Ledger[T ~string, K Numeric] struct { + + // ID identifies the ledger. + ID T + + // Amounts is a list of monies associated with this ledger. + Amounts []K + + // SumFn is a function that can be used to sum the amounts + // in this ledger. + SumFn func(...K) K +} + +func PrintLedger[ + T ~string, + K Numeric, + L ~struct { + ID T + Amounts []K + SumFn func(...K) K + }, +](l L) { + fmt.Printf("%s has a sum of %v\n", l.ID, l.SumFn(l.Amounts...)) +} + +func main() { + PrintLedger(Ledger[string, int]{ + ID: "fake", + Amounts: []int{1, 2, 3}, + SumFn: Sum[int], + }) +} diff --git a/test/typeparam/issue50690a.out b/test/typeparam/issue50690a.out new file mode 100644 index 0000000000..293276716f --- /dev/null +++ b/test/typeparam/issue50690a.out @@ -0,0 +1 @@ +fake has a sum of 6 diff --git a/test/typeparam/issue50690b.go b/test/typeparam/issue50690b.go new file mode 100644 index 0000000000..498b9d37e1 --- /dev/null +++ b/test/typeparam/issue50690b.go @@ -0,0 +1,41 @@ +// run -gcflags=-G=3 + +// Copyright 2022 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 Printer[T ~string] struct { + PrintFn func(T) +} + +func Print[T ~string](s T) { + fmt.Println(s) +} + +func PrintWithPrinter[T ~string, S ~struct { + ID T + PrintFn func(T) +}](message T, obj S) { + obj.PrintFn(message) +} + +type PrintShop[T ~string] struct { + ID T + PrintFn func(T) +} + +func main() { + PrintWithPrinter( + "Hello, world.", + PrintShop[string]{ + ID: "fake", + PrintFn: Print[string], + }, + ) +} diff --git a/test/typeparam/issue50690b.out b/test/typeparam/issue50690b.out new file mode 100644 index 0000000000..f75ba05f34 --- /dev/null +++ b/test/typeparam/issue50690b.out @@ -0,0 +1 @@ +Hello, world. diff --git a/test/typeparam/issue50690c.go b/test/typeparam/issue50690c.go new file mode 100644 index 0000000000..aa9258f932 --- /dev/null +++ b/test/typeparam/issue50690c.go @@ -0,0 +1,36 @@ +// run -gcflags=-G=3 + +// Copyright 2022 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 Printer[T ~string] struct { + PrintFn func(T) +} + +func Print[T ~string](s T) { + fmt.Println(s) +} + +func PrintWithPrinter[T ~string, S struct { + ID T + PrintFn func(T) +}](message T, obj S) { + obj.PrintFn(message) +} + +func main() { + PrintWithPrinter( + "Hello, world.", + struct { + ID string + PrintFn func(string) + }{ID: "fake", PrintFn: Print[string]}, + ) +} diff --git a/test/typeparam/issue50690c.out b/test/typeparam/issue50690c.out new file mode 100644 index 0000000000..f75ba05f34 --- /dev/null +++ b/test/typeparam/issue50690c.out @@ -0,0 +1 @@ +Hello, world.