Repository navigation
refactor(ruby): make the mixin capture's language seam explicit (#401 follow-up) - #412
Conversation
…ow-up to redhat-et#401) The maintainer's redhat-et#401 review point 1, which he left open for a follow-up PR. @reference.mixin is a language-neutral capture name and the consumer arm correctly spends no Lang:: branch to reach it — but emitCapturedMixinBase was entirely Ruby (Ruby fields, Ruby directive names, Lang::Ruby refs) while sitting in the shared ingest_relations.h. A second language that adopted the capture would have been fed to Ruby's guards and silently dropped. Move the reader to src/ingest_ruby.h as emitCapturedRubyMixinBase (the include order allows it: relations -> ruby -> sidecap), take the language from the call site, and EXPECTS( lang == Ruby ) inside it. Ruby is the capture's only emitter today; tags.scm and the consumer comment now say so and name what a second language must add — its own reader and a per-language dispatch, never Ruby's guards. PHP's in-body `use Trait;` is cited as the documented sibling. Also folds the redhat-et#401 review's non-blocking points 2-4, which are accuracy fixes to comments and a test that redhat-et#401 itself put on main: - point 3: six comments were stale or anachronistic. graph.h / ingest_cache.h's 131 history / model.h / resolve.h named the NEW reader as the parser-version-131 mechanism; history lines now keep captureRubyMixinBases and add "since 173: ...". rubyinheritcheck.sh:29 and legotransitivecheck.sh:21 named the retired function as current. rubyMixinTargets' header implied the capture reads the constant's byte through it; it does not call it. - point 2: one construction is not byte-identical — the Include record stops at kMaxImportContainerDepth (256 AST levels) while the query-borne inherit reference has no such bound, so past that depth the reference has no directive to join to. Pathological, disclosed; a sentence in the CHANGELOG and the helper comment makes "byte-identical" exact. - point 4: the arm's text-guard negative, Logger.new( MixA ), has a receiver, so the receiver half of rubyNamedDirective refused it too — a change that dropped only the NAME comparison would have passed. Add a receiverless class-body `raise MixA` (name guard alone, 0 refs) and extend/prepend lines, so one count covers all three directives: 4 -> 6. No behaviour change; byte-identical against the merged main (map / --deps / --skipped and the --callers/--callees/--impact/--uses read verbs over the Rails corpus, the mixin fixture and the Ruby fixtures). kParserVer unchanged — no extraction semantics move.
joyful-ii-V-I
left a comment
There was a problem hiding this comment.
Thanks @mpapis — this is exactly the follow-up we asked for, and it checks out.
What we verified
- No behaviour change. Our byte-identity harness (
test/langidentitycheck.sh, base1bcb6540vs this head) over the Ruby and Python fixture corpora plus a real Ruby repo and a real Python repo: 294 argv, 294 SAME, 0 DIFF, 0 ERROR; the shared-file== Lang::ratchet stays at 352 (the newEXPECTS( lang == Lang::Ruby )is iningest_ruby.h, a per-language file). 28 more--lego/--deps/--treeruns, including the 260-level fixture, differ only in the moved notice's source line number. - The seam is the shape we suggested. The reader lives in
src/ingest_ruby.h, the language comes from the call site, the contract is asserted, and the comments attags.scmand the consumer arm say that Ruby is the only emitter today and what a second language has to add.--callersshows one call site. - Point 4 is a real improvement. We made a mutant that drops only the name half of the directive guard. Your arm fails on it (
expected ruby_bases_unscoped="6" … got "7", from the newraise MixAline). The previous arm passes the same mutant. Your arm also passes on the base binary, so the change is test-only. - No
kParserVerbump is needed: theingest_cache.handtags.scmhunks only change comments (qschemetripcheckandqextractionkeycheckare green). The touched gates (rubyinheritcheck,legotransitivecheck,chacheck,rubyreachcheck,rubyclassrecvcheck) are green on base and head, and green under ASan too.--quality-deltagates 0. Its one row, the 6-parameter new symbol, is the expected cost of passinglang.
One wording fix before merge (our mistake, not yours)
The new sentence about the 260-level construction, in both the CHANGELOG and the emitCapturedRubyMixinBase comment, says the answer then carries ruby_bases_unscoped="1". The comment adds "a second degraded-notice line". We re-ran it on this head and on main with the original fixture (260 nested modules, include DeepMix innermost). Here is what we got:
--lego=DeepMix -> implementors="1" <impl n="N259" …/>
stderr -> one line: "import-container nesting past the depth bound — deeper imports not captured"
ruby_bases_unscoped -> absent on every verb (map, --tree, --deps, --lego, with or without --legend=full)
So the "gains one implementor" half is right. The attribute and the second notice come from our #401 note, which we measured on #401's pre-merge head, and they don't reproduce on the merged code. Something like this would match what happens:
…so past that depth (260 nested
modules with aninclude Minnermost)--legolists the includer as an implementor, while the depth-bound notice says deeper imports were not captured. No real code nests 256 levels deep.
Please change the code comment the same way. You can keep or drop the point-5 grep guard; we'd keep it.
…redhat-et#412 review) The maintainer's one wording fix before merge — and it was their redhat-et#401 note's error, not this PR's. The disclosure (CHANGELOG and emitCapturedRubyMixinBase's comment) claimed the deep construction gains a ruby_bases_unscoped="1" and a second degraded-notice line. They re-ran it on main and this head: ruby_bases_unscoped is absent on every verb, and the depth-bound stderr notice stays the single degraded line. I reproduced the same on this head with 260 nested modules + an innermost include: --lego lists the includer (implementors="1", <impl n="M259">), stderr carries exactly one "import-container nesting past the depth bound" notice, and ruby_bases_unscoped appears on none of map/--tree/--deps/--lego. The 'gains one implementor' half was right and stays. The comment now quotes the actual notice and states the attribute does not appear. Point-5 grep guard kept, per 'we'd keep it'.
mpapis
left a comment
There was a problem hiding this comment.
Corrected in e13a2afa — both sites, your wording.
The disclosure claimed the deep construction carries ruby_bases_unscoped="1" and a second degraded-notice line. It does not, on main or this head. I reproduced your fixture independently (260 nested modules, include DeepMix innermost) and got exactly what you measured:
--lego=DeepMix→implementors="1"with<impl n="M259"— the "gains one implementor" half was right, so it stays;- stderr → a single
[math degraded] ingest: import-container nesting past the depth bound — deeper imports not captured; ruby_bases_unscoped→ 0 occurrences across map,--tree,--deps,--legoand with--legend=full.
The CHANGELOG sentence now reads as you wrote it, and the helper comment names the notice it quotes and says the attribute appears on no verb. (Credit where due: I saw implementors="1" and no attribute when I first tried to reproduce this in #401, which is exactly why I cited your measurement instead of claiming my own — I should have gone and checked rather than leaving the attribute in the text.)
Point 4: the mutant test is the nicest confirmation of that arm I've had — that dropping only the name half now turns raise MixA into a 7th reference and fails the count, where the old arm sailed through.
Point 5: guard kept, per your preference.
Tripwires stay green — qschemetripcheck and qextractionkeycheck both PASS, confirming the ingest_cache.h/tags.scm hunks are comment-only and no kParserVer bump is needed. rubyinheritcheck (arm still reads 6), legotransitivecheck, chacheck, rubyreachcheck, rubyclassrecvcheck, deckcheck, docdriftcheck all PASS, and --quality-delta still gates 0 with the single expected 6-parameter new-symbol row.
Authored by @mpapis with AI assistance (DeepSeek V4.1 Flash / Qwen 3.8 Flash) under his supervision.
# Conflicts: # CHANGELOG.md # src/ingest_relations.h
joyful-ii-V-I
left a comment
There was a problem hiding this comment.
Thanks. e13a2af fixes the one open point (S1): the CHANGELOG entry and the helper comment now describe what the merged code does for the 260-level construction. Your independent reproduction matches ours. The seam is the shape #401 asked for, and the change remains output-neutral.
CI on acfb42b: every job passes except memguardcheck B12 on one macOS Release shard. That is a known intermittent failure, unrelated to this change.
This is ready. One step remains before it can merge: the integration train in #415 is about to land, and it touches the same part of CHANGELOG.md. Once #415 is on main, please merge origin/main into this branch again (the only conflict should be CHANGELOG.md). We'll merge it after that run is green.
|
#415 (the integration train) is now merged into main. When you have a moment, please merge origin/main into this branch. CHANGELOG.md should be the only conflict. We will merge once that CI run is green. |
# Conflicts: # CHANGELOG.md
|
Thanks for the re-merge. I re-checked the commits after the first review: |
|
Merged. Thanks @mpapis: this makes the Ruby mixin seam explicit and corrects the comments, and the turnaround on the review was quick. |
Follow-up to #401, taking the one point the maintainer left open there:
No behaviour change; byte-identical against the merged
main.Point 1 — make the language seam explicit
@reference.mixinis a language-neutral capture name, and the consumer arm spends noLang::branch to reach it — correct. ButemitCapturedMixinBasewas entirely Ruby (Ruby fields, Ruby directive names,Lang::Rubyrefs) while sitting in the sharedingest_relations.h. A second language that adopted the capture would have been fed to Ruby's guards and silently dropped.src/ingest_ruby.hasemitCapturedRubyMixinBase— the include order allows it (relations → ruby → sidecap).le.lang) and is asserted inside:EXPECTS( lang == Lang::Ruby ).queries/ruby/tags.scmand the consumer comment now state that Ruby is the capture's only emitter today, and name what a second language must add: its own reader plus a per-language dispatch at the arm — never Ruby's guards. PHP's in-bodyuse Trait;is cited as the documented sibling.Also folds #401's non-blocking points 2–4
These are accuracy fixes to comments and a test that #401 itself put on
main.Point 3 — six stale / anachronistic comments.
graph.h,ingest_cache.h's 131 history entry,model.handresolve.hnamed the new reader as the parser-version-131 mechanism. History lines now keepcaptureRubyMixinBasesand add "since 173: …".rubyinheritcheck.sh:29andlegotransitivecheck.sh:21named the retired function as current; both corrected.rubyMixinTargets' header implied the capture reads the constant's byte through it — it does not call it; now stated.Point 2 — the one non-byte-identical construction. The Include record still comes from the
captureIncludeswalk, capped atkMaxImportContainerDepth(256 AST levels), while the query-borne inherit reference has no such bound, so past that depth the reference has no directive to join to. Pathological and disclosed; a sentence in the CHANGELOG and the helper comment makes "byte-identical" exact. (I could not re-derive it against1bcb6540— that base already contains #401 — so I cite the maintainer's measurement rather than claim my own reproduction.)Point 4 — sharpen the arm. Its text-guard negative
Logger.new( MixA )has a receiver, so the receiver half ofrubyNamedDirectiverefused it too — a change that dropped only the name comparison would still have passed. Added a receiverless class-bodyraise MixA(refused by the name guard alone, 0 refs) andextend/prependlines, so one count covers all three directives: 4 → 6.Point 5 — left in, flagged
The grep guard that pins the capture's presence and the retired definition's absence stays (it is the only thing that catches "the old walk was restored"). #401 noted keeping-or-dropping it is the maintainers' call; I have not dropped it. Say the word and it goes.
Verification
main(1bcb6540): map,--deps --limit=100000,--skipped, and--callers/--callees/--impact/--usesover the Rails corpus, the mixin fixture and the Ruby fixtures — empty diff.rubyinheritcheck(arm now reads 6),legotransitivecheck,rubyreachcheck,chacheck,rubyclassrecvcheck,rubyattrscheck,rubyrakejbuildercheck— ALL PASS.--quality-delta=origin/main..HEADgates 0. One non-gatingnew-symbolrow:emitCapturedRubyMixinBaseat 6 params vs a bar of 5 — the direct cost of takinglangfrom the call site, which is what point 1 asked for; new-symbol rows never gate.kParserVerunchanged — no extraction semantics move.Authored by @mpapis with AI assistance (DeepSeek V4.1 Flash / Qwen 3.8 Flash) under his supervision.
Summary by CodeRabbit
extend,prepend, and receiverlessraisecases.--legonow reports an implementor and marks the result as unscoped.