Skip to content

fix(cpp): -> on an absl::StatusOr reaches the class it holds; a using-declared member of a dependent base is found - #2539

Merged
colbymchenry merged 3 commits into
mainfrom
claude/cpp-statusor-holders
Oct 13, 2026
Merged

colbymchenry merged 3 commits into
mainfrom
claude/cpp-statusor-holders

Conversation

@colbymchenry

Copy link
Copy Markdown
Owner

Stacked on #2537 (base branch claude/cpp-test-fixture-calls). Merge #2537 first; GitHub then retargets this PR to main.

Summary

Two gaps in C++ member lookup:

  • -> on an abseil holder. In protocolbuffers/protobuf, absl::StatusOr<FeatureResolver> resolver = FeatureResolver::Create(…); resolver->MergeFeatures(…) was guessed by the method's name. The declared type's last name, StatusOr, is no class of the project's, and only std::unique_ptr / std::shared_ptr / std::optional were followed to what they hold.
  • A using-declared member of a dependent base. In facebook/rocksdb's typed_cache.h, using BasicTypedCacheHelperFns<TValue>::DownCastValue; makes a bare DownCastValue(v) the base's. But a bare name skipped every base that depends on the class's template parameters, which is C++'s rule only without a using-declaration.

What changed (name-matcher.ts)

  • cppHeldType (was cppStdHeldType) also reads absl::StatusOr, absl::optional and absl::NoDestructor as holders. matchCppHeldTypeCall takes a holder whose qualified name no project type has (isCppLibraryHolder). That is the same rule std's holders had through isCppLibraryType, which also wanted a lowercase name.
  • cppUsingDeclares reads the using Base<…>::name; declarations a class body writes. A bare call (cppScopeMethod) and a bare data member (cppInheritedDataMember) are looked up in a dependent base when the class brings that name in from that base.

Validation

Site-keyed edge diffs (diff-sites.mjs) of #2537's head b0ed652c against this branch. Both arms were built from the same tree, kernel included.

repo removed added retargeted metadata-only
google/leveldb 0 0 0 0
fmtlib/fmt 0 0 0 0
protocolbuffers/protobuf 5 7 5 9
facebook/rocksdb 0 0 0 9
redis/redis (C, control) 0 0 0 0

Every site was read:

  • Added (7): loc->file_name() / loc->line() on an absl::optional<SourceLocation>. taken->AsView(), type_url->AsView() and type_url->ToString() on an absl::StatusOr<MaybeOwnedString>. tmp_container->field() / temp_container->field() on an absl::NoDestructor<FieldWithArena<…>>.
  • Retargeted (5), wrong → right:
    • msg_ptr->get() on an absl::StatusOr<PythonConstMessagePointer> (was ScopedPythonPtr::get at 0.65);
    • three bare MutableRepeatedField(…) calls in RepeatedFieldPrimitiveAccessor, which writes using RepeatedFieldWrapper<T>::MutableRepeatedField; (was RepeatedPtrFieldWrapper's at 0.7, now RepeatedFieldWrapper's at 0.9).
  • Removed (5), all wrong guesses:
    • has_value() on an absl::optional (directly, or held by an absl::StatusOr): it is the library's, not Option::has_value;
    • m->string_value() / m2->struct_value() on an absl::StatusOr<Proto> whose Proto is a template parameter (a generated test message): not google::protobuf::Value's.
  • Metadata-only:
    • protobuf: 9 resolver->MergeFeatures(…) / status_or_mutator->get() calls keep their target, now at 0.9 by lookup instead of a 0.7 / 0.65 guess;
    • rocksdb: 9 using-declared bare calls in typed_cache.h (DownCastValue, GetBasicHelper, …) keep their target, now placed by class lookup instead of name match.

Tests

__tests__/cpp-absl-holder-receiver.test.ts has 4 cases checking targets and confidence:

  • -> on absl::StatusOr, absl::optional and absl::NoDestructor, beside a namesake method;
  • a project's own absl::StatusOr is no holder;
  • a using-declared member of a dependent base, for a bare call (beside a namespace function of that name) and for a data member (with the base in a header of its own, so the file's own scan can't stand in);
  • with two dependent bases, the one the using-declaration names.

The red phase on #2537's code fails all but the project's-own-StatusOr guard.

Mutation check. 7 guards were each disabled alone (mutate-sor.mjs), and each one fails a test:

  • each of the three new holders;
  • the project-type check;
  • the using-declaration for bare calls, for data members, and the base it names.

I dropped one check I'd first written, the </> balance check in isCppLibraryHolder. Its mutation survived, because a type with unbalanced brackets is already turned away earlier (normalizeCppTypeName → cppTypeBeganAbove).

Full suite: 6,978 passed, 82 skipped. 6 tests failed in 3 files (git index currency, the MCP daemon PID-reuse takeover, the daemon rebuild), all in areas this change does not touch. The run overlapped a mutation run, and those 3 files pass when rerun alone (17 passed).

🤖 Generated with Claude Code

colbymchenry and others added 2 commits October 12, 2026 22:49
…xture's method

The index holds a gtest `TEST_F(Fixture, Name)` / `TEST_P` body as a
function named `TEST_F`; gtest makes it a method of a class derived from
`Fixture`. #2533 gave such a body the fixture's data members
(cppTestFixture), not its methods. rocksdb's tests call `DBTestBase`'s
`Put(…)`, `Flush()`, `Key(i)` and `NewIterator(…)` bare, and those calls
were guessed by name, often reaching a namesake in another class at 0.4
(`ColumnFamilyTestBase::Put`); `auto iter = NewIterator(…)` gave `iter`
no type, so `iter->SeekToFirst()` reached the nested
`MemTableRep::Iterator`.

- cppImplicitThisMethod is #2479's lookup, taken out of
  matchCppImplicitThisCall: the caller's class, its bases, the classes
  around it, with cppTestFixture as the owner of a gtest body.
  matchCppImplicitThisCall keeps the local-shadowing check for the link.
- cppExpressionType types a bare call by the same lookup, as on `this`,
  instead of the class's first declaration. Of two classes sharing a name
  in different test files it reads the caller's own, and in a template a
  dependent base a using-declaration names still counts, as before
  (protobuf's `using RepeatedFieldWrapper<T>::MutableRepeatedField;`).
  `this` in a gtest body points to the fixture.
- A `->` call on a value of a type from outside the project no longer
  takes the type's last name for the class it is in
  (isCppArrowThroughOutsideType); the name strategies decide.
  matchCppDeclaredTypeCall steps aside for `->` on a value before #2532's
  outside-type check, so typing JsonTest's `auto m = ToProto<Any>(…)` (an
  `absl::StatusOr<Any>`) had sent `m->value()` to protobuf's own
  `hpb::StatusOr::value` at 0.9.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ng-declared member of a dependent base is found

Two gaps in C++ member lookup. In protobuf, `absl::StatusOr<FeatureResolver>
resolver = …; resolver->MergeFeatures(…)` was guessed by the method's name:
the declared type's last name, `StatusOr`, is no class of the project's,
and only std's smart pointers and optional were followed to what they
hold. In rocksdb's typed_cache.h, `using
BasicTypedCacheHelperFns<TValue>::DownCastValue;` makes a bare
`DownCastValue(v)` the base's, but a bare name skipped every base that
depends on the class's template parameters.

- cppHeldType (was cppStdHeldType) also reads `absl::StatusOr`,
  `absl::optional` and `absl::NoDestructor` as holders, and
  matchCppHeldTypeCall takes one whose name no project type has
  (isCppLibraryHolder) — std's holders as before.
- cppUsingDeclares reads the `using Base::name;` declarations a class body
  writes. cppScopeMethod and cppInheritedDataMember look a bare name up
  in a dependent base when the class brings it in from that base.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Base automatically changed from claude/cpp-test-fixture-calls to main October 13, 2026 17:42
# Conflicts:
#	CHANGELOG.md
#	src/resolution/name-matcher.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant