From: davidbalbert@... Date: 2014-12-05T03:21:40+00:00 Subject: [ruby-core:66713] [ruby-trunk - Bug #9580] Refinements regression in IRB Issue #9580 has been updated by David Albert. I'm embarrassed that I missed these responses to my bug report. I had forgotten that I had created this issue. Sorry! Shugo: `using Foo, global: true` would fix the problem, though it feels a bit awkward to me. I guess this is because an IRB session feels like it should be one continuous lexical scope, even though I realize that this isn't how things work in practice. Perhaps we could add an instance method to `Binding` that would allow refinements to stay active across multiple calls to eval for that binding instance? It might look something like this: ```ruby module Foo refine Object do def foo puts "foo" end end end b = binding # Normal behavior: b.eval("using Foo") b.eval("foo") #=> NoMethodError # Special behavior, useful for building REPLs: b.retain_refinements! b.eval("using Foo") b.eval("foo") #=> foo ``` `Binding#retain_refinements!`, could be made to apply only to refinements activated with `main.using` (`top_using` in `eval.c`) rather than `Module#using` (`mod_using` in `eval.c`). Maybe `retain_toplevel_refinements!` would be a better name for the method. I'm not sure. If this was implemented, IRB could call `Binding#retain_refinements!` on the binding used for its session in `lib/irb/workspace.rb` and IRB would behave as I originally expected. At this point, I realize that I'm talking about a design change, not a bug fix, which I know is a bigger deal. Which of the proposed solutions is better (`using(mod, global: true)` vs `Binding#retain_refinements!`) depends on who you think should be responsible for knowing about the behavior. If you think it should be the user of the REPL, then the first solution makes sense. If you think it should be the author of the REPL, then the second solution makes sense. I think this is something that the user of the REPL shouldn't have to know about because it's a pretty small edge case, and it has surprising results for a casual Ruby user who hasn't thought about how IRB is implemented. It was even confusing for me, and at the time that I created this issue, I had some experience reading through the source of both IRB and Pry. That said, I do see the merits of the other solution and hope that one of them gets accepted, even if it isn't the one I prefer. Thanks! David ---------------------------------------- Bug #9580: Refinements regression in IRB https://bugs.ruby-lang.org/issues/9580#change-50313 * Author: David Albert * Status: Open * Priority: Normal * Assignee: * Category: * Target version: * ruby -v: 2.2.0dev * Backport: 1.9.3: UNKNOWN, 2.0.0: UNKNOWN, 2.1: UNKNOWN ---------------------------------------- The problem: Top level refinements do not work in IRB. They worked in 2.0.0-p451, but don't work in 2.1.0, 2.1.1, or today's trunk. Details: Here some code in a file: #refine.rb module A refine String do def asdf :asdf end end end using A p "foo".asdf In all versions, of Ruby between 2.0.0-p451 and 2.2.0dev, running this file, prints `:asdf`. This is the expected behavior. Ruby 2.0.0 also prints the "Refinements are experimental" warning, as expected: # Ruby 2.0.0-p451 $ ruby refine.rb refine.rb:2: warning: Refinements are experimental, and the behavior may change in future versions of Ruby! :asdf # Ruby 2.1.0, 2.1.1, and 2.2.0dev $ ruby refine.rb :asdf In Ruby 2.0.0-p451, the same code also works in IRB, also as expected: irb(main):001:0> "#{RUBY_VERSION}-#{RUBY_PATCHLEVEL}" => "2.0.0-451" irb(main):002:0> module A irb(main):003:1> refine String do irb(main):004:2* def asdf irb(main):005:3> :asdf irb(main):006:3> end irb(main):007:2> end irb(main):008:1> end (irb):3: warning: Refinements are experimental, and the behavior may change in future versions of Ruby! => # irb(main):009:0> using A => main irb(main):010:0> "foo".asdf => :asdf However, in all newer versions of Ruby (2.1.0, 2.1.1, and 2.2.0dev), this code raises a `NoMethodError` in IRB: irb(main):001:0> RUBY_VERSION => "2.1.0" irb(main):002:0> module A irb(main):003:1> refine String do irb(main):004:2* def asdf irb(main):005:3> :asdf irb(main):006:3> end irb(main):007:2> end irb(main):008:1> end => # irb(main):009:0> using A => main irb(main):010:0> "foo".asdf NoMethodError: undefined method `asdf' for "foo":String from (irb):10 from out/bin/irb:11:in `
' irb(main):001:0> RUBY_VERSION => "2.1.1" irb(main):002:0> module A irb(main):003:1> refine String do irb(main):004:2* def asdf irb(main):005:3> :asdf irb(main):006:3> end irb(main):007:2> end irb(main):008:1> end => # irb(main):009:0> using A => main irb(main):010:0> "foo".asdf NoMethodError: undefined method `asdf' for "foo":String from (irb):10 from bin/irb:11:in `
' irb(main):001:0> RUBY_VERSION => "2.2.0" irb(main):002:0> module A irb(main):003:1> refine String do irb(main):004:2* def asdf irb(main):005:3> :asdf irb(main):006:3> end irb(main):007:2> end irb(main):008:1> end => # irb(main):009:0> using A => main irb(main):010:0> "foo".asdf NoMethodError: undefined method `asdf' for "foo":String from (irb):10 from bin/irb:11:in `
' This seems like a bug because the code behaves differently in IRB than how it behaves in the file. If it's the intended behavior, it's frustrating because it makes it harder to prototype code that uses refinements in the REPL. This issue is not specific to IRB. I get the same behavior in Pry (works in 2.0.0, doesn't work in newer Ruby versions). This makes me think the issue is not inside the IRB source, but rather has something to do with `using`'s behavior in the `Binding` objects that IRB and Pry are probably using. I haven't looked at the Pry or IRB source in quite a long time, so this paragraph is mostly speculation. Please let me know if there's any more info I can provide to make this easier to fix -- https://bugs.ruby-lang.org/