cmd/compile: prevent shapifying of pointer shape type

CL 641955 changes the Unified IR reader to not doing shapify when
reading reshaping expression, prevent losing of the original type.

This is an oversight, as the main problem isn't about shaping during the
reshaping process itself, but about the specific case of shaping a
pointer shape type. This bug occurs when instantiating a generic
function within another generic function with a pointer shape type as
type parameter, which will convert `*[]go.shape.T` to `*go.shape.uint8`,
resulting in the loss of the original expression's type.

This commit changes Unified IR reader to avoid pointer shaping for
`*[]go.shape.T`, ensures that the original type is preserved when
processing reshaping expressions.

Updates #71184
Updates #73947
Fixes #74260
Fixes #75461

Change-Id: Icede6b73247d0d367bb485619f2dafb60ad66806
Reviewed-on: https://go-review.googlesource.com/c/go/+/704095
Auto-Submit: Cuong Manh Le <cuong.manhle.vn@gmail.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: David Chase <drchase@google.com>
Reviewed-by: Junyang Shao <shaojunyang@google.com>
This commit is contained in:
Cuong Manh Le
2025-09-15 17:31:46 +07:00
committed by Gopher Robot
parent a27261c42f
commit a5866ebe40
2 changed files with 91 additions and 33 deletions

View File

@@ -49,9 +49,6 @@ type pkgReader struct {
// but bitwise inverted so we can detect if we're missing the entry
// or not.
newindex []index
// indicates whether the data is reading during reshaping.
reshaping bool
}
func newPkgReader(pr pkgbits.PkgDecoder) *pkgReader {
@@ -119,10 +116,6 @@ type reader struct {
// find parameters/results.
funarghack bool
// reshaping is used during reading exprReshape code, preventing
// the reader from shapifying the re-shaped type.
reshaping bool
// methodSym is the name of method's name, if reading a method.
// It's nil if reading a normal function or closure body.
methodSym *types.Sym
@@ -937,8 +930,19 @@ func shapify(targ *types.Type, basic bool) *types.Type {
// types, and discarding struct field names and tags. However, we'll
// need to start tracking how type parameters are actually used to
// implement some of these optimizations.
pointerShaping := basic && targ.IsPtr() && !targ.Elem().NotInHeap()
// The exception is when the type parameter is a pointer to a type
// which `Type.HasShape()` returns true, but `Type.IsShape()` returns
// false, like `*[]go.shape.T`. This is because the type parameter is
// used to instantiate a generic function inside another generic function.
// In this case, we want to keep the targ as-is, otherwise, we may lose the
// original type after `*[]go.shape.T` is shapified to `*go.shape.uint8`.
// See issue #54535, #71184.
if pointerShaping && !targ.Elem().IsShape() && targ.Elem().HasShape() {
return targ
}
under := targ.Underlying()
if basic && targ.IsPtr() && !targ.Elem().NotInHeap() {
if pointerShaping {
under = types.NewPtr(types.Types[types.TUINT8])
}
@@ -1014,25 +1018,7 @@ func (pr *pkgReader) objDictIdx(sym *types.Sym, idx index, implicits, explicits
// arguments.
for i, targ := range dict.targs {
basic := r.Bool()
isPointerShape := basic && targ.IsPtr() && !targ.Elem().NotInHeap()
// We should not do shapify during the reshaping process, see #71184.
// However, this only matters for shapify a pointer type, which will
// lose the original underlying type.
//
// Example with a pointer type:
//
// - First, shapifying *[]T -> *uint8
// - During the reshaping process, *uint8 is shapified to *go.shape.uint8
// - This ends up with a different type with the original *[]T
//
// For a non-pointer type:
//
// - int -> go.shape.int
// - go.shape.int -> go.shape.int
//
// We always end up with the identical type.
canShapify := !pr.reshaping || !isPointerShape
if dict.shaped && canShapify {
if dict.shaped {
dict.targs[i] = shapify(targ, basic)
}
}
@@ -2470,10 +2456,7 @@ func (r *reader) expr() (res ir.Node) {
case exprReshape:
typ := r.typ()
old := r.reshaping
r.reshaping = true
x := r.expr()
r.reshaping = old
if types.IdenticalStrict(x.Type(), typ) {
return x
@@ -2596,10 +2579,7 @@ func (r *reader) funcInst(pos src.XPos) (wrapperFn, baseFn, dictPtr ir.Node) {
info := r.dict.subdicts[idx]
explicits := r.p.typListIdx(info.explicits, r.dict)
old := r.p.reshaping
r.p.reshaping = r.reshaping
baseFn = r.p.objIdx(info.idx, implicits, explicits, true).(*ir.Name)
r.p.reshaping = old
// TODO(mdempsky): Is there a more robust way to get the
// dictionary pointer type here?

View File

@@ -0,0 +1,78 @@
go build main.go
! stdout .
! stderr .
-- main.go --
package main
import (
"demo/registry"
)
func main() {
_ = registry.NewUserRegistry()
}
-- go.mod --
module demo
go 1.24
-- model/user.go --
package model
type User struct {
ID int
}
func (c *User) String() string {
return ""
}
-- ordered/map.go --
package ordered
type OrderedMap[K comparable, V any] struct {
m map[K]V
}
func New[K comparable, V any](options ...any) *OrderedMap[K, V] {
orderedMap := &OrderedMap[K, V]{}
return orderedMap
}
-- registry/user.go --
package registry
import (
"demo/model"
"demo/ordered"
)
type baseRegistry = Registry[model.User, *model.User]
type UserRegistry struct {
*baseRegistry
}
type Registry[T any, P PStringer[T]] struct {
m *ordered.OrderedMap[string, P]
}
type PStringer[T any] interface {
*T
String() string
}
func NewRegistry[T any, P PStringer[T]]() *Registry[T, P] {
r := &Registry[T, P]{
m: ordered.New[string, P](),
}
return r
}
func NewUserRegistry() *UserRegistry {
return &UserRegistry{
baseRegistry: NewRegistry[model.User](),
}
}