You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
TSNeo4jBackend.get_call_graph() omits synthesized anonymous-callback nodes — backend parity gap #176
TSNeo4jBackend.get_call_graph() does not add the synthesized anonymous-callback nodes, so the two TypeScript backends are no longer behaviorally interchangeable for the call graph. The in-memory TSCodeanalyzer.get_call_graph() adds a node per synthesized callable with synthesized=True; the Neo4j backend does not, so graph.nodes[sig].get("synthesized") returns True on one backend and None on the other for the same signature.
Found while reviewing the tsc_only / synthesized-callable work on fix/issue-174 (#174), which introduced TSSynthesizedCallable and wired it through both backends.
Where
In-memory backend adds the nodes — cldk/analysis/typescript/codeanalyzer/codeanalyzer.py:323-325:
Neo4j backend omits the matching loop — cldk/analysis/typescript/neo4j/neo4j_backend.py:411-434. get_call_graph() adds only internal Callable nodes (via _all_callable_props()) and external phantom nodes (via get_external_symbols()), then runs the CALLS edge query. The get_synthesized_callables() helper already exists on this class (neo4j_backend.py:267-274) and is already used by get_application() (line 242) — it's just not used in get_call_graph().
Why it matters
The TSAnalysisBackend ABC and the TSNeo4jBackend docstring both promise the backends are drop-in interchangeable. When a CALLS edge targets a synthesized signature, nx.DiGraph.add_edge auto-creates the endpoint as a bare node with no attributes, so:
Any consumer iterating graph.nodes(data=True) and filtering on attrs.get("synthesized") silently gets different answers per backend.
(Note: this assumes :AnonymousCallable nodes are also labeled :Symbol so the MATCH (s:Symbol)-[:CALLS]->(t:Symbol) edge query reaches them. If they are not :Symbol, the edges to synthesized callables are dropped entirely on Neo4j and the signatures show up as orphan nodes on codeanalyzer — worth confirming against the emitted graph schema either way.)
Fix
Add the same loop to TSNeo4jBackend.get_call_graph() before the edge query, mirroring the in-memory backend:
Superseded 2026-07-09 by #243: the v2 Neo4j reconstruct is driven by the published schema.neo4j.json contract, closing this class of backend-parity gap by construction. Epic #238.
Activity
rahlk commentedon Jul 14, 2026
Superseded 2026-07-09 by #243: the v2 Neo4j reconstruct is driven by the published schema.neo4j.json contract, closing this class of backend-parity gap by construction. Epic #238.