golang/go

reflect: TypeOf interface with methods using generic types includes package path in type

Closed this issue · 6 comments

GO Version: 1.19.1

reflect.TypeOf from an interface containing methods using generic types includes the complete package path within the type.

This functionality is use in gomock (reflect mode) and generates not compile-able code.

Example see https://go.dev/play/p/0P8R4Au1Wer

The string representation of the interfaces method type from the playground example looks like the following.
func() test.TestGenericType[play.ground/test.TestType]

Effect in gomock: golang/mock#677

The paths in the instantiating types is there to disambiguate the package of the instantiating type.

Consider this program: https://go.dev/play/p/cGmCAwJzn9I

foo/a/x.I and foo/b/x.I are different types, but if we didn't include the package path they would look identical.

For the top-level type, that's ok, as PkgPath can be used to disambiguate the types (as shown in my example). But for instantiating types there is no such mechanism.

One thing we could do is remove the path from the name and just have ambiguous names. There still needs to be underlying uniqueness of the names for the linker+runtime, which means we'd need 2 names for everything.

Another thing we could do is provide a reflect API for accessing the instantiating types, so you could call PkgPath on them. Then the names themselves being unique is less important.

Things to ponder. I'm not convinced that the current state is all that bad, partly because I don't understand what gomock does and why this extra path trips it up.

I'm going to mark for 1.20 so that we make a decision one way or another before the release.

Yeah, I think that the current state is not bad. The question is why this is a problem for gomock. It looks like it is used for generating code? It probably could be more careful about the instantiation names before writing it to source file.

gomock use the type directly from the reflect type (test.TestGenericType[play.ground/test.TestType]) and generate the mocks with this path.

see https://github.com/golang/mock/blob/73266f9366fcf2ccef0b880618e5a9266e4136f4/mockgen/model/model.go#L396

So, if the behavior from reflect is 'ok' then gomock should check the generic type by adding the correct import and modify the type itself?

like

import (
    test_ "play.ground/test"
)

func() test.TestGenericType[test_.TestType] {
    ...
}

Repo to reproduce:
https://github.com/daolis/gomocktest

Yes, you'd have to do something like that. Certainly you will need to generate that import statement somehow, as you can't describe Foo[pkg.Bar] without having the correct import statement for pkg. This is a problem that only comes up with generics because normally you know the package of Foo, it's the one you're running gomock for, and you don't need an import statement because the code gomock generates is within Foos package. But the package of Bar could be some arbitrary other package.

If I'm understanding correctly "what we could do is provide a reflect API for accessing the instantiating types" means, I think #54393 is a relevant proposal in that direction.

One thing we could do is remove the path from the name and just have ambiguous names. There still needs to be underlying uniqueness of the names for the linker+runtime, which means we'd need 2 names for everything.

FWIW, we already have separate type names for reflection and linker+runtime, so I think it's already feasible to change the reflect.Type.String result from test.TestGenericType[play.ground/test.TestType] to test.TestGenericType[test.TestType].

Arguably that would be more consistent with how reflection has historically handled defined types, but it would also be a change from Go 1.18/1.19. I've filed #55924 to discuss this.

I agree that #54393 is the proper way for gomock to handle this case. In the mean time, I think gomock will have to do best-effort parsing of the link names, sorry. See #55924 for a few other special cases it may need to be careful about.

Closing this issue because I don't think there's further action to take on it.