Open Merovius opened 7 months ago
cc @griesemer
That's a very good question.
I even think, perhaps erroneously, that the case F[int]() != G()
is arguably true but could also be false with my current reading of the spec.
I'm not sure I've read that right but the scoping rules around type declaration seem to speak in term of visibility not equality.
As far as I remember, named type equality is still the equality of the name (identifier), type definition literal, and package path. But it might have changed.
https://go.dev/ref/spec#Declarations_and_scope https://go.dev/ref/spec#Type_identity
Just a remark, I have no answer.
In this case, all types involved are named types. So, the question is whether those named types are "other types" or not. Determining whether that is the case is the contention. ...
https://go.dev/ref/spec#Instantiations mentions, "instantiating a function produces a new non-generic function." So, every unique instantiation of F
is a different function. That means a type declared within is different (in the identity sense) for each instantiation. This is true even if the type parameter isn't used in the type: https://go.dev/play/p/y8f4lMuhDUj (note the change to F
). I recall there was some discussion on social media about this some months ago, but I'm struggling to find references now.
I believe this is the fallacy in your argument:
F[int]() == F[int]()
andG() == G()
, demonstrating that the created type is not tied to a call, but to the type definition AST node.
It is not solely the AST, i.e. the syntactic location, which determines type identity. All type parameters which dominate the type definition participate as well.
@zephyrtronium Ah, that actually makes sense, thank you. The behavior for non-used type parameters feels counterintuitive to me, but I guess it does follow from the spec and at least now I know :) I'll close this.
func F[T comparable]() any {
return struct{ _ [0]T }{}
}
func G() any {
return struct{ _ [0]int }{}
}
So it's the named types having the slightly surprising behavior -- if you use unnamed types, these two are now identical.
Yup, after rereading, seems that type names (identifiers) are unique and scoped, that's why named types are all different as per the spec.
And the type definition includes the list of type arguments indeed but for x, there was none anyway so it doesn't even matter here.
Reopening this (with title change) as a documentation issue. Similar questions have come up repeatedly and the spec could probably benefit from more explicit prose or related examples to be very clear.
@griesemer
Reopening this (with title change) as a documentation issue.
Will this be documented as an unspecified behavior?
This is implicitly specified because "each distinct instantiation of a generic function produces a distinct non-generic function; and each of those functions has its own distinct local types, very much like any other non-generic function". Leaving this one open as a documentation issue and closing https://github.com/golang/go/issues/58573.
What did you do?
Playground Link
What did you see happen?
true true true true
What did you expect to see?
true true true true
. The output is clearly intuitively correct and the most sensible behavior of this program. However, I'm having trouble justifying it from the spec.For equality of interfaces, the spec says:
The program is constructed such that the dynamic types all have exactly one dynamic value, hence we need to check if the dynamic types are identical:
In this case, all types involved are named types. So, the question is whether those named types are "other types" or not. Determining whether that is the case is the contention. The only relevant answer I can find says:
As well as later:
Lastly, going back to Type identity, we also note:
That's all the relevant spec pieces I can really find.
Now my argument:
F[int]() == F[int]()
andG() == G()
, demonstrating that the created type is not tied to a call, but to the type definition AST node.G() != F[int]()
demonstrates that different type definition AST nodes lead to non-identical types. Strengthening the assertion that the actual AST node determines type identity.Buf
F[int]() != F[string]()
shows that the same type definition can lead to different types. Even though 1. the type definition is not generic (it has no type parameters) and 2. it is a named type, so its identity should solely be determined by its type definition.To be clear, again: This is obviously the only sensible and expected way for this code to behave, but it seems there is a hole in the spec for this behavior.