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.