Files
codegraph/__tests__/zz-scratch2.test.ts
T
6103f5e228 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>
2026-07-12 19:56:35 -05:00

42 lines
2.0 KiB
TypeScript

import { describe, it, beforeAll } from 'vitest';
import { extractFromSource } from '../src/extraction';
import { initGrammars, loadAllGrammars, getParser } from '../src/extraction/grammars';
beforeAll(async () => { await initGrammars(); await loadAllGrammars(); });
const CASES: Record<string,string> = {
dot_plus: 'V f(V a, V b) { return a.operator+(b); }',
arrow_plus: 'V f(V* a, V b) { return a->operator+(b); }',
dot_sub: 'V f(V a) { return a.operator[](3); }',
dot_call: 'V f(V a) { return a.operator()(3); }',
dot_eq: 'bool f(V a, V b) { return a.operator==(b); }',
dot_bool: 'bool f(V a) { return a.operator bool(); }',
qualified: 'V f(V a, V b) { return V::operator+(a, b); }',
free_op: 'V f(V a, V b) { return operator+(a, b); }',
this_op: 'struct V { V g(V b) { return this->operator+(b); } };',
member_op: 'struct V { V x; V g(V b) { return x.operator+(b); } };',
arrow_deref: 'V f(V a) { return a.operator->(); }',
dot_notop: 'bool f(V a) { return a.operator!(); }',
};
describe('dump', () => {
it('all', () => {
const p: any = getParser('cpp' as any);
for (const [k, code] of Object.entries(CASES)) {
const tree = p.parse(code);
const dump = (n: any, d = 0): string => {
let out = `${' '.repeat(d)}${n.type}${n.childCount === 0 ? ' ' + JSON.stringify(n.text) : ''}\n`;
for (let i = 0; i < n.childCount; i++) out += dump(n.child(i), d + 1);
return out;
};
const call = (function find(n: any): any {
if (n.type === 'call_expression') return n;
for (let i = 0; i < n.childCount; i++) { const r = find(n.child(i)); if (r) return r; }
return null;
})(tree.rootNode);
const refs = extractFromSource('t.cpp', code).unresolvedReferences.filter((r: any) => r.referenceKind === 'calls');
console.log(`\n=== ${k}: ${code}\n${call ? dump(call) : '(no call_expression)'}refs: ${JSON.stringify(refs.map((r: any) => r.referenceName))} hasError=${tree.rootNode.hasError}`);
}
});
});