go/types, types2: stash receivers on method selections Selector expressions on methods drop the receiver when producing a function signature. This violates a Types.Info.Instances invariant: // Invariant: Instantiating Uses[id].Type() with Instances[id].TypeArgs // results in an equivalent of Instances[id].Type. This change stashes the receiver in a new field, which is then checked for at recording time. Fixes #79657 Change-Id: Iea3dac14d22501f67fae01a79fd32edfd1e3ced4 Reviewed-on: https://go-review.googlesource.com/c/go/+/784060 TryBot-Bypass: Mark Freeman <markfreeman@google.com> Reviewed-by: Robert Griesemer <gri@google.com>
diff --git a/src/cmd/compile/internal/types2/api_test.go b/src/cmd/compile/internal/types2/api_test.go index 4d3c077..832d7cd 100644 --- a/src/cmd/compile/internal/types2/api_test.go +++ b/src/cmd/compile/internal/types2/api_test.go
@@ -3110,3 +3110,56 @@ t.Errorf("got:\n%s\nwant:\n%s", got, want) } } + +func TestIssue79657(t *testing.T) { + src := `package p + +type T[P any] struct{} +func (T[P]) M() {} +func (T[P]) N[Q any]() {} +func (*T[P]) L[Q any]() {} + +var ( + x = T[int]{} + _ = x.M + _ = T[int].M + _ = x.N[bool] + _ = T[int].N[bool] + _ = x.L[bool] + _ = (*T[int]).L[bool] +) +` + + info := &Info{Instances: make(map[*syntax.Name]Instance)} + mustTypecheck(src, nil, info) + + test := func(inst Instance, want string) { + if recv := inst.Type.(*Signature).Recv(); recv != nil { + if got := recv.Type().String(); got != want { + t.Errorf("instance %v has receiver type %s, want %s", inst, got, want) + } + } else { + t.Errorf("instance %v has no receiver", inst) + } + } + + n, l := 0, 0 + for id, inst := range info.Instances { + switch id.Value { + case "M": + t.Errorf("unexpected instance %v", inst) + case "N": + n++ + test(inst, "p.T[int]") + case "L": + l++ + test(inst, "*p.T[int]") + } + } + if n != 2 { + t.Errorf("found %d instances of N, want 2", n) + } + if l != 2 { + t.Errorf("found %d instances of L, want 2", l) + } +}
diff --git a/src/cmd/compile/internal/types2/call.go b/src/cmd/compile/internal/types2/call.go index 8398901..945d8be 100644 --- a/src/cmd/compile/internal/types2/call.go +++ b/src/cmd/compile/internal/types2/call.go
@@ -911,6 +911,7 @@ x.mode_ = value x.typ_ = &Signature{ tparams: sig.tparams, + recvold: methodExprSentinel, params: NewTuple(params...), results: sig.results, variadic: sig.variadic, @@ -924,8 +925,9 @@ x.mode_ = value - // remove receiver + // remove/stash receiver sig := *obj.typ.(*Signature) + sig.recvold = sig.recv sig.recv = nil x.typ_ = &sig }
diff --git a/src/cmd/compile/internal/types2/instantiate.go b/src/cmd/compile/internal/types2/instantiate.go index 3c4044e..c3372e9 100644 --- a/src/cmd/compile/internal/types2/instantiate.go +++ b/src/cmd/compile/internal/types2/instantiate.go
@@ -146,6 +146,11 @@ case *Signature: assert(expanding == nil) // function instances cannot be reached from Named types + // Note that orig may be a generic method on a generic type. In that case, orig + // is an instantiated type. It will not have receiver type parameters, but will + // still have ordinary type parameters. + assert(orig.RecvTypeParams() == nil) + assert(orig.TypeParams() != nil) tparams := orig.TypeParams() // TODO(gri) investigate if this is needed (type argument and parameter count seem to be correct here)
diff --git a/src/cmd/compile/internal/types2/recording.go b/src/cmd/compile/internal/types2/recording.go index 8144bb5..53be6a1 100644 --- a/src/cmd/compile/internal/types2/recording.go +++ b/src/cmd/compile/internal/types2/recording.go
@@ -131,6 +131,23 @@ assert(ident != nil) assert(typ != nil) if m := check.Instances; m != nil { + // If this is an instance of a method value/expression, replace the + // receiver for the Signature stored in Instances (go.dev/issue/79657). + if sig, _ := typ.(*Signature); sig != nil && sig.recvold != nil { + copy := *sig + copy.recvold = nil // not strictly necessary + if sig.recvold == methodExprSentinel { + pars := *copy.params + copy.recv = pars.vars[0] + pars.vars = pars.vars[1:] + copy.params = &pars + m[ident] = Instance{newTypeList(targs), ©} + return + } + copy.recv = sig.recvold + m[ident] = Instance{newTypeList(targs), ©} + return + } m[ident] = Instance{newTypeList(targs), typ} } }
diff --git a/src/cmd/compile/internal/types2/selection.go b/src/cmd/compile/internal/types2/selection.go index 2d882b2..4e546a8 100644 --- a/src/cmd/compile/internal/types2/selection.go +++ b/src/cmd/compile/internal/types2/selection.go
@@ -92,6 +92,7 @@ func (s *Selection) Type() Type { switch s.kind { case MethodVal: + // TODO(mark) Align this with call.go if possible. // The type of x.f is a method with its receiver type set // to the type of x. sig := *s.obj.(*Func).typ.(*Signature) @@ -107,6 +108,7 @@ // TODO(gri) Compute this eagerly to avoid allocations. sig := *s.obj.(*Func).typ.(*Signature) arg0 := *sig.recv + sig.recvold = sig.recv // stash receiver (for consistency with call.go) sig.recv = nil arg0.typ = s.recv var params []*Var @@ -117,7 +119,7 @@ return &sig } - // In all other cases, the type of x.f is the type of x. + // In all other cases, the type of x.f is the type of f. return s.obj.Type() }
diff --git a/src/cmd/compile/internal/types2/signature.go b/src/cmd/compile/internal/types2/signature.go index bf1cb23..7ce6401 100644 --- a/src/cmd/compile/internal/types2/signature.go +++ b/src/cmd/compile/internal/types2/signature.go
@@ -26,10 +26,16 @@ tparams *TypeParamList // type parameters from left to right, or nil scope *Scope // function scope for package-local and non-instantiated signatures; nil otherwise recv *Var // nil if not a method + recvold *Var // receiver dropped via method selection; or nil params *Tuple // (incoming) parameters from left to right; or nil results *Tuple // (outgoing) results from left to right; or nil variadic bool // true if the last parameter's type is of the form ...T + // If recvold is the sentinel value [methExpr], then recvold should + // instead be sourced from params[0]. Otherwise, recvold points to + // the receiver of the original method signature from which this + // function signature was cloned via a selector expression. + // If variadic, the last element of params ordinarily has an // unnamed Slice type. As a special case, in a call to append, // it may be string, or a TypeParam T whose typeset ⊇ {string, []byte}. @@ -37,6 +43,9 @@ // T at such a type. } +// sentinel value for detecting method expressions +var methodExprSentinel = &Var{} + // NewSignatureType creates a new function type for the given receiver, // receiver type parameters, type parameters, parameters, and results. //
diff --git a/src/cmd/compile/internal/types2/sizeof_test.go b/src/cmd/compile/internal/types2/sizeof_test.go index a3697b6..68959aa 100644 --- a/src/cmd/compile/internal/types2/sizeof_test.go +++ b/src/cmd/compile/internal/types2/sizeof_test.go
@@ -26,7 +26,7 @@ {Struct{}, 24, 48}, {Pointer{}, 8, 16}, {Tuple{}, 12, 24}, - {Signature{}, 28, 56}, + {Signature{}, 32, 64}, {Union{}, 12, 24}, {Interface{}, 40, 80}, {Map{}, 16, 32},
diff --git a/src/cmd/compile/internal/types2/subst.go b/src/cmd/compile/internal/types2/subst.go index 5441cf6..c79c821 100644 --- a/src/cmd/compile/internal/types2/subst.go +++ b/src/cmd/compile/internal/types2/subst.go
@@ -246,6 +246,7 @@ tparams: tparams, // instantiated signatures have a nil scope recv: recv, + recvold: t.recvold, params: params, results: results, variadic: t.variadic,
diff --git a/src/go/types/api_test.go b/src/go/types/api_test.go index 69fbbf8..ea5cf66 100644 --- a/src/go/types/api_test.go +++ b/src/go/types/api_test.go
@@ -3192,3 +3192,56 @@ t.Errorf("instantiated: got type %s, want %s", got, want) } } + +func TestIssue79657(t *testing.T) { + src := `package p + +type T[P any] struct{} +func (T[P]) M() {} +func (T[P]) N[Q any]() {} +func (*T[P]) L[Q any]() {} + +var ( + x = T[int]{} + _ = x.M + _ = T[int].M + _ = x.N[bool] + _ = T[int].N[bool] + _ = x.L[bool] + _ = (*T[int]).L[bool] +) +` + + info := &Info{Instances: make(map[*ast.Ident]Instance)} + mustTypecheck(src, nil, info) + + test := func(inst Instance, want string) { + if recv := inst.Type.(*Signature).Recv(); recv != nil { + if got := recv.Type().String(); got != want { + t.Errorf("instance %v has receiver type %s, want %s", inst, got, want) + } + } else { + t.Errorf("instance %v has no receiver", inst) + } + } + + n, l := 0, 0 + for id, inst := range info.Instances { + switch id.Name { + case "M": + t.Errorf("unexpected instance %v", inst) + case "N": + n++ + test(inst, "p.T[int]") + case "L": + l++ + test(inst, "*p.T[int]") + } + } + if n != 2 { + t.Errorf("found %d instances of N, want 2", n) + } + if l != 2 { + t.Errorf("found %d instances of L, want 2", l) + } +}
diff --git a/src/go/types/call.go b/src/go/types/call.go index 5439032..530b833 100644 --- a/src/go/types/call.go +++ b/src/go/types/call.go
@@ -914,6 +914,7 @@ x.mode_ = value x.typ_ = &Signature{ tparams: sig.tparams, + recvold: methodExprSentinel, params: NewTuple(params...), results: sig.results, variadic: sig.variadic, @@ -972,8 +973,9 @@ x.mode_ = value - // remove receiver + // remove/stash receiver sig := *obj.typ.(*Signature) + sig.recvold = sig.recv sig.recv = nil x.typ_ = &sig }
diff --git a/src/go/types/instantiate.go b/src/go/types/instantiate.go index 6488494..4851a6f 100644 --- a/src/go/types/instantiate.go +++ b/src/go/types/instantiate.go
@@ -149,6 +149,11 @@ case *Signature: assert(expanding == nil) // function instances cannot be reached from Named types + // Note that orig may be a generic method on a generic type. In that case, orig + // is an instantiated type. It will not have receiver type parameters, but will + // still have ordinary type parameters. + assert(orig.RecvTypeParams() == nil) + assert(orig.TypeParams() != nil) tparams := orig.TypeParams() // TODO(gri) investigate if this is needed (type argument and parameter count seem to be correct here)
diff --git a/src/go/types/recording.go b/src/go/types/recording.go index 809246b..1e6ae7e 100644 --- a/src/go/types/recording.go +++ b/src/go/types/recording.go
@@ -134,6 +134,23 @@ assert(ident != nil) assert(typ != nil) if m := check.Instances; m != nil { + // If this is an instance of a method value/expression, replace the + // receiver for the Signature stored in Instances (go.dev/issue/79657). + if sig, _ := typ.(*Signature); sig != nil && sig.recvold != nil { + copy := *sig + copy.recvold = nil // not strictly necessary + if sig.recvold == methodExprSentinel { + pars := *copy.params + copy.recv = pars.vars[0] + pars.vars = pars.vars[1:] + copy.params = &pars + m[ident] = Instance{newTypeList(targs), ©} + return + } + copy.recv = sig.recvold + m[ident] = Instance{newTypeList(targs), ©} + return + } m[ident] = Instance{newTypeList(targs), typ} } }
diff --git a/src/go/types/selection.go b/src/go/types/selection.go index 6f8da7a..15f3390 100644 --- a/src/go/types/selection.go +++ b/src/go/types/selection.go
@@ -95,6 +95,7 @@ func (s *Selection) Type() Type { switch s.kind { case MethodVal: + // TODO(mark) Align this with call.go if possible. // The type of x.f is a method with its receiver type set // to the type of x. sig := *s.obj.(*Func).typ.(*Signature) @@ -110,6 +111,7 @@ // TODO(gri) Compute this eagerly to avoid allocations. sig := *s.obj.(*Func).typ.(*Signature) arg0 := *sig.recv + sig.recvold = sig.recv // stash receiver (for consistency with call.go) sig.recv = nil arg0.typ = s.recv var params []*Var @@ -120,7 +122,7 @@ return &sig } - // In all other cases, the type of x.f is the type of x. + // In all other cases, the type of x.f is the type of f. return s.obj.Type() }
diff --git a/src/go/types/signature.go b/src/go/types/signature.go index 7ae2c07..8ee862a 100644 --- a/src/go/types/signature.go +++ b/src/go/types/signature.go
@@ -27,10 +27,16 @@ tparams *TypeParamList // type parameters from left to right, or nil scope *Scope // function scope for package-local and non-instantiated signatures; nil otherwise recv *Var // nil if not a method + recvold *Var // receiver dropped via method selection; or nil params *Tuple // (incoming) parameters from left to right; or nil results *Tuple // (outgoing) results from left to right; or nil variadic bool // true if the last parameter's type is of the form ...T + // If recvold is the sentinel value [methExpr], then recvold should + // instead be sourced from params[0]. Otherwise, recvold points to + // the receiver of the original method signature from which this + // function signature was cloned via a selector expression. + // If variadic, the last element of params ordinarily has an // unnamed Slice type. As a special case, in a call to append, // it may be string, or a TypeParam T whose typeset ⊇ {string, []byte}. @@ -38,6 +44,9 @@ // T at such a type. } +// sentinel value for detecting method expressions +var methodExprSentinel = &Var{} + // NewSignature returns a new function type for the given receiver, parameters, // and results, either of which may be nil. If variadic is set, the function // is variadic, it must have at least one parameter, and the last parameter
diff --git a/src/go/types/sizeof_test.go b/src/go/types/sizeof_test.go index 2f18595..34b681d 100644 --- a/src/go/types/sizeof_test.go +++ b/src/go/types/sizeof_test.go
@@ -25,7 +25,7 @@ {Struct{}, 24, 48}, {Pointer{}, 8, 16}, {Tuple{}, 12, 24}, - {Signature{}, 28, 56}, + {Signature{}, 32, 64}, {Union{}, 12, 24}, {Interface{}, 40, 80}, {Map{}, 16, 32},
diff --git a/src/go/types/subst.go b/src/go/types/subst.go index 0bdec06..885af89 100644 --- a/src/go/types/subst.go +++ b/src/go/types/subst.go
@@ -249,6 +249,7 @@ tparams: tparams, // instantiated signatures have a nil scope recv: recv, + recvold: t.recvold, params: params, results: results, variadic: t.variadic,