Conversation
Converts the matched substring to an Integer like String#to_i, but returns nil when nothing could be parsed, matching CRuby's use of rb_int_parse_cstr. ConvertBytes gains byteListToInumOrNil for this. Also add the missing colon to the "undefined group name reference" IndexError raised by nameToBackrefError, matching CRuby. See https://bugs.ruby-lang.org/issues/21932 Part of jruby#9746 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| } | ||
|
|
||
| /** | ||
| * MRI: match_integer_at |
There was a problem hiding this comment.
I see different styles for this in this file, is there a specific one I should follow like
/** match_integer_at
*
*/
There was a problem hiding this comment.
The format you have in place is fine. We probably should introduce an annotation for associating our methods with their CRuby equivalents, so they can be audited by tools and included in docs. Has been a TODO for me for a while.
There was a problem hiding this comment.
I went ahead and did a quick spike on brainstorming a possible annotation of MRI or CRuby. Here is a proposal: On the annotation idea: here is a concrete proposal for linking a Java method to the CRuby C function it ports, followed by a survey of what migrating the existing comments would involve. I can move this to a separate issue if you'd rather not have it on this PR.
Proposal
An annotation in org.jruby.anno:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({METHOD, CONSTRUCTOR, TYPE, FIELD})
@Repeatable(CRuby.List.class)
public @interface CRuby {
String value(); // the C function or macro, e.g. "match_integer_at"
String file() default ""; // e.g. "re.c"; optional, omitted when ambiguous
String note() default ""; // e.g. "first half"; optional free text
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({METHOD, CONSTRUCTOR, TYPE, FIELD})
@interface List { CRuby[] value(); }
}@CRuby("match_size") works as the short form when there is nothing else to say.
Open questions:
- Name:
@CRubymatches your wording.@MRImatches the comments we already have. I lean toward@CRuby, but the examples below work the same with@MRI, so I'm happy with either (the sketch and examples use@CRuby). - Fields: is
fileworth having? It makes the link auditable and lets docs link to the C source, but it has to be maintained.noteexists so qualifiers like "first half" aren't lost. - Retention: I used
RUNTIMEto match the otherorg.jruby.annoannotations and@Documentedso it shows up in Javadoc.CLASSwould work too if you don't want it reflectable.
Before and after
This PR (integer_at)
Before:
/**
* MRI: match_integer_at
*/
@JRubyMethod
public IRubyObject integer_at(ThreadContext context, IRubyObject idx) {After:
@CRuby(value = "match_integer_at", file = "re.c") // or @MRI(...) if we pick that name
@JRubyMethod
public IRubyObject integer_at(ThreadContext context, IRubyObject idx) {Several C functions (RubyArrayNative#rejectBang)
Before:
// MRI: ary_reject_bang and reject_bang_i
public IRubyObject rejectBang(ThreadContext context, Block block) {After (with @MRI instead, it would read @MRI(value = "ary_reject_bang", file = "array.c") and so on):
@CRuby(value = "ary_reject_bang", file = "array.c")
@CRuby(value = "reject_bang_i", file = "array.c")
public IRubyObject rejectBang(ThreadContext context, Block block) {The bare /** match_size */ Javadoc form converts the same way, and qualifiers like "first half" (// MRI: rb_str_count, first half) go into note. Comments inside a method body, like // MRI: nurat_canonicalize, negation part in RubyRational, can't take the annotation. They stay as comments, or the helper they call (canonicalizeShouldNegate) can take the annotation instead.
Migration survey
I wrote a dry-run script to see what a migration would involve. It runs under JRuby, uses the JDK's javac Tree API to attach each comment to the declaration below it, and checks every name against a C-identifier index of a CRuby checkout. It does not modify any files.
core/src/main/java (1,560 files)
| Category | Count | Meaning |
|---|---|---|
| clean | 1,068 | Comment above a declaration is just one C name, so it converts mechanically |
| multi | 9 | Several C names, so it needs the repeatable form |
| prose | 100 | C name plus a description. The annotation is added and the prose stays |
| qualified | 90 | C name plus free text ("first half", "sort of"). Needs human review |
| inner | 33 | Comment is inside a method body, so it can't take an annotation |
| orphan | 4 | Comment is not above any declaration |
| mention | 353 | Says "MRI" but names no C function ("MRI doesn't..."), so it is not a mapping |
- Mechanically convertible (clean + multi + prose): 1,177. Of those, 1,109 name only functions that exist in CRuby, and 1,065 have a single unambiguous source file.
- Comment forms: of the 1,304 actual mappings, 741 (57%) are bare
/** rb_foo */Javadoc and 563 useMRI:, so most can't be found by grepping forMRI:. - Coverage: 370 of 3,874
@JRubyMethodmethods (9.6%) have any C reference today. - Stale names: 79 references (78 distinct names) weren't found anywhere in the CRuby tree. For example
rb_fix_truncate(RubyFixnum),rb_ary_subseq_stepandrb_big_truncatehave no hits in a grep of CRuby. These are likely renames or removals that a drift checker would surface.
The inner and orphan cases are about 4% of the total and would probably stay as comments.
Possible next steps
- Agree on the annotation's name and fields.
- Turn the script into a real codemod with an
--applymode, run one package per PR. I'm happy to do this if the design looks right. - Possibly add a build-time manifest (Java method to C function) and a CI check that the C names still exist in the CRuby version we target.
|
@headius Before I mark this ready, I'd like your take on the "Open question" section in the description. CRuby's Note 7's own examples, run on CRuby 4.1.0dev and this PR:
And the five examples in
Should we keep matching CRuby, and do you think it's worth reporting upstream? |
Yeah it seems inconsistent so we can raise it upstream and at least get clarification. Pretty edge behavior in any case. |
|
Should I raise this or will you? In both cases where should we raise it? In the ruby bug tracker or a PR for the lang specs? |
|
Marking this ready for review. As discussed above, it follows CRuby 4.1 ( |
Implements
MatchData#integer_atfrom Ruby 4.1 (Feature #21932), part of #9746.Converts the matched substring to an Integer like
String#to_i, but returnsnilwhen nothing could be parsed, as CRuby does viarb_int_parse_cstr.RubyMatchData:integer_at, ported frommatch_integer_at(index/name resolution, base validation, negative indexes)ConvertBytes: newbyteListToInumOrNil, which parses in place and returnsnilonly when CRuby does. Existingto_i/Integer()behavior is unchanged.nameToBackrefError: adds the missing colon so the message matches CRuby ("undefined group name reference: x"). This also affectsvalues_at/byteoffsetwith a String-pattern match.Testing
spec/ruby/core/matchdata/integer_at_spec.rbpasses in default, interpreter, and forced JIT modes.MatchData,String#to_i,Integer(),to_r,to_c, StringScanner) pass.rake spec:ruby:fastcompared against unmodifiedruby-4.1: no new failures.Note: CRuby vs. the agreed behavior in Feature #21932
This PR matches CRuby 4.1 and the current ruby/spec, but both differ from the behavior agreed in the feature discussion. Note 7 (recording Matz's decision) says
integer_atshould behave like$N&.to_i, returningnilonly when the group did not match:"foo"0nil""(empty)0nilnilnilCRuby's implementation (72eb59d0b2) predates note 7 and uses
rb_int_parse_cstr, which returnsnilwhen nothing was parsed (so"T"isnilbut"-T"is0). The ruby/spec example "returns nil on non-integer matches" encodes that, and CRuby'sStringScanner#integer_atalso returnsnilfor an empty capture. CRuby's own rdoc still says it is equivalent to$N&.to_i.Decision: this PR follows CRuby, and we'll raise the discrepancy upstream for clarification (see discussion below). If CRuby moves to the
to_ibehavior, a small follow-up here will switchinteger_atto the existingto_iparsing and removeConvertBytes.byteListToInumOrNil.Follow-up (not in this PR)
integer_at(0, nil)raisesTypeError: no implicit conversion from nil to integer, while CRuby 4.1 saysno implicit conversion of nil into Integer. This is from the 4.1 TypeError wording change (Bug #21864) and affects every nil-to-Integer conversion, not just this method. The messages are produced at:api/Convert.java#L664,#L670,#L709RubyNumeric.java#L290,#L324RubyBasicObject.java#L3061no implicit conversion to float from string→of String into Float):api/Convert.java#L533,#L595The specs already expect the new wording via
raise_consistent_errorand are currently tagged. I'm happy to do this as a separate PR if you'd like it tracked in #9746.🤖 Generated with Claude Code