internal/breakings: type strings for functions

Remove arg and return value names from function signatures.

Change-Id: Ifb3a63f35d0942c69c42ac46eb37e6146a6a6964
Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/813944
Reviewed-by: Ethan Lee <ethanalee@google.com>
TryBot-Bypass: Jonathan Amsterdam <jba@google.com>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
diff --git a/internal/breakings/api.go b/internal/breakings/api.go
index 90169dc..aa285e4 100644
--- a/internal/breakings/api.go
+++ b/internal/breakings/api.go
@@ -12,16 +12,65 @@
 	"go/ast"
 	"go/printer"
 	"go/token"
+	"strings"
 )
 
 // typeString returns a string for the given type expression that represents the type.
 // If two such strings are equal, then the corresponding types are equal.
 // typeString returns "?" if typeExpr is nil.
 func typeString(typeExpr ast.Expr) string {
-	if typeExpr == nil {
+	switch t := typeExpr.(type) {
+	case nil:
 		return "?"
+	case *ast.FuncType:
+		return "func" + sigString(t)
+	default:
+		return nodeString(t)
 	}
-	return nodeString(typeExpr)
+}
+
+// sigString returns a string representation of a function signature
+// without parameter or return names (e.g., "(int, int) bool").
+func sigString(ft *ast.FuncType) string {
+	if ft == nil {
+		return ""
+	}
+	paramTypes := fieldListTypes(ft.Params)
+	resTypes := fieldListTypes(ft.Results)
+
+	var buf strings.Builder
+	buf.WriteString("(")
+	buf.WriteString(strings.Join(paramTypes, ", "))
+	buf.WriteString(")")
+
+	if len(resTypes) == 1 {
+		buf.WriteString(" ")
+		buf.WriteString(resTypes[0])
+	} else if len(resTypes) > 1 {
+		buf.WriteString(" (")
+		buf.WriteString(strings.Join(resTypes, ", "))
+		buf.WriteString(")")
+	}
+	return buf.String()
+}
+
+// fieldListTypes returns the types of a field list, ignoring the field names.
+// Note that "field" here means more than just a struct field: it could be
+// the arguments or return values of a function.
+func fieldListTypes(fl *ast.FieldList) []string {
+	if fl == nil {
+		return nil
+	}
+	var typeStrings []string
+	for _, f := range fl.List {
+		// convert "x, y, z T", to "T, T, T"
+		n := max(1, len(f.Names))
+		tstr := typeString(f.Type)
+		for i := 0; i < n; i++ {
+			typeStrings = append(typeStrings, tstr)
+		}
+	}
+	return typeStrings
 }
 
 // nodeString returns a string for node.
diff --git a/internal/breakings/api_test.go b/internal/breakings/api_test.go
index 4a4aa88..049163e 100644
--- a/internal/breakings/api_test.go
+++ b/internal/breakings/api_test.go
@@ -24,8 +24,7 @@
 		{"<-chan int", "<-chan int"},
 		{"func()", "func()"},
 		{"func(int, int) bool", "func(int, int) bool"},
-		// FIX: remove argument names
-		// {"func(a, b int) (c bool)", "func(int, int) bool"},
+		{"func(a, b int) (c bool, _ int)", "func(int, int) (bool, int)"},
 	}
 
 	for _, tc := range testCases {