Files
codegraph/src/resolution
Colby MchenryandClaude Opus 5.5 5994edb34e fix(cpp): a member declared in an anonymous union, like protobuf's union { Impl_ _impl_; };, is found as a member of the class around it (#2731)
A class's data members are read from its own lines, skipping those of
the functions and classes nested in it. The index keeps an anonymous
union or struct as a nested `<anonymous>` node, so its lines were skipped
too, though C++ looks its members up as the enclosing class's
([class.union.anon]).

Every generated protobuf message keeps its fields in `union { Impl_
_impl_; };`, so a call through `_impl_` was guessed by the method's name:
- `this_._impl_.type_url_.Destroy()` reached `Arena::Destroy`;
- `_impl_._cached_size_.Get()` reached `FieldArenaRep::Get`;
- `_impl_._has_bits_.Clear()` reached the message's own `Clear`.

fmt's `value` keeps its argument in a union of `string`, `custom` and the
rest in the same way.

cppNestedSpans now leaves out an anonymous union or struct with no name
and no declarator (isCppAnonymousMembers):
- a bare `union {` or `struct {` head;
- `};` after the closing brace.
A union with a declarator, `union { … } u;`, keeps its members to itself,
and so does a partial specialization, which the index also keeps as
`<anonymous>` but whose head names its template.

A member-chain diagnostic on main 81fd0078 found the anonymous union
behind most of protobuf's untyped chains. Kit A/B against 81fd0078:

| repo | removed | added | retargeted | same target, now typed |
|---|---|---|---|---|
| protobuf | 2 | 54 | 349 | 231 |
| fmt | 0 | 0 | 1 | 1 |
| rocksdb | 0 | 0 | 2 | 0 |
| leveldb, redis | identical | | | |

- Retargets go from bare-name guesses to the member's class:
  ArenaStringPtr (Set, Get, IsDefault, Destroy), CachedSize::Get,
  HasBits::Clear, RepeatedPtrField, SourceContext and MessageLite.
- The 2 removals are `ptr.string_value->clear()` on a `std::string*`, which
  had reached `Map::clear`.
- fmt's and rocksdb's retargets pick the overload their argument fits now
  that a union member has a type: `map(value_.uint128_value)` takes
  `native_uint128`, and `Add(…, props.oldest_ancestor_time)` the `uint64_t`
  overload.
- About 45 changed sites read by hand were all right.

Mutations: dropping the nested-span exclusion, the declarator check or
the head check each fail the new tests. Dropping the name check survives,
because the head check covers named types; it stays as a cheap early exit.
A matching change to the caller's-declaration check left the kit A/B
byte-identical, so it was not kept.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 06:55:34 +00:00
..