go/types: types.Named hides intermediate names

93 views
Skip to first unread message

Stephen Searles

unread,
Nov 19, 2019, 1:10:44 PM11/19/19
to golang-nuts
I'm not sure if this could be considered a bug or not, but it's unfortunate behavior for certain patterns of type analysis. The problem is with a type like:

type X time.Time

When you inspect type X, you find that it is analyzed as a types.Named. Its immediate underlying type is a types.Struct. I have a few problems with this:
  • This hides the fact that there is more information about type X available (time.Time's documentation)
  • This makes the location and identity of the actual struct definition hard to find because the types.Struct is not an object that you can find the source position of
  • The dependency relationship between these types is not obvious (as in, changing/renaming time.Time will affect the program using X, but not the other way around)
Here's a small demo of the situation described here: https://play.golang.org/p/oqYRDIO7kmf

Please let me know if I'm something missing, but as it stands, I think the only way around these issues would be to analyze the syntax of every defined type to check if it's got an underlying name or not. Does this sound like something that could be safely improved within go/types?

Thanks,
Stephen

Max

unread,
Nov 20, 2019, 6:18:28 AM11/20/19
to golang-nuts

It's part of the language specifications https://golang.org/ref/spec#Types. It says:
type (
  B1
string
  B2 B1
)

"The underlying type of [...] B1 and B2 is string"

In other words, when you write
type X time.Time
there is absolutely no direct connection between the types X and time.Time:
the only connection is that they both have the same underlying type, which is some unnamed struct, as you wrote.

Max

unread,
Nov 20, 2019, 6:27:19 AM11/20/19
to golang-nuts
Addendum:

this may be surprising from the point of view of object-oriented programming, class inheritance etc.
but at risk of stating the obvious, in Go there is no class inheritance:
type X time.Time

creates a new type X that is (almost) completely unrelated to time.Time:
1. the new type X does not have the methods of time.Time,
2. and X cannot be used interchangeably with time.Time.

So it makes sense that `types.Type` does not provide any information to link them.

the only connection is their identical underlying type, which allows to explicitly convert between them, i.e. the following works:
var x X
var t time.Time = time.Time(x)
var x2 X = X(t)

Stephen Searles

unread,
Nov 20, 2019, 5:24:16 PM11/20/19
to golang-nuts
Thanks for the response. I understand that what I'm asking is a bit beyond the scope of what a compiler would need to correctly implement the language, which is what the spec details. The question/request here is more about usability of the types package for purposes that are more human/developer-driven, like documentation and static analysis. For purposes like these, having some way to associate the types would be helpful, because while there is no "direct" connection that the compiler would care about, there is absolutely an "indirect" connection that humans or other tools may care about.

The fact that the types are not hierarchically related actually underscores why this matters. Picking on time.Time still: one might create a new type based on time.Time to handle a detail like (un)marshaling to the correct time.Location, given that such information is not conveyed by any of the formats supported by the time package. Making it easy to see that such a type is underlied by time.Time would make it easier for tools to, say, identify that this code should be checked for correctness with regard to timezones. I could imagine other use cases of this kind of association in things like security scanning or searching for usage of possible PII (for GDPR/privacy compliance).

The only way I see is to accomplish this right now is to inspect the AST. It's surprisingly complicated and feels a little silly because I'd bet I'm repeating work that the types package (and friends) already did. Here's implementing such a lookup for my toy example: https://play.golang.org/p/Ph7YV0a8JU4. Is there something I'm missing that could make this simpler?

Thinking about possible solutions, changing the behavior of the types.Named.Underlying() method seems like the naive approach. I would guess that returning an underlying types.Named would be considered a breaking change. It wouldn't change the method signature, but it would break code that currently assumes the value returned there is a more concrete type. Adding a method like

 func (Named) UnderlyingName() *Named

could work, but I also see some downsides of that approach, like that it proliferates API in an already complex package. Adding something to types.Info might be the most reasonable option, to reduce the impact of such an addition.

Thanks again,
Stephen
Reply all
Reply to author
Forward
0 new messages