Skip to content

spec: add example for method value in case of embedded method #47863

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
cuonglm opened this issue Aug 21, 2021 · 26 comments
Closed

spec: add example for method value in case of embedded method #47863

cuonglm opened this issue Aug 21, 2021 · 26 comments
Labels
FrozenDueToAge NeedsFix The path to resolution is known, but the work has not been done.

Comments

@cuonglm
Copy link
Member

cuonglm commented Aug 21, 2021

The Selectors section in spec has an example to state that for s of type S, where T is an embedded field of S, and T has method M, then s.M() is a shorthand for s.T.M():

type T0 struct {
	x int
}

func (*T0) M0()

type T1 struct {
	y int
}

func (T1) M1()

type T2 struct {
	z int
	T1
	*T0
}

p.M0()       // ((*p).T0).M0()      M0 expects *T0 receiver
p.M1()       // ((*p).T1).M1()      M1 expects T1 receiver

But it lacks of the example where embedded field is use as method value, thus user can interpret that f := p.M0 is not a shorthand of f := ((*p).T0).M0.

I think we should add an example for method value to prevent that confusion.


I filed this issue after long discussion with @go101 about the behavior of this program:

package main

type T int

func (t T) M() { print(t) }

type S struct{ *T }

var t = new(T)
var s = S{T: t}

func main() {
	f := t.M
	g := s.M
	*t = 5
	f()
	g()
}

I think any Go compiler implementation must ensure the output is 00 to conform with the spec, but @go101 disagree, because s.M is not guaranteed to expand to s.T.M.

Quote from @go101:

s.M() is shorthand for s.T.M()
!=
s.M is shorthand for s.T.M

@cuonglm
Copy link
Member Author

cuonglm commented Aug 21, 2021

@zigo101
Copy link

zigo101 commented Aug 21, 2021

s.M is not guaranteed to expand to s.T.M

I mean, immediately. An implementation might delay the expansion as needed.

@cuonglm
Copy link
Member Author

cuonglm commented Aug 21, 2021

I think it must, according to following lines from the spec:

For a value x of type T or *T where T is not a pointer or interface type, x.f denotes the field or method at the shallowest depth in T where there is such an f. If there is not exactly one f with shallowest depth, the selector expression is illegal.

@mdempsky
Copy link
Contributor

I agree the correct semantics are to print 00. The declared receiver parameter type is T, so it should be evaluated as such at the time of the method value expression.

@gopherbot
Copy link
Contributor

Change https://golang.org/cl/344209 mentions this issue: spec: add example for method value in case of embedded method

@zigo101
Copy link

zigo101 commented Aug 22, 2021

  1. For a value x of type T or *T where T is not a pointer or interface type, x.f denotes the field or method at the shallowest depth in T where there is such an f. If there is not exactly one f with shallowest depth, the selector expression is illegal.
  2. For a value x of type I where I is an interface type, x.f denotes the actual method with name f of the dynamic value of x. If there is no method with name f in the method set of I, the selector expression is illegal.

It is not very clear what the meaning of the "denote" words used in the two rules. Must expand to immediately or not?
In fact, the current gc implementation makes an inconsistency in implementing the word:

package main

type (
	T int
	S struct{ *T }
	I interface { M() }
)

func (t T) M() { print(t) }

var (
	t = new(T)
	s = S{T: t}
	i I = &s
)

func main() {
	g := s.M // expand immediately
	h := i.M // expand with a delay
	*t = 5
	g() // 0
	h() // 5
}

@zigo101
Copy link

zigo101 commented Aug 22, 2021

I personally recommend to define "denote" as "expand with a delay", to simplify many explanations. We can view the current gc implementation as a result of considering program performance.

@cuonglm
Copy link
Member Author

cuonglm commented Aug 22, 2021

@go101 I dont see any inconsistent. "i.M" evaluated the value of "i", and expand to the interface method "I.M".

The actual relevant part in spec is Method Values:

The method value x.M is a function value that is callable with the same arguments as a method call of x.M. The expression x is evaluated and saved during the evaluation of the method value; the saved copy is then used as the receiver in any calls, which may be executed later.

@zigo101
Copy link

zigo101 commented Aug 22, 2021

@cuonglm
If denote means expand to immediately, then the above quoted rule 2 conflicts with your last quote.

@mdempsky
Copy link
Contributor

It is not very clear what the meaning of the "denote" words used in the two rules. Must expand to immediately or not?

"Denote" in those paragraphs isn't explaining any dynamic behavior. It's just explaining what field/method is associated with a particular selector expression. In go/types terminology, it's explaining the relationship represented by Info.Selections.

The section @cuonglm quotes is indeed the part that explains the dynamic behavior. Moreover, I believe the sentences:

As with selectors, a reference to a non-interface method with a value receiver using a pointer will automatically dereference that pointer: pt.Mv is equivalent to (*pt).Mv.

As with method calls, a reference to a non-interface method with a pointer receiver using an addressable value will automatically take the address of that value: t.Mp is equivalent to (&t).Mp.

are meant to describe the behavior in question (i.e., that the receiver argument is "expanded immediately" in your terminology). However, I think the examples could better demonstrate this.

@zigo101
Copy link

zigo101 commented Aug 22, 2021

  1. For a value x of type I where I is an interface type, x.f denotes the actual method with name f of the dynamic value of x.

In go/types terminology, it (the "denote" word) 's explaining the relationship represented by Info.Selections.

The dynamic value of an interface is often hard to determined at compile time. So it is hard to recored the dynamic type into in Info.Selections.

So maybe the description of the rule 2 should be also tweaked,

@zigo101
Copy link

zigo101 commented Aug 22, 2021

Another problem needs to be clarified is: does the type S has a method M?

type T int

func (t T) M() { print(t) }

type S struct{ *T }

@cuonglm's opinion is the type S has not a method M, but its method set includes M., which is some weird.

If S has a method M, then it looks many descriptions in the spec need also to be tweaked.

@cuonglm
Copy link
Member Author

cuonglm commented Aug 22, 2021

@go101 To clarify, since when your opinion is about the spec, then S does not have method M, T does. M is the promoted method of S from embedded field T, and M appears in S's method set. I find all these descriptions in the spec are quite clear:

Quote from the embedded part:

If S contains an embedded field *T, the method sets of S and *S both include promoted methods with receiver T or *T.

I can't find any place in the spec can be interpreted that S has method M, since when a method:

is said to be bound to its receiver base type and the method name is visible only within selectors for type T or *T.

I also show you a go/types program to demonstrate that:

https://play.golang.org/p/5LdUMIoKaQd

But you refused because:

The std library is coupled with gc very closely. I mean, the std library is not that standard.

and:

I mean go/types is also just an implementation.

which I also don't agree. go/types is the go ahead tool to reflect/verify the Go specification.


If we're talking about the gc implementation, then yes.

The compiler does generate wrapper method M for S and *S. But these methods are hidden to the user, and are generated in later compile passes, and not present when we're doing typechecking.

@zigo101
Copy link

zigo101 commented Aug 22, 2021

The https://golang.org/ref/spec#Method_sets section says clearly:

The method set of an interface type is its interface. The method set of any other type T consists of all methods declared with receiver type T.

So I think the spec admits that there is a method M declared for S. My understanding is the declaration is an implicit one.

I don't care about whether or not s.M should be expanded intermediately or not. I just think thinking there is an implicit method M declared for S will make many explanations simpler, otherwise, many contents in spec need to be tweaked.

@cuonglm
Copy link
Member Author

cuonglm commented Aug 22, 2021

@go101 seems you missed some sentences after that one:

Further rules apply to structs containing embedded fields, as described in the section on struct types.

It's not about expanding or not, it's about the receiver of the method must be evaluated. Thus receiver of "s.M" is "s.(*t)" must be evaluated.

Adding implicit "M" for "S" actually make everything more complicated.

@zigo101
Copy link

zigo101 commented Aug 22, 2021

OK. So a promoted method of S should not be called a method of S.

I think then the following contents need to be tweaked:

If the expression x has static type T and M is in the method set of type T, x.M is called a method value. The method value x.M is a function value that is callable with the same arguments as a method call of x.M. The expression x is evaluated and saved during the evaluation of the method value; the saved copy is then used as the receiver in any calls, which may be executed later.

It doesn't consider the case x.M is a promoted method.

  1. For a value x of type I where I is an interface type, x.f denotes the actual method with name f of the dynamic value of x. If there is no method with name f in the method set of I, the selector expression is illegal.

Here, the "actual method" wording also doesn't consider promoted method cases.

@cuonglm
Copy link
Member Author

cuonglm commented Aug 22, 2021

It doesn't consider the case x.M is a promoted method.

I don't follow this, the spec clearly state that If the expression x has static type T and M is in the method set of type T. In your case, s has static type S, and M is in the method set of S (via the embedded type T).

Here, the "actual method" wording also doesn't consider promoted method cases.

Same here, I don't follow. In your case, i.M, the actual method with name M of the dynamic value of i.

OK. So a promoted method of S should not be called a method of S.

The only place in the spec that mention a method of is in Method expressions section:

It is legal to derive a function value from a method of an interface type. The resulting function takes an explicit receiver of that interface type.

@zigo101
Copy link

zigo101 commented Aug 22, 2021

I don't follow this, the spec clearly state that If the expression x has static type T and M is in the method set of type T. In your case, s has static type S, and M is in the method set of S (via the embedded type T).

It is not the line, the inaccuracy is in this line The expression x is evaluated and saved during the evaluation of the method value; the saved copy is then used as the receiver in any calls.

In your case, i.M, the actual method with name M of the dynamic value of i.

The spec says the actual method with name f of the dynamic value.

@cuonglm
Copy link
Member Author

cuonglm commented Aug 22, 2021

It is not the line, the inaccuracy is in this line The expression x is evaluated and saved during the evaluation of the method value; the saved copy is then used as the receiver in any calls.

Do you mean the inaccuracy is the word method value? I don't see any there. A method value is defined as:

If the expression x has static type T and M is in the method set of type T, x.M is called a method value

So it covers both T's own methods and promoted method.

The spec says the actual method with name f of the dynamic value.

Yes. but what's the problem? As @mdempsky explained above, that part does not explain the dynamic behavior, It's just explaining what field/method is associated with a particular selector expression.

@seankhliao seankhliao added the NeedsFix The path to resolution is known, but the work has not been done. label Aug 22, 2021
@zigo101
Copy link

zigo101 commented Aug 23, 2021

It is not the line, the inaccuracy is in this line The expression x is evaluated and saved during the evaluation of the method value; the saved copy is then used as the receiver in any calls.
Do you mean the inaccuracy is the word method value? I don't see any there.

No, I mean this: "The expression x is evaluated and saved".

The spec says the actual method with name f of the dynamic value.
Yes. but what's the problem? As @mdempsky explained above, that part does not explain the dynamic behavior, It's just explaining what field/method is associated with a particular selector expression.

Here the "actual method" might be a "promoted method", and if the dynamic value is a S value, then it is not okay to say the method of S, by your opinion.

@cuonglm
Copy link
Member Author

cuonglm commented Aug 23, 2021

No, I mean this: "The expression x is evaluated and saved".

Why is this inaccuracy? x can be any expression, which can be either a non-blank identifier denotes a variable (s), or a selector expression (s.(*t)). You can find in spec the definition of expression.

Here the "actual method" might be a "promoted method", and if the dynamic value is a S value, then it is not okay to say the method of S, by your opinion.

I don't see the conflicts, either:

  • I repeat again, as @mdempsky pointed out, this part does not explain the dynamic behavior, or in other word, does not explain the evaluation of the interface value. In i.M, the interface value i is evaluated, we have nothing to do with the evaluation of the dynamic value of i (which is &s).
  • If you want to be precise wording, here's the spec does not say "actual method of", but "the actual method with name f of".

Is there a S's own method named M? No.
Is there a S's promoted method named M? Yes.

So the actual method with name M of S is the promoted method of embedded T in S.

@zigo101
Copy link

zigo101 commented Aug 23, 2021

If the expression x has static type T and M is in the method set of type T, x.M is called a method value. The method value x.M is a function value that is callable with the same arguments as a method call of x.M. The expression x is evaluated and saved during the evaluation of the method value; the saved copy is then used as the receiver in any calls, which may be executed later.

Okay, let's use the s.M example, the spec says "The expression s is evaluated and saved during the evaluation". In fact, *s.t is evaluated and saved.

@zigo101
Copy link

zigo101 commented Aug 23, 2021

  1. For a value x of type I where I is an interface type, x.f denotes the actual method with name f of the dynamic value of x. If there is no method with name f in the method set of I, the selector expression is illegal.

Okay, we use the var i I = s example. The dynamic value of i is a copy of s, call it s1. Does s1 have an actual method with name M?

@mdempsky
Copy link
Contributor

Here's how I understand method values:

If x is a non-interface type, then method value x.M evaluates x the same as it would in a normal method call; e.g., implicit addressing or dereference. Intuitively, it's evaluated to a value of the same receiver type as appears in the respective declared method in source.

If x is an interface type, then method value x.M simply evaluates x as is and saves that. It doesn't do anything with the dynamic value/type. Any implicit dereferences (e.g., x has dynamic type *T, but the declared method is T.M) are deferred until the call actually executes.

Sorry, I'm too confused with all the back and forth to understand where exactly the confusion is.

@cuonglm
Copy link
Member Author

cuonglm commented Aug 23, 2021

Sorry, I'm too confused with all the back and forth to understand where exactly the confusion is.

Yup, I have the same feel.

@go101 since when this issue is now closed, would you mind opening other issue to discuss about places where you think are still confused in the spec.

@zigo101
Copy link

zigo101 commented Aug 24, 2021

Personally, I still think the spec is not clear and even contains conflicts, as explained above.

But okay. Something remaining to do:

  • it would be better to add an interface method evaluation example too in the code.
  • this issue should be closed now.
  • need to confirm whether or not this is a bug?

@griesemer griesemer removed their assignment Dec 8, 2021
@golang golang locked and limited conversation to collaborators Dec 8, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
FrozenDueToAge NeedsFix The path to resolution is known, but the work has not been done.
Projects
None yet
Development

No branches or pull requests

6 participants