From: dbfeldman@... Date: 2021-01-11T19:01:54+00:00 Subject: [ruby-core:102004] [Ruby master Bug#17519] set_visibility fails when a prepended module and a refinement both exist Issue #17519 has been updated by fledman (David Feldman). although the title makes it sound obscure, this bug is actually fairly easy to trigger when using rspec and rails: * activesupport >= 5 prepends a module onto Hash * i18n >= 1.3 refines several methods on Hash * power_assert (used by test-unit and minitest) refines most of the basic operators of the core classes e.g. try to [`expect(some_hash).to receive(:except)`](https://github.com/rspec/rspec-rails/issues/2394) ---------------------------------------- Bug #17519: set_visibility fails when a prepended module and a refinement both exist https://bugs.ruby-lang.org/issues/17519#change-89850 * Author: fledman (David Feldman) * Status: Open * Priority: Normal * ruby -v: ruby 3.0.0p0 (2020-12-25 revision 95aff21468) [x86_64-darwin19] * Backport: 2.5: UNKNOWN, 2.6: UNKNOWN, 2.7: UNKNOWN, 3.0: UNKNOWN ---------------------------------------- the set_visibility functions (aka public/private/protected) fail with NameError when: * called on a specific object's singleton class * for a specific method * and both of the following are true * any module has been prepended to the object's class * a refinement exists for the specific method note that the refinement does not need to ever be used I have reproduced this on 3.0.0, 2.7.2, and 2.6.6 (those were the only 3 version I tested) ``` ruby def test_visibility(function) h1 = {x:1, y:1} h2 = {x:2, y:2} h1.singleton_class.send(:private, function) h2.singleton_class.send(:public, function) begin puts h1.public_send(function, :x) rescue NoMethodError => err puts "hit NoMethodError as expected: #{err.inspect}" end puts h2.public_send(function, :x) end ``` succeeds without any prepended modules or refinements: ```ruby irb> test_visibility :except hit NoMethodError as expected: #<NoMethodError: private method `except' called for {:x=>1, :y=>1}:Hash> {:y=>2} => nil ``` succeeds with only a prepended module: ```ruby irb> module Nothing; end; Hash.prepend(Nothing); Hash.ancestors => [Nothing, Hash, Enumerable, Object, PP::ObjectMixin, Kernel, BasicObject] irb> test_visibility :except hit NoMethodError as expected: #<NoMethodError: private method `except' called for {:x=>1, :y=>1}:Hash> {:y=>2} => nil ``` succeeds with only a refinement: ```ruby irb> module NeverUsed refine Hash do def except(*keys) {never: 'used'} end end end => #<refinement:Hash@NeverUsed> irb> test_visibility :except hit NoMethodError as expected: #<NoMethodError: private method `except' called for {:x=>1, :y=>1}:Hash> {:y=>2} => nil ``` fails with both a refinement and a prepended module: ```ruby irb> module Nothing; end; Hash.prepend(Nothing); Hash.ancestors => [Nothing, Hash, Enumerable, Object, PP::ObjectMixin, Kernel, BasicObject] irb> module NeverUsed refine Hash do def except(*keys) {never: 'used'} end end end => #<refinement:Hash@NeverUsed> irb> test_visibility :except Traceback (most recent call last): 6: from .rubies/ruby-3.0.0/bin/irb:23:in `<main>' 5: from .rubies/ruby-3.0.0/bin/irb:23:in `load' 4: from .rubies/ruby-3.0.0/lib/ruby/gems/3.0.0/gems/irb-1.3.0/exe/irb:11:in `<top (required)>' 3: from (irb):25:in `<main>' 2: from (irb):5:in `test_visibility' 1: from (irb):5:in `private' NameError (undefined method `except' for class `#<Class:#<Hash:0x00007ffd6b176f18>>') Did you mean? exec # non-refined method still works irb> test_visibility :fetch hit NoMethodError as expected: #<NoMethodError: private method `fetch' called for {:x=>1, :y=>1}:Hash> 2 => nil ``` -- https://bugs.ruby-lang.org/ Unsubscribe: <mailto:ruby-core-request@ruby-lang.org?subject=unsubscribe> <http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-core>