summaryrefslogtreecommitdiff
path: root/test/ruby/test_call.rb
AgeCommit message (Collapse)Author
2025-04-02Fix assertion failure with anonymous splatsJeremy Evans
When calling a method that accepts an anonymous splat and literal keywords without any arguments, an assertion failure was previously raised. Set rest_index to 0 when setting rest to the frozen hash, so the args_argc calculation is accurate. While here, add more tests for methods with anonymous splats with and without keywords and keyword splats to confirm behavior is correct. Also add a basic bootstrap test that would hit the previous assertion failure. Co-authored-by: Jean Boussier <[email protected]> Notes: Merged: https://github.com/ruby/ruby/pull/13046
2024-09-18Fix evaluation order issue in f(**h, &h.delete(key))Jeremy Evans
Previously, this would delete the key in `h` before keyword splatting `h`. This goes against how ruby handles `f(*a, &a.pop)` and similar expressions. Fix this by having the compiler check whether the block pass expression is safe. If it is not safe, then dup the keyword splatted hash before evaluating the block pass expression. For expression: `h=nil; f(**h, &h.delete(:key))` VM instructions before: ``` 0000 putnil ( 1)[Li] 0001 setlocal_WC_0 h@0 0003 putself 0004 getlocal_WC_0 h@0 0006 getlocal_WC_0 h@0 0008 putobject :key 0010 opt_send_without_block <calldata!mid:delete, argc:1, ARGS_SIMPLE> 0012 splatkw 0013 send <calldata!mid:f, argc:1, ARGS_BLOCKARG|FCALL|KW_SPLAT>, nil 0016 leave ``` VM instructions after: ``` 0000 putnil ( 1)[Li] 0001 setlocal_WC_0 h@0 0003 putself 0004 putspecialobject 1 0006 newhash 0 0008 getlocal_WC_0 h@0 0010 opt_send_without_block <calldata!mid:core#hash_merge_kwd, argc:2, ARGS_SIMPLE> 0012 getlocal_WC_0 h@0 0014 putobject :key 0016 opt_send_without_block <calldata!mid:delete, argc:1, ARGS_SIMPLE> 0018 send <calldata!mid:f, argc:1, ARGS_BLOCKARG|FCALL|KW_SPLAT|KW_SPLAT_MUT>, nil 0021 leave ``` This is the same as 07d3bf4832532ae7446c9a6924d79aed60a7a9a5, except that it removes unnecessary hash allocations when using the prism compiler. Fixes [Bug #20640] Notes: Merged: https://github.com/ruby/ruby/pull/11645 Merged-By: jeremyevans <[email protected]>
2024-09-18Revert "Fix evaluation order issue in f(**h, &h.delete(key))"Jeremy Evans
This reverts commit 07d3bf4832532ae7446c9a6924d79aed60a7a9a5. No failures in the pull request CI, but there are now allocation test failures.
2024-09-18Fix evaluation order issue in f(**h, &h.delete(key))Jeremy Evans
Previously, this would delete the key in h before keyword splatting h. This goes against how ruby handles f(*a, &a.pop) and similar expressions. Fix this by having the compiler check whether the block pass expression is safe. If it is not safe, then dup the keyword splatted hash before evaluating the block pass expression. For expression: `h=nil; f(**h, &h.delete(:key))` VM instructions before: ``` 0000 putnil ( 1)[Li] 0001 setlocal_WC_0 h@0 0003 putself 0004 getlocal_WC_0 h@0 0006 getlocal_WC_0 h@0 0008 putobject :key 0010 opt_send_without_block <calldata!mid:delete, argc:1, ARGS_SIMPLE> 0012 splatkw 0013 send <calldata!mid:f, argc:1, ARGS_BLOCKARG|FCALL|KW_SPLAT>, nil 0016 leave ``` VM instructions after: ``` 0000 putnil ( 1)[Li] 0001 setlocal_WC_0 h@0 0003 putself 0004 putspecialobject 1 0006 newhash 0 0008 getlocal_WC_0 h@0 0010 opt_send_without_block <calldata!mid:core#hash_merge_kwd, argc:2, ARGS_SIMPLE> 0012 getlocal_WC_0 h@0 0014 putobject :key 0016 opt_send_without_block <calldata!mid:delete, argc:1, ARGS_SIMPLE> 0018 send <calldata!mid:f, argc:1, ARGS_BLOCKARG|FCALL|KW_SPLAT|KW_SPLAT_MUT>, nil 0021 leave ``` Fixes [Bug #20640] Notes: Merged: https://github.com/ruby/ruby/pull/11206 Merged-By: jeremyevans <[email protected]>
2024-09-13Prevent warnings "the block passed to ... may be ignored"Yusuke Endoh
Notes: Merged: https://github.com/ruby/ruby/pull/11611
2024-08-27Remove incorrect setting of KW_SPLAT_MUT flagJeremy Evans
Fixes [Bug #20701] Co-authored-by: Pablo Herrero <[email protected]> Notes: Merged: https://github.com/ruby/ruby/pull/11468 Merged-By: jeremyevans <[email protected]>
2024-07-18Avoid unnecessary array allocations for f(arg, *arg, **arg, **arg), f(*arg, ↵Jeremy Evans
a: lvar), and other calls The `f(arg, *arg, **arg, **arg)` case was previously not optimized. The optimizer didn't optimize this case because of the multiple keyword splats, and the compiler didn't optimize it because the `f(*arg, **arg, **arg)` optimization added in 0ee3960685e283d8e75149a8777eb0109d41509a didn't apply. I found it difficult to apply this optimization without changing the `setup_args_core` API, since by the time you get to the ARGSCAT case, you don't know whether you were called recursively or directly, so I'm not sure if it was possible to know at that point whether the array allocation could be avoided. This changes the dup_rest argument in `setup_args_core` from an int to a pointer to int. This allows us to track whether we have allocated a caller side array for multiple splats or splat+post across recursive calls. Check the pointed value (*dup_rest) to determine the `splatarray` argument. If dup_rest is 1, then use `splatarray true` (caller-side array allocation), then set *dup_rest back to 0, ensuring only a single `splatarray true` per method call. Before calling `setup_args_core`, check whether the array allocation can be avoided safely using `splatarray false`. Optimizable cases are: ``` // f(*arg) SPLAT // f(1, *arg) ARGSCAT LIST // f(*arg, **arg) ARGSPUSH SPLAT HASH nd_brace=0 // f(1, *arg, **arg) ARGSPUSH ARGSCAT LIST HASH nd_brace=0 ``` If so, dup_rest is set to 0 instead of 1 to avoid the allocation. After calling `setup_args_core`, check the flag. If the flag includes `VM_CALL_ARGS_SPLAT`, and the pointed value has changed, indicating `splatarray true` was used, then also set `VM_CALL_ARGS_SPLAT_MUT` in the flag. My initial attempt at this broke the `f(*ary, &ary.pop)` test, because we were not duplicating the ary in the splat even though it was modified later (evaluation order issue). The initial attempt would also break `f(*ary, **ary.pop)` or `f(*ary, kw: ary.pop)` cases for the same reason. I added test cases for those evaluation order issues. Add setup_args_dup_rest_p static function that checks that a given node is safe. Call that on the block pass node to determine if the block pass node is safe. Also call it on each of the hash key/value nodes to test that they are safe. If any are not safe, then set dup_rest = 1 so that `splatarray true` will be used to avoid the evaluation order issue. This new approach has the affect of optimizing most cases of literal keywords after positional splats. Previously, only static keyword hashes after positional splats avoided array allocation for the splat. Now, most dynamic keyword hashes after positional splats also avoid array allocation. Add allocation tests for dynamic keyword keyword hashes after positional splats. setup_args_dup_rest_p is currently fairly conservative. It could definitely be expanded to handle additional node types to reduce allocations in additional cases. Notes: Merged: https://github.com/ruby/ruby/pull/11161
2024-05-31Make error messages clear blocks/keywords are disallowed in index assignmentJeremy Evans
Blocks and keywords are allowed in regular index. Also update NEWS to make this more clear. Co-authored-by: Nobuyoshi Nakada <[email protected]>
2024-04-04Prevent "assigned but unused variable" warningsYusuke Endoh
2024-03-17[Bug #20218] Reject keyword arguments in indexNobuyoshi Nakada
2023-12-14Remove unused variables in test_call_op_asgn_keywords_mutableJeremy Evans
2023-12-14Fix op asgn method calls passing mutable keyword splatsJeremy Evans
When passing the keyword splat to [], it cannot be mutable, because mutating the keyword splat inside [] would result in changes to the keyword splat passed to []=.
2023-12-12Fix op asgn calls with keywordsJeremy Evans
Examples of such calls: ```ruby obj[kw: 1] += fo obj[**kw] &&= bar ``` Before this patch, literal keywords would segfault in the compiler, and keyword splat usage would result in TypeError. This handles all cases I can think of: * literal keywords * keyword splats * combined with positional arguments * combined with regular splats * both with and without blocks * both popped and non-popped cases This also makes sure that to_hash is only called once on the keyword splat argument, instead of twice, and make sure it is called before calling to_proc on a passed block. Fixes [Bug #20051] Co-authored-by: Nobuyoshi Nakada <[email protected]>
2023-12-09Ensure super(**kw, &block) calls kw.to_hash before block.to_procJeremy Evans
Similar as previous commit, but handles the super case with explicit arguments.
2023-12-09Ensure f(**kw, &block) calls kw.to_hash before block.to_procJeremy Evans
Previously, block.to_proc was called first, by vm_caller_setup_arg_block. kw.to_hash was called later inside CALLER_SETUP_ARG or setup_parameters_complex. This adds a splatkw instruction that is inserted before sends with ARGS_BLOCKARG and KW_SPLAT and without KW_SPLAT_MUT. This is not needed in the KW_SPLAT_MUT case, because then you know the value is a hash, and you don't need to call to_hash on it. The splatkw instruction checks whether the second to top block is a hash, and if not, replaces it with the value of calling to_hash on it (using rb_to_hash_type). As it is always before a send with ARGS_BLOCKARG and KW_SPLAT, second to top is the keyword splat, and top is the passed block.
2023-12-07Prevent modification of splat array inside setup_parameters_complexJeremy Evans
For the following: ``` def f(*a); a end p f(*a, kw: 3) ``` `setup_parameters_complex` pushes `{kw: 3}` onto `a`. This worked fine back when `concatarray true` was used and `a` was already a copy. It does not work correctly with the optimization to switch to `concatarray false`. This duplicates the array on the callee side in such a case. This affects cases when passing a regular splat and a keyword splat (or literal keywords) in a method call, where the method does not accept keywords. This allocation could probably be avoided, but doing so would make `setup_parameters_complex` more complicated.
2023-11-18Ensure keyword splat method argument is hashJeremy Evans
Commit e87d0882910001ef3b0c2ccd43bf00cee8c34a0c introduced a regression where the keyword splat object passed by the caller would be directly used by callee as keyword splat parameters, if it implemented #to_hash. The return value of #to_hash would be ignored in this case.
2023-06-17Split the bmethod proc test to avoid redefinitionNobuyoshi Nakada
2023-04-25Speed up calling iseq bmethodsJeremy Evans
Currently, bmethod arguments are copied from the VM stack to the C stack in vm_call_bmethod, then copied from the C stack to the VM stack later in invoke_iseq_block_from_c. This is inefficient. This adds vm_call_iseq_bmethod and vm_call_noniseq_bmethod. vm_call_iseq_bmethod is an optimized method that skips stack copies (though there is one copy to remove the receiver from the stack), and avoids calling vm_call_bmethod_body, rb_vm_invoke_bmethod, invoke_block_from_c_proc, invoke_iseq_block_from_c, and vm_yield_setup_args. Th vm_call_iseq_bmethod argument handling is similar to the way normal iseq methods are called, and allows for similar performance optimizations when using splats or keywords. However, even in the no argument case it's still significantly faster. A benchmark is added for bmethod calling. In my environment, it improves bmethod calling performance by 38-59% for simple bmethod calls, and up to 180% for bmethod calls passing literal keywords on both sides. ``` ./miniruby-iseq-bmethod: 18159792.6 i/s ./miniruby-m: 13174419.1 i/s - 1.38x slower bmethod_simple_1 ./miniruby-iseq-bmethod: 15890745.4 i/s ./miniruby-m: 10008972.7 i/s - 1.59x slower bmethod_simple_0_splat ./miniruby-iseq-bmethod: 13142804.3 i/s ./miniruby-m: 11168595.2 i/s - 1.18x slower bmethod_simple_1_splat ./miniruby-iseq-bmethod: 12375791.0 i/s ./miniruby-m: 8491140.1 i/s - 1.46x slower bmethod_no_splat ./miniruby-iseq-bmethod: 10151258.8 i/s ./miniruby-m: 8716664.1 i/s - 1.16x slower bmethod_0_splat ./miniruby-iseq-bmethod: 8138802.5 i/s ./miniruby-m: 7515600.2 i/s - 1.08x slower bmethod_1_splat ./miniruby-iseq-bmethod: 8028372.7 i/s ./miniruby-m: 5947658.6 i/s - 1.35x slower bmethod_10_splat ./miniruby-iseq-bmethod: 6953514.1 i/s ./miniruby-m: 4840132.9 i/s - 1.44x slower bmethod_100_splat ./miniruby-iseq-bmethod: 5287288.4 i/s ./miniruby-m: 2243218.4 i/s - 2.36x slower bmethod_kw ./miniruby-iseq-bmethod: 8931358.2 i/s ./miniruby-m: 3185818.6 i/s - 2.80x slower bmethod_no_kw ./miniruby-iseq-bmethod: 12281287.4 i/s ./miniruby-m: 10041727.9 i/s - 1.22x slower bmethod_kw_splat ./miniruby-iseq-bmethod: 5618956.8 i/s ./miniruby-m: 3657549.5 i/s - 1.54x slower ``` Notes: Merged: https://github.com/ruby/ruby/pull/7522
2023-04-25Generalize cfunc large array splat fix to fix many additional cases raising ↵Jeremy Evans
SystemStackError Originally, when 2e7bceb34ea858649e1f975a934ce1894d1f06a6 fixed cfuncs to no longer use the VM stack for large array splats, it was thought to have fully fixed Bug #4040, since the issue was fixed for methods defined in Ruby (iseqs) back in Ruby 2.2. After additional research, I determined that same issue affects almost all types of method calls, not just iseq and cfunc calls. There were two main types of remaining issues, important cases (where large array splat should work) and pedantic cases (where large array splat raised SystemStackError instead of ArgumentError). Important cases: ```ruby define_method(:a){|*a|} a(*1380888.times) def b(*a); end send(:b, *1380888.times) :b.to_proc.call(self, *1380888.times) def d; yield(*1380888.times) end d(&method(:b)) def self.method_missing(*a); end not_a_method(*1380888.times) ``` Pedantic cases: ```ruby def a; end a(*1380888.times) def b(_); end b(*1380888.times) def c(_=nil); end c(*1380888.times) c = Class.new do attr_accessor :a alias b a= end.new c.a(*1380888.times) c.b(*1380888.times) c = Struct.new(:a) do alias b a= end.new c.a(*1380888.times) c.b(*1380888.times) ``` This patch fixes all usage of CALLER_SETUP_ARG with splatting a large number of arguments, and required similar fixes to use a temporary hidden array in three other cases where the VM would use the VM stack for handling a large number of arguments. However, it is possible there may be additional cases where splatting a large number of arguments still causes a SystemStackError. This has a measurable performance impact, as it requires additional checks for a large number of arguments in many additional cases. This change is fairly invasive, as there were many different VM functions that needed to be modified to support this. To avoid too much API change, I modified struct rb_calling_info to add a heap_argv member for storing the array, so I would not have to thread it through many functions. This struct is always stack allocated, which helps ensure sure GC doesn't collect it early. Because of how invasive the changes are, and how rarely large arrays are actually splatted in Ruby code, the existing test/spec suites are not great at testing for correct behavior. To try to find and fix all issues, I tested this in CI with VM_ARGC_STACK_MAX to -1, ensuring that a temporary array is used for all array splat method calls. This was very helpful in finding breaking cases, especially ones involving flagged keyword hashes. Fixes [Bug #4040] Co-authored-by: Jimmy Miller <[email protected]> Notes: Merged: https://github.com/ruby/ruby/pull/7522
2023-01-13Do not use VM stack for splat arg on cfuncKoichi Sasada
On the cfunc methods, if a splat argument is given, all array elements are expanded on the VM stack and it can cause SystemStackError. The idea to avoid it is making a hidden array to contain all parameters and use this array as an argv. This patch is reviesed version of https://github.com/ruby/ruby/pull/6816 The main change is all changes are closed around calling cfunc logic. Fixes [Bug #4040] Co-authored-by: Jeremy Evans <[email protected]> Notes: Merged: https://github.com/ruby/ruby/pull/7109
2022-09-25[Bug #19021] Fix safe call w/ conditional assignJohn Hawthorn
As of fbaac837cfba23a9d34dc7ee144d7940248222a2, when we were performing a safe call (`o&.x=`) with a conditional assign (`||= 1`) and discarding the result the stack would end up in a bad state due to a missing pop. This commit fixes that by adjusting the target label of the branchnil to be before a pop in that case (as was previously done in the non-conditional assignment case). Notes: Merged: https://github.com/ruby/ruby/pull/6437
2020-06-18Dup splat array in certain cases where there is a block argumentJeremy Evans
This makes: ```ruby args = [1, 2, -> {}]; foo(*args, &args.pop) ``` call `foo` with 1, 2, and the lambda, in addition to passing the lambda as a block. This is different from the previous behavior, which passed the lambda as a block but not as a regular argument, which goes against the expected left-to-right evaluation order. This is how Ruby already compiled arguments if using leading arguments, trailing arguments, or keywords in the same call. This works by disabling the optimization that skipped duplicating the array during the splat (splatarray instruction argument switches from false to true). In the above example, the splat call duplicates the array. I've tested and cases where a local variable or symbol are used do not duplicate the array, so I don't expect this to decrease the performance of most Ruby programs. However, programs such as: ```ruby foo(*args, &bar) ``` could see a decrease in performance, if `bar` is a method call and not a local variable. This is not a perfect solution, there are ways to get around this: ```ruby args = Struct.new(:a).new([:x, :y]) def args.to_a; a; end def args.to_proc; a.pop; ->{}; end foo(*args, &args) # calls foo with 1 argument (:x) # not 2 arguments (:x and :y) ``` A perfect solution would require completely disabling the optimization. Fixes [Bug #16504] Fixes [Bug #16500] Notes: Merged: https://github.com/ruby/ruby/pull/3157
2017-10-02test_call.rb: refine test_safe_callnobu
* test/ruby/test_call.rb (test_safe_call): rhs should not be evaluated when the receiver is nil. simplified the assertion for [Bug #13964]. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@60100 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2017-10-02compile.c: fix stack consitency errornobu
* compile.c (iseq_compile_each0): fix stack consitency error on attr-assign with safe navigation operator when the receiver is nil, should pop it too. [ruby-core:83078] [Bug #13964] git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@60099 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2016-10-22compile.c: order with splattingnobu
* compile.c (setup_args): duplicate splatting array if more arguments present to obey left-to-right execution order. [ruby-core:77701] [Bug# 12860] git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@56469 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2016-01-09symbol.h: unexpected safe callnobu
* symbol.h (is_attrset_id): ASET is an attrset ID. fix unexpected safe call instead of an ordinary ASET. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@53485 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2015-12-16Add frozen_string_literal: false for all filesnaruse
When you change this to true, you may need to add more tests. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@53141 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2015-12-16parse.y: fix block_call&.callnobu
* parse.y (block_command, block_call): fix `&.` calls after block_call. [Feature #11537] git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@53138 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2015-11-06change DOTQnobu
* defs/id.def (token_ops), parse.y (parser_yylex): change DOTQ from ".?" to "&.". [ruby-core:71363] [Feature #11537] git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@52462 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2015-11-03parse.y: revert lbracketnobu
* parse.y (lbracket): remove .? before aref. [Feature #11537] revert r52422 and r52424 git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@52430 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2015-11-02parse.y: lbracketnobu
* parse.y (lbracket): support .? before aref. [Feature #11537] git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@52422 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2015-10-30* test/ruby/test_call.rb: added test for safe navigation operator.hsbt
[fix GH-1066] git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@52383 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2015-10-23test_call.rb: add testsnobu
* test_call.rb (test_safe_call): Add test cases for safe navigation operator assignment. [Fix GH-1064] Validate: * can assign an attribute which is `nil` * can "or assign" an attribute which is `nil` git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@52238 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2015-10-23safe navigation attrsetnobu
* compile.c (iseq_compile_each): support safe navigation of simple attribute assignment. [Feature #11537] * parse.y (mlhs_node, lhs, attrset_gen): ditto. keep mid non-attrset as the sign of safe navigation. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@52234 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2015-10-22Safe navigation operatornobu
* compile.c (iseq_peephole_optimize): peephole optimization for branchnil jumps. * compile.c (iseq_compile_each): generate save navigation operator code. * insns.def (branchnil): new opcode to pop the tos and branch if it is nil. * parse.y (NEW_QCALL, call_op, parser_yylex): parse token '.?'. [Feature #11537] git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@52214 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2014-03-12vm_insnhelper.c: disable fastpath if splatnobu
* vm_insnhelper.c (vm_callee_setup_arg): disable fastpath if splat argument, since argc may differ for each calls. [ruby-core:61422] [Bug #9622] git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@45321 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2008-09-24* test: assert_raises has been deprecated since a long time ago.nobu
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@19536 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2004-02-18 * test/*: should not depend on $KCODE.nahi
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@5764 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2003-09-05* lib/optparse.rb (OptionParser#order, #permute, #parse): allow annobu
array as argument. * test/ruby/test_*.rb: moved invariants to left side in assert_equal, and use assert_nil, assert_raises and so on. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@4516 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2003-09-05* test/ruby/test_*.rb: replace 'assert(a == b)' with assert_equal(a, b)'nahi
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@4512 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2003-09-04* test/ruby: tests for ruby itself.nahi
* test/ruby/test_*.rb: split sample/test.rb into 28 test/unit testcases. some tests could not be translates... search '!!' mark to see it. * test/csv/test_csv.rb: should require 'csv', not '../lib/csv'. test runner should set load path correctly. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@4498 b2dd03c8-39d4-4d8f-98ff-823fe69b080e