fix(cpp): resolve explicit operator calls (a.operator+(b)) to the operator method (#1268)

* fix(cpp): resolve explicit operator calls (a.operator+(b)) to the operator method (#1247)

tree-sitter-cpp can't parse an operator_name in field position: the
call_expression carries `function: <receiver>` plus an ERROR child
wrapping the operator_name instead of a field_expression callee, so the
extractor emitted a calls ref named just the receiver (`a`) and the edge
never resolved — while the operator method itself indexed fine.

Two-part fix, scoped to the explicit call form (infix `a + b` / `a[i]`
need receiver type inference and are tracked in #1258):

- extraction: recover the operator_name from the ERROR child and emit
  `<receiver>.operator+` (`->` receivers normalized, `this->` emits the
  bare name), like any other member call
- resolution: matchMethodCall's dot pattern now admits an operator
  method part (cpp-gated; symbol chars failed the \w match), so
  receiver-type inference + resolveMethodOnType validate the target —
  a same-named operator on an unrelated class can't capture the edge

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(cpp): harden explicit operator-call recovery against real-world shapes (#1247)

Validated on nlohmann/json (dozens of explicit operator[] / operator* /
operator< call sites). Two refinements the synthetic fixtures missed:

- normalize spaced call-site operator names (`it.operator * ()`,
  `other.operator < (*this)`) to the compact form definitions index as
- drop the ref for a complex receiver (`obj()->operator+`, member chains
  ending in a call) instead of emitting a bare operator name: exact-name
  fallback GUESSED among unrelated same-named operators (linked a
  std::map operator[] call to an in-repo operator[]) — silent miss,
  never a wrong edge

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Colby Mchenry
2026-07-12 19:56:35 -05:00
committed by GitHub
co-authored by Claude Fable 5
parent ecc8b307ac
commit 6103f5e228
8 changed files with 300 additions and 1 deletions
+12 -1
View File
@@ -1459,7 +1459,18 @@ export function matchMethodCall(
// (with its existing single-candidate / receiver-overlap guards). Without this
// a multi-dot extension-method call (C# DI `builder.Services.AddCoreServices()`,
// `Guard.Against.X()`) matched no pattern and never resolved.
const dotMatch = ref.referenceName.match(/^([\w.]+)\.(\w+:?(?:\w+:)*)$/);
// C++ explicit operator call `a.operator+(b)` reaches the resolver as
// `a.operator+` (#1247) — the operator's symbol chars (`+`, `==`, `[]`, `()`)
// fail the \w method part of the plain pattern, so admit them explicitly.
// Names like `operatorTable` stay on the plain pattern (tried first); the
// operator form requires at least one non-word char after `operator`, and
// every downstream strategy compares the method part by exact string
// equality, so a stray match can't invent an edge.
const dotMatch =
ref.referenceName.match(/^([\w.]+)\.(\w+:?(?:\w+:)*)$/) ??
(ref.language === 'cpp'
? ref.referenceName.match(/^([\w.]+)\.(operator[^\w\s.]+)$/)
: null);
const colonMatch = ref.referenceName.match(/^(\w+)::(\w+)$/);
// Lua/Luau method calls use a single colon (`lg:log`); R uses `$` (`lg$log`).
// Recognize these receiver/method separators so local-variable receiver-type