Skip to content

Commit 070aa54

Browse files
joyful-ii-V-Iclaude
andcommitted
merge(lane/field-base-member 6b8f539): Rule 2b reads a member a base class declares onto train 5
lane/field-base-member at 6b8f539 ONLY (signed off). Its tip 1965da7 is left out: that recalleval arm 6b change is superseded by redhat-et#268's arm 6b on main. No extraction change, so no parser or cache version moves. One copy of what redhat-et#280 already put on main: Narrower::kFieldWalkCap and Narrower::memberFieldNames (byte-identical body) and graph.h's single memberFields line (its comment now names both readers, Rule 2c's member-field veto and Rule 2b's declared-member set). expandWalkLevel keeps the lane's bool form: it visits exactly the names main's void form did and also reports a walk the cap cut, which fieldEntryAt refuses on; methodOnTypeOrBases, inBaseClosure and memberFieldHides ignore the result as before. The final-segment question keeps main's identityClaim guard with the lane's FieldRecvTables argument. test/fieldnarrowcheck.sh: this lane and lane/rule2b-assignment-veto each added an arm (v). Both are kept; this lane's is renamed (w), with its own fixture variables (FIX8, wMissing, wPinned, $TMP/w7*), and resolve.h's and CHANGELOG's references follow. docs/LIMITS.md is regenerated (221 caps). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 parents 97598b0 + 6b8f539 commit 070aa54

5 files changed

Lines changed: 260 additions & 58 deletions

File tree

CHANGELOG.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,36 @@ had reached only because the parameter's name spells the class.
7373
`test/fieldnarrowcheck.sh` arm s3 are the gates, all red on the previous commit. `qschemetripcheck` is re-pinned for the
7474
parser version.
7575

76+
### Fixed — a member declared in a base class had no type in the derived class, so every call through it guessed
77+
78+
Rule 2b types a bare member receiver from the `Class#field` table, and it looked the field up on the caller's own class
79+
only. A member the class inherits was never found: in `class SampleProfileLoader final : public
80+
SampleProfileLoaderBaseImpl<Function>`, `Reader->getSummary()` names the base's `std::unique_ptr<SampleProfileReader>
81+
Reader;` and took the bare-name ladder, which gave a split over every `getSummary`, a locality pick, or no edge. When the
82+
class declares no member of that name, Rule 2b now walks its bases breadth-first, the way it already walks a type's bases
83+
for a method. The shallowest level with a base declaring the member decides, and it has to be exactly one base whose
84+
member type was captured. A member counts as declared when it is in the field side table, typed or not. So an own
85+
`std::optional<Widget> Reader;` whose type the capture skips still hides the base's `Reader`, and so does one at any
86+
base level before the hit. Two bases declaring the member at one level refuse, and so does a walk the 16-name cap cut
87+
short. A local of that name still vetoes the narrow. A class template's dependent base is walked like any other base,
88+
though C++ lookup never searches one for a bare name. This is a disclosed floor. It was measured first: 5 of the
89+
2,203 sites this change moves sit in such a template, and all 5 are right. Three reach the template's non-dependent
90+
base, and two reach a `using Base::G;`.
91+
92+
Measured with `--pin-census --no-cache`, C rows joined on (caller id, callee, line) against the previous commit:
93+
rocksdb @ 0e2801ac3 retargets 374 sites (236 gain an edge, 138 change target, 0 lose one; bound +236), and
94+
llvm-project @ 4d5358b1d retargets 1,829 (731 gained, 1,098 changed, 0 lost; bound +735). A seeded, blinded, stratified
95+
sample of 60 retargets graded against source came out 52 better, 3 same and 5 worse, and in all 60 the grader traced the
96+
receiver to a member of a base class. The 5 worse sites are limits Rule 2b already had, now reached through a base
97+
member: a type name shared by classes in two namespaces (`llvm::Module` and `sandboxir::Module` twice, `Sema` and
98+
`comments::Sema` once), and two overload picks that ignore the argument count. `SampleProfile.cpp:1962`, the
99+
`Reader->read()` that motivated the change, still gets no edge. The assignment `Reader = std::move(...)` five lines up
100+
records a local binding, and the local-shadow veto refuses the member; a fixture with the assignment deleted narrows.
101+
102+
`test/fieldnarrowcheck.sh` arm w is the gate. w1, w2, w4 (the narrow and its `prov="final-segment"`), w10 and the w12
103+
floor are red on the previous commit. The refusals were each shown red on a mutated build: counting only typed
104+
members as declared reds w6 and w7, taking the first declaring base reds w8, and probing a level the cap cut instead of refusing reds w10w.
105+
76106
### Fixed — a member held by `std::unique_ptr` or `std::shared_ptr` had no type, so every call through it guessed
77107

78108
The member-field capture that feeds Rule 2b read a qualified type only when a plain name sat directly under the `::`.

docs/LIMITS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -775,7 +775,7 @@ Discloses: **none**
775775

776776
| constant | value | class | note |
777777
| --- | --- | --- | --- |
778-
| `kFieldWalkCap` | `16` | INDEXING | total visited names — bounds depth and width together (methodOnTypeOrBases and memberFieldHides) |
778+
| `kFieldWalkCap` | `16` | INDEXING | total visited names — bounds depth and width together (methodOnTypeOrBases, memberFieldHides and fieldEntryAt) |
779779

780780
### `src/search.h`
781781

src/graph.h

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2226,7 +2226,7 @@ inline Graph buildGraph( const IngestResult& ing, const ScipOverlay* scip = null
22262226
const ScopedRecvDecls scopedRecvDecls = buildScopedRecvDecls( ing );
22272227
const HashMap<std::string, std::vector<std::string>> usingReexports = buildUsingReexports( ing );
22282228
const Narrower narrower( canonByName, varType, scopedRecvDecls, fileIncludes, symFileId, usingReexports );
2229-
const HashMap<std::string, char> memberFields = Narrower::memberFieldNames( ing ); // Rule 2c's member-field veto, "<Owner>#<field>" (C/C++)
2229+
const HashMap<std::string, char> memberFields = Narrower::memberFieldNames( ing ); // Rule 2c's member-field veto and Rule 2b's declared-member set, "<Owner>#<field>" (C/C++)
22302230
// Issue #74: the same Narrower over Java's containment-derived `Class::method` map, so a proven
22312231
// `Type::method` receiver resolves through the ONE type-side probe (methodOnTypeOrBases) instead of a
22322232
// second copy of its base walk. A separate instance rather than extra keys in canonByName: merging
@@ -2763,19 +2763,18 @@ inline Graph buildGraph( const IngestResult& ing, const ScipOverlay* scip = null
27632763
{
27642764
narrowed = narrowTo( narrower.rule2cClassNameRecv( r, ing.symbols[ r.fromSymbol ].scope, { classNames, fieldNarrow.localNameSet, memberFields }, chaUp ), r, cand );
27652765
}
2766-
// P2-D Rule 2b (receiver-FIELD type, W1-P1-12): a named-receiver call `f.m()` / `f->m()` whose receiver
2767-
// names a FIELD of the caller's enclosing class resolves to the method on the field's DECLARED type
2768-
// (walking direct bases when the type itself doesn't define it), BEFORE the bare-name spray — the
2769-
// bare-field member call is the idiomatic C++ shape Rule 2's local-binding table can never see. Fires
2770-
// only when NO local binding shadows the name, the class#field→type fact is unambiguous corpus-wide
2771-
// (tombstoned otherwise), and the type (or exactly one base) defines the method — every other shape
2772-
// degrades to the unchanged honest ladder. Skipped when already pinned canonically / by Rule 1 / Rule 2
2766+
// P2-D Rule 2b (receiver-FIELD type, W1-P1-12): a named-receiver call `f.m()` / `f->m()` whose receiver names a FIELD of the caller's
2767+
// enclosing class — or of the one base declaring it, when the class declares none — resolves to the method on the field's DECLARED
2768+
// type (walking direct bases when the type itself doesn't define it), BEFORE the bare-name spray: the bare-field member call is the
2769+
// idiomatic C++ shape Rule 2's local-binding table can never see. Fires only when NO local binding shadows the name, the
2770+
// class#field→type fact is unambiguous corpus-wide (tombstoned otherwise), and the type (or exactly one base) defines the method —
2771+
// every other shape degrades to the unchanged honest ladder. Skipped when already pinned canonically / by Rule 1 / Rule 2
27732772
// (Rule 2 first: a typed LOCAL beats a same-named field in real C++ lookup, and the veto inside 2b
27742773
// refuses any locally-declared name outright).
27752774
bool fieldTypeNarrowed = false; // Rule 2b decided the site: its prov="final-segment" question reads the field entry
27762775
if( !scipPinned && !canonical && !narrowed )
27772776
{
2778-
narrowed = narrowTo( narrower.rule2bFieldRecvType( r, ing.symbols[ r.fromSymbol ].scope, fieldNarrow.fieldTypeByClass, fieldNarrow.localNameSet, chaUp ), r, cand );
2777+
narrowed = narrowTo( narrower.rule2bFieldRecvType( r, ing.symbols[ r.fromSymbol ].scope, { fieldNarrow.fieldTypeByClass, memberFields, chaUp }, fieldNarrow.localNameSet ), r, cand );
27792778
fieldTypeNarrowed = narrowed;
27802779
}
27812780
const bool receiverTypeNarrowed = narrowed && !narrowedBeforeReceiverRules; // Rule 2, 2c or 2b chose the candidates (S6-C reads it)
@@ -3272,7 +3271,7 @@ inline Graph buildGraph( const IngestResult& ing, const ScipOverlay* scip = null
32723271
// a qualified written type decided this site by its last name — Rule 2 or 2b narrowed on it, or CHA-lite pruned by it — so every edge it
32733272
// commits is prov="final-segment" (resolve.h finalSegmentTypeAt, fieldFinalSegmentAt); never a class-identity CLAIM, whose one class was verified
32743273
const bool finalSegmentType = ( ( receiverTypeNarrowed || censusCone ) && !identityClaim && narrower.finalSegmentTypeAt( r ) )
3275-
|| ( fieldTypeNarrowed && narrower.fieldFinalSegmentAt( r, ing.symbols[ r.fromSymbol ].scope, fieldNarrow.fieldTypeByClass ) );
3274+
|| ( fieldTypeNarrowed && narrower.fieldFinalSegmentAt( r, ing.symbols[ r.fromSymbol ].scope, { fieldNarrow.fieldTypeByClass, memberFields, chaUp } ) );
32763275
for( NodeId to : tier )
32773276
{
32783277
if( to == r.fromSymbol )

0 commit comments

Comments
 (0)