Skip to content

Commit

Permalink
Don't use delegator to install helper methods to main object
Browse files Browse the repository at this point in the history
IRB used delegator to install command as a method of frozen main object.
Command is not a method now. We can drop it.
  • Loading branch information
tompng committed Nov 14, 2024
1 parent a6fae8b commit 3548123
Show file tree
Hide file tree
Showing 6 changed files with 42 additions and 30 deletions.
9 changes: 7 additions & 2 deletions lib/irb.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1463,16 +1463,21 @@ def truncate_prompt_main(str) # :nodoc:
end
end

def basic_object_safe_main_call(method)
main = @context.main
Object === main ? main.__send__(method) : Object.instance_method(method).bind_call(main)
end

def format_prompt(format, ltype, indent, line_no) # :nodoc:
format.gsub(/%([0-9]+)?([a-zA-Z%])/) do
case $2
when "N"
@context.irb_name
when "m"
main_str = @context.main.to_s rescue "!#{$!.class}"
main_str = basic_object_safe_main_call(:to_s) rescue "!#{$!.class}"
truncate_prompt_main(main_str)
when "M"
main_str = @context.main.inspect rescue "!#{$!.class}"
main_str = basic_object_safe_main_call(:inspect) rescue "!#{$!.class}"
truncate_prompt_main(main_str)
when "l"
ltype
Expand Down
2 changes: 1 addition & 1 deletion lib/irb/command/internal_helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def ruby_args(arg)
# Use throw and catch to handle arg that includes `;`
# For example: "1, kw: (2; 3); 4" will be parsed to [[1], { kw: 3 }]
catch(:EXTRACT_RUBY_ARGS) do
@irb_context.workspace.binding.eval "IRB::Command.extract_ruby_args #{arg}"
@irb_context.workspace.binding.eval "::IRB::Command.extract_ruby_args #{arg}"
end || [[], {}]
end
end
Expand Down
3 changes: 2 additions & 1 deletion lib/irb/completion.rb
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,8 @@ def eval_global_variables
end

def eval_class_constants
::Module.instance_method(:constants).bind(eval("self.class")).call
klass = ::Object.instance_method(:class).bind_call(receiver)
::Module.instance_method(:constants).bind_call(klass)
end
end
}
Expand Down
32 changes: 6 additions & 26 deletions lib/irb/workspace.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
# by Keiju ISHITSUKA(keiju@ruby-lang.org)
#

require "delegate"

require_relative "helper_method"

IRB::TOPLEVEL_BINDING = binding
Expand All @@ -16,7 +14,7 @@ class WorkSpace
# set self to main if specified, otherwise
# inherit main from TOPLEVEL_BINDING.
def initialize(*main)
if main[0].kind_of?(Binding)
if Binding === main[0]
@binding = main.shift
elsif IRB.conf[:SINGLE_IRB]
@binding = TOPLEVEL_BINDING
Expand Down Expand Up @@ -70,37 +68,16 @@ def initialize(*main)
unless main.empty?
case @main
when Module
@binding = eval("IRB.conf[:__MAIN__].module_eval('binding', __FILE__, __LINE__)", @binding, __FILE__, __LINE__)
@binding = eval("::IRB.conf[:__MAIN__].module_eval('::Kernel.binding', __FILE__, __LINE__)", @binding, __FILE__, __LINE__)
else
begin
@binding = eval("IRB.conf[:__MAIN__].instance_eval('binding', __FILE__, __LINE__)", @binding, __FILE__, __LINE__)
@binding = eval("::IRB.conf[:__MAIN__].instance_eval('::Kernel.binding', __FILE__, __LINE__)", @binding, __FILE__, __LINE__)
rescue TypeError
fail CantChangeBinding, @main.inspect
end
end
end

case @main
when Object
use_delegator = @main.frozen?
else
use_delegator = true
end

if use_delegator
@main = SimpleDelegator.new(@main)
IRB.conf[:__MAIN__] = @main
@main.singleton_class.class_eval do
private
define_method(:binding, Kernel.instance_method(:binding))
define_method(:local_variables, Kernel.instance_method(:local_variables))
# Define empty method to avoid delegator warning, will be overridden.
define_method(:exit) {|*a, &b| }
define_method(:exit!) {|*a, &b| }
end
@binding = eval("IRB.conf[:__MAIN__].instance_eval('binding', __FILE__, __LINE__)", @binding, *@binding.source_location)
end

@binding.local_variable_set(:_, nil)
end

Expand All @@ -111,6 +88,9 @@ def initialize(*main)
attr_reader :main

def load_helper_methods_to_main
# Do not load helper methods to frozen objects and BasicObject
return unless Object === @main && !@main.frozen?

ancestors = class<<main;ancestors;end
main.extend ExtendCommandBundle if !ancestors.include?(ExtendCommandBundle)
main.extend HelpersContainer if !ancestors.include?(HelpersContainer)
Expand Down
19 changes: 19 additions & 0 deletions test/irb/command/test_cd.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ def foo
end
end
class BO < BasicObject
def baz
"this is baz"
end
end
binding.irb
RUBY
end
Expand All @@ -40,6 +46,19 @@ def test_cd
assert_match(/irb\(Foo\):006>/, out)
end

def test_cd_basic_object_or_frozen
out = run_ruby_file do
type "cd BO.new"
type "cd 1"
type "cd Object.new.freeze"
type "exit"
end

assert_match(/irb\(#<BO:.+\):002>/, out)
assert_match(/irb\(1\):003>/, out)
assert_match(/irb\(#<Object:.+\):004>/, out)
end

def test_cd_moves_top_level_with_no_args
out = run_ruby_file do
type "cd Foo"
Expand Down
7 changes: 7 additions & 0 deletions test/irb/test_context.rb
Original file line number Diff line number Diff line change
Expand Up @@ -652,6 +652,13 @@ def main.inspect; to_s.inspect; end
assert_equal('irb("aaaaaaaaaaaaaaaaaaaaaaaaaaaa...)>', irb.send(:format_prompt, 'irb(%M)>', nil, 1, 1))
end

def test_prompt_main_basic_object
main = BasicObject.new
irb = IRB::Irb.new(IRB::WorkSpace.new(main), TestInputMethod.new)
assert_match(/irb\(#<BasicObject:.+\)/, irb.send(:format_prompt, 'irb(%m)>', nil, 1, 1))
assert_match(/irb\(#<BasicObject:.+\)/, irb.send(:format_prompt, 'irb(%M)>', nil, 1, 1))
end

def test_prompt_main_raise
main = Object.new
def main.to_s; raise TypeError; end
Expand Down

0 comments on commit 3548123

Please sign in to comment.