Skip to content

fix(cpp): a call on an element read by subscript, statuses[i].IsNotFound(), calls the element's method - #2602

Merged
colbymchenry merged 1 commit into
mainfrom
claude/cpp-element-access
Oct 15, 2026
Merged

colbymchenry merged 1 commit into
mainfrom
claude/cpp-element-access

Conversation

@colbymchenry

Copy link
Copy Markdown
Owner

Summary

This is the follow-up left after #2601: calls on an element read by subscript.

The extractor can't spell a subscripted receiver, so it records statuses[i].IsNotFound(), buffers[key].Flush() and iters[i]->Valid() by their bare names. The name strategies then gave those calls to whichever class had a method of that name. Examples:

  • rocksdb's values[i].c_str() and column_family_names_[i].c_str() on std::strings went to googletest's FilePath::c_str at 0.9;
  • rocksdb's first_key_result[0].columns() on PinnableAttributeGroups went to ArenaWrappedDBIter::columns;
  • protobuf's message_generators_[i]->GenerateInterface(printer) went to ImmutableMessageGenerator rather than the MessageGenerator the vector holds.

Separately, a local an element initializes (auto& s = statuses[i], const auto& key = keys_[ki]) had no type.

What changed:

  • Subscript typing: C++ expression typing (cppSubscriptElement) now reads the element a subscript gives:

    • an array's element;
    • what a pointer points at;
    • a library map's mapped value;
    • a sequence's element (library containers, RepeatedField / RepeatedPtrField / autovector);
    • what a class's operator[] returns.

    It gives nothing for a std::string's char, or for a class whose operator[] the index doesn't hold.

  • Arrays: declarations now record their array dimensions, which the type text doesn't show: LRUCache shard_[kNumShards], std::vector<FileMetaData*> files_[kNumLevels], Status grid[2][3].

    • This covers locals, parameters and data members, and survives following an alias.
    • A subscript, *arr, arr-> and a range-for over an array read the type the array is declared with.
  • More receivers read from the source: fix(cpp): a call on the member of the pair a map's iterator points at, it->second.Flush(), calls the value's method #2601's bare-name receiver reader (cppUnspelledReceiver) now takes any number of subscripts after a name or (*name).

    • Examples: table[0][1].Flush(), (*m)[key].Flush(), cfs[i].first.Flush().
    • It reads the line with its literals blanked, so a key like buffers["a]b"] doesn't end the subscript.
  • Overload arguments: cppArgumentType drops its own one-level name[i] reader for the shared subscript typing. A map's values[key] passed to an overloaded method is now its value type rather than a std::pair, and arrays, nested subscripts and operator[] work there too.

Validation

  • New test __tests__/cpp-element-access-calls.test.ts (5 tests): all 5 fail on main and pass here.

  • Mutations: switching off each part fails its test:

    • subscript typing: 6 tests, including the existing overload test for keys[i];
    • subscript receivers: 4;
    • operator[]: 1;
    • a map's mapped value: 3;
    • array dimensions: 2;
    • locals' dimensions: 2;
    • data members' dimensions: 1;
    • dimensions through an alias: 1;
    • a subscript of an array: 2;
    • *arr: 1;
    • a range-for over an array: 1;
    • overload arguments by subscript: 2.
  • A/B (full index, main vs fix) on google/leveldb, fmtlib/fmt, redis (C control), facebook/rocksdb and protocolbuffers/protobuf.

    • An instrumented arm logged the decision behind every changed site. Every site was triaged by category, then line by line for each high-confidence removal and a sample of every target class.
    • rocksdb, 513 removed: library calls on elements that had been guessed onto project methods.
      • By method: size 191, push_back 60, c_str 45, reset 25, empty 21, …
      • The elements are std::string, std::vector, std::set, std::map, std::deque and std::unique_ptr.
      • For example, key.size() on const auto& key = keys_[ki] over a std::vector<std::string> reached Slice::size at 0.9.
      • Also 5 calls on the caller's own template parameter, like values[i].size() over a std::vector<TValue>.
    • rocksdb, 292 added and 843 retargeted:
      • Targets: Slice 256, WideColumn 124, PinnableAttributeGroup 102, IteratorBase 88, PinnableWideColumns 75, ColumnFamilyHandle 61, Status 53, DB 47, WalFile 31, TruncatedRangeDelIterator 26, autovector 25, …
      • autovector is reached for mems_list[k]->size() over an autovector of autovector pointers.
      • Some calls now take the InternalKey overload of InternalKeyComparator::Compare, for bottom_files[i]->largest arguments.
    • rocksdb, 807 metadata-only: same targets, confidence up to 0.9.
    • protobuf:
      • 58 removed. Of these, 23 are descriptor accessors like fields[i]->number() and extensions[0]->full_name(). FieldDescriptor declares them in its class and defines them with PROTOBUF_DEFINE_ACCESSOR macros, so no node exists for them. Every removed edge pointed at another class's namesake, like C#'s FieldGeneratorBase::number. The rest are library calls.
      • 5 added; 34 retargeted to the Java, C++ and Objective-C generator bases, ZeroCopyInputStream, DescriptorDatabase and EnumValueDescriptor; 66 metadata-only.
    • leveldb:
      • 12 removed: files_[level].size() over std::vector<FileMetaData*> files_[kNumLevels], which had been guessed as Slice::size.
      • 14 added: shard_[s].Insert(…) over LRUCache shard_[kNumShards], IteratorWrapper, files[i]->Read(…).
      • 1 retargeted, 11 metadata-only.
    • fmt:
      • 6 removed (library);
      • 4 added and 10 retargeted, to vendored gmock's MatcherBase, MatcherDescriberInterface and TestEventListener;
      • 2 metadata-only.
    • redis: byte-identical.
  • Arrays: the first version read a class without an indexed operator[] as an array of itself. The A/B showed where that misfires:

    • std::vector<FileMetaData*> files_[kNumLevels] read one level too deep;
    • PinnableSlice values[2] read as chars through Slice::operator[].

    So arrays now come from the declarator instead. Between the two versions, 35 sites in leveldb and rocksdb changed, each toward the declared type.

  • Full suite: 7,160 passed, 0 failed (82 skipped).

Remaining, out of scope:

  • m.at(k).Foo() and v.front().Foo() chains, which need library containers' element-returning members;
  • this->arr_[i].Foo(), which doesn't occur in the five repos.

🤖 Generated with Claude Code

…ound()`, calls the element's method

The extractor records a call on a subscripted receiver, like
`statuses[i].IsNotFound()`, `buffers[key].Flush()` or `iters[i]->Valid()`,
by its bare name, which went to whichever class had a method of that name,
and a local an element initializes (`auto& s = statuses[i]`) had no type.

- C++ expression typing reads a subscript: an array's element, what a
  pointer points at, a library map's mapped value, a sequence's element, or
  what a class's `operator[]` returns.
- Declarations record their array dimensions (locals, parameters and data
  members, also through an alias), so a subscript, `*arr`, `arr->` and a
  range-for over an array read the type the array is declared with.
- The bare-name receiver reader takes subscripts after a name or `(*name)`,
  reading the line with its literals blanked.
- Overload arguments use the same subscript typing: a map's `values[key]`
  is its value type, no longer a `std::pair`.

A/B vs main, every changed site triaged: leveldb 12 removed, 14 added,
1 retargeted, 11 metadata-only; fmt 6/4/10/2; redis identical; rocksdb
513/292/843/807; protobuf 58/5/34/66.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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