-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
239 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
package foo | ||
|
||
type Bar struct {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
package foo | ||
|
||
type Bar struct {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
package reflectutils | ||
|
||
import ( | ||
"path" | ||
"reflect" | ||
"regexp" | ||
"strconv" | ||
"strings" | ||
) | ||
|
||
var versionRE = regexp.MustCompile(`/v(\d+)$`) | ||
|
||
// TypeName is an alternative to reflect.Type's .String() method. The only | ||
// expected difference is that if there is a package that is versioned, the | ||
// version will appear in the package name. | ||
// | ||
// For example, if there is a foo/v2 package with a Bar type, and you ask | ||
// for for the TypeName, you'll get "foo/v2.Bar" instead of the "foo.Bar" that | ||
// reflect returns. | ||
func TypeName(t reflect.Type) string { | ||
ts := t.String() | ||
pkgPath := t.PkgPath() | ||
if pkgPath != "" { | ||
if versionRE.MatchString(pkgPath) { | ||
version := path.Base(pkgPath) | ||
pn := path.Base(path.Dir(pkgPath)) | ||
revised := strings.Replace(ts, pn, pn+"/"+version, 1) | ||
if revised != ts { | ||
return revised | ||
} | ||
return "(" + version + ")" + ts | ||
} | ||
return ts | ||
} | ||
switch t.Kind() { //nolint:exhaustive // not intended to be exhaustive | ||
case reflect.Ptr: // TODO: change to Pointer when go 1.17 support lapses | ||
return "*" + TypeName(t.Elem()) | ||
case reflect.Slice: | ||
return "[]" + TypeName(t.Elem()) | ||
case reflect.Map: | ||
return "map[" + TypeName(t.Key()) + "]" + TypeName(t.Elem()) | ||
case reflect.Array: | ||
return "[" + strconv.Itoa(t.Len()) + "]" + TypeName(t.Elem()) | ||
case reflect.Func: | ||
return "func" + fmtFunc(t) | ||
case reflect.Chan: | ||
switch t.ChanDir() { | ||
case reflect.BothDir: | ||
return "chan " + TypeName(t.Elem()) | ||
case reflect.SendDir: | ||
return "chan<- " + TypeName(t.Elem()) | ||
case reflect.RecvDir: | ||
return "<-chan " + TypeName(t.Elem()) | ||
default: | ||
return ts | ||
} | ||
case reflect.Struct: | ||
if t.NumField() == 0 { | ||
return "struct {}" | ||
} | ||
fields := make([]string, t.NumField()) | ||
for i := 0; i < t.NumField(); i++ { | ||
f := t.Field(i) | ||
if f.Anonymous { | ||
fields[i] = TypeName(f.Type) | ||
} else { | ||
fields[i] = f.Name + " " + TypeName(f.Type) | ||
} | ||
} | ||
return "struct { " + strings.Join(fields, "; ") + " }" | ||
case reflect.Interface: | ||
n := t.Name() | ||
if n != "" { | ||
return n | ||
} | ||
if t.NumMethod() == 0 { | ||
return "interface {}" | ||
} | ||
methods := make([]string, t.NumMethod()) | ||
for i := 0; i < t.NumMethod(); i++ { | ||
m := t.Method(i) | ||
methods[i] = m.Name + fmtFunc(m.Type) | ||
} | ||
return "interface { " + strings.Join(methods, "; ") + " }" | ||
default: | ||
return ts | ||
} | ||
} | ||
|
||
func fmtFunc(t reflect.Type) string { | ||
inputs := make([]string, t.NumIn()) | ||
for i := 0; i < t.NumIn(); i++ { | ||
inputs[i] = TypeName(t.In(i)) | ||
} | ||
outputs := make([]string, t.NumOut()) | ||
for i := 0; i < t.NumOut(); i++ { | ||
outputs[i] = TypeName(t.Out(i)) | ||
} | ||
switch t.NumOut() { | ||
case 0: | ||
return "(" + strings.Join(inputs, ", ") + ")" | ||
case 1: | ||
return "(" + strings.Join(inputs, ", ") + ") " + outputs[0] | ||
default: | ||
return "(" + strings.Join(inputs, ", ") + ") (" + strings.Join(outputs, ", ") + ")" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
package reflectutils | ||
|
||
import ( | ||
"reflect" | ||
"testing" | ||
|
||
v1 "github.com/muir/reflectutils/internal/foo" | ||
v2 "github.com/muir/reflectutils/internal/foo/v2" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestVersionedNames(t *testing.T) { | ||
type xbar struct { | ||
v2.Bar | ||
} | ||
type ybar struct { | ||
xbar | ||
v1 v1.Bar //nolint:structcheck,unused // unused and we don't care | ||
} | ||
cases := []struct { | ||
thing interface{} | ||
want string | ||
}{ | ||
{ | ||
thing: v1.Bar{}, | ||
}, | ||
{ | ||
thing: v2.Bar{}, | ||
want: "foo/v2.Bar", | ||
}, | ||
{ | ||
thing: &v1.Bar{}, | ||
want: "*foo.Bar", | ||
}, | ||
{ | ||
thing: &v2.Bar{}, | ||
want: "*foo/v2.Bar", | ||
}, | ||
{ | ||
thing: []v2.Bar{}, | ||
want: "[]foo/v2.Bar", | ||
}, | ||
{ | ||
thing: (func(*v1.Bar, *v2.Bar) (string, error))(nil), | ||
want: "func(*foo.Bar, *foo/v2.Bar) (string, error)", | ||
}, | ||
{ | ||
thing: [8]v2.Bar{}, | ||
want: "[8]foo/v2.Bar", | ||
}, | ||
{ | ||
thing: make(chan *v2.Bar), | ||
want: "chan *foo/v2.Bar", | ||
}, | ||
{ | ||
thing: make(chan *v1.Bar), | ||
want: "chan *foo.Bar", | ||
}, | ||
{ | ||
thing: (chan<- *v1.Bar)(nil), | ||
want: "chan<- *foo.Bar", | ||
}, | ||
{ | ||
thing: (chan<- *v2.Bar)(nil), | ||
want: "chan<- *foo/v2.Bar", | ||
}, | ||
{ | ||
thing: (<-chan *v2.Bar)(nil), | ||
want: "<-chan *foo/v2.Bar", | ||
}, | ||
{ | ||
thing: (<-chan *v1.Bar)(nil), | ||
want: "<-chan *foo.Bar", | ||
}, | ||
{ | ||
thing: (func(interface { | ||
V1() v1.Bar | ||
V2() v2.Bar | ||
}))(nil), | ||
want: "func(interface { V1() foo.Bar; V2() foo/v2.Bar })", | ||
}, | ||
{ | ||
thing: (func(interface{}))(nil), | ||
}, | ||
{ | ||
thing: struct { | ||
v1 v1.Bar | ||
v2 v2.Bar | ||
}{}, | ||
want: "struct { v1 foo.Bar; v2 foo/v2.Bar }", | ||
}, | ||
{ | ||
thing: struct{}{}, | ||
}, | ||
{ | ||
thing: struct { | ||
v2.Bar | ||
v1 v1.Bar | ||
}{}, | ||
want: "struct { foo/v2.Bar; v1 foo.Bar }", | ||
}, | ||
{ | ||
thing: struct { | ||
ybar | ||
}{}, | ||
// want: "struct { reflectutils.ybar }", | ||
}, | ||
} | ||
|
||
for _, tc := range cases { | ||
want := tc.want | ||
if want == "" { | ||
want = reflect.TypeOf(tc.thing).String() | ||
} | ||
t.Logf("%+v wanting %s", tc.thing, want) | ||
assert.Equal(t, want, TypeName(reflect.TypeOf(tc.thing))) | ||
} | ||
} |