I am using golang.org/x/tools/go/ssa to build the SSA form for TinyGo.
We recently added generics support, which was surprisingly easy because the ssa package does all the hard work of instantiating generic functions.
Unfortunately, it resulted in an issue. Previously, we could rely on *types.Named to have pointer identity, meaning, that we could create a map[*types.Named]someOtherType and rely on the fact that every unique named type would have only one entry in the map.
What did you expect to see?
I expected this to remain the same while adding support for generics.
What did you see instead?
It broke. It works most of the time, but in some cases (in particular, methods on generic structs) there can be two *types.Named objects for the same named type.
I've tried to look at the documentation and comments inside the package what the intended behavior is, but I see somewhat conflicting information. In any case, it would be very helpful if *types.Named would be unique again.
This comment seems to suggest *types.Named is intended to have pointer identity but doesn't at the moment.
If ctxt is non-nil, it may be used to de-duplicate the instance against previous instances with the same identity. As a special case, generic *Signature origin types are only considered identical if they are pointer equivalent, so that instantiating distinct (but possibly identical) signatures will yield different instances. The use of a shared context does not guarantee that identical instances are deduplicated in all cases.
The comment at the top of src/go/types/named.go says that *types.Named may not be unique:
// Some invariants to keep in mind: each declared Named type has a single
// corresponding object, and that object's type is the (possibly generic) Named
// type. Declared Named types are identical if and only if their pointers are
// identical. On the other hand, multiple instantiated Named types may be
// identical even though their pointers are not identical. One has to use
// Identical to compare them. For instantiated named types, their obj is a
// synthetic placeholder that records their position of the corresponding
// instantiation in the source (if they were constructed during type checking).
From this, it appears to be a design decision to change the uniqueness of *types.Named pointers for generic types.
This is a problem for me, as I need a way to create unique IDs per *types.Named instance (types.Identical is not a solution, because you can't use it in a map). I tried typ.Obj().Pkg().Path() + "." + typ.Obj().Name() but that doesn't work for named types inside functions (which create a new scope but may have the same name as a named type in the outer scope).
I'm reporting this as a bug as it feels like a regression to me, even though it's not technically a regression.
The text was updated successfully, but these errors were encountered:
As @dominikh suggests, the solution is to use something like typeutil.Map.
This was indeed a design decision: given that instances can be created concurrently in separate packages, the cost of guaranteeing that they are canonical was considered to be too high. We may be able to improve our guarantees in the future, but since this will never be possible without a shared context, which necessarily pins a lot of types in memory, we won't ever be able to guarantee that Named types are always canonical.