fix(cpp): a method a macro of its file defines, like protobuf's PROTOBUF_DEFINE_ACCESSOR(FieldDescriptor, number, int), is indexed where the macro is invoked (#2605)

protobuf declares its descriptor accessors in their classes and defines
them below with macros, like `PROTOBUF_DEFINE_ACCESSOR(FieldDescriptor,
number, int)`; its reflection pastes `Reflection::Get##TYPENAME` in
another. The parser never sees those definitions (the preParse blanks the
invocations) and a declaration mints no node, so the methods had none and
every call to them linked to nothing.

- After either extractor (the kernel or the wasm walker), a step over the
  original source expands the file's own function-like macros whose body
  defines `Class::method(...) ... {`, at each invocation at namespace
  scope, and mints a method node per definition: named and qualified like
  an out-of-line definition, contained by the file and the same-file
  class, with the expanded head as its signature. Constructors,
  destructors and variadic macros are left out.
- The parse worker leaves the raw kernel fast path only for a file that
  may define one (a cheap text test), so the step sees it.
- The C++ resolver reads such a method's return type and parameters from
  its signature, and never takes it for a deleted overload.

A/B vs main on the kernel path, every changed site triaged: leveldb and
redis identical; fmt +13 nodes and rocksdb +11 (gtest's TestEventRepeater
overrides); protobuf +257 nodes, 6,203 calls and 2 references added, 252
calls retargeted, 11 metadata-only, none removed. Kernel and wasm dumps
are byte-identical, protobuf's one pre-existing Swift line aside.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Colby Mchenry
2026-10-16 00:53:01 +00:00
committed by GitHub
co-authored by Claude Opus 5.5
parent 4727a068e4
commit f7951a13db
7 changed files with 977 additions and 9 deletions
+29 -8
View File
@@ -9438,17 +9438,31 @@ function cppSubscriptElement(type: CppWrittenType, at: UnresolvedRef, context: R
return element ? { ...element, text: element.text.replace(/&+\s*$/, '').trim() } : null;
}
/**
* The expanded definition a C++ method that a macro defines carries as its
* signature (cpp-macro-methods.ts), `inline const FileDescriptor*
* Descriptor::file() const` for protobuf's `PROTOBUF_DEFINE_ACCESSOR(Descriptor,
* file, const FileDescriptor*)`: its source is the invocation, which spells
* neither its return type nor its parameters. Null for any other node.
*/
function cppMacroMethodHead(fn: Node): string | null {
if (fn.kind !== 'method' || !fn.signature) return null;
const name = fn.name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
return new RegExp(`::\\s*${name}\\s*\\(`).test(fn.signature) ? fn.signature : null;
}
/**
* The return type of a C or C++ function, as its declaration writes it:
* `std::vector<const FieldDescriptor*>` for `std::vector<const
* FieldDescriptor*> FieldNumberOrder(…)`. (The index keeps only `vector`.)
* Null for none that can be read: a constructor, `void`, `auto` or
* `decltype(…)`.
* `decltype(…)`. A method a macro defines is read from its expansion.
*/
function cppWrittenReturnType(fn: Node, context: ResolutionContext): string | null {
const lines = context.getFileLines?.(fn.filePath) ?? context.readFile(fn.filePath)?.split(/\r?\n/);
if (!lines) return null;
const head = lines.slice(fn.startLine - 1, fn.startLine + 2).join(' ').slice(fn.startColumn);
const macro = cppMacroMethodHead(fn);
const lines = macro === null ? context.getFileLines?.(fn.filePath) ?? context.readFile(fn.filePath)?.split(/\r?\n/) : null;
if (macro === null && !lines) return null;
const head = macro ?? lines!.slice(fn.startLine - 1, fn.startLine + 2).join(' ').slice(fn.startColumn);
const name = fn.name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/^operator/, 'operator\\s*');
const declared = new RegExp(String.raw`^(.*?)(?:\b[A-Za-z_]\w*\s*(?:<[^<>;{}]*>)?\s*::\s*)*\b${name}\s*\(`).exec(head);
let written = declared?.[1];
@@ -11169,9 +11183,11 @@ const CPP_DELETED_FUNCTIONS = new WeakMap<ResolutionContext, Map<string, boolean
/**
* Whether the C++ function or method `n`, named `name`, is declared deleted:
* from its name to the declaration's `;` or body, it says `= delete`
* (protobuf's `void reset(T* PROTOBUF_NULLABLE) = delete;`).
* (protobuf's `void reset(T* PROTOBUF_NULLABLE) = delete;`). A method a macro
* defines has a body.
*/
function isCppDeletedFunction(n: Node, name: string, context: ResolutionContext): boolean {
if (cppMacroMethodHead(n) !== null) return false;
let memo = CPP_DELETED_FUNCTIONS.get(context);
if (!memo) CPP_DELETED_FUNCTIONS.set(context, (memo = new Map()));
const hit = memo.get(n.id);
@@ -16859,7 +16875,11 @@ function cppTemplateArgumentsEnd(text: string, at: number): number {
/** The balanced `( … )` after `name` from `line`/`column` of `file` (up to a dozen lines), or null. */
function cppParenListAfter(file: string, line: number, column: number, name: string, context: ResolutionContext): string | null {
const lines = context.getFileLines?.(file) ?? context.readFile(file)?.split(/\r?\n/) ?? [];
const text = lines.slice(line - 1, line + 11).join('\n');
return cppParenListIn(lines.slice(line - 1, line + 11).join('\n'), column, name);
}
/** The `( … )` that follows `name` in `text`, from `column` on, without its parentheses. */
function cppParenListIn(text: string, column: number, name: string): string | null {
const at = new RegExp(`\\b${name.replace(/[~]/g, '\\$&')}\\s*(?:<[^<>()]*>)?\\s*\\(`).exec(text.slice(column));
if (!at) return null;
const open = column + at.index + at[0].length - 1;
@@ -16913,10 +16933,11 @@ const CPP_OVERLOAD_PARAMETERS = new WeakMap<ResolutionContext, Map<string, strin
* absl::optional<Semantic> semantic)` below the class, whose declaration
* gave `semantic` its `= absl::nullopt`. Its own list when the class
* declares the method with the same parameter types and no default, or
* not at all.
* not at all. A method a macro defines writes its list in its expansion.
*/
function cppOverloadParameters(n: Node, name: string, context: ResolutionContext): string | null {
const own = cppParenListAfter(n.filePath, n.startLine, 0, name, context);
const macro = cppMacroMethodHead(n);
const own = macro !== null ? cppParenListIn(macro, 0, name) : cppParenListAfter(n.filePath, n.startLine, 0, name, context);
if (own === null || n.kind !== 'method') return own;
let memo = CPP_OVERLOAD_PARAMETERS.get(context);
if (!memo) CPP_OVERLOAD_PARAMETERS.set(context, (memo = new Map()));