Skip to content

Do not hide ZSUPER methods, fix consistency of owner and instance_methods - #6242

Merged
eregon merged 2 commits into
ruby:masterfrom
eregon:do_not_hide_zsuper_methods
Aug 20, 2022
Merged

Do not hide ZSUPER methods, fix consistency of owner and instance_methods#6242
eregon merged 2 commits into
ruby:masterfrom
eregon:do_not_hide_zsuper_methods

Conversation

@eregon
eregon requested review from XrXr and jeremyevans August 15, 2022 13:07
@jeremyevans

Copy link
Copy Markdown
Contributor

I'll review if matz approves the behavior change at the developer meeting.

@eregon

eregon commented Aug 18, 2022

Copy link
Copy Markdown
Member Author

@matz agreed during the meeting to remove ZSUPER methods, i.e., public/protected/private behave like alias or like define_method(:alias, instance_method(:original)).
That goes further than this PR and makes everything simpler.
So I close this and I'll make a PR to remove ZSUPER methods :)

@eregon eregon closed this Aug 18, 2022
@eregon

eregon commented Aug 18, 2022

Copy link
Copy Markdown
Member Author

Replaced by #6251

@matz

matz commented Aug 18, 2022

Copy link
Copy Markdown
Contributor

No, it's opposite. I agree with this request, and disagree with #6251.

I disagree to making copy, to keep symbolic semantics, i.e., public is just declaring visibility of a method public, not keeping the definition at the moment. If you replaced (or removed) the private method of superclass, the public one should also be affected.

@eregon

eregon commented Aug 18, 2022

Copy link
Copy Markdown
Member Author

@matz Oh, I thought during the meeting you said you were OK with copy-semantics #6251 and the semantics of the example shown as part of the meeting log, I even tried to confirm during the meeting to be sure I understood correctly, but apparently I must have misunderstood.

IMHO #6251 is much cleaner and unifies the behavior with TruffleRuby and JRuby.
It has several additional advantages over this PR:

  • Method/UnboundMethod always points to a given method definition which never changes with Make public/protected/private copy the method instead of creating a method entry using super #6251 (was the case before except for ZSUPER methods). I think Ruby users expect a Method/UnboundMethod is a handle to a given method definition, but it's not for ZSUPER methods. I think nobody wants that behavior of e.g. Method#call dynamically calling different method definitions for the same Method object (happens only for ZSUPER methods). Method/UnboundMethod are expected to capture/snapshot in Ruby, isn't it?
  • Much simpler implementation
  • Faster implementation, because there is no need for loops to deref ZSUPER methods
  • Compatible with the current TruffleRuby and JRuby behavior. TruffleRuby is unlikely to implement ZSUPER methods because lots of complexity for apparently no incompatibility in practice (no bug report to TruffleRuby and JRuby about this AFAIK).

@matz Could you confirm you want this PR and not #6251 given these trade-offs?

@eregon eregon reopened this Aug 18, 2022
@eregon

eregon commented Aug 18, 2022

Copy link
Copy Markdown
Member Author

An example in Ruby code:

class P
  private def foo
    1
  end
end

class C < P
  public :foo
end

m = C.new.method(:foo)
p m
p m.call

class P
  alias old_foo foo # silence warning
  private def foo
    2
  end
end

p m
p m.call

On 3.0.3, TruffleRuby, JRuby and #6251:

ruby 3.0.3p157 (2021-11-24 revision 3fb7d2cadc) [x86_64-linux]
#<Method: C(P)#foo() zsuper_method.rb:2>
1
#<Method: C(P)#foo() zsuper_method.rb:2>
1

On this PR:

ruby 3.2.0dev (2022-08-15T14:20:15Z do_not_hide_zsuper.. 2cbf520bb1) [x86_64-linux]
#<Method: C(P)#foo() zsuper_method.rb:2>
1
#<Method: C(P)#foo() zsuper_method.rb:17>   # line changes
2   # result changes

So this an unfortunate side effect, and I think unsolvable (except by removing ZSUPER methods).
(I don't expect anyone does that and redefines in superclass after public, so in practice does not matter much, but it's part of the general semantics)
It only "worked" on 3.0.3 because the lookup in Kernel#method/Module#instance_method skips ZSUPER, which does not accurately represent C#foo but is really just P#foo (v1) with "fetched on class C, shown in inspect".

The semantics of #6251 are clear and already proven (it's the same semantics as alias_method).
The semantics of this PR is still better than current releases (it fixes the consistency between owner and instance_methods), but is not nearly as clean and well-defined as #6251.

@matz

matz commented Aug 18, 2022

Copy link
Copy Markdown
Contributor

Sorry for miscommunication. See the following example:

class C
  private def foo(); "C" end
end
class D<C
  public :foo 
end
m = D.instance_method(:foo)
class C
  def foo(); "D" end
end
d=D.new
d.foo                #=> should be "D"; "C" is *unacceptable*
m.bind_call(d)       #=> "D" or "C"; negotiable; "C" is better (?)
m.owner              #=> C or D; D is acceptable
m2 = D.instance_method(:foo)
m2.bind_call(d)      #=> should be "D"

Making copy by public/private/protected (by CRuby) is unacceptable, because it ignores the method redefinition afterwards. That's the difference from alias (it was intentionally designed to keep the definition at the moment).

In other words, if it could follow the latest definition, it's OK to copy internally. It's implementation detail.

And I admit that it's a very slight edge case, so the other implementations may choose to behave differently as implementation dependent. I am not sure how desirable it is, considering the effort that have already put in the JRuby/TruffleRuby to keep compatibility with CRuby.

@matz

matz commented Aug 18, 2022

Copy link
Copy Markdown
Contributor

The semantics of #6251 are clear and already proven (it's the same semantics as alias_method).
The semantics of this PR is still better than current releases (it fixes the consistency between owner and instance_methods), but is not nearly as clean and well-defined as #6251.

As I said above, "the same semantics as alias_method" is unacceptable. Maybe we just abandon this PR as well, and just make Method#owner point to the class that made the method public.

@eregon

eregon commented Aug 18, 2022

Copy link
Copy Markdown
Member Author

Thank you for clarifying.
Then I think we should merge this PR, @jeremyevans and @XrXr, could you review it?

and just make Method#owner point to the class that made the method public.

I think that's very hard (to implement and to reason about) without allowing #method/#instance_method to return a ZSUPER method entry.
Specifically the current code on master uses the super method entry and only sets klass in the Method object, which leads to various inconsistencies and surprising behavior (notably it cannot set easily the correct Method#owner, and it caused issues for Method#private?/protected?/public?):

ruby/proc.c

Lines 1703 to 1720 in 7c1ed47

if (me->def->type == VM_METHOD_TYPE_ZSUPER) {
if (me->defined_class) {
VALUE klass = RCLASS_SUPER(RCLASS_ORIGIN(me->defined_class));
id = me->def->original_id;
me = (rb_method_entry_t *)rb_callable_method_entry_with_refinements(klass, id, &iclass);
}
else {
VALUE klass = RCLASS_SUPER(RCLASS_ORIGIN(me->owner));
id = me->def->original_id;
me = rb_method_entry_without_refinements(klass, id, &iclass);
}
goto again;
}
method = TypedData_Make_Struct(mclass, struct METHOD, &method_data_type, data);
RB_OBJ_WRITE(method, &data->recv, obj);
RB_OBJ_WRITE(method, &data->klass, klass);

So I think this PR is as close as it gets to fixing Method#owner with minimal changes and still preserve compatibility for {Method,UnboundMethod}#== to compare methods.

Results of the script above:

code matz comment CRuby 3.0 & 3.1 this PR #6251 & TruffleRuby JRuby
d.foo should be "D"; "C" is unacceptable "D" "D" "C" "C"
m.bind_call(d) "D" or "C"; negotiable; "C" is better (?) "C" "D" "C" "C"
m.owner C or D; D is acceptable C D D C
m2.bind_call(d) should be "D" "D" "D" "C" "C"

So this PR fits your semantics requirements, and fixes owner (and the consistency with instance_methods) + makes it simpler what Kernel#method/Module#instance_method returns, i.e. the first method in the ancestry chain just like actual method lookup used for regular calls (recv.foo).

@eregon

eregon commented Aug 18, 2022

Copy link
Copy Markdown
Member Author

I also realized, with this PR VM_METHOD_TYPE_ZSUPER is less special than before (good!) and is mostly "a method entry whose body is (z)super" and whose "source method" information is resolved dynamically as it's not copied on the zsuper method entry itself to stay dynamic.
Notably it's not treated specially anymore in #method/#instance_method and one can actually get a Method/UnboundMethod for such a method created by public/protected/private which is very nice/intuitive/simple and more consistent with other method types and with regular method lookup.

Comment thread proc.c Outdated

@jeremyevans jeremyevans left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks mostly good. See inline comments.

Comment thread spec/ruby/core/unboundmethod/equal_value_spec.rb
Comment thread proc.c Outdated
@eregon
eregon force-pushed the do_not_hide_zsuper_methods branch from d358bf6 to 80d2d1c Compare August 19, 2022 09:58
Based on jeremyevans@c95e7e5

Among other things, this fixes calling visibility methods (public?,
protected?, and private?) on them.  It also fixes #owner to show the
class the zsuper method entry is defined in, instead of the original
class it references.

For some backwards compatibility, adjust #parameters and #source_location,
to show the parameters and source location of the method originally
defined. Also have the parameters and source location still be shown
by #inspect.

Clarify documentation of {Method,UnboundMethod}#owner.

Add tests based on the description of https://bugs.ruby-lang.org/issues/18435
and based on ruby#5356 (comment)

Fixes [Bug #18435] [Bug #18729]

Co-authored-by: Benoit Daloze <[email protected]>
@eregon
eregon force-pushed the do_not_hide_zsuper_methods branch 2 times, most recently from e71a6f5 to bf151da Compare August 19, 2022 10:58
@eregon

eregon commented Aug 19, 2022

Copy link
Copy Markdown
Member Author

I've cleaned up the PR, the diff should be easier to review now as it's quite a bit smaller/clearer.
I now resolve ZSUPER methods through rb_method_entry_t directly (in zsuper_resolve) instead of Method/UnboundMethod objects which should avoid some extra allocations and indirections.

@eregon
eregon force-pushed the do_not_hide_zsuper_methods branch from bf151da to 3ad79b7 Compare August 19, 2022 11:02
@eregon
eregon merged commit 209631a into ruby:master Aug 20, 2022
eregon added a commit to eregon/ruby that referenced this pull request Sep 29, 2022
* So it's easy to review ruby#6242 +
  ruby#6467 and there are less changes
  overall.
eregon added a commit to eregon/ruby that referenced this pull request Sep 29, 2022
* So it's easy to review ruby#6242 +
  ruby#6467 and there are less changes
  overall.
eregon added a commit that referenced this pull request Sep 29, 2022
* So it's easy to review #6242 +
  #6467 and there are less changes
  overall.
matzbot pushed a commit that referenced this pull request Oct 1, 2022
…7d32a5e54088b6b4014529bbf2b4b8c1a96029,c6319026caa6c8f0f569f80011e8502349a04b14,aa490f9442c32cd0e1e449ac817f410bd5924c8b: [Backport #18435]

	Fix {Method,UnboundMethod}#super_method for zsuper methods

	* We need to resolve the zsuper method first, and then look the super
	  method of that.
	---
	 proc.c                                            | 25 ++++++++++++-----------
	 spec/ruby/core/method/super_method_spec.rb        | 15 +++-----------
	 spec/ruby/core/unboundmethod/super_method_spec.rb | 16 ++++++---------
	 3 files changed, 22 insertions(+), 34 deletions(-)

	Add specs for {Method,UnboundMethod}#owner of a zsuper method

	---
	 spec/ruby/core/method/owner_spec.rb        | 6 ++++++
	 spec/ruby/core/unboundmethod/owner_spec.rb | 7 +++++++
	 2 files changed, 13 insertions(+)

	Resolve zsuper method during lookup but preserve owner separately

	* See https://bugs.ruby-lang.org/issues/18729#note-34
	* See [Bug #18729]
	---
	 proc.c                   | 109 +++++++++++++++++++++++++----------------------
	 test/ruby/test_method.rb |  66 +++++++++++++++++++++++-----
	 2 files changed, 114 insertions(+), 61 deletions(-)

	Extend tests for a zsuper method of which the method it resolved to
	 has been removed

	---
	 test/ruby/test_method.rb | 41 +++++++++++++++++++++++++++++++++++++++++
	 1 file changed, 41 insertions(+)

	Reduce diff to proc.c @ b0b9f72

	* So it's easy to review #6242 +
	  #6467 and there are less changes
	  overall.
	---
	 proc.c                   | 76 ++++++++++++++++++------------------------------
	 test/ruby/test_method.rb |  7 +++--
	 2 files changed, 34 insertions(+), 49 deletions(-)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants