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), &copy}
+				return
+			}
+			copy.recv = sig.recvold
+			m[ident] = Instance{newTypeList(targs), &copy}
+			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), &copy}
+				return
+			}
+			copy.recv = sig.recvold
+			m[ident] = Instance{newTypeList(targs), &copy}
+			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,