Skip to content

TS bulk accessors: getter/setter signature collisions break dual-backend parity (latent) #300

Description

@rahlk

PROBLEM

The TS analyzer disambiguates a get x() / set x() pair only in the dict key inside a class's methods map — sig#get / sig#set (TSNeo4jBackend._method_key, cldk/analysis/typescript/neo4j/neo4j_backend.py) — but TSCallable.signature is the same string for both, derived from the property name rather than the accessor kind (e.g. src/models.User.isAdmin). Only accessor_kind distinguishes them.

The two backends then diverge:

  • In-memory (cldk/analysis/typescript/codeanalyzer/codeanalyzer.py): _add_class iterates cl.methods.values() and _add_callable does self._callables[c.signature] = c. Both accessors share a signature, so the second silently overwrites the first. Every consumer of _iter_callables() / _callablesget_callables_overview, get_method_bodies, get_decorated_callables, get_callsites_for, get_method — sees one row per pair, last-writer-wins, with the loser's decorators, call sites and body text dropped.
  • Neo4j: each accessor is its own :Callable node, so the same query returns two rows sharing one signature, uncollapsed.

Net: overview row counts differ (1 vs 2), bodies and call sites reflect only the surviving accessor in-memory, and decorator handling diverges too — _OVERVIEW_RETURN aggregates with collect(DISTINCT d.name) while TSCallableOverview.from_callable keeps c.decorators with duplicates intact.

SCOPE BOUNDARY

Signature identity for TS accessors, and the decorator dedup semantics that fold into the same family. Not the analyzer's emission — this is an SDK-side identity problem. Not the Python or Java facades.

GOALS

  • Fold accessor_kind into TSCallable.signature so a getter/setter pair no longer collides — mirroring the #get/#set disambiguation _method_key already does, but at the signature level so both backends see it consistently.
  • Apply the same disambiguation to the in-memory _callables map and _methods_by_class values.
  • Reconcile decorator dedup: collect(DISTINCT ...) versus list-with-duplicates, one way or the other.
  • Extend the fixture with a real paired get x()/set x() and a callable decorated twice with the same name.

CAVEATS AND KNOWN RISKS

  • Latent, not observed. The current sample app has only an unpaired getter (src/models.User.isAdmin), so nothing exercises this today. It surfaces the moment any analyzed app declares a paired accessor.
  • tests/analysis/typescript/test_typescript_bulk_parity_live.py (added for #298) runs against that same fixture and therefore cannot see either divergence — the suite is not wrong, it is under-fixtured. Extending the fixture is what makes the parity suite able to catch this.
  • Changing signature changes a public, user-visible field. Anyone holding TS signature strings is affected, so this is breaking for accessors and belongs in a major.

DEFINITION OF DONE

  • On a fixture containing a paired get/set, both backends return the same row count and the same per-accessor bodies, call sites and decorators.
  • A callable decorated twice with one name behaves identically across backends.
  • The live parity suite fails before the fix and passes after.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions