apidiff: dedupe embedded method set removal reports

When an embedded method is removed, apidiff reports the same
removal for both the value and pointer method sets. Report it
only from the pointer method set check.

Add tests for joining object and field names.

Change-Id: I2c9d520483c61f0489c84da1c073b5c5d25728cc
Reviewed-on: https://go-review.googlesource.com/c/exp/+/802620
Reviewed-by: Jonathan Amsterdam <jba@google.com>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Alan Donovan <adonovan@google.com>
diff --git a/apidiff/compatibility.go b/apidiff/compatibility.go
index e83fdc3..64f98c9 100644
--- a/apidiff/compatibility.go
+++ b/apidiff/compatibility.go
@@ -302,6 +302,11 @@
 			// object. So use the part string to distinguish them.
 			if receiverNamedType(oldMethod).Obj() != otn {
 				part = fmt.Sprintf(", method set of %s", msname)
+				// The same embedded method removal appears in both the value and
+				// pointer method sets. Report it only once, from the pointer check.
+				if _, ok := oldt.(*types.Pointer); !ok {
+					continue
+				}
 			}
 			d.incompatible(objectWithSide{oldMethod, false}, part, "removed")
 		} else {
diff --git a/apidiff/messageset.go b/apidiff/messageset.go
index 633ae58..5232d0a 100644
--- a/apidiff/messageset.go
+++ b/apidiff/messageset.go
@@ -1,6 +1,3 @@
-// TODO: show that two-non-empty dotjoin can happen, by using an anon struct as a field type
-// TODO: don't report removed/changed methods for both value and pointer method sets?
-
 package apidiff
 
 import (
diff --git a/apidiff/messageset_test.go b/apidiff/messageset_test.go
new file mode 100644
index 0000000..003526d
--- /dev/null
+++ b/apidiff/messageset_test.go
@@ -0,0 +1,19 @@
+package apidiff
+
+import "testing"
+
+func TestDotjoin(t *testing.T) {
+	tests := []struct {
+		s1, s2 string
+		want   string
+	}{
+		{"", "part", "part"},
+		{"obj", "", "obj"},
+		{"AnonField", "Inner", "AnonField.Inner"},
+	}
+	for _, test := range tests {
+		if got := dotjoin(test.s1, test.s2); got != test.want {
+			t.Errorf("dotjoin(%q, %q) = %q, want %q", test.s1, test.s2, got, test.want)
+		}
+	}
+}
diff --git a/apidiff/testdata/messageset.go b/apidiff/testdata/messageset.go
new file mode 100644
index 0000000..2f29f7e
--- /dev/null
+++ b/apidiff/testdata/messageset.go
@@ -0,0 +1,13 @@
+package p
+
+// old
+type AnonField struct {
+	Inner struct {
+		X int
+	}
+}
+
+// new
+type AnonField struct {
+	// i AnonField.Inner: removed
+}
diff --git a/apidiff/testdata/method_sets.go b/apidiff/testdata/method_sets.go
index 5d0dbb5..d4b0339 100644
--- a/apidiff/testdata/method_sets.go
+++ b/apidiff/testdata/method_sets.go
@@ -92,7 +92,6 @@
 // i embedm.EV1: changed from func() to func(int)
 func (embedm2) EV1(int) {}
 
-// i embedm.EV2, method set of SM: removed
 // i embedm.EV2, method set of *SM: removed
 
 // i (*embedm).EP2, method set of *SM: removed