Skip to content

fix(extractors): extract bare super(...) constructor calls as call edges#2065

Open
carlos-alm wants to merge 1 commit into
fix/issue-1927-native-engine-monorepo-workspace-packagefrom
fix/issue-1929-bare-super-constructor-calls-are-never
Open

fix(extractors): extract bare super(...) constructor calls as call edges#2065
carlos-alm wants to merge 1 commit into
fix/issue-1927-native-engine-monorepo-workspace-packagefrom
fix/issue-1929-bare-super-constructor-calls-are-never

Conversation

@carlos-alm

Copy link
Copy Markdown
Contributor

Summary

Bare super(...) constructor calls (distinct from super.method()) were never extracted as call edges by either engine — extractCallInfo (WASM/TS) and extract_call_info (native Rust) had no branch for the super keyword-callee shape, so a class whose constructor is only ever reached via super(...) (never directly new'd) was misclassified dead-unresolved.

  • WASM/TS (src/extractors/javascript.ts): extractCallInfo now returns { name: 'constructor', receiver: 'super' } for fnType === 'super', reusing the existing this/super hierarchy dispatch (resolveThisDispatch in cha.ts) that already resolves super.method() — no new resolution path needed. Added the matching (call_expression function: (super) @callsuper_fn) @callsuper_node query pattern (mirrored in src/domain/parser.ts and src/domain/wasm-worker-entry.ts) and a dispatchQueryMatch branch, so both the walk path and the query path (used by the WASM worker pool) extract it identically.
  • Native Rust (crates/codegraph-core/src/extractors/javascript.rs): mirrored extract_call_info's new "super" arm, and split handle_call_expr's combined this/super early-return guard so super(...) now routes through extract_call_info while still skipping callback-reference-call extraction on the arguments (unchanged behavior, still guards against the false cross-file edges that guard was added to prevent).

Both engines now produce the same edge: Subclass.constructor -> ParentClass.constructor.

Out-of-scope findings filed separately

While adding fixture coverage I found a genuine pre-existing bug in the shared resolveThisDispatch resolver: when a qualified name like Shape.constructor only exists in an unrelated file (no same-file candidate), it wrongly returns the cross-file match instead of continuing the walk or giving up — filed as #2062 with a concrete repro (this is why the TypeScript fixture below uses Container/Box rather than reusing the existing Shape name from hierarchy.ts). Also filed #2063 for an unrelated pre-existing fixture-documentation gap (missing new X() -> X.constructor expected-edges entries from #1892).

Test plan

  • tests/parsers/javascript.test.ts — new describe block covering plain class, class-expression, and callback-reference-arg suppression for bare super(...)
  • tests/engines/query-walk-parity.test.ts — new case asserting the walk and query extraction paths agree on bare super(...)
  • tests/integration/phase-8.5-cha-dispatch.test.ts (describe.each(['wasm', 'native'])) — new fixture (Vehicle.ts/Car.ts) and assertion that Car.constructor -> Vehicle.constructor is emitted on both engines
  • tests/benchmarks/resolution/fixtures/{javascript,typescript} — extended inheritance.js/super-dispatch.ts with bare-super(...) constructor chains and their expected edges; also credited a correctly-resolved Ellipse.constructor -> Circle.constructor edge in the pre-existing hierarchy.ts fixture that this fix now produces
  • crates/codegraph-core/src/extractors/javascript.rs — new bare_super_call_extracted_as_constructor_call Rust unit test; existing super_call_args_do_not_emit_callback_reference_calls still passes unchanged
  • npm run lint — clean
  • npm run build — clean
  • Native addon rebuilt locally (napi build --platform --release, codesigned) and verified directly against the fixture

Closes #1929

extractCallInfo (WASM/TS) and extract_call_info (native Rust) had no
branch for the super keyword-callee call shape, so a bare super(...)
constructor call was silently dropped -- unlike super.method(), which
already resolves via the this/super hierarchy dispatch. Any base class
reached only through subclass constructors (never directly new'd) was
misclassified dead-unresolved.

Model super(...) as a constructor call with receiver=super so it flows
through the existing resolveThisDispatch machinery unchanged. Adds the
matching (call_expression function: (super)) query pattern (mirrored in
parser.ts and wasm-worker-entry.ts) so the WASM worker's query path
extracts it identically to the walk path, and mirrors the native
extractor so both engines produce the same Subclass.constructor ->
ParentClass.constructor edge.
@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Codegraph Impact Analysis

16 functions changed15 callers affected across 1 files

  • dispatchQueryMatch in src/extractors/javascript.ts:326 (2 transitive callers)
  • extractCallInfo in src/extractors/javascript.ts:3548 (20 transitive callers)
  • Vehicle in tests/benchmarks/resolution/fixtures/javascript/inheritance.js:38 (0 transitive callers)
  • Vehicle.constructor in tests/benchmarks/resolution/fixtures/javascript/inheritance.js:39 (0 transitive callers)
  • Car in tests/benchmarks/resolution/fixtures/javascript/inheritance.js:44 (0 transitive callers)
  • Car.constructor in tests/benchmarks/resolution/fixtures/javascript/inheritance.js:45 (0 transitive callers)
  • SportsCar in tests/benchmarks/resolution/fixtures/javascript/inheritance.js:51 (0 transitive callers)
  • SportsCar.constructor in tests/benchmarks/resolution/fixtures/javascript/inheritance.js:52 (0 transitive callers)
  • Container in tests/benchmarks/resolution/fixtures/typescript/super-dispatch.ts:27 (0 transitive callers)
  • Container.constructor in tests/benchmarks/resolution/fixtures/typescript/super-dispatch.ts:28 (0 transitive callers)
  • Box in tests/benchmarks/resolution/fixtures/typescript/super-dispatch.ts:31 (0 transitive callers)
  • Box.constructor in tests/benchmarks/resolution/fixtures/typescript/super-dispatch.ts:32 (0 transitive callers)
  • Car in tests/fixtures/cha-dispatch/Car.ts:3 (0 transitive callers)
  • Car.constructor in tests/fixtures/cha-dispatch/Car.ts:6 (0 transitive callers)
  • Vehicle in tests/fixtures/cha-dispatch/Vehicle.ts:1 (0 transitive callers)
  • Vehicle.constructor in tests/fixtures/cha-dispatch/Vehicle.ts:4 (0 transitive callers)

@greptile-apps

greptile-apps Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a long-standing missing call-edge for bare super(...) constructor calls — both the WASM/TS and native Rust engines previously had no branch for the super-keyword callee shape, leaving classes reached only via super(...) misclassified as dead-unresolved. The fix is mirrored symmetrically across both engines and wired into the query path so the WASM worker pool behaves identically to the manual tree walk.

  • TS (WASM): extractCallInfo gains a 'super' branch returning { name: 'constructor', receiver: 'super' }; the new callsuper_fn/callsuper_node query pattern is added to both parser.ts and wasm-worker-entry.ts; dispatchQueryMatch routes it alongside extractCallbackReferenceCalls.
  • Rust (native): The combined this/super early-return guard is split so super(...) now pushes a constructor Call before returning, while still skipping callback-reference-call extraction on arguments.
  • Tests: New Rust unit test, TS parser tests (plain class, class-expression, arg suppression), a query-walk parity case, a cross-engine integration test with Vehicle/Car fixtures, and extended benchmark expected-edges for both JS and TS fixtures.

Confidence Score: 4/5

The core fix is sound and well-tested across both engines; the only concern is a latent TS/Rust behavioural difference for callback-typed super() arguments that the new tests do not exercise.

Both engines now correctly emit the constructor call edge; coverage is thorough across unit, parity, and integration tests. The one gap is that the TS/Rust difference in callback-reference-call extraction for super() args is untested for the function-typed-param case.

src/extractors/javascript.ts — the callsuper_node branch in dispatchQueryMatch and the walk-path fall-through in handleCallExpr both invoke extractCallbackReferenceCalls; whether that matches the documented intent deserves a second look.

Important Files Changed

Filename Overview
src/extractors/javascript.ts Adds super branch to extractCallInfo and a callsuper_node dispatch in the query path; walk path falls through to line 1714's extractCallbackReferenceCalls for super() args (no early return, unlike this)
crates/codegraph-core/src/extractors/javascript.rs Splits the combined this/super early-return guard; super now routes through extract_call_info to emit a constructor call, then returns early (skipping callback-reference-call extraction)
src/domain/parser.ts Adds (call_expression function: (super) @callsuper_fn) @callsuper_node pattern to COMMON_QUERY_PATTERNS, mirrored correctly in wasm-worker-entry.ts
src/domain/wasm-worker-entry.ts Mirrors the new callsuper_fn/callsuper_node pattern from parser.ts into the WASM worker's COMMON_QUERY_PATTERNS — change is mechanical and correct
tests/parsers/javascript.test.ts New describe block covers plain class, class-expression, and argument suppression cases; the suppression test uses non-function-typed params so it passes even though extractCallbackReferenceCalls is called
tests/engines/query-walk-parity.test.ts Adds regression guard for #1929; the parity framework normalizes and deep-compares all calls, definitions, and imports between walk and query paths
tests/integration/phase-8.5-cha-dispatch.test.ts Adds cross-engine integration test asserting Car.constructor → Vehicle.constructor is emitted by both wasm and native; uses Vehicle.ts/Car.ts fixtures
tests/benchmarks/resolution/fixtures/javascript/expected-edges.json Adds expected edges for Car.constructor → Vehicle.constructor and SportsCar.constructor → Car.constructor, matching the new inheritance.js fixture additions
tests/benchmarks/resolution/fixtures/typescript/expected-edges.json Adds Ellipse.constructor → Circle.constructor (same-file) and Box.constructor → Container.constructor (new super-dispatch.ts fixture) edges
tests/fixtures/cha-dispatch/Vehicle.ts New CHA-dispatch fixture: simple Vehicle class with typed constructor, used by the cross-file integration test
tests/fixtures/cha-dispatch/Car.ts New CHA-dispatch fixture: Car extends Vehicle with a bare super(make) call, exercising the cross-file constructor-edge extraction

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["call_expression\nfunction: (super)"] --> B{Engine?}
    B --> C[TS Walk Path]
    B --> D[TS Query Path]
    B --> E[Rust]
    C --> C3["extractCallbackReferenceCalls called\n⚠️ may emit spurious calls"]
    D --> D3["extractCallbackReferenceCalls called\n⚠️ may emit spurious calls"]
    E --> E3["return early ✅\nCallback ref calls skipped"]
    C3 --> R["constructor call edge emitted"]
    D3 --> R
    E3 --> R
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A["call_expression\nfunction: (super)"] --> B{Engine?}
    B --> C[TS Walk Path]
    B --> D[TS Query Path]
    B --> E[Rust]
    C --> C3["extractCallbackReferenceCalls called\n⚠️ may emit spurious calls"]
    D --> D3["extractCallbackReferenceCalls called\n⚠️ may emit spurious calls"]
    E --> E3["return early ✅\nCallback ref calls skipped"]
    C3 --> R["constructor call edge emitted"]
    D3 --> R
    E3 --> R
Loading

Fix All in Claude Code

Reviews (1): Last reviewed commit: "fix(extractors): extract bare super(...)..." | Re-trigger Greptile

Comment on lines +390 to 395
} else if (c.callsuper_node) {
// Bare `super(...)` constructor call — see extractCallInfo's 'super' branch.
const callInfo = extractCallInfo(c.callsuper_fn!, c.callsuper_node);
if (callInfo) calls.push(callInfo);
calls.push(...extractCallbackReferenceCalls(c.callsuper_node, callbackParamShapes));
} else if (c.assign_node) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 TS/Rust divergence: super() args still pass through extractCallbackReferenceCalls

The Rust path explicitly returns early after pushing the constructor call, skipping extract_callback_reference_calls for super() arguments (with the comment "skipped for the same reason as this(args) above"). In the TS walk path handleCallExpr, there is no early return for fn.type === 'super' — it falls through to line 1714, and now the query path (dispatchQueryMatch) also explicitly calls extractCallbackReferenceCalls here, making both TS paths consistent with each other. However, if a constructor param is function-typed — e.g., super(onInit) where onInit matches a callback-shaped parameter — the TS engine will emit a spurious callback-reference call while the Rust engine won't. The new test covers only non-callback-typed params, so this case goes undetected. Consider either adding an early-return guard in the walk path for fn.type === 'super' (mirroring Rust), or adding a parity fixture that includes a function-typed super() argument to document the intended cross-engine behaviour.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Claude Code

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