mruby-regexp: add the regexp form of String#[] and String#slice - #7054
Merged
Conversation
`String#[]` and its `slice` twin accepted an Integer, a String and a Range, but every other argument funnelled through `mrb_ensure_int_type()` in `str_convert_range()`, so a Regexp raised a TypeError that names Integer instead of hinting at what was missing: ```ruby "hello"[/l+/] # CRuby: "ll", mruby: TypeError "hello".slice(/l+/) # CRuby: "ll", mruby: TypeError "hello"[/(l+)(o)/, 1] # CRuby: "ll", mruby: TypeError "hello"[/(?<x>l+)/, :x] # CRuby: "ll", mruby: TypeError "hello"[/z/] # CRuby: nil, mruby: TypeError :hello[/l+/] # CRuby: "ll", mruby: TypeError ``` Override `[]` in `mrblib/string_regexp.rb`, following the pattern `split` already uses there: alias the C-defined method as `__aref` and hand every non-Regexp argument list back to it before inspecting anything, so those forms keep the arity and type errors `mrb_get_args()` raises, down to the no-argument call. A Regexp goes through `Regexp#match` rather than `#match?`, because the match globals have to be published here, including the clearing a failed match does. A capture argument reaches `MatchData#[]` untouched: it already normalizes a negative index, answers nil for an index past the last group and raises `IndexError` for a name that resolves to none, which is what CRuby does for `str[re, capture]`. `slice` is registered separately from `[]` rather than aliased to it, in mruby and in CRuby alike, so it is aliased to the override as well. That is also what makes `sym[re]` work: `Symbol#[]` is an alias of `Symbol#slice` (mruby-symbol-ext), which delegates to `String#slice`. `vm_op_getidx()` answers `str[Integer]`, `str[String]` and `str[Range]` from C and guards on the receiver's class alone, so those three keep bypassing the override. They are exactly the forms it would have delegated back unchanged, so behaviour is identical and they cost nothing. A Regexp index leaves the opcode through its fallback and arrives as an ordinary send. The write side, `str[re] = repl` and `str.slice!(re)`, is left out: both are destructive and belong with the other destructive-method work.
📝 WalkthroughWalkthroughThe change adds regexp-aware ChangesRegexp indexing
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant StringIndex
participant Regexp
participant MatchData
StringIndex->>Regexp: match string with regexp
Regexp->>MatchData: create match result
StringIndex->>MatchData: select full match or capture
MatchData-->>StringIndex: return selected string or nil
Possibly related PRs
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/mrblib/string_regexp.rb`:
- Line 247: Update the regexp branch of String#[] around the Regexp === args[0]
guard to invoke the built-in Regexp#match implementation directly, bypassing any
overridden match method while preserving normal MatchData and match-global
behavior. Add a regression test using a real Regexp with an overridden match
that verifies String#[] continues returning the expected match result.
🪄 Autofix
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: 2ab8d55f-c3ba-4e16-b53f-a7c6ed88d60e
📒 Files selected for processing (5)
mrbgems/mruby-regexp/README.mdmrbgems/mruby-regexp/mrblib/string_regexp.rbmrbgems/mruby-regexp/mrblib/symbol_regexp.rbmrbgems/mruby-regexp/test/regexp.rbmrbgems/mruby-regexp/test/symbol_regexp.rb
This was referenced Aug 9, 2026
This was referenced Aug 9, 2026
This was referenced Aug 9, 2026
This was referenced Aug 9, 2026
This was referenced Aug 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
String#[]and itsslicetwin accept an Integer, a String and a Range, but not aRegexp.
str_convert_range()insrc/string.cfunnels every non-String, non-Rangeargument through
mrb_ensure_int_type(), so the regexp forms raise a TypeError that namesInteger, which does not hint at what is actually missing.
mruby-regexpnever overrode[]orslice, and the gem README listed this under Limitations.Change
mrbgems/mruby-regexp/mrblib/string_regexp.rboverrides[], following the patternsplitalready uses in the same file: the C-defined method is aliased as__aref, andthe override hands every non-Regexp argument list straight back to it.
The delegation happens before any argument is inspected, so the non-regexp forms keep the
arity and type errors
mrb_get_args()raises. With no arguments at all,args[0]is nil,the guard fails, and
__aref()raises the same ArgumentError as before.Module#===readsthe argument's real type, since
is_a?is redefinable.A Regexp goes through
Regexp#matchand not#match?: the match globals have to bepublished here, including the clearing a failed match does, which is why the MatchData is
fetched even when no capture was asked for. The capture argument reaches
MatchData#[]untouched, which already gives the CRuby semantics: a negative index counts back from the
last group, an index past the last group is nil, and a name that resolves to no group
raises IndexError.
sliceis a second method table entry for the same C function rather than an alias of[], in mruby and in CRuby alike, so the override is aliased tosliceas well. That isalso what makes
sym[re]work, and it needs no change of its own:Symbol#[]is an aliasof
Symbol#slicein mruby-symbol-ext, which delegates toString#slice.Interaction with the inline index opcodes
vm_op_getidx()answersstr[Integer],str[String]andstr[Range]from C, andvm_op_getidx0()answersstr[0]. Both guard on the receiver's class only, not on whetherString#[]has been redefined, so those forms keep bypassing this override. That isharmless and desirable: they are exactly the ones the override would have delegated back to
__aref()unchanged, so behaviour is identical and they pay nothing. A Regexp index fallsinto the
defaultarm of the opcode's type switch, leaves throughgetidx_fallback, andarrives here as an ordinary send.
str[i, len]and everyslicecall are not opcode receivers and do reach the override,paying a Ruby frame on their way to
__aref(measured at roughly 3x the direct C call on atight 300k-iteration loop). This is the cost of the feature living in mrblib; moving the
regexp branch into C would avoid it, at the price of a callback into the VM from
str_convert_range().Documentation
The gem README loses its No regexp form of
String#[]limitation and gains the newforms in the usage list, and the header comment of
mrblib/symbol_regexp.rbno longer sayssym[/re/]is missing.The write side,
str[re] = replandstr.slice!(re), is deliberately out of scope: bothare destructive and belong with the other destructive-method work. The removed limitation
item was about reading only, so it is dropped rather than narrowed.
Tests
mrbgems/mruby-regexp/test/regexp.rbgains assertions for the plain match, captures byindex / name / symbol, negative and out-of-range capture indexes, failed matches, the match
globals, the untouched non-regexp forms including a String subclass receiver (which the
opcodes never answer), the argument errors, and an argument that lies about its own type.
test/symbol_regexp.rbgains thesym[re]forms, guarded by askipbecause mrbtestbuilds this gem's tests without mruby-symbol-ext.
rake testpasses with no failures, both in a default build and in one withMRB_UTF8_STRING. Checked against CRuby 4.0.6.Summary by CodeRabbit
New Features
String#[]andString#slice, including capture selection and named captures.Bug Fixes
Documentation