Skip to content

mruby-regexp: add the regexp form of String#[] and String#slice - #7054

Merged
matz merged 1 commit into
mruby:masterfrom
takumin:string-aref-regexp
Aug 9, 2026
Merged

mruby-regexp: add the regexp form of String#[] and String#slice#7054
matz merged 1 commit into
mruby:masterfrom
takumin:string-aref-regexp

Conversation

@takumin

@takumin takumin commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Problem

String#[] and its slice twin accept an Integer, a String and a Range, but not a
Regexp. str_convert_range() in src/string.c funnels every non-String, non-Range
argument through mrb_ensure_int_type(), so the regexp forms raise a TypeError that names
Integer, which does not hint at what is actually missing. mruby-regexp never overrode
[] or slice, and the gem README listed this under Limitations.

"hello"[/l+/]            # CRuby: "ll",  mruby: TypeError (Regexp cannot be converted to Integer)
"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

Change

mrbgems/mruby-regexp/mrblib/string_regexp.rb overrides [], following the pattern
split already uses in the same file: the C-defined method is aliased as __aref, and
the 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#=== reads
the argument's real type, since is_a? is redefinable.

A Regexp goes through Regexp#match and not #match?: the match globals have to be
published 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.

slice is 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 to slice as well. That is
also what makes sym[re] work, and it needs no change of its own: Symbol#[] is an alias
of Symbol#slice in mruby-symbol-ext, which delegates to String#slice.

Interaction with the inline index opcodes

vm_op_getidx() answers str[Integer], str[String] and str[Range] from C, and
vm_op_getidx0() answers str[0]. Both guard on the receiver's class only, not on whether
String#[] has been redefined, so those forms keep bypassing this override. That is
harmless 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 falls
into the default arm of the opcode's type switch, leaves through getidx_fallback, and
arrives here as an ordinary send.

str[i, len] and every slice call 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 a
tight 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 new
forms in the usage list, and the header comment of mrblib/symbol_regexp.rb no longer says
sym[/re/] is missing.

The write side, str[re] = repl and str.slice!(re), is deliberately out of scope: both
are 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.rb gains assertions for the plain match, captures by
index / 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.rb gains the sym[re] forms, guarded by a skip because mrbtest
builds this gem's tests without mruby-symbol-ext.

rake test passes with no failures, both in a default build and in one with
MRB_UTF8_STRING. Checked against CRuby 4.0.6.

Summary by CodeRabbit

  • New Features

    • Added regular expression support to String#[] and String#slice, including capture selection and named captures.
    • Added support for regular expression indexing and slicing on symbols when symbol slicing is available.
    • Existing string and symbol indexing forms remain supported.
  • Bug Fixes

    • Improved handling of unmatched patterns, invalid arguments, and lookalike regexp objects.
  • Documentation

    • Updated regexp documentation to reflect the newly supported operations.

`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.
@takumin
takumin requested a review from matz as a code owner August 9, 2026 15:34
@coderabbitai

coderabbitai Bot commented Aug 9, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The change adds regexp-aware String#[] and String#slice, including capture selection and legacy indexing delegation. It documents and tests regexp-based Symbol slicing when mruby-symbol-ext is available.

Changes

Regexp indexing

Layer / File(s) Summary
String regexp indexing
mrbgems/mruby-regexp/mrblib/string_regexp.rb, mrbgems/mruby-regexp/README.md
String#[] preserves the C implementation for non-regexp forms and supports regexp matches, captures, validation, and slice aliasing. Documentation describes the supported APIs.
String indexing validation
mrbgems/mruby-regexp/test/regexp.rb
Tests cover matches, captures, match globals, failed matches, delegation, subclasses, errors, and real regexp type checks.
Symbol regexp integration
mrbgems/mruby-regexp/mrblib/symbol_regexp.rb, mrbgems/mruby-regexp/test/symbol_regexp.rb
Comments and conditional tests cover regexp-based Symbol#[] and Symbol#slice, including capture selection and non-matching patterns.

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
Loading

Possibly related PRs

  • mruby/mruby#6995: Related mruby-regexp Symbol and Regexp integration with different matching behavior.
  • mruby/mruby#7006: Implements related regexp-aware String#[] and String#slice behavior and tests.
  • mruby/mruby#7048: Related named-capture handling and regexp tests.

Suggested reviewers: matz

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding regexp support to String#[] and String#slice.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 9233195 and 46d88a8.

📒 Files selected for processing (5)
  • mrbgems/mruby-regexp/README.md
  • mrbgems/mruby-regexp/mrblib/string_regexp.rb
  • mrbgems/mruby-regexp/mrblib/symbol_regexp.rb
  • mrbgems/mruby-regexp/test/regexp.rb
  • mrbgems/mruby-regexp/test/symbol_regexp.rb

Comment thread mrbgems/mruby-regexp/mrblib/string_regexp.rb
@matz
matz merged commit f2f84be into mruby:master Aug 9, 2026
21 checks passed
@takumin
takumin deleted the string-aref-regexp branch August 9, 2026 21:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants