The following code
package p
type T struct{}
type T1 = T
func (T1) m() {}
func _() {
(&T{}).m()
}
is valid (I believe) and accepted by cmd/compile, gccgo, and go/types. The following code
package p
type T struct{}
type T1 = *T
func (T1) m() {}
func _() {
(&T{}).m()
}
is still accepted by cmd/compile but rejected by gccgo (error: invalid receiver type) and go/types (with a misleading error message). Per the spec ( https://tip.golang.org/ref/spec#Method_declarations )
Its (receiver) type must be of the form T or *T (possibly using parentheses) where T is a type name. The type denoted by T is called the receiver base type; it must not be a pointer or interface type and it must be defined in the same package as the method.
this code shouldn't be permitted: The receiver is of the form T (here: T1) and T (here: T1) is a type name. But T1 stands for *T which is a pointer type.
cc: @ianlancetaylor @robpike @rsc for comments (do we agree that the spec is correct here).
The spec seems unambiguous: "of the form" T or *T clearly refers to the syntax. It seems inconsistent that you can use an alias A declared type A = T to declare methods on T, but you can't use an alias A declared type A = *T to declare methods on *T. I wonder if there are practical advantages to being able to declare a method on *T using the alias A, in a package that makes heavy use of build tags and conditional compilation. Other than than it doesn't seem particularly useful.
@alandonovan Are you arguing that both programs above should be invalid?
I have mentioned this in https://github.com/golang/go/issues/22005
In fact, the wording for which types can be embedded in spec is also not accurate now: https://go101.org/article/type-embedding.html
No, I agree with you that spec defines the first as valid (receiver type is "of the form T" and the base type T denotes a struct) and the second as invalid (receiver type is of the form *T and T denotes a pointer, *U), so gccgo and go/types are correct and the bug is in cmd/go.
The inconsistency I was referring to is that in the second example, the spec disallows the use of an alias where its expanded type would be valid. It seems simpler to me to allow an alias A or *A to be used in exactly the places where its expansion would be valid. But it clearly matters little in practice.
But when I look in the spec at the definition of aliases I see this:
Alias declarations
An alias declaration binds an identifier to the given type.
AliasDecl = identifier "=" Type .
Within the scope of the identifier, it serves as an alias for the type.
type (
nodeList = []Node // nodeList and []Node are identical types
Polar = polar // Polar and polar denote identical types
)
This suggests to me that T1 is identical to *T, and thus a valid method receiver.
I think with the current spec both examples should compile, although I admit using a type alias to hide a pointer is bad style. I would expect that an alias is exactly that, an alias, no matter if it contains any pointers or not, so if the alias resolves to anything that is valid as a method receiver, it should be accepted as such.
Otherwise the spec should be clarified on how aliases work together with method definitions.
@beoran We all agree that an alias is exactly that, an alias. But an alias is also syntactically a type name, and the spec is clear about method receivers: They must be of the form T or *T, where T is a type name (alias or not) - this is a syntactic specification. And then it says that this T cannot be a pointer type.
Thus, as is, the code above should not be accepted. Maybe it should, but then the spec would have to be adjusted.
I agree that the second example is invalid.
I would say that I consider an alias is not a type in itself, but an identifier that expands to the type it is an alias for. In other words, if I do type PFoo = *Foo, then I would expect PFoo to work everywhere as if I had written *Foo. I find it surprising that in the case of method receivers it is not so.
If it is to stay like it is now, I would suggest to add the phrase, cannot be a pointer type "or an alias to a pointer type" to the spec if method receivers to clarify this.
I'm fine with the second example being invalid.
I am also OK with the second example being not valid, but as others an I have argued, the spec of aliases and method receivers is then not detailed enough to make clear to everyone why it should be so. Please consider adding the phrase I suggested in my previous message to the spec.
I'll cast my dissenting 2c that I think the second example should be valid, and that cmd/compile is correct here.
Given type T = *U, then T and *U are interchangeable. If we allow func (*U) m() then I don't see why to disallow func (T) m().
@mdempsky So you're saying that the wording in the spec should be adjusted?
@griesemer Yeah, I think so. I never really liked the "must be of the form" wording, since it's a syntactic rather than semantic restriction, but I think it was okay before type aliases.
I'd suggest changing the first sentence of the quoted paragraph to: "Its (receiver) type must be a defined type T or a pointer to a defined type T."
I'm inclined to agree with @mdempsky:
type T = <expr>, you can _always_ substitute T for <expr> and vice versa - they are 100% equivalent. I don't know of any other place in Go where that doesn't hold. (Am I missing something?)Also, I think we all agree this code is invalid:
type A = chan int
func (A) m() {}
But - since an alias is a type name - there's nothing in the current text that explains why:
Its (receiver) type must be of the form T or *T (possibly using parentheses) where T is a type name. The type denoted by T is called the receiver base type; it must not be a pointer or interface type and it must be defined in the same package as the method.
Matthew's rewrite correctly explains: 'chan int' is not a defined type.
Yes, that is exactly what I was getting at. Aliases should be 100% equivalent to the type they alias, even if it is a complex type.
@rsc Regarding your example
type A = chan int
func (A) m() {}
The spec does say (note emphasis): "... it (A in this example) must not be a pointer or interface type and it must be _defined_ in the same package as the method."
Here A is a _declared_ type alias, but it's not a _defined_ type - _defined_ has taken on a very specific meaning in the spec ever since we introduced alias types. In the above rule, this is exactly the meaning intended, and thus A doesn't qualify.
But I admit that it's perhaps too subtle a point, all incorporated in that one word _defined_, which is easily confused with _declared_.
Regarding the fact that the rule is syntactic: One might argue that's a plus as it aids readability: It's always syntactically clear if a method operates on a value or a pointer receiver.
That said, I don't have a strong sense of what's the right approach at the moment. Need to think about this some more.
I would say it is definitely is too subtle, especially on the point of how aliases are supposed to work in this case. No matter which way you decide, allowed or not, in both cases, I feel the spec should be updated to explain more in detail how and which aliases can and cannot be used as method receivers.
@beoran The spec is clear. A type alias is a type name which stands, represents, denotes (choose your pick) the type it was declared with in the corresponding alias declaration. The alias type name means exactly that type, nothing else, even if it is a complex type as you say in your comment.
The issue here is not aliases, the issue is whether the wording on receiver types should be using a syntactic rule for the form of the receiver, or not, with restrictions on the elements of that syntactic rule.
That is what prevents the use of a alias to a pointer type.
I'm going to make this a spec issue. If we accept the respective fix, we will need to update go/types and gccgo as well.
We also should update @22005 (per @go101 ) once we have accepted this fix (in a separate CL).
Change https://golang.org/cl/142757 mentions this issue: spec: clarify rules for receiver base types
Thanks for the fix.
Change https://golang.org/cl/143179 mentions this issue: go/types: accept recv base type that is alias to a pointer type
Most helpful comment
I'm inclined to agree with @mdempsky:
type T = <expr>, you can _always_ substitute T for<expr>and vice versa - they are 100% equivalent. I don't know of any other place in Go where that doesn't hold. (Am I missing something?)Also, I think we all agree this code is invalid:
But - since an alias is a type name - there's nothing in the current text that explains why:
Matthew's rewrite correctly explains: 'chan int' is not a defined type.