Mechanism
The TS analyzer disambiguates a get x() / set x() accessor pair only in the dict key it
uses inside a class/interface's methods map -- sig#get / sig#set (see
TSNeo4jBackend._method_key, cldk/analysis/typescript/neo4j/neo4j_backend.py) -- but the
underlying TSCallable.signature field on each accessor is the same string for both (e.g.
src/models.User.isAdmin), because it's derived from the property name, not the accessor kind.
accessor_kind ("getter"/"setter") is the only field that tells them apart.
This is invisible on the current sample-app fixture because it only has an unpaired getter
(src/models.User.isAdmin, no matching setter). It will surface the moment any app under test
declares a paired get/set on the same property.
In-memory backend (cldk/analysis/typescript/codeanalyzer/codeanalyzer.py)
_add_class iterates cl.methods.values() (a dict keyed by name#get/name#set) and calls
_add_callable for each, which does:
self._callables[c.signature] = c
Since both accessors share c.signature, the setter (or whichever is inserted second) silently
overwrites the getter in _callables. Every accessor consumer built on _iter_callables() /
_callables -- get_callables_overview, get_method_bodies, get_decorated_callables,
get_callsites_for, get_method, etc. -- sees exactly one row for the pair (last-writer-wins),
with whichever accessor lost silently dropped, including any of its own decorators, call sites, or
body text.
Neo4j backend (cldk/analysis/typescript/neo4j/neo4j_backend.py)
Each accessor is emitted as its own :Callable node, so MATCH (c:Callable) WHERE c._module IN $mods (used by get_callables_overview, get_callsites_for, etc.) returns two rows sharing
the same signature property -- one per accessor -- with no collapsing.
This means the two backends diverge on any app with a paired accessor:
- Overview row count -- in-memory: 1 row for the pair; Neo4j: 2 rows.
- Bodies / call sites -- in-memory only ever reflects the surviving accessor's code and call
sites; Neo4j reflects both, correctly separated per accessor.
- Decorators -- folds into the same bug family:
TSNeo4jBackend._OVERVIEW_RETURN aggregates
with collect(DISTINCT d.name), so duplicate decorator names on one node dedupe in Neo4j, while
the in-memory TSCallableOverview.from_callable keeps c.decorators as-is (duplicates
preserved) -- another silent divergence source for any callable carrying the same decorator name
twice.
Why this wasn't caught
tests/analysis/typescript/test_typescript_bulk_parity_live.py (the live dual-backend parity
suite added for #298) runs against the same sample-app fixture, which has no paired accessor and
no duplicate-decorator-name case, so it can't see either divergence today. The suite would catch
this the moment the fixture (or any app it's pointed at) has a real get x()/set x() pair on the
same class, or a callable decorated twice with the same decorator name.
Suggested fix (not done here)
Give getter/setter callables (and the in-memory _callables map, _methods_by_class values, etc.)
a signature that folds in accessor_kind so the pair no longer collides -- mirroring the #get/
#set disambiguation the Neo4j backend's _method_key already does for its own internal dict key,
but at the TSCallable.signature level so every consumer (both backends) sees it consistently.
Decorator dedup semantics (collect(DISTINCT ...) vs list-with-duplicates) should be reconciled at
the same time, one way or the other.
Filed as a latent-bug follow-up while closing out #298 (TS bulk accessors) per final-review
finding B; not blocking that merge since today's fixture can't exercise it.
Mechanism
The TS analyzer disambiguates a
get x()/set x()accessor pair only in the dict key ituses inside a class/interface's
methodsmap --sig#get/sig#set(seeTSNeo4jBackend._method_key,cldk/analysis/typescript/neo4j/neo4j_backend.py) -- but theunderlying
TSCallable.signaturefield on each accessor is the same string for both (e.g.src/models.User.isAdmin), because it's derived from the property name, not the accessor kind.accessor_kind("getter"/"setter") is the only field that tells them apart.This is invisible on the current sample-app fixture because it only has an unpaired getter
(
src/models.User.isAdmin, no matching setter). It will surface the moment any app under testdeclares a paired
get/seton the same property.In-memory backend (
cldk/analysis/typescript/codeanalyzer/codeanalyzer.py)_add_classiteratescl.methods.values()(a dict keyed byname#get/name#set) and calls_add_callablefor each, which does:Since both accessors share
c.signature, the setter (or whichever is inserted second) silentlyoverwrites the getter in
_callables. Every accessor consumer built on_iter_callables()/_callables--get_callables_overview,get_method_bodies,get_decorated_callables,get_callsites_for,get_method, etc. -- sees exactly one row for the pair (last-writer-wins),with whichever accessor lost silently dropped, including any of its own decorators, call sites, or
body text.
Neo4j backend (
cldk/analysis/typescript/neo4j/neo4j_backend.py)Each accessor is emitted as its own
:Callablenode, soMATCH (c:Callable) WHERE c._module IN $mods(used byget_callables_overview,get_callsites_for, etc.) returns two rows sharingthe same
signatureproperty -- one per accessor -- with no collapsing.This means the two backends diverge on any app with a paired accessor:
sites; Neo4j reflects both, correctly separated per accessor.
TSNeo4jBackend._OVERVIEW_RETURNaggregateswith
collect(DISTINCT d.name), so duplicate decorator names on one node dedupe in Neo4j, whilethe in-memory
TSCallableOverview.from_callablekeepsc.decoratorsas-is (duplicatespreserved) -- another silent divergence source for any callable carrying the same decorator name
twice.
Why this wasn't caught
tests/analysis/typescript/test_typescript_bulk_parity_live.py(the live dual-backend paritysuite added for #298) runs against the same sample-app fixture, which has no paired accessor and
no duplicate-decorator-name case, so it can't see either divergence today. The suite would catch
this the moment the fixture (or any app it's pointed at) has a real
get x()/set x()pair on thesame class, or a callable decorated twice with the same decorator name.
Suggested fix (not done here)
Give getter/setter callables (and the in-memory
_callablesmap,_methods_by_classvalues, etc.)a signature that folds in
accessor_kindso the pair no longer collides -- mirroring the#get/#setdisambiguation the Neo4j backend's_method_keyalready does for its own internal dict key,but at the
TSCallable.signaturelevel so every consumer (both backends) sees it consistently.Decorator dedup semantics (collect(DISTINCT ...) vs list-with-duplicates) should be reconciled at
the same time, one way or the other.
Filed as a latent-bug follow-up while closing out #298 (TS bulk accessors) per final-review
finding B; not blocking that merge since today's fixture can't exercise it.