Repository navigation
fix(resolve): a member field typed std::string narrowed to an in-repo class named string - #257
Conversation
…he caller's own class
`int Decoy::plainCaller( Target& other ) { return other.pick( 1 ); }` answered
--callees=plainCaller with ONE edge to Decoy::pick: precise, no amb=, wrong. Rule 2 narrowed
a receiver only through a typed local (LocalBindKind::Type); a parameter's written type
(ParamType) was captured but read only by collectFieldUseSites, so the call fell through to
the name ladder and the S6-C locality tie-break gave the caller's own class the scope credit
(pin census: mech=locality, 2 -> 1).
The fix: Rule 2, and CHA-lite through the same lookup (Narrower::recvVarTypeName), reads
ParamType records LEXICALLY. graph.h buildScopedRecvDecls lists, for every name with a
ParamType record, all its declarations in the definition (each VarDecl with its scope span)
and attaches each Type/ParamType record to the declaration whose VarDecl shares its record
position (Binding::startByte, new, in the padding after `kind`: sizeof unchanged, pinned).
The innermost declaration covering the call site decides; an untyped one, a tie, or no
covering declaration answers nothing. Names with no ParamType record keep the flat varType
table byte-identically.
Why not the obvious fold into varType: measured first. It fixed arms 7/8/11/15 and minted
three NEW precise wrong edges on the gate fixture (arms 12-14: a range-for variable's type
reaching a later `auto` loop of the same name, a same-named field read after the loop, a
parameter hidden by an untyped loop variable), and arm 10's tombstone kept the locality pin.
Qualifier guard: a written type is its final segment and class names carry no namespace, so
`const std::map<K, V>& ref; ref.lower_bound( q )` narrowed to an unrelated in-repo `map` —
three such edges on a private 129,759-call-site C++/ObjC++ corpus. A declaration's qualified
written type now rides its Type/ParamType RawBind (importedName, ingest_binds.h
qualifiedNameText) and the lexical lookup refuses it: removes all three, forgoes 11 correct
narrows through qualified in-repo types (those keep main's answer). kParserVer 96 -> 97 (+
quality.h mirror): the record format is unchanged, its content is not, and a warm 96 blob
would narrow a qualified type. An include-visibility guard was measured first and rejected:
path-precise includes miss include-root spellings and it refused ~150 correct narrows there.
Measured, --pin-census --no-cache, main f8e6087 binary vs this change:
private corpus: 587 of 129,759 sites change target — 373 splits narrow (300 Rule 2,
73 CHA cone), 147 declined calls gain an edge (bound 80,432 -> 80,583), 66 pins/splits
move to the parameter's type, 1 edge lost (a friend function scoped inside its class
self-loops); 956 more keep their target, now decided by Rule 2. Every category sampled
and read against the source. Wall time within noise (3 cold runs each, 1.66-2.51 s).
this repo src/: 53 splits -> one Rule-2 pin; nothing else moves target.
Gate: test/narrowcheck.sh arms 7-18 (generated fixture). RED on main: 10 rows (7, 8, 10 x2,
11, 12, 13, 14, 15). RED on the flat-table fold: 10 x2, 12, 13, 14. RED on the lexical lookup
without the qualifier guard: 17. GREEN on this commit: 23 PASS, and under ASan.
Controls that encoded "a parameter has no binding" moved to an untyped `auto` receiver:
narrowcheck (control/param.cpp -> control/untyped.cpp), chacheck, chaconecheck g5,
localitycheck (its call no longer reached the tie-break it tests), resolverhonestycheck F9
(check_signal had gone vacuous on one edge). fieldnarrowcheck (h) 7 -> 6: shadowParam(
Decoy& m_x ) now resolves to the parameter's type; (s1) also asserts Pool::acquire is not
linked (red on main).
Out of scope, stated: an untyped receiver still reaches the locality tie-break; typed locals
keep the flat table and its qualified-type collision. The reported arity split on a typed
local (pick(int) + pick(int,int) for a 1-arg call) is B2.2's documented argCount > params
rule and reproduces identically on the bare-name ladder.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CHANGELOG.md: both Unreleased entries kept (#240's Changed, this lane's Fixed). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ument constructor and a 30-complexity builder
quality-delta over the merge-base range reported gating=3 on this lane's own rows: ctorTypeOf's
byte-bounds tail cloned nodeTextOf (duplication + new-clone-of-reused-helper), and Narrower's
constructor went 4 -> 6 parameters against a bar of 5. Non-gating but real: the new
buildScopedRecvDecls at cognitive complexity 30, and bindsVisitNode 88 -> 94 with three new
parameters across pushRawBind/emitBind/emitDeclBinds.
- ctorTypeOf and qualifiedNameText read through nodeTextOf.
- ScopedRecvDecls is one struct holding the table and the bindings its indices point into, so the
Narrower takes one argument for both (5 parameters).
- buildScopedRecvDecls is three single-loop steps: addParamTypedNames, addRecvDeclScopes,
attachRecvDeclTypes.
- DeclType { name, qualified } travels as one argument. pushTypedBind is now the ONE record body
and pushRawBind a one-line wrapper with no qualified text, so pushRawBind and emitBind keep
their signatures, and declaredTypeOf takes the declaration branch's two ternaries out of
bindsVisitNode. (A first cut stamped importedName after the push, emitFnBind's shape — and
quality-delta reported it as a 70-token clone of emitFnBind.)
Zero behaviour change, measured: --pin-census and the default map on the private corpus are
byte-identical (cmp) to the previous commit's binary, as is the census over src/; narrowcheck,
chacheck, fieldnarrowcheck, resolverhonestycheck, fieldusescheck, shadowcheck and
cacheidentitycheck ALL PASS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
quality-delta --quality-delta=$(git merge-base origin/main HEAD)..HEAD reported gating=2 after the refactor: api-surface contract-change on Narrower::Narrower and its one call site in buildGraph (4 -> 5 parameters). That is the fix itself — the Narrower has to be handed the lexical declaration table — so it is acked through the binary (--quality-ack --ack-only=api-surface), which wrote exactly two +ack rows, one per symbol, and nothing else. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…d, a moved parser-version pin, two one-line verdicts Full suite at 17a9f5f: gates=632 pass=627 skip=2 fail=3, all three this lane's own. showcasecapturecheck (H): the 0.6.1 release capture publishes --at=src/graph.h:3406 as the FIRST body line of rankGraphTeleport, so it has zero slack — the ~100 lines this lane added to graph.h above it made 3406 resolve to buildGraph. The capture is the release PR's to regenerate (a lane recording it publishes local branch names), and the code had a better home anyway: the lexical table builder (buildScopedRecvDecls and its three steps) moves from graph.h to resolve.h, beside the ScopedRecvDecls type and Narrower::recvVarTypeName that reads it. graph.h's diff is now five lines in, five out, at the Narrower construction; rankGraphTeleport is back at 3404 on this tree. qschemetripcheck: its manifest hashes ingest_cache.h's kParserVer declaration, which moved 96 -> 97 with the quality.h mirror in the same diff (qextractionkeycheck green), so this is the gate's own re-pin case: UPDATE_GOLDEN=1, and the new pin equals the current= hash the suite printed. gateexitcheck (G2): narrowcheck's expectRows helper and arm 18 reported through a one-line 'A && ok || no'; both are if/else now. No behaviour change: --pin-census and the default map over the private corpus are byte-identical (cmp) to the fix commit's binary. qschemetripcheck, gateexitcheck, showcasecapturecheck, narrowcheck, chacheck, chaconecheck, localitycheck, fieldnarrowcheck, resolverhonestycheck, fieldusescheck, shadowcheck, fnptrcheck, cacheidentitycheck, qextractionkeycheck, manifestcheck ALL PASS. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…caller's own class
`auto other = make(); return other->pick( 1 );` inside Decoy answered --callees with one
precise edge to Decoy::pick, no amb=. The S6-C locality tie-break excluded field-of-this,
field-of-var and super() receivers but kept every NamedVar receiver on the premise "a typed
var already narrowed above" — and a receiver no rule typed never narrowed. The scope-segment
credit then handed the same-file tie to the caller's own class.
The fix: a NamedVar receiver whose type no receiver rule (2, 2c, 2b) established keeps the
tie-break's credit only up to the end of the caller's FILE segment (`path::`), so same-file
and same-directory locality still count and the class segment does not. That covers an
untyped local, a member Rule 2b cannot read (`std::unique_ptr<Target> rep_; rep_->pick()`)
and a typed variable whose type defines no such method. `this->` and bare calls keep the
whole tie-break.
Measured first, with --pin-census --no-cache, the lane tip's binary against this logic built
behind a temporary environment switch (removed; the final binary is measured in the next
commit): rocksdb 121 call sites change target (114 locality pins -> splits, 7 splits widen),
a private C++ corpus 71 (51 + 20), django 72, rails 145 (119 + 26), this repo's src/, vue-core
and go/net 0. Samples read against the source: 14 of 14 rocksdb pins wrong, 14 of 16 on the
private corpus — mostly delegation (`rep_->Name()` in Wrapper::Name, `tree_.upper_bound( key )`
in btree_container::upper_bound); one right pin is now a 3-way split that keeps it.
Skipping the tie-break entirely for these receivers moved NO target on any of the seven
corpora, but relabelled every {caller itself, X} tier from locality to unique (262 rocksdb,
53 private, 24 django, 101 rails) and dropped its lpin= disclosure — so the file credit stays.
Stated floor, not closed: tier 1 still admits same-file candidates alone, so a delegation
whose true target lives in another file lands on a same-file namesake before the tie-break.
Gate: test/localitycheck.sh arms 5-9 on a generated fixture. RED on the lane tip: 5
(untyped local), 6 (member of an unreadable type), 7 (typed, no such method) — each a
locality pin to the caller's class. Arm 8 (file credit kept, mech=locality) is RED on the
skip-the-tie-break variant; arm 9 is the Rule-2 control. GREEN on this commit.
Controls moved for the reason the fix exists: localitycheck's HIGH-1 probe was an untyped
local that no longer earns scope credit, so it could not tell a byte-prefix tie-break from a
segment-aware one; it is `this->go()` in a class template with a dependent base now, plus a
census liveness row (pre=2). clsrecvcheck (B)/(C)/(E) asserted the caller-own-class pin
STANDS for `item.validate( v )`; they assert the honest two-way split now (the contrast with
the firing route is unchanged) and (F) reads ambiguous=3 with no locality pin. canoncheck's
comment describing the fixture is updated. chacheck, chaconecheck, resolverhonestycheck and
fieldnarrowcheck pass unchanged: their untyped controls are scope-less free functions.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…esake, and an in-repo qualifier refused a parameter `std::map<int, int> table; table.find( k )` pinned an in-repo map::find: Rule 2's flat per-function table records a written type's final segment, and class names carry no namespace. The parameter-receiver lane refused every QUALIFIED parameter type for the same reason; typed locals, constructor-inferred locals and C++ assignments kept narrowing. Measured before choosing the rule, --pin-census --no-cache, lane tip vs switches (removed): refusing ANY qualifier on locals moved rocksdb 424 sites, a private C++ corpus 27, src/ 9. Joined to each receiver's qualified text: every rocksdb and src/ one was a non-std qualifier (ROCKSDB_NAMESPACE::Status, test::SleepingBackgroundTask, rw::notes::NoteIndex), every sampled one read correct, and it minted new wrong ladder pins where the refused narrow fell to a same-dir namesake (`value_slice.data()` -> MultiGetJNIKeys::data). The only wrong narrows it removed on the three corpora were six `std::map` locals on the private corpus. The same join on the lane's parameter guard: refusing only std:: instead moves rocksdb 94 sites (e.g. `WriteBatch::Handler* handler; handler->MarkCommit( xid )` off WriteBatchInternal::MarkCommit), private 12, src/ 5, all sampled correct but ONE: an `ankerl::unordered_dense::map<…>&` alias template that narrows to the in-repo map again. The fix: `std` is reserved to the implementation, so no in-repo class IS a std:: type. resolve.h namesStdType is the one predicate: the lexical (parameter) lookup refuses a std:: written type, and the flat table tombstones the variable on one (a tombstone, because the flat table cannot tell which declaration of the name is in scope). Every other qualifier narrows on its final segment again, parameters included. The C++ assignment `x = std::map<K, V>()` recorded no qualified text; it goes through pushTypedBind with declaredTypeOf's constructor DeclType now, so kParserVer 97 -> 98 (+ quality.h mirror): format unchanged, content changed. Stated floor, pinned by narrowcheck arm 24: a qualifier that is neither std nor the class's own namespace still narrows by name; closing it needs the namespace chain in Symbol::scope. Final binary (this commit, both fixes) vs the lane tip, --pin-census --no-cache: rocksdb 211 sites change target (117 locality, 94 qualified parameters; bound= 200,009 -> 200,036), private 88 (70 locality, 6 std::map locals stop narrowing, 12 qualified parameters), django 72, rails 145, src/ 5, vue-core 0, go/net 0. The assignment capture moved no site on these corpora. Wall time on rocksdb within noise (3 cold runs each, load avg 42: 1.69-2.19 s vs 1.73-2.80 s). Gate: test/narrowcheck.sh arms 17-24. RED on the lane tip: 19 (typed local), 20 (constructor local), 21 (assignment), 23 (in-repo qualified parameter), 24 (the floor: the lane refused it). RED without the assignment capture: 21. GREEN on this commit. Arm 17 moves from `ext::map` to `std::map` (the refused shape) and 22 is the in-repo qualified local control. quality-delta (working tree vs the previous commit): gating=0; a first cut's prefix-strip loop cloned lintdetail::ltrim and is gone (qualifiedNameText already strips a leading `::`). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…yped-locality The lane pushed a fix commit for three full-suite reds after this branch was cut; two of them bind this branch's change as well, and the resolution keeps both sides' intent. src/graph.h: the lane moved the lexical receiver-table builder to resolve.h so graph.h stays line-neutral above rankGraphTeleport (the 0.6.1 showcase capture seeds --at=src/graph.h:3406 with zero slack). Took theirs, re-applied this branch's std-guard comment to the moved block, and made this branch's own graph.h change line-neutral too (+9/-9 against the lane tip, rankGraphTeleport back at 3404): the tie-break rationale moves into resolve.h receiverLocalityCap (the cap itself, beside sharedLocality), the flat varType key uses the existing buildShadowKey (identical bytes), and the flat-table tombstone note joins namesStdType's comment. No behaviour change: --pin-census over src/, rocksdb and the private corpus is byte-identical (cmp) to f346283's binary. test/narrowcheck.sh: took this branch's arms 17-24 with expectNarrow's verdict as if/else (gateexitcheck G2, the lane's same fix to expectRows and arm 18). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… line this branch moved qschemetripcheck hashes ingest_cache.h's kParserVer declaration; f346283 moved it 97 -> 98 with the quality.h mirror in the same diff (qextractionkeycheck green). This is the gate's own re-pin case: UPDATE_GOLDEN=1, and the new pin (8ca11f67…) equals the current= hash the red run printed. kQSnapCacheScheme stays 12 and kCacheVersion 22 — record layouts and snapshot semantics are unchanged. The RE-PIN LOG entry says so, and names the 96 -> 97 move the parameter-receiver lane re-pinned without one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… class named string
`struct Record { std::string name_; int nameLength() { return name_.size(); } };`
pinned name_.size() to an in-repo `string::size` (census mech=receiver-rule, no
amb=). The field capture (ingest_relations.h captureFields) keeps a qualified
type's final segment, and all three readers of that record took `string` for
any in-repo class of the name: Rule 2b's field-type table, the HAS-A edges
(Record -> string), and the member index (--uses=string.len pinned name_.len and
this->name_.len). The previous commit refused a std:: written type for locals
and parameters; fields were the third place a written type is read.
The fix: a field's compose record carries the namespace its type was written in
as its qualifier (writtenTypeNamespace) — the (name, immediate qualifier) pair a
call ref already carries, so `std::string` is `string` in `std`. resolve.h
fieldTypeWrittenInStd is the one predicate; buildFieldNarrowTables records ""
for such a field and the HAS-A loop draws no edge. The member index reads the
same table, so it needs no change of its own. Every other qualifier narrows as
before (`store::Text body_; body_.size()` still reaches Text::size).
kParserVer 98 -> 99 + the quality.h mirror: format unchanged, content changed.
It records "" rather than skipping the field, because a skip is a measured
wrong answer: rocksdb's test_util/testutil.h `StringSource { std::string
contents_; }` and db/log_test.cc `StringSource { Slice& contents_; }` share the
key StringSource#contents_ (class names carry no namespace), the disagreement
tombstones it, and dropping the std record at capture pinned four testutil.h
contents_.size() calls to Slice::size. An empty type tombstones the entry
exactly as a second real type does.
Audit before touching a compose ref's fields: every reader of
Reference::qualifier excludes compose refs — the resolve loop and pin census
(isResolvableCallReference), model.h macro retag and shadow suppression
(isCompose filters), the inherit-only loops (graph.h isInherit), and the
Python/Rust/Elixir-only readers (quality.h, keepRustQualifiedCandidates,
elixir_resolve.h, expandElixirImplementationReferences). The other loops over
ing.references that do not filter isCompose never read qualifier (eval.h,
main.cpp byte stats, impurity and LCOM4 name scans, scip/filter/workspace
copies); the ref sort key is (fileId, startByte, name, role, isInherit). The
qualifier already rides the cache record, so no layout change.
Measured, --pin-census --no-cache and the default map, cfa5792's binary vs
this commit: rocksdb, a private C++ corpus and src/ (a frozen copy, one tree
for both binaries) are byte-identical, and rocksdb's --metrics is too. Counted
with --match, the corpora have 1,258 / 143 / 1,987 fields written `std::X`;
only rocksdb's 1,116 std::string/wstring fields share a name with an in-repo
type (gtest's `typedef ::std::string string`, no members, no HAS-A edge). So a
probe measured the reach: rocksdb plus one `namespace shim { struct string {
size empty data c_str clear append len } }`. The previous binary pins 384 call
sites to shim::string by receiver-rule; this commit leaves 7, all gtest
parameters written `const ::string&` (global namespace, not std, not fields).
357 sites move: 279 land on exactly the unmodified rocksdb census row, 78 are
c_str calls the probe's own second c_str makes ambiguous. --metrics: 455 class
rows lose a composed type from cbo= (446 by one, 9 by two).
Gate: test/fieldnarrowcheck.sh arm q (new fixtures, so (h)'s gauge is
untouched). RED on cfa5792 and on f346283: q1 (Rule 2b), q3 (HAS-A), q5 x2
(--uses, bare and this->). RED on a refuse-every-qualifier capture variant:
controls q2, q4, q6 and the four q7 tombstone rows. RED on the skip-at-capture
variant: exactly the four q7 rows — StringSource/StringSink collisions in both
record orders. (Variants measured on the f346283-based tree; the rebase onto
cfa5792 touched neither captureFields nor the two readers.) GREEN here, 44
PASS. qschemetripcheck re-pinned for the parser version with a dated note, as
its message directs (new pin == the current= it printed).
graph.h stays line-neutral above rankGraphTeleport (3404), so the 0.6.1
capture's published seed still resolves (showcasecapturecheck green). A first
cut hoisted the std-qualified call guard's lambda into a shared function;
--quality-delta read it as a 47-token clone of docparse.h's extension
lookups (gating=3), so the call guard keeps its lambda and the field readers
use a Reference-level predicate. quality-delta (working tree vs HEAD):
gating=0, six minor rows (churn; captureFields LOC 120 -> 121).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Advanced Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (26)
💤 Files with no reviewable changes (1)
Included review availability: Your plan provides up to 8 included reviews per hour; 4 remain after this review. 📝 WalkthroughWalkthroughThe change adds lexical receiver-type narrowing, preserves qualified type provenance, excludes ChangesReceiver narrowing and type provenance
Priority: ➖ Normal Estimated code review effort: 4 (Complex) | ~45 minutes Change: Bug fix Suggested reviewers: Merge Risk: ⚪ Minimal · up to The reviewed receiver and field-resolution changes have no remaining supported merge-blocking risk. 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Full details: Docstring CoverageExplanation Docstring coverage is 54.69% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 64 functions across 22 files. (3 skipped: 3 unsupported.)
✨ Finishing Touches 💡 2📝 Generate docstrings 💡
⚔️ Resolve merge conflicts 💡
🧪 Generate unit tests (beta)
Comment |
…ane's base); no conflicts, no pins move Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ype, arms renumbered after #254's 25, identity claims carry no prov="final-segment" origin/main bcd3b01 brings #248, #254 and #257. Two files conflicted; rerere was off and no recorded resolution was used. - src/resolve.h: #254 added a namespace-level RecvVarType {name, writtenQualified}, and this lane added a nested Narrower::RecvVarType {name, declared}. Git merged the second in beside the first, so it would have shadowed it. Both are now ONE namespace-level struct {name, writtenQualified, declared}; recvVarType fills all three, and finalSegmentTypeAt is #254's own. - test/narrowcheck.sh: #254's arm 25 (prov="final-segment") keeps its number. This lane's arms 25-34 become 26-35; expectProv/provOf take an optional map file ("${5:-}" under set -u). - src/graph.h (line-neutral; rankGraphTeleport stays at 3404): an identity CLAIM verified its one class before answering, so its edges are not a last-name guess. Two lines change in place: `&& !identityClaim` joins the finalSegmentType condition, and its comment is reworded. A class-qualified step-1 narrow keeps the disclosure. - Arm 36, `hs::DiskHealth& d; d.fine()` claiming the inherited HealthBase::fine through namespace evidence: (36a) the edge, (36b) no prov=, (36c) `Skip::Iterator& it; it.key()` keeps prov="final-segment". A build without the graph.h condition fails (36b) with prov=[final-segment]. - CHANGELOG entry and ack text: the arm numbers only. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…no side hashed the merged manifest Main's pin (37dac79a) hashed parserVer 96 over redhat-et#249's new deserializeSnapshot; redhat-et#248 (db0e8cce, 97), redhat-et#254 (8ca11f67, 98) and redhat-et#257 (01ea4c63, 99) each hashed their own number over the manifest before redhat-et#249. The merged tree declares 99 over redhat-et#249's function, which none of them hashed. kParserVer is assigned in merge order and equals what each lane already declared (redhat-et#248 97, redhat-et#254 98, redhat-et#257 99); kCacheVersion stays 22, kQSnapCacheScheme 12. New pin 95818fe5 == the current= the gate printed before UPDATE_GOLDEN; qschemetripcheck and qextractionkeycheck ALL PASS on this tree. RE-PIN LOG entry added. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…gration/train-2b on main bcd3b01 Conflicts and resolutions (rerere disabled): - src/ingest_cache.h / src/quality.h: kParserVer and its mirror -> 100 (the PR declared 97; assigned in merge order over train 2's 99). redhat-et#244's note renumbered and placed above train 2's 99/98/97 notes. - test/qschemetripcheck.sh: redhat-et#244's RE-PIN LOG entry kept, renumbered to 100, above train 2's entry; its "MERGE origin/main into PR redhat-et#244 ... stays 97" entry dropped (superseded by the train's single re-derivation). - test/qschemetrip.hash: main's value kept as a placeholder; re-derived ONCE on the final train tree. - test/fieldnarrowcheck.sh: train 2's section (q) (std-typed member fields, redhat-et#257) kept whole; redhat-et#244's TS/JS literal receiver section header and corpus variables replace the old "KNOWN GAP" header both sides started from. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ely resolved edge redhat-et#254 marks an edge prov="final-segment" when a parameter's or local's QUALIFIED written type chose it by its last name alone. Rule 2b makes the same guess for a member field: `store::Text body_; body_.size()` narrowed on `Text`, never checked `store`, and its edge read as uniquely resolved. The fix: redhat-et#257 gave a field's compose record the namespace its type was written in, so Rule 2b's "Class#field" table now holds resolve.h FlatRecvType ({type, writtenQualified}), the shape redhat-et#254's flat table already uses. Both tables fold through one core, recordFlatRecvTypeFact (the binding fold, recordFlatRecvType, now calls it). Narrower::fieldEntryAt is the one "Class#field" lookup, shared by rule2bFieldRecvType and the new fieldFinalSegmentAt, so the narrow and its disclosure cannot read different entries. buildGraph records that Rule 2b decided the site and ORs fieldFinalSegmentAt into finalSegmentType. Decisions are unchanged: the mark discloses, never demotes. An unqualified field narrow stays unmarked, a std:: field never narrows, and extraction is unchanged, so kParserVer stays 99. The legend text ("a qualified type matched by last name only") already covers fields, so no legend pin moves. graph.h stays line-neutral above rankGraphTeleport (3404). Measured, the integration/train-2 binary (92b4c91) against this change, --no-cache: --pin-census is byte-identical on rocksdb, a private C++ corpus and src/ (frozen copy). The default map is byte-identical on all three (rocksdb 31,711 B). Across the full map (--top-k=1000000), final-segment marks grow on rocksdb 355 -> 382 (+27, +567 B), private 98 -> 143 and src/ 80 -> 81. Every sampled new mark is a qualified field type: InternalStats::CompactionStatsFull compaction_stats_ -> SetMicros, toku::locktree_manager ltm_ -> set_max_lock_memory, strkern::Byteset256 heads -> contains. Gate: test/fieldnarrowcheck.sh arm r (49 PASS). RED on the train binary: (r1), the qualified field's mark, and (r5), the compact legend term. RED on a variant that marks every Rule 2b narrow: (r3) alone (unqualified field stays unmarked). Controls: (r2), the std field has no edge, and (r4), the census still names Rule 2b's single edge. narrowcheck, chacheck, chaconecheck, localitycheck, resolverhonestycheck, fieldusescheck and clsrecvcheck all pass. quality-delta (working tree vs HEAD): gating=0 after one ack through the binary (--ack-only=gating). It raises buildGraph's existing short-horizon-churn ack from 21 to 69: the resolve loop that decides a Rule 2b narrow lives in buildGraph, and moving the decision into Narrower would still edit buildGraph and grow the constructor redhat-et#254 was reviewed down from. buildGraph complexity 780 -> 782 is a minor row. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Stacked on #248 and #254; review only the last commit (50129f8). It lands in the coordinator's integration train after #248 and #254, which is where main gets merged in, kParserVer gets its final number and the qschemetrip hash is re-derived.
The defect
struct Record { std::string name_; int nameLength() { return name_.size(); } };pinnedname_.size()to an in-repostring::size: censusmech=receiver-rule, one precise edge, noamb=. The field capture keeps a qualified type's final segment. All three readers of that record tookstringfor any in-repo class with that name:Record → string).--uses=string.lenpinnedname_.lenandthis->name_.len).The previous commit on this stack refused a
std::written type for locals and parameters; fields are the third place a written type is read.The change
qualifier, the same (name, immediate qualifier) pair a call carries. kParserVer 98 → 99 as committed, plus the quality.h mirror (the final number is assigned in the train); the cache format is unchanged.resolve.h fieldTypeWrittenInStdis the one predicate.buildFieldNarrowTablesrecords""for such a field, and the HAS-A loop draws no edge. The member index reads the same table.store::Text body_; body_.size()still reachesText::size.""and not a skip: rocksdb's twoStringSourceclasses share the keyStringSource#contents_(std::stringin testutil.h,Slice&in log_test.cc). Skipping the std record un-tombstones that key, and four testutil.hcontents_.size()calls then pinSlice::size.Audit. Every reader of
Reference::qualifierexcludes compose refs:isResolvableCallReference);isComposefilters;The other
ing.referencesloops that don't filterisComposenever readqualifier, and the ref sort key excludes it.Measured
--pin-census --no-cacheand the default map, cfa5792 vs this commit:src/(frozen copy) are byte-identical, and so is rocksdb's--metrics.std::field type's name and defines the called member. Fields writtenstd::X: 1,258 on rocksdb, 1,987 onsrc/, 143 on the private corpus (counted with--match).shim::stringdefiningsize,empty,data,c_str,clear,appendandlen.shim::string: 384 before → 7 after. The 7 are gtestconst ::string¶meters (global namespace, not std).c_strcalls the probe's own secondc_strmakes ambiguous.--metrics: 455 class rows lose a composed type.Gates
test/fieldnarrowcheck.sharm q: 44 PASS.--around,--usesand--for.gates=632 pass=629 skip=2 fail=1. The two skips are the environmental ones (argvdiffcheck, editchecknotecheck). The one failure wasversioncheck, a true positive about the binary's stamp: it readbuilt_from=e20790c92+dirtybecause I built before amending the commit, and the amend changed onlytest/qschemetrip.hashandtest/qschemetripcheck.sh. After a relink,versioncheck,fieldnarrowcheckandg1freshcheckpass standalone, and the relinked binary's probe census and map are byte-identical to the binary the suite ran.Pins moved
test/qschemetrip.hash: the parser version moved, and the new pin equals the gate's printedcurrent=on cfa5792 + this commit. It gets re-derived on the train's merged tree.🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes
Documentation