Feature #7836

Need a way to get Method and UnboundMethod objects to methods overridden by prepended modules

Added by john mair over 2 years ago. Updated about 1 year ago.

[ruby-core:52160]
Status:Open
Priority:Normal
Assignee:Yukihiro Matsumoto

Description

See the following code:

module P
  def hello
    puts "from P"
    super
  end
end

class A
  def hello
    puts 'from A'
  end

  prepend P
end

A.instance_method(:hello).source_location == P.instance_method(:hello).source_location  #=> true

Discussion

Since A.instance_method(:hello) effectively returns P.instance_method(:hello) it is impossible to get an UnboundMethod object to the original A#hello method.

Tools like Pry need to access UnboundMethod objects to every active method in the system for debugging purposes.

Possible solution

Simply allow instance_method() to take a second boolean parameter, indicating whether methods injected by prepended modules are to be included, it would default to true:

example:

A.instance_method(:hello) #=> same as P#hello
A.instance_method(:hello, false) #=> return strictly A#hello

0001-proc.c-include-prepended-method-flag.patch Magnifier (3.17 KB) Nobuyoshi Nakada, 03/05/2013 08:32 PM


Related issues

Related to Ruby trunk - Bug #7842: An alias of a "prepend"ed method skips the original method when calling super Closed 02/13/2013
Related to Ruby trunk - Feature #9781: Feature Proposal: Method#super_method Closed 04/28/2014

Associated revisions

Revision 39224
Added by Nobuyoshi Nakada over 2 years ago

proc.c: skip prepends

  • proc.c (mnew): skip prepending modules and return the method bound on the given class. [Bug #7836]

Revision 39224
Added by Nobuyoshi Nakada over 2 years ago

proc.c: skip prepends

  • proc.c (mnew): skip prepending modules and return the method bound on the given class. [Bug #7836]

History

#1 Updated by Nobuyoshi Nakada over 2 years ago

  • Tracker changed from Feature to Bug

#2 Updated by Nobuyoshi Nakada over 2 years ago

  • Description updated (diff)
  • ruby -v set to 2.0.0dev

It's a bug.

#3 Updated by Nobuyoshi Nakada over 2 years ago

  • Status changed from Open to Closed
  • % Done changed from 0 to 100

This issue was solved with changeset r39224.
john, thank you for reporting this issue.
Your contribution to Ruby is greatly appreciated.
May Ruby be with you.


proc.c: skip prepends

  • proc.c (mnew): skip prepending modules and return the method bound on the given class. [Bug #7836]

#4 Updated by Marc-Andre Lafortune over 2 years ago

  • Status changed from Closed to Open
  • Assignee set to Yukihiro Matsumoto

Matz, could you please confirm?

I'm not sure what is the right approach. What should A.instance_method(:hello) return:

  1. the method that A.new.hello will execute, or
  2. the method defined in A?

I feel that 1) a more accurate description of the behavior in 1.9.3. It reflects my understanding. Otherwise, strictly speaking, String.instance_method(:object_id) would raise a NameError!

I would expect:

meth = A.instance_method :hello
a = A.new
meth.bind(a).call # should be the same effect as a.bar
meth.source_location # thus should be the same as a.method(:hello).source_location
meth.owner # and thus should be the same as a.method(:hello).owner

This is always true in Ruby 1.9, and this patch changes that.

I agree with John Mair that there should be a way to get the proper instance method of a class, as he suggests. I would add that String.instance_method(:object_id, false) should raise a NameError, as there is no such instance method defined in String.

#5 Updated by john mair over 2 years ago

@ marcandre. Another possible approach is to provide UnboundMethod#super. We do something similar in our Method wrapper for Pry: https://github.com/pry/pry/blob/master/lib/pry/method.rb#L394-L402

While we're at it, other useful methods for Method/UnboundMethod could be private?, public?, aliases, singleton? :) but that could be asking for too much ;)

John

#6 Updated by Nobuyoshi Nakada over 2 years ago

  • Tracker changed from Bug to Feature

#7 Updated by Nobuyoshi Nakada over 2 years ago

  • % Done changed from 100 to 0

#9 Updated by Marc-Andre Lafortune over 2 years ago

banister (john mair) wrote:

@ marcandre. Another possible approach is to provide UnboundMethod#super.

Not a bad idea, for Method also, although I'm not sure if it would be useful to many. You might want to make another feature request for this (and provide a decent justification!).

While we're at it, other useful methods for Method/UnboundMethod could be private?, public?, aliases, singleton? :) but that could be asking for too much ;)

These might be more problematic:

  • privacy It is not really an attribute of the method itself. It's an attribute of the class, i.e. does the class provide public access to a method.
    class F
      def priv; end
      alias_method :pub, :priv
      private :priv
    end
    F.new.pub # => nil
    F.new.priv # => NoMethodError: private method `priv' called
    F.instance_method(:pub) == F.instance_method(:priv) # => true

So I think that Module#private_method_defined? and Module#private_instance_methods are the ones you want to use.

  • aliases Not sure who would use this, but you can already easily do this by comparing the unbound methods:
    String.instance_methods.group_by{|x| String.instance_method(x)}.map(&:last).reject(&:one?)
     # => [[:==, :===], [:[], :slice], [:length, :size], [:succ, :next], [:succ!, :next!], [:to_s, :to_str], [:concat, :<<],
               [:intern, :to_sym], [:kind_of?, :is_a?], [:send, :__send__], [:object_id, :__id__], [:to_enum, :enum_for]]

Or if you prefer:

    class UnboundMethod
      def aliases
        owner.instance_methods.select{|m| owner.instance_method(m) == self}
      end
    end

    String.instance_method(:size).aliases # => [:length, :size]
  • singleton?

This is a property of the owner, no? Module#singleton_class? already accepted: https://bugs.ruby-lang.org/issues/7609

So if you really want, you will be able to roll your own easily:

    class UnboundMethod
      def singleton?
        owner.is_a?(Class) && owner.singleton_class?
      end
    end

#10 Updated by Marc-Andre Lafortune over 2 years ago

  • Target version set to 2.1.0
  • Category set to core

nobu: Patch looks good, but I would go further and have String.instance_method(:object_id, false) also raise a NameError, for consistency with String.instance_methods(false).include? :object_id # => false.

Did Matz confirm any of this?

#11 Updated by Ilya Vorontsov over 2 years ago

Also it should be mentioned that there is no way to get Method for super call. So one cannot know, for example, number of argments of method down the ancestry chain. For prepending methods there is a workaround - to save link on prepend_features. Or to maintain full hierarchy of methods, but it looks akward.
E.g. I needed such method when created #coerce(other,meth) prepending either usual #coerce(other) method or already defined #coerce(other,meth). It's hard to know whether prepended module should call super with one or two arguments.

#12 Updated by Hiroshi SHIBATA over 1 year ago

  • Target version changed from 2.1.0 to current: 2.2.0

#13 Updated by Nobuyoshi Nakada about 1 year ago

  • Related to Feature #9781: Feature Proposal: Method#super_method added

#14 Updated by Nobuyoshi Nakada about 1 year ago

  • Description updated (diff)

Can't you achieve this by Method#super_method?

Also available in: Atom PDF