]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/compile: distinguish bound calls/field access in getInstInfo
authorDan Scales <danscales@google.com>
Wed, 19 Jan 2022 22:46:58 +0000 (14:46 -0800)
committerDan Scales <danscales@google.com>
Mon, 24 Jan 2022 17:07:30 +0000 (17:07 +0000)
Given we have support for field access to type params with a single
structural type, we need to distinguish between methods calls and field
access when we have an OXDOT node on an expression which is a typeparam
(or correspondingly a shape). We were missing checks in getInstInfo,
which figures out the dictionary format, which then caused problems when
we generate the dictionaries. We don't need/want dictionary entries for
field access, only for bound method calls. Added a new function
isBoundMethod() to distinguish OXDOT nodes which are bound calls vs.
field accesses on a shape.

Removed isShapeDeref() - we can't have field access or method call on a
pointer to variable of type param type.

Fixes #50690

Change-Id: Id692f65e6f427f28cd2cfe474dd30e53c71877a7
Reviewed-on: https://go-review.googlesource.com/c/go/+/379674
Trust: Dan Scales <danscales@google.com>
Reviewed-by: Keith Randall <khr@golang.org>
src/cmd/compile/internal/noder/stencil.go
test/typeparam/issue50690a.go [new file with mode: 0644]
test/typeparam/issue50690a.out [new file with mode: 0644]
test/typeparam/issue50690b.go [new file with mode: 0644]
test/typeparam/issue50690b.out [new file with mode: 0644]
test/typeparam/issue50690c.go [new file with mode: 0644]
test/typeparam/issue50690c.out [new file with mode: 0644]

index 66c73a9427632ae5bdc39f68b767888e27c57930..50b6c0efcd72bedce3f8f3fd2ad57ff99efed6e3 100644 (file)
@@ -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 (file)
index 0000000..5af3c9e
--- /dev/null
@@ -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 (file)
index 0000000..2932767
--- /dev/null
@@ -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 (file)
index 0000000..498b9d3
--- /dev/null
@@ -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 (file)
index 0000000..f75ba05
--- /dev/null
@@ -0,0 +1 @@
+Hello, world.
diff --git a/test/typeparam/issue50690c.go b/test/typeparam/issue50690c.go
new file mode 100644 (file)
index 0000000..aa9258f
--- /dev/null
@@ -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 (file)
index 0000000..f75ba05
--- /dev/null
@@ -0,0 +1 @@
+Hello, world.