[ruby/irb] Don't use delegator to install helper methods to main

object
(https://github.com/ruby/irb/pull/1031)

IRB used delegator to install command as a method of frozen main object.
Command is not a method now. We can drop it.

https://github.com/ruby/irb/commit/2f1c593801
This commit is contained in:
tomoya ishida 2024-11-19 22:17:07 +09:00 committed by git
parent edf5a738a3
commit bc92379664
6 changed files with 42 additions and 30 deletions

View File

@ -1463,16 +1463,21 @@ module IRB
end end
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: def format_prompt(format, ltype, indent, line_no) # :nodoc:
format.gsub(/%([0-9]+)?([a-zA-Z%])/) do format.gsub(/%([0-9]+)?([a-zA-Z%])/) do
case $2 case $2
when "N" when "N"
@context.irb_name @context.irb_name
when "m" 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) truncate_prompt_main(main_str)
when "M" when "M"
main_str = @context.main.inspect rescue "!#{$!.class}" main_str = basic_object_safe_main_call(:inspect) rescue "!#{$!.class}"
truncate_prompt_main(main_str) truncate_prompt_main(main_str)
when "l" when "l"
ltype ltype

View File

@ -19,7 +19,7 @@ module IRB
# Use throw and catch to handle arg that includes `;` # Use throw and catch to handle arg that includes `;`
# For example: "1, kw: (2; 3); 4" will be parsed to [[1], { kw: 3 }] # For example: "1, kw: (2; 3); 4" will be parsed to [[1], { kw: 3 }]
catch(:EXTRACT_RUBY_ARGS) do 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 end
end end

View File

@ -156,7 +156,8 @@ module IRB
end end
def eval_class_constants 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
end end
} }

View File

@ -4,8 +4,6 @@
# by Keiju ISHITSUKA(keiju@ruby-lang.org) # by Keiju ISHITSUKA(keiju@ruby-lang.org)
# #
require "delegate"
require_relative "helper_method" require_relative "helper_method"
IRB::TOPLEVEL_BINDING = binding IRB::TOPLEVEL_BINDING = binding
@ -16,7 +14,7 @@ module IRB # :nodoc:
# set self to main if specified, otherwise # set self to main if specified, otherwise
# inherit main from TOPLEVEL_BINDING. # inherit main from TOPLEVEL_BINDING.
def initialize(*main) def initialize(*main)
if main[0].kind_of?(Binding) if Binding === main[0]
@binding = main.shift @binding = main.shift
elsif IRB.conf[:SINGLE_IRB] elsif IRB.conf[:SINGLE_IRB]
@binding = TOPLEVEL_BINDING @binding = TOPLEVEL_BINDING
@ -70,37 +68,16 @@ EOF
unless main.empty? unless main.empty?
case @main case @main
when Module 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 else
begin 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 rescue TypeError
fail CantChangeBinding, @main.inspect fail CantChangeBinding, @main.inspect
end end
end 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) @binding.local_variable_set(:_, nil)
end end
@ -111,6 +88,9 @@ EOF
attr_reader :main attr_reader :main
def load_helper_methods_to_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 ancestors = class<<main;ancestors;end
main.extend ExtendCommandBundle if !ancestors.include?(ExtendCommandBundle) main.extend ExtendCommandBundle if !ancestors.include?(ExtendCommandBundle)
main.extend HelpersContainer if !ancestors.include?(HelpersContainer) main.extend HelpersContainer if !ancestors.include?(HelpersContainer)

View File

@ -19,6 +19,12 @@ module TestIRB
end end
end end
class BO < BasicObject
def baz
"this is baz"
end
end
binding.irb binding.irb
RUBY RUBY
end end
@ -40,6 +46,19 @@ module TestIRB
assert_match(/irb\(Foo\):006>/, out) assert_match(/irb\(Foo\):006>/, out)
end 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 def test_cd_moves_top_level_with_no_args
out = run_ruby_file do out = run_ruby_file do
type "cd Foo" type "cd Foo"

View File

@ -652,6 +652,13 @@ module TestIRB
assert_equal('irb("aaaaaaaaaaaaaaaaaaaaaaaaaaaa...)>', irb.send(:format_prompt, 'irb(%M)>', nil, 1, 1)) assert_equal('irb("aaaaaaaaaaaaaaaaaaaaaaaaaaaa...)>', irb.send(:format_prompt, 'irb(%M)>', nil, 1, 1))
end 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 def test_prompt_main_raise
main = Object.new main = Object.new
def main.to_s; raise TypeError; end def main.to_s; raise TypeError; end