mruby-regexp: fix String#sub/#gsub argument handling - #6989
Conversation
…ng#sub/#gsub
Both methods took `pattern, replacement = nil` and branched on whether a
block was given, so `"abc".sub(/b/, "X") { "Y" }` ran the block and
returned "aYc". CRuby ignores the block whenever a replacement argument
is present, and returns "aXc".
Switch both to a splat so the number of arguments actually given can be
told apart from an omitted replacement, and dispatch on that instead of
on the block. This also lets them reject a bad argument count with the
CRuby message: `gsub` always reports 1..2, while `sub` reports 1..2 with
a block and exactly 2 without one.
`"abcb".gsub(/b/)` fell through to `__gsub_str` with `nil.to_s` as the
replacement and returned "ac", silently deleting every match. CRuby
returns an Enumerator over the matched substrings, which also makes
`gsub(pattern).each { ... }` perform the substitution.
Enumerator lives in mruby-enumerator, which mruby-regexp does not
require at runtime (mruby-string-ext takes the same approach for
String#each_char), so declare it as a test dependency only. Both gems
are in stdlib.gembox, so a normal build has it.
📝 WalkthroughWalkthrough
ChangesRegexp substitution behavior
Estimated code review effort: 2 (Simple) | ~10 minutes Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@mrbgems/mruby-regexp/mrbgem.rake`:
- Around line 7-9: Change the mruby-enumerator declaration in the gem
specification from add_test_dependency to add_dependency so mruby-regexp’s
String#gsub implementation can access to_enum at runtime; keep the existing
mruby-enumerator dependency and core option unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 073cf1bb-c45e-40b3-8b28-e1a9f3090fdd
📒 Files selected for processing (3)
mrbgems/mruby-regexp/mrbgem.rakemrbgems/mruby-regexp/mrblib/string_regexp.rbmrbgems/mruby-regexp/test/regexp.rb
`String#gsub` without a block returns an enumerator, but that is the only path in this gem reaching `to_enum`. Without mruby-enumerator, `to_enum` is core `Kernel#to_enum`, which raises NotImplementedError -- the same deal as `Kernel#loop` and `String#each_char` in mruby-string-ext, neither of which depends on mruby-enumerator either. A plain dependency would drag Enumerator, and thus Fiber, into builds that never take that path. Declare the dependency only when the build already has the gem. mrbtest runs each gem's tests in a state holding just its declared dependencies, so this is what lets the enumerator test run at all; without it the test skips itself. A gem arriving only through another gem's dependency is not visible at that point, which likewise just skips the test.
String#subandString#gsubinmruby-regexpbranched on whether a block was given rather than on how many arguments were actually passed. Two behaviours diverged from CRuby as a result.1. The block won over the replacement argument
CRuby ignores the block whenever a replacement argument is present. Both methods now take a splat, so an omitted replacement can be told apart from an explicit one, and dispatch on the argument count instead.
This also lets them reject a bad argument count with the CRuby message.
gsubalways reports1..2;subreports1..2with a block and exactly2without one, matching CRuby.2.
gsubwithout a block deleted every matchIt fell through to
__gsub_strwithnil.to_sas the replacement. It now returns an enumerator over the matched substrings, sogsub(pattern).each { ... }performs the substitution as in CRuby.Enumeratorlives inmruby-enumerator, whichmruby-regexpdoes not require at runtime — without it,to_enumis coreKernel#to_enum, which raisesNotImplementedError, exactly as withKernel#loopandString#each_charinmruby-string-ext. The gem declares the dependency only when the build already contains it, so no build gains Enumerator (and thus Fiber) it did not ask for. This is what lets the enumerator test run: mrbtest gives each gem's tests anmrb_open_core()state holding just its declared dependencies. WhenEnumeratoris undefined the test skips itself. Both gems are instdlib.gembox, so a normal build runs it.Notes
"abc".gsub(/b/, nil)returns"ac"where CRuby raisesTypeError. That predates this change and is left alone.rake testpasses (1890 OK / 0 KO / 0 Crash).mruby-regexp+mruby-string-extonly (test skips, neither Enumerator nor Fiber linked), andmruby-regexp+mruby-setwheremruby-enumeratorarrives transitively (test skips).Summary by CodeRabbit
String#subandString#gsubargument-count validation.gsubenumeration behavior.