Skip to content

fix(extract): resolve explicit C++ operator-overload calls (#1247 case 1)#1260

Open
aznikline wants to merge 1 commit into
colbymchenry:mainfrom
aznikline:fix/cpp-operator-overload-call-edge
Open

fix(extract): resolve explicit C++ operator-overload calls (#1247 case 1)#1260
aznikline wants to merge 1 commit into
colbymchenry:mainfrom
aznikline:fix/cpp-operator-overload-call-edge

Conversation

@aznikline

Copy link
Copy Markdown

What

Closes the explicit-form half of #1247: a call to an operator-overload method written in its explicit a.operator+(b) form never recorded a call edge, so the operator+ method node had zero callers and its impact radius was invisible.

This is case (1) from the scoping discussion in the issue. The harder cases — infix a + b and subscript a[i] — parse as binary_expression / subscript_expression (not call_expression) and need receiver type inference from an expression, which @Dshuishui split off into #1258. This PR does not touch those.

Root cause

tree-sitter-cpp can't parse the .operator<sym> member access. For every explicit form — a.operator+(b), a.operator[](i), a.operator==(b) — the AST comes out as:

call_expression [a.operator+(b)]
  identifier [a]            ← namedChild(0) = receiver
  ERROR [.operator+]       ← wraps operator_name
    operator_name [operator+]
  argument_list [(b)]

The call_expression has no function field and an ERROR node wrapping operator_name. So the extractor's generic path took namedChild(0) (the receiver a) as the callee and emitted a useless calls:"a" ref — the operator+ method node was never linked.

The operator+ method node itself is indexed correctly on the definition side (named operator+), so this is purely a call-site extraction + resolution gap.

Fix (C/C++ only, both parts gated)

Extractor (tree-sitter.ts): before the generic namedChild(0) fallback, detect a call_expression whose subtree contains an operator_name (nested under the ERROR node), read it as the callee, and qualify it with a simple-identifier receiver → a.operator+. For a non-identifier receiver (call result, deref, …) with no static name to route through inference, emit the bare operator+ so matchByExactName still links it when the overload name is unique in scope.

Resolver (name-matcher.ts): the recv.method / Class::method method-segment gate was \w+, which excludes +/[/( — so even after the extractor emits a.operator+, the pattern dropped it before receiver-type inference could run. Widen the method segment to also accept operator + an overload-symbol tail, C/C++ only. The symbol set is the C++ overloadable operators; an unbounded [^.]+ would over-match, so it's enumerated.

The receiver-type inference chain (inferCppReceiverTyperesolveMethodOnType, which validates the method exists on the inferred type) was already in place — only the extraction gap and the pattern gate stopped it.

Scope

  • Covers: explicit a.operator+(b), a.operator[](i), a.operator==(b), and the rest of the overloadable-operator set, in both .method() and Class::method shapes.
  • Does not cover: infix a + b, subscript a[i] — those need expression-level receiver type inference (C++: operator overloads via infix (a + b) / subscript (a[i]) get no call edge (needs receiver type inference) #1258).
  • Safe by construction: the extractor branch only fires when an operator_name actually exists in the subtree, and only ever emits operator-prefixed names (no ordinary call site produces those); the resolver widening is language-gated to C/C++.

Test

Added to __tests__/resolution.test.ts:

  • resolves an explicit a.operator+(b) call to the operator+ method (#1247 case 1) — end-to-end: indexes a Vec with operator+ plus an add(Vec, Vec) that calls a.operator+(b), and asserts add is a caller of the operator+ method.

Before the fix this emitted calls:"a" and resolved to nothing; after, it links to operator+.

Full suite green: 2363 passed, 4 skipped, 0 failed (build + vitest run).

…nry#1247 case 1)

Closes the explicit-form half of colbymchenry#1247. tree-sitter-cpp can't parse the
`.operator<sym>` member access in `a.operator+(b)` / `a.operator[](i)` /
`a.operator==(b)` and lands it in an ERROR node wrapping an `operator_name`,
with the receiver as the call_expression's first named child and no
`function` field — so the extractor took the receiver as the callee
(`calls:"a"`) and the `operator+` method node never gained a caller.

Two-part recovery, both C/C++-only:
- extractor: read the `operator_name` out of the ERROR subtree and qualify it
  with a simple-identifier receiver (`a.operator+`) so receiver-type inference
  routes it to the right overload; bare `operator+` fallback for non-identifier
  receivers resolves via exact-name match when unique.
- resolver: widen the `recv.method` / `Class::method` method-segment gate to
  also accept `operator` + an overload-symbol tail; `+`/`[`/`(` aren't in
  \w so the old pattern dropped these refs before inference could run.

Infix `a + b` and subscript `a[i]` parse as binary_expression /
subscript_expression (not call_expression) and need receiver type inference
from an expression — tracked separately in colbymchenry#1258.

Test: `resolves an explicit a.operator+(b) call to the operator+ method`.
Full suite green (2363 tests).
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