feat(resolver): resolve property calls on object destructuring rest parameters (JS)#1350
feat(resolver): resolve property calls on object destructuring rest parameters (JS)#1350carlos-alm wants to merge 5 commits into
Conversation
…arameters (JS)
When a function parameter uses object destructuring with a rest element
(`...rest`) and the rest object's property is then called, codegraph now
resolves the callee.
Resolution chain (Phase 8.3f):
1. JS extractor seeds typeMap['obj.X'] = { type: 'X' } for shorthand
properties in object literals (`var obj = { e4 }`).
2. New extractObjectRestParamBindingsWalk records objectRestParamBinding
{ callee, argIndex, restName } from `function f({ a, ...rest })`.
3. build-edges.ts cross-references with paramBindings from f(obj) calls
to seed typeMap[restName] = { type: argName, confidence: 0.65 }.
4. resolveByMethodOrGlobal: typeMap[restName] → obj; typeMap[obj.method]
→ method → resolved edge.
Adds:
- ObjectRestParamBinding type + ExtractorOutput.objectRestParamBindings field
- objectRestParamBindings serialised through WASM worker boundary
- extractObjectRestParamBindingsWalk in javascript.ts
- Object literal shorthand property seeding in handleVarDeclaratorTypeMap
- Phase 8.3f typeMap seeding in buildCallEdgesJS
- 6 unit tests in tests/parsers/javascript.test.ts
- Integration test (WASM engine) in issue-1336-object-rest-param-resolution.test.ts
- Jelly micro-test fixture tests/benchmarks/resolution/fixtures/jelly-micro/rest/
Files issue #1348 (missing WASM worker serialisation for paramBindings/
returnTypeMap/callAssignments) and #1349 (native engine parity) as follow-ups.
Closes #1336
|
Claude finished @carlos-alm's task —— View job PR Review: feat(resolver): resolve property calls on object destructuring rest parameters (JS)
Root Cause AnalysisProblem Being Solved: When a JS function uses Why This Approach: The PR adds a 4-step chain:
This approach correctly reuses the existing resolution machinery rather than inventing a new mechanism. Risk Assessment: Low-to-medium. The feature is WASM-only, purely additive, and explicitly scoped. The known gaps ( Backlog Compliance
Critical Concerns🔴 C1:
|
Greptile SummaryThis PR implements Phase 8.3f of the codegraph resolution pipeline, enabling call-edge resolution for property calls on object-destructuring rest parameters in JavaScript (e.g.
Confidence Score: 5/5Safe to merge; all changes are additive and guarded by optional-chaining short-circuits that prevent any impact on files without the new fields. The extraction logic is correct and well-tested; the object-literal seeding in handleVarDeclaratorTypeMap is properly placed after all existing early-return paths; the WASM serialization is symmetric; and the two WASM-parity gaps (#1348, #1349) are explicitly acknowledged and tracked as follow-ups. No existing behavior is changed. No files require special attention. The integration test in issue-1336-object-rest-param-resolution.test.ts is expected to remain blocked until #1348 lands. Important Files Changed
Sequence DiagramsequenceDiagram
participant Extractor as javascript.ts (Extractor)
participant TypeMap as typeMap
participant BuildEdges as build-edges.ts
participant Resolver as resolveByMethodOrGlobal
Extractor->>TypeMap: "seedProtoProperties(obj, objectNode)<br/>typeMap[obj.e4] = { type: e4, conf: 0.85 }"
Extractor->>BuildEdges: "objectRestParamBindings<br/>[ { callee:f3, argIndex:0, restName:eerest } ]"
Extractor->>BuildEdges: "paramBindings<br/>[ { callee:f3, argIndex:0, argName:obj } ]"
Note over BuildEdges: Phase 8.3f cross-reference
BuildEdges->>TypeMap: "typeMap.set(eerest, { type:obj, conf:0.65 })"
Note over BuildEdges: buildFileCallEdges processes eerest.e4()
BuildEdges->>Resolver: resolve receiver eerest, prop e4
Resolver->>TypeMap: "typeMap.get(eerest) → { type: obj }"
Resolver->>TypeMap: "typeMap.get(obj.e4) → { type: e4 }"
Resolver-->>BuildEdges: edge: f3 → e4
Reviews (2): Last reviewed commit: "Merge branch 'main' into feat/resolver-d..." | Re-trigger Greptile |
| if (symbols.objectRestParamBindings?.length && symbols.paramBindings?.length) { | ||
| for (const orpb of symbols.objectRestParamBindings) { | ||
| for (const pb of symbols.paramBindings) { | ||
| if (pb.callee === orpb.callee && pb.argIndex === orpb.argIndex) { | ||
| if (!typeMap.has(orpb.restName)) { | ||
| typeMap.set(orpb.restName, { type: pb.argName, confidence: 0.65 }); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Phase 8.3f silently skipped in WASM engine path
symbols.paramBindings is never populated when the WASM engine is used because paramBindings is not included in SerializedExtractorOutput (or the serializeExtractorOutput function in wasm-worker-entry.ts). The condition symbols.paramBindings?.length is therefore always falsy in WASM mode, so the typeMap seeding on lines 847-851 never runs and eerest.e4() is never resolved.
The integration test in issue-1336-object-rest-param-resolution.test.ts forces engine: 'wasm' and expects the f3 → e4 edge to exist, but this seeding step is silently bypassed. PR #1348 (follow-up for serialising paramBindings) must be resolved before this code path is functional with the WASM engine, or paramBindings must be added to the WASM protocol in this PR alongside objectRestParamBindings.
There was a problem hiding this comment.
Fixed. Added paramBindings to SerializedExtractorOutput in wasm-worker-protocol.ts, serialized it in wasm-worker-entry.ts serializeExtractorOutput, and deserialized it in wasm-worker-pool.ts deserializeResult. The Phase 8.3f condition in build-edges.ts:844 now fires correctly in the WASM worker pool path.
Also updated the integration test to use a two-file fixture (callee in helpers.js, caller in main.js) so the test actually forces Phase 8.3f to be the resolution mechanism — the previous single-file fixture accidentally resolved f3→e4 via the same-file name-lookup fallback, not Phase 8.3f.
| if (pb.callee === orpb.callee && pb.argIndex === orpb.argIndex) { | ||
| if (!typeMap.has(orpb.restName)) { | ||
| typeMap.set(orpb.restName, { type: pb.argName, confidence: 0.65 }); | ||
| } | ||
| } |
There was a problem hiding this comment.
First matching call-site wins; subsequent call sites with different arguments are silently dropped
The if (!typeMap.has(orpb.restName)) guard prevents seeding the rest name more than once. When the same function is called from multiple sites with different arguments — e.g. f3(objA) followed by f3(objB) — only the first matching paramBinding (whichever appears first in the array) seeds typeMap['restName']. Property calls exercised through the second call site's argument type are never resolved. This is an expected constraint of the single-typeMap model, but it means callers relying on objB's properties will silently produce no edges.
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!
There was a problem hiding this comment.
Acknowledged. The !typeMap.has(orpb.restName) guard implements first-call-site-wins semantics, which is an accepted limitation of the single-typeMap-per-file model. A comment has been added to build-edges.ts documenting this constraint and explaining that a multi-typeMap or constraint-based solver would be needed to cover all call sites. No code change is needed for this PR.
| fnName = nameN.text; | ||
| paramsNode = | ||
| valueN.childForFieldName('parameters') ?? findChild(valueN, 'formal_parameters'); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| if (fnName && paramsNode) { | ||
| let paramIdx = 0; | ||
| for (let i = 0; i < paramsNode.childCount; i++) { | ||
| const child = paramsNode.child(i); | ||
| if (!child) continue; | ||
| const ct = child.type; |
There was a problem hiding this comment.
Class methods and object-literal methods not covered
extractObjectRestParamBindingsWalk handles function_declaration, generator_function_declaration, and variable_declarator (arrow/function expressions), but not method_definition nodes inside class bodies or method shorthand inside object literals. A pattern like class Foo { bar({ a, ...rest }) { rest.m(); } } would never emit a binding for bar. This is a known scope limitation of this phase, but worth noting as a gap for follow-up if class-method patterns become relevant.
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!
There was a problem hiding this comment.
Acknowledged as a known scope limitation of Phase 8.3f. Tracked in #1357 for follow-up. Out of scope for this PR which focuses on the core function/arrow-function case.
Codegraph Impact Analysis18 functions changed → 18 callers affected across 6 files
|
|
Superseded by #1355 which includes both WASM and native engine implementations in one self-contained PR. |
… Phase 8.3f (#1350) paramBindings was missing from SerializedExtractorOutput, wasm-worker-entry.ts serializeExtractorOutput, and wasm-worker-pool.ts deserializeResult. Without it, build-edges.ts Phase 8.3f typeMap seeding (line 844) was never triggered in the WASM engine path — symbols.paramBindings was always undefined after deserialization. Closes the gap identified in the PR #1350 review.
…irst-call-site-wins (#1350) - Extract magic 0.65 into REST_BINDING_CONFIDENCE constant in javascript.ts, per CLAUDE.md convention for behavioral constants - Add comment in build-edges.ts documenting the first-call-site-wins limitation of the single-typeMap model
…tion mechanism (#1350) The previous single-file fixture resolved f3→e4 via the same-file name-lookup fallback, not through Phase 8.3f typeMap seeding. The updated fixture defines e4 in helpers.js (imported) so the same-file fallback cannot trigger: the edge must be produced by Phase 8.3f cross-referencing objectRestParamBindings with paramBindings from the WASM worker output.
|
Addressed all feedback from the Claude and Greptile reviews: C1 / Greptile P1 — The integration test was also updated to use a two-file fixture (callee defined in C4 / CLAUDE.md — Greptile P2 — First-call-site-wins: Greptile P2 — Class methods not covered: |
Summary
Resolves #1336. When a function parameter uses object destructuring with a rest element (
...rest) and the rest object's property is called, codegraph now resolves the callee.Pattern:
Resolution chain (Phase 8.3f)
handleVarDeclaratorTypeMapnow callsseedProtoPropertiesfor object literals:var obj = { e4 }seedstypeMap['obj.e4'] = { type: 'e4' }.extractObjectRestParamBindingsWalkrecords{ callee: 'f3', argIndex: 0, restName: 'eerest' }fromfunction f3({ a, ...eerest }).buildCallEdgesJScross-referencesobjectRestParamBindingswithparamBindingsto seedtypeMap['eerest'] = { type: 'obj' }whenf3(obj)is called.resolveByMethodOrGlobal:typeMap['eerest'] → obj;typeMap['obj.e4'] → 'e4';lookup.byName('e4')→ resolved edge.Changes
src/types.ts— addObjectRestParamBindinginterface andExtractorOutput.objectRestParamBindingsoptional fieldsrc/extractors/javascript.ts— addextractObjectRestParamBindingsWalk; extendhandleVarDeclaratorTypeMapto seed object literal shorthand/pair properties into typeMapsrc/domain/graph/builder/stages/build-edges.ts— Phase 8.3f typeMap seeding from rest param bindings × param bindingssrc/domain/wasm-worker-protocol.ts/wasm-worker-entry.ts/wasm-worker-pool.ts— serialize/deserializeobjectRestParamBindingsthrough WASM worker boundarytests/parsers/javascript.test.ts— 6 unit tests for extraction and seedingtests/integration/issue-1336-object-rest-param-resolution.test.ts— integration test (WASM engine)tests/benchmarks/resolution/fixtures/jelly-micro/rest/— fixture + expected-edges for the Jelly micro-test corpusFollow-up issues
paramBindings,returnTypeMap,callAssignments