-
Notifications
You must be signed in to change notification settings - Fork 15
feat(resolver): Phase 8.3f — object destructuring rest parameter resolution (WASM + native) #1355
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
878bc9a
0835479
3c164f2
31d7fe7
f3a42fe
054c953
12ba1cd
f02b4f8
898e9dc
4d033d6
e984226
5a7a09a
fa0f4d8
92321a2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,95 @@ | ||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||
| * Integration test for #1336 + #1349: resolve property calls on object | ||||||||||||||||||||||||
| * destructuring rest parameters — both WASM and native engines. | ||||||||||||||||||||||||
| * | ||||||||||||||||||||||||
| * When a function parameter uses object destructuring with a rest element (`...rest`), | ||||||||||||||||||||||||
| * and the rest object's property is then called, codegraph should resolve the callee. | ||||||||||||||||||||||||
| * | ||||||||||||||||||||||||
| * Pattern: | ||||||||||||||||||||||||
| * function f3({ e1: eee1, ...eerest }) { eerest.e4(); } | ||||||||||||||||||||||||
| * f3(obj); | ||||||||||||||||||||||||
| * | ||||||||||||||||||||||||
| * Resolution chain (Phase 8.3f): | ||||||||||||||||||||||||
| * 1. Extractor seeds typeMap['obj.e4'] = { type: 'e4' } from `var obj = { e4 }`. | ||||||||||||||||||||||||
| * 2. Extractor records objectRestParamBinding { callee: 'f3', argIndex: 0, restName: 'eerest' }. | ||||||||||||||||||||||||
| * 3. Extractor records paramBinding { callee: 'f3', argIndex: 0, argName: 'obj' } from f3(obj). | ||||||||||||||||||||||||
| * 4. Phase 8.3f seeds typeMap['eerest'] = { type: 'obj' }. | ||||||||||||||||||||||||
| * 5. resolveByMethodOrGlobal: typeMap['eerest'] → obj; typeMap['obj.e4'] → e4 → resolved. | ||||||||||||||||||||||||
| * | ||||||||||||||||||||||||
| * WASM: resolved via Phase 8.3f typeMap chain in buildCallEdgesJS. | ||||||||||||||||||||||||
| * Native: resolved via same-file name lookup (step 2 in Rust resolve_call_targets); | ||||||||||||||||||||||||
| * the Phase 8.3f post-pass (buildObjectRestParamPostPass) provides the typeMap-chain | ||||||||||||||||||||||||
| * fallback for cross-file cases not directly imported. | ||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| import fs from 'node:fs'; | ||||||||||||||||||||||||
| import os from 'node:os'; | ||||||||||||||||||||||||
| import path from 'node:path'; | ||||||||||||||||||||||||
| import Database from 'better-sqlite3'; | ||||||||||||||||||||||||
| import { afterAll, beforeAll, describe, expect, it } from 'vitest'; | ||||||||||||||||||||||||
| import { buildGraph } from '../../src/domain/graph/builder.js'; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| const FIXTURE_CODE = ` | ||||||||||||||||||||||||
| function e1() { console.log('31'); } | ||||||||||||||||||||||||
| function e4() { console.log('34'); } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| var obj = { e1, e4 }; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| function f3({ e1: eee1, ...eerest }) { | ||||||||||||||||||||||||
| eee1(); // call through named destructuring alias | ||||||||||||||||||||||||
| eerest.e4(); // call through rest binding — expected edge: f3 → e4 | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| f3(obj); | ||||||||||||||||||||||||
| `; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| let tmpWasm: string; | ||||||||||||||||||||||||
| let tmpNative: string; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| beforeAll(async () => { | ||||||||||||||||||||||||
| tmpWasm = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-1336-wasm-')); | ||||||||||||||||||||||||
| fs.writeFileSync(path.join(tmpWasm, 'rest.js'), FIXTURE_CODE); | ||||||||||||||||||||||||
| await buildGraph(tmpWasm, { engine: 'wasm', incremental: false, skipRegistry: true }); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| tmpNative = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-1336-native-')); | ||||||||||||||||||||||||
| fs.writeFileSync(path.join(tmpNative, 'rest.js'), FIXTURE_CODE); | ||||||||||||||||||||||||
| await buildGraph(tmpNative, { incremental: false, skipRegistry: true }); | ||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| afterAll(() => { | ||||||||||||||||||||||||
| fs.rmSync(tmpWasm, { recursive: true, force: true }); | ||||||||||||||||||||||||
| fs.rmSync(tmpNative, { recursive: true, force: true }); | ||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| function readCallEdges(dbPath: string) { | ||||||||||||||||||||||||
| const db = new Database(dbPath, { readonly: true }); | ||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||
| return db | ||||||||||||||||||||||||
| .prepare( | ||||||||||||||||||||||||
| `SELECT n1.name AS src, n2.name AS tgt, e.kind, e.dynamic | ||||||||||||||||||||||||
| FROM edges e | ||||||||||||||||||||||||
| JOIN nodes n1 ON e.source_id = n1.id | ||||||||||||||||||||||||
| JOIN nodes n2 ON e.target_id = n2.id | ||||||||||||||||||||||||
| WHERE e.kind = 'calls' | ||||||||||||||||||||||||
| ORDER BY n1.name, n2.name`, | ||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||
| .all() as Array<{ src: string; tgt: string; kind: string; dynamic: number }>; | ||||||||||||||||||||||||
| } finally { | ||||||||||||||||||||||||
| db.close(); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| describe('Issue #1336 + #1349: object destructuring rest parameter call resolution', () => { | ||||||||||||||||||||||||
| it('WASM: emits a calls edge from f3 to e4 via eerest.e4() rest-receiver resolution', () => { | ||||||||||||||||||||||||
| const edges = readCallEdges(path.join(tmpWasm, '.codegraph', 'graph.db')); | ||||||||||||||||||||||||
| const edge = edges.find((e) => e.src === 'f3' && e.tgt === 'e4'); | ||||||||||||||||||||||||
| expect(edge).toBeDefined(); | ||||||||||||||||||||||||
| expect(edge!.dynamic).toBe(0); | ||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| it('Native: emits a calls edge from f3 to e4 via eerest.e4() rest-receiver resolution', () => { | ||||||||||||||||||||||||
| const edges = readCallEdges(path.join(tmpNative, '.codegraph', 'graph.db')); | ||||||||||||||||||||||||
| const edge = edges.find((e) => e.src === 'f3' && e.tgt === 'e4'); | ||||||||||||||||||||||||
| expect(edge).toBeDefined(); | ||||||||||||||||||||||||
| expect(edge!.dynamic).toBe(0); | ||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||
|
Comment on lines
+89
to
+94
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
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!
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed — added |
||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The typeMap key is
orpb.restName(e.g.,"rest","opts","props") with no function-scope qualifier. If a file contains two functions that each destructure-rest with the same name — a common pattern — the!typeMap.has(orpb.restName)guard silently discards the second seeding. Anyrest.method()call in the second function will then be resolved using the first function's argument type, producing incorrect call edges. The same issue is present in the WASM path's Phase 8.3f seeding block inbuildCallEdgesJS.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This scoping collision was filed as issue #1358 and deferred — fixing it requires keying typeMap entries by function scope rather than just restName, which coordinates changes in seeding + resolution lookup. The current PR addresses the primary pattern documented in the fixture test. The gap is tracked and not lost.