Skip to content

Commit ba10d0b

Browse files
authored
Move function name logic to function package (#106)
Adjust the logic of NameOf for more recent versions of Go.
1 parent 47b0945 commit ba10d0b

File tree

4 files changed

+97
-40
lines changed

4 files changed

+97
-40
lines changed

cmp/compare.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -346,8 +346,7 @@ func (s *state) callTRFunc(f, v reflect.Value) reflect.Value {
346346
if !s.statelessCompare(want, want).Equal() {
347347
return want
348348
}
349-
fn := getFuncName(f.Pointer())
350-
panic(fmt.Sprintf("non-deterministic function detected: %s", fn))
349+
panic(fmt.Sprintf("non-deterministic function detected: %s", function.NameOf(f)))
351350
}
352351
return want
353352
}
@@ -367,8 +366,7 @@ func (s *state) callTTBFunc(f, x, y reflect.Value) bool {
367366
go detectRaces(c, f, y, x)
368367
want := f.Call([]reflect.Value{x, y})[0].Bool()
369368
if got := <-c; !got.IsValid() || got.Bool() != want {
370-
fn := getFuncName(f.Pointer())
371-
panic(fmt.Sprintf("non-deterministic or non-symmetric function detected: %s", fn))
369+
panic(fmt.Sprintf("non-deterministic or non-symmetric function detected: %s", function.NameOf(f)))
372370
}
373371
return want
374372
}

cmp/internal/function/func.go

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,15 @@
22
// Use of this source code is governed by a BSD-style
33
// license that can be found in the LICENSE.md file.
44

5-
// Package function identifies function types.
5+
// Package function provides functionality for identifying function types.
66
package function
77

8-
import "reflect"
8+
import (
9+
"reflect"
10+
"regexp"
11+
"runtime"
12+
"strings"
13+
)
914

1015
type funcType int
1116

@@ -47,3 +52,36 @@ func IsType(t reflect.Type, ft funcType) bool {
4752
}
4853
return false
4954
}
55+
56+
var lastIdentRx = regexp.MustCompile(`[_\p{L}][_\p{L}\p{N}]*$`)
57+
58+
// NameOf returns the name of the function value.
59+
func NameOf(v reflect.Value) string {
60+
fnc := runtime.FuncForPC(v.Pointer())
61+
if fnc == nil {
62+
return "<unknown>"
63+
}
64+
fullName := fnc.Name() // e.g., "long/path/name/mypkg.(*MyType).(long/path/name/mypkg.myMethod)-fm"
65+
66+
// Method closures have a "-fm" suffix.
67+
fullName = strings.TrimSuffix(fullName, "-fm")
68+
69+
var name string
70+
for len(fullName) > 0 {
71+
inParen := strings.HasSuffix(fullName, ")")
72+
fullName = strings.TrimSuffix(fullName, ")")
73+
74+
s := lastIdentRx.FindString(fullName)
75+
if s == "" {
76+
break
77+
}
78+
name = s + "." + name
79+
fullName = strings.TrimSuffix(fullName, s)
80+
81+
if i := strings.LastIndexByte(fullName, '('); inParen && i >= 0 {
82+
fullName = fullName[:i]
83+
}
84+
fullName = strings.TrimSuffix(fullName, ".")
85+
}
86+
return strings.TrimSuffix(name, ".")
87+
}

cmp/internal/function/func_test.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// Copyright 2019, The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE.md file.
4+
5+
package function
6+
7+
import (
8+
"bytes"
9+
"reflect"
10+
"testing"
11+
)
12+
13+
type myType struct{ bytes.Buffer }
14+
15+
func (myType) valueMethod() {}
16+
func (myType) ValueMethod() {}
17+
18+
func (*myType) pointerMethod() {}
19+
func (*myType) PointerMethod() {}
20+
21+
func TestNameOf(t *testing.T) {
22+
tests := []struct {
23+
fnc interface{}
24+
want string
25+
}{
26+
{TestNameOf, "function.TestNameOf"},
27+
{func() {}, "function.TestNameOf.func1"},
28+
{(myType).valueMethod, "function.myType.valueMethod"},
29+
{(myType).ValueMethod, "function.myType.ValueMethod"},
30+
{(myType{}).valueMethod, "function.myType.valueMethod"},
31+
{(myType{}).ValueMethod, "function.myType.ValueMethod"},
32+
{(*myType).valueMethod, "function.myType.valueMethod"},
33+
{(*myType).ValueMethod, "function.myType.ValueMethod"},
34+
{(&myType{}).valueMethod, "function.myType.valueMethod"},
35+
{(&myType{}).ValueMethod, "function.myType.ValueMethod"},
36+
{(*myType).pointerMethod, "function.myType.pointerMethod"},
37+
{(*myType).PointerMethod, "function.myType.PointerMethod"},
38+
{(&myType{}).pointerMethod, "function.myType.pointerMethod"},
39+
{(&myType{}).PointerMethod, "function.myType.PointerMethod"},
40+
{(*myType).Write, "function.myType.Write"},
41+
{(&myType{}).Write, "bytes.Buffer.Write"},
42+
}
43+
for _, tt := range tests {
44+
t.Run("", func(t *testing.T) {
45+
got := NameOf(reflect.ValueOf(tt.fnc))
46+
if got != tt.want {
47+
t.Errorf("NameOf() = %v, want %v", got, tt.want)
48+
}
49+
})
50+
}
51+
}

cmp/options.go

Lines changed: 4 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ package cmp
77
import (
88
"fmt"
99
"reflect"
10-
"runtime"
1110
"strings"
1211

1312
"github.com/google/go-cmp/cmp/internal/function"
@@ -132,8 +131,7 @@ func (f pathFilter) filter(s *state, vx, vy reflect.Value, t reflect.Type) appli
132131
}
133132

134133
func (f pathFilter) String() string {
135-
fn := getFuncName(reflect.ValueOf(f.fnc).Pointer())
136-
return fmt.Sprintf("FilterPath(%s, %v)", fn, f.opt)
134+
return fmt.Sprintf("FilterPath(%s, %v)", function.NameOf(reflect.ValueOf(f.fnc)), f.opt)
137135
}
138136

139137
// FilterValues returns a new Option where opt is only evaluated if filter f,
@@ -182,8 +180,7 @@ func (f valuesFilter) filter(s *state, vx, vy reflect.Value, t reflect.Type) app
182180
}
183181

184182
func (f valuesFilter) String() string {
185-
fn := getFuncName(f.fnc.Pointer())
186-
return fmt.Sprintf("FilterValues(%s, %v)", fn, f.opt)
183+
return fmt.Sprintf("FilterValues(%s, %v)", function.NameOf(f.fnc), f.opt)
187184
}
188185

189186
// Ignore is an Option that causes all comparisons to be ignored.
@@ -279,7 +276,7 @@ func (tr *transformer) apply(s *state, vx, vy reflect.Value) {
279276
}
280277

281278
func (tr transformer) String() string {
282-
return fmt.Sprintf("Transformer(%s, %s)", tr.name, getFuncName(tr.fnc.Pointer()))
279+
return fmt.Sprintf("Transformer(%s, %s)", tr.name, function.NameOf(tr.fnc))
283280
}
284281

285282
// Comparer returns an Option that determines whether two values are equal
@@ -327,7 +324,7 @@ func (cm *comparer) apply(s *state, vx, vy reflect.Value) {
327324
}
328325

329326
func (cm comparer) String() string {
330-
return fmt.Sprintf("Comparer(%s)", getFuncName(cm.fnc.Pointer()))
327+
return fmt.Sprintf("Comparer(%s)", function.NameOf(cm.fnc))
331328
}
332329

333330
// AllowUnexported returns an Option that forcibly allows operations on
@@ -427,30 +424,3 @@ func flattenOptions(dst, src Options) Options {
427424
}
428425
return dst
429426
}
430-
431-
// getFuncName returns a short function name from the pointer.
432-
// The string parsing logic works up until Go1.9.
433-
func getFuncName(p uintptr) string {
434-
fnc := runtime.FuncForPC(p)
435-
if fnc == nil {
436-
return "<unknown>"
437-
}
438-
name := fnc.Name() // E.g., "long/path/name/mypkg.(mytype).(long/path/name/mypkg.myfunc)-fm"
439-
if strings.HasSuffix(name, ")-fm") || strings.HasSuffix(name, ")·fm") {
440-
// Strip the package name from method name.
441-
name = strings.TrimSuffix(name, ")-fm")
442-
name = strings.TrimSuffix(name, ")·fm")
443-
if i := strings.LastIndexByte(name, '('); i >= 0 {
444-
methodName := name[i+1:] // E.g., "long/path/name/mypkg.myfunc"
445-
if j := strings.LastIndexByte(methodName, '.'); j >= 0 {
446-
methodName = methodName[j+1:] // E.g., "myfunc"
447-
}
448-
name = name[:i] + methodName // E.g., "long/path/name/mypkg.(mytype)." + "myfunc"
449-
}
450-
}
451-
if i := strings.LastIndexByte(name, '/'); i >= 0 {
452-
// Strip the package name.
453-
name = name[i+1:] // E.g., "mypkg.(mytype).myfunc"
454-
}
455-
return name
456-
}

0 commit comments

Comments
 (0)