Skip to content

internal.h: move writing a string's encoding and coderange inside - #7179

Merged
matz merged 2 commits into
mruby:masterfrom
takumin:string-encoding-writes-internal
Aug 15, 2026
Merged

internal.h: move writing a string's encoding and coderange inside#7179
matz merged 2 commits into
mruby:masterfrom
takumin:string-encoding-writes-internal

Conversation

@takumin

@takumin takumin commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

Stacked on #7178, which is the first commit here. The second commit is this PR's own change, and the diff to read is git diff 33723b692..HEAD. Merging #7178 first leaves this one a single commit.

include/mruby/string.h hands out both of a string's answers about its bytes and both ways of writing them:

#define RSTR_CODERANGE(s)          /* read  */
#define RSTR_CODERANGE_SET(s, cr)  /* write */
#define RSTR_ENCODING(s)           /* read  */
#define RSTR_ENCODING_SET(s, e)    /* write */
#define RSTR_ENC_COPY(dst, src)
#define RSTR_ENC_CR_COPY(dst, src)
#define RSTR_ENC_CR_COPY_FOR_SUBSTR(dst, src)

Reading one is asking the string what its bytes were found to be, and asking costs it nothing. Writing one is making a claim about them, and a claim the bytes do not support is caught nowhere: a wrong encoding index has the bytes read as something they are not, and a string wrongly saying it reads whole and sound walks straight through the check a regexp makes of its subject before handing it to the engine.

This PR moves the writes to include/mruby/internal.h, which is where what has to be answered for already lives.

The line CRuby draws in the same place

rb_enc_str_coderange() is in include/ruby/encoding.h; ENC_CODERANGE_CLEAR and ENC_CODERANGE_SET are in internal/encoding/coderange.h. Read published, written inside.

What stays behind

The answers themselves. Naming one is reading, and what reads MRB_STR_CODERANGE_7BIT off a string has to be able to say it:

#define MRB_STR_CODERANGE_UNKNOWN 0
#define MRB_STR_CODERANGE_7BIT    1
#define MRB_STR_CODERANGE_VALID   2
#define MRB_STR_CODERANGE_BROKEN  3
#define MRB_STR_ENCODING_DEFAULT  0
#define MRB_STR_ENCODING_BINARY   1

along with the _SHIFT / _BITS / _MASK triples the readers expand to, and RSTR_CODERANGE, RSTR_ENCODING, RSTR_BINARY_P, RSTR_SINGLE_BYTE_P.

Nothing in the tree has to follow

Ten files write one of the fields, 46 writes in all:

$ git grep -c 'RSTR_CODERANGE_SET\|RSTR_ENCODING_SET\|RSTR_ENC_COPY\|RSTR_ENC_CR_COPY' -- '*.c'
mrbgems/mruby-encoding/src/encoding.c:2
mrbgems/mruby-regexp/src/regexp.c:1
mrbgems/mruby-sprintf/src/sprintf.c:2
mrbgems/mruby-string-bitops/src/string_bitops.c:1
mrbgems/mruby-string-ext/src/string.c:12
mrbgems/mruby-time/src/time.c:1
src/numeric.c:2
src/object.c:4
src/string.c:19
src/symbol.c:2

Every one of them already includes mruby/internal.h, and every one includes mruby/string.h before it:

mrbgems/mruby-encoding/src/encoding.c                string.h:2     internal.h:4
mrbgems/mruby-regexp/src/regexp.c                    string.h:10    internal.h:15
mrbgems/mruby-sprintf/src/sprintf.c                  string.h:8     internal.h:11
mrbgems/mruby-string-bitops/src/string_bitops.c      string.h:12    internal.h:13
mrbgems/mruby-string-ext/src/string.c                string.h:5     internal.h:7
mrbgems/mruby-time/src/time.c                        string.h:12    internal.h:13
src/numeric.c                                        string.h:10    internal.h:12
src/object.c                                         string.h:10    internal.h:12
src/string.c                                         string.h:16    internal.h:18
src/symbol.c                                         string.h:10    internal.h:17

So no #include line is added or moved:

$ git diff 33723b692..HEAD --stat
 include/mruby/internal.h | 68 ++++++++++++++++++++++++++++++++++++++++++++++++
 include/mruby/string.h   | 43 +++---------------------------
 2 files changed, 72 insertions(+), 39 deletions(-)

$ git diff 33723b692..HEAD | grep '^[+-]#include'
$

No #ifdef MRUBY_STRING_H guard is needed around the moved block either: what a macro spells has to be defined where it is expanded, not where it is written, and the four names it reaches for stay in mruby/string.h.

The amalgamated header carries them

lib/mruby/amalgam.rb inlines mruby/internal.h whole, so the move needs no change there. Generating mruby.h on master and on this branch and comparing the two, sorted so a block that only moved does not show:

$ diff <(sort master/amalgam/mruby.h) <(sort this-pr/amalgam/mruby.h) | grep '^<'
$ diff <(sort master/amalgam/mruby.h) <(sort this-pr/amalgam/mruby.h) | grep '^>' | grep '#'
> #define RSTR_SINGLE_BYTE_P(s) \
> #else
> #endif
> #ifdef MRB_UTF8_STRING

Nothing is dropped, and what is added is #7178's macro and the #ifdef MRB_UTF8_STRING the moved block brings with it. The rest of the 40 added lines are the comments the two commits write. The moved writes land at mruby.h:8800 onwards in the generated header, inside the mruby/internal.h section.

Generated code

A move of macro definitions, so nothing changes in what is generated. gcc 13.3.0 -O3, x86-64, .text of bin/mruby, against the same objects built from master:

build master this PR
full-debug 2636642 2636642 (±0)
bintest 1820341 1820341 (±0)
cxx_abi 1850166 1850166 (±0)
byte-string 1795973 1795973 (±0)
build_config/default.rb 1722135 1722135 (±0)
32-bit, full-core, i686-linux-gnu-gcc 1994304 1994304 (±0)

Objects differing between this commit and #7178, comparing objdump -d over every .o in the build:

build objects differing
full-debug 291 0
bintest 301 0
cxx_abi 291 0
byte-string 288 0
build_config/default.rb 270 0
32-bit 291 0

cxx_abi is the one build in ci/gcc-clang that goes through the C++ compiler, and the moved block is reached from mrbgems/mruby-string-bitops, mrbgems/mruby-time and src/symbol.c there as everywhere else.

Testing

rake -m test, on each commit, all green, 0 KO, 0 crash, 0 warnings, and the same counts as master:

build Total OK Skip
full-debug 2303 2300 3
bintest 2304 2293 11
bintest, the binary tests 117 117 0
cxx_abi 2304 2293 11
byte-string 2240 2194 46
build_config/default.rb 2086 2040 46
32-bit, full-core, i686-linux-gnu-gcc 2284 2272 12
the same, with enable_debug 2284 2280 4

build_config/host-m32.rb needs a multilib toolchain, so the 32-bit build above is full-core with the compiler set to i686-linux-gnu-gcc instead.

No test accompanies the change. A macro spells what it spelled and is expanded where it was expanded, so no string answers anything different.

For a gem outside the tree

One that writes either field has to include mruby/internal.h after this. That is a compile error and not a quiet change of behavior, which is what a gem making a claim about a string's bytes should get.

Not in this PR

What the two fields mean, how wide they are, and where in the flags word they sit are all untouched. So is every write in the tree: the 46 above are the same 46, spelled the same way, in the same order.

Summary by CodeRabbit

  • Refactor
    • Moved the macros that write a string's encoding index and character-range from the public string header to the internal one, leaving the reads and the named values where they were.

Nine places asked the same question of a string and spelled it out each
time: whether its coderange stands at 7BIT, or its bytes are read as
bytes. Both answers mean the same thing to the caller, which is that a
character index into the string is already a byte index. `mrb_str_char_to_byte`
returns the index it was handed, `mrb_str_byte_to_char` likewise,
`mrb_str_check_byte_pos` has no boundary to check, and `mrb_str_rindex_m`
hands the whole search to `mrb_str_byterindex_m`.

`RSTR_SINGLE_BYTE_P` gives that question a name. It is derived from what
the string carries rather than carried alongside it, so it is not the
`MRB_STR_SINGLE_BYTE` flag coming back: nothing stores it and nothing has
to keep it up to date. The expansion is what the call sites spelled, so
nothing changes in what is generated.
Reading either field is a fact about the string, and mruby/string.h goes
on handing both back to anyone who includes it. Writing one is a claim,
and a claim the bytes do not support is caught nowhere: an encoding index
has the bytes read as something they are not, and a string wrongly saying
it reads whole and sound goes straight through the check a regexp makes
of its subject before handing it to the engine.

So the writes move to where they can be answered for. This is the line
CRuby draws in the same place, with `rb_enc_str_coderange()` in
ruby/encoding.h and `ENC_CODERANGE_CLEAR` in internal/encoding/coderange.h.

Nothing in the tree has to follow: all ten files that write either field
already include mruby/internal.h, and all ten include mruby/string.h
before it. No guard is needed around the block, since what a macro spells
has to be defined where it is used and not where it is written.

The answers themselves stay in mruby/string.h. Naming one is reading, and
what reads MRB_STR_CODERANGE_7BIT off a string has to be able to say it.
@takumin
takumin requested a review from matz as a code owner August 15, 2026 00:24
@coderabbitai

coderabbitai Bot commented Aug 15, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6bf8d9a3-05e3-486a-80e0-7fdb9eb1c824

📥 Commits

Reviewing files that changed from the base of the PR and between 406205f and 7bb5927.

📒 Files selected for processing (4)
  • include/mruby/internal.h
  • include/mruby/string.h
  • mrbgems/mruby-string-ext/src/string.c
  • src/string.c

📝 Walkthrough

Walkthrough

The change adds shared string metadata macros, introduces RSTR_SINGLE_BYTE_P, and updates core and string-extension operations to use byte-oriented handling for all single-byte strings.

Changes

String metadata and byte-oriented handling

Layer / File(s) Summary
String metadata contracts
include/mruby/internal.h, include/mruby/string.h
Packed coderange and encoding updates now use shared internal macros. RSTR_SINGLE_BYTE_P identifies ASCII and binary strings.
Core string byte paths
src/string.c
Index conversion, String#chomp!, byte-position validation, and String#rindex use the single-byte predicate.
String extension byte paths
mrbgems/mruby-string-ext/src/string.c
String#ord, scrubbing, codepoint extraction, and character splitting use the single-byte predicate.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Merge Risk: ⚪ Minimal · up to 7bb59

This localized header change moves string metadata writers without identified behavior or build risk; no actionable merge-blocking risk remains after normal checks and review.

Possibly related PRs

Suggested labels: core, mrbgems

Suggested reviewers: matz, dearblue

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately identifies the main change: moving string encoding and coderange write operations into internal.h.
✨ 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants