The type parameters proposal (https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md#reflection) states:
We do not propose to change the reflect package in any way. When a type or function is instantiated, all of the type parameters will become ordinary non-generic types. The String method of a reflect.Type value of an instantiated type will return the name with the type arguments in square brackets. For example, List[int].
It's ambiguous what "name" should be used. Also, whether only reflect.Type.String should have been affected, or reflect.Type.Name too.
cmd/compile generates two names for each type: a "link name" that corresponds to type identity (so the linker can deduplicate it against other identical types), and a "reflect name" that's simplified somewhat for user presentation via reflect APIs.
However, for instantiated types' reflect names, it currently uses the link names of the type arguments. For example: https://go.dev/play/p/JAP28idHc9z
Can/should we change this behavior?
Some noteworthy distinctions:
- Reflect names use package name, whereas link names use package path. (reflect.Type.String says either is allowed though.)
- Link names add "·N" suffixes to locally defined types to disambiguate them, whereas reflect names omit this.
- Link names include package-path-qualifiers for non-exported struct fields and interface methods.
- Link names include a
=
byte to identify embedded fields.
/cc @rsc @ianlancetaylor @griesemer @golang/compiler
Comment From: mdempsky
Some noteworthy distinctions:
Another distinction: GOEXPERIMENT=unified supports defined types that are declared within type-parameterized functions, which implicitly parameterize the defined type.
The implicit type arguments are relevant to type identity, so they need to be included in the link name. But they're also currently included in the reflect name, for the same reason as above: https://go.dev/play/p/ORCQud4MPp6?v=gotip (The mangling used is T[implicits;explicits]
, where implicits and explicits are comma-separated type argument lists. If either list is empty, then the semicolon is omitted.)
I've thought it might be better/clearer if local types were given reflect names like Function.Type
, so the above example would print main.F[int].T[string]
instead of main.T[int;string]
. (The link name would still need vargen disambiguation, because a function can have multiple identically-named defined types in separate blocks.)
Comment From: ianlancetaylor
My expectation was always that for a generic type T
, instantiated with a type argument A
, the reflect.Type.Name
method would return T[A]
or T[pkg2.A]
and the reflect.Type.String
method would return pkg.T[pkg2.A]
. It clearly should not use the link name for type arguments. And I agree that for a local generic type (if such a thing is ever supported) the reflect name should be F[A1].T[A2]
.
Comment From: godcong
I think the function of reflect and go is a bit unrelated.
strType := reflect.TypeOf(string("abc"))
if strType == string { //do something... } //no way to compare this
if strType.XxxType() == string { //do something... } // If we could compare this, a lot of functionality would be possible
For example, you can define the type in the key of the map:
maps:=map[type]somevalue
//get the value
v:=maps[sometype]
There is also no need to use too much type conversion in the code
switch any(generic).type {
case xxx:
}
The current reflect doesn't do anything like this:
switch reflect.TypeOf(generic){
case //string or "string" what you can case???? What about if it's the type of a project on github?
}
With the current functionality, you can't do something with reflect
that you should be able to do
Comment From: LorenzoWF
Hi,
I agree with everybody, link names returned from reflect.Type.String()
with package included is difficult in many scenarios, for example, when we working to generate code from reflection (this is my case), but as is, maybe this workaround will works for you:
func recursiveRemovePathFromTypeString(ts []rune, idx int) ([]rune, int, bool) {
sb := make([]rune, 0, len(ts[idx:]))
listFollow := false
for {
if idx >= len(ts) {
break
}
c := ts[idx]
exit := false
switch c {
case '[':
sb = append(sb, c)
var subS []rune
follow := true
for follow {
subS, idx, follow = recursiveRemovePathFromTypeString(ts, idx+1)
sb = append(sb, subS...)
}
case ']':
sb = append(sb, c)
exit = true
case ',':
sb = append(sb, c)
listFollow = true
exit = true
case '/':
sb = sb[:0]
default:
sb = append(sb, c)
}
if exit {
break
}
idx++
}
return sb, idx, listFollow
}
func removePathFromTypeString(ts string) string {
r, _, _ := recursiveRemovePathFromTypeString([]rune(ts), 0)
return string(r)
}
Testing with status.Struct[test/reflect/status.Struct[test/reflect/status.Status,test/reflect/status.Status],int]
will be returned status.Struct[status.Struct[status.Status,status.Status],int]
and with this value you can use on go/ast
.
Full example on: https://go.dev/play/p/5-WJr3zRFOW
Another simpler solution is just strings.ReplaceAll(reflect.TypeOf(v).String(), "/", "_")
, then you can use on go/ast
.