From 2f1c5938018ccc7cec07e37359bb4bf28d933d40 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Tue, 19 Nov 2024 22:17:07 +0900 Subject: [PATCH] Don't use delegator to install helper methods to main object (#1031) IRB used delegator to install command as a method of frozen main object. Command is not a method now. We can drop it. --- lib/irb.rb | 9 ++++++-- lib/irb/command/internal_helpers.rb | 2 +- lib/irb/completion.rb | 3 ++- lib/irb/workspace.rb | 32 ++++++----------------------- test/irb/command/test_cd.rb | 19 +++++++++++++++++ test/irb/test_context.rb | 7 +++++++ 6 files changed, 42 insertions(+), 30 deletions(-) diff --git a/lib/irb.rb b/lib/irb.rb index 528892797..e60e8e1e2 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -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 diff --git a/lib/irb/command/internal_helpers.rb b/lib/irb/command/internal_helpers.rb index 249b5cded..a01ddb1d4 100644 --- a/lib/irb/command/internal_helpers.rb +++ b/lib/irb/command/internal_helpers.rb @@ -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 diff --git a/lib/irb/completion.rb b/lib/irb/completion.rb index 7f102dcdf..36a8b084f 100644 --- a/lib/irb/completion.rb +++ b/lib/irb/completion.rb @@ -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 } diff --git a/lib/irb/workspace.rb b/lib/irb/workspace.rb index 632b43243..ced9d7866 100644 --- a/lib/irb/workspace.rb +++ b/lib/irb/workspace.rb @@ -4,8 +4,6 @@ # by Keiju ISHITSUKA(keiju@ruby-lang.org) # -require "delegate" - require_relative "helper_method" IRB::TOPLEVEL_BINDING = binding @@ -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 @@ -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 @@ -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</, 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\(#/, out) + assert_match(/irb\(1\):003>/, out) + assert_match(/irb\(#/, out) + end + def test_cd_moves_top_level_with_no_args out = run_ruby_file do type "cd Foo" diff --git a/test/irb/test_context.rb b/test/irb/test_context.rb index d2c007af3..b02d8dbe0 100644 --- a/test/irb/test_context.rb +++ b/test/irb/test_context.rb @@ -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\(#', nil, 1, 1)) + assert_match(/irb\(#', nil, 1, 1)) + end + def test_prompt_main_raise main = Object.new def main.to_s; raise TypeError; end