Skip to content

[TS] Invoke declared class getters during property reads #577

Open

Description

@CaelmBleidd

Current delivery (2026-10-17)

Getter invocation remains unimplemented. Published #595 is a safety boundary, not completion of this issue's original acceptance.

  • [TS] Stop unsupported accessor and method-value storage access #595 is open, not merged, at 371a4659321831999ecacb4835c90785b0a23af6, stacked on [TS] Integrate computed class members with distinct symbol identity #578 at d6c770ca5154cb55d2f3207374d5d0aa615d4fd8. All 7 CI checks are green at that exact head. Its recorded local full TS run has 1786 tests: 1647 passed, 139 skipped, zero failures/errors; Detekt main/test passed.
  • EtsIR still does not identify class accessors separately from ordinary prototype methods. Allocated-instance reads and ambiguous writes to a method-like declaration without a real data field now stop explicitly. A prior write cannot fabricate an own value for a getter without a setter. Actual own data fields, ordinary/inherited calls, and the existing method-capture-order regression remain supported; a narrow signature proof preserves unambiguous ordinary-method writes.
  • The unchanged Matrix reproduction now exhausts with zero terminal states, the explicit rows accessor/method-value limitation, and no engine failure. It no longer silently produces undefined. The unchanged SegmentTree constructor/query control still returns symbolic/native 3.

Still required: preserve accessor kind through the frontend/IR and invoke the selected getter with its actual receiver, including effects, exceptions, and inherited lookup. Setter effects, unsupported accessor forms, and complete method-value semantics need their own correct handling; #595 does not implement them. Keep this issue open after the safety PR is reviewed/merged. The original source, mismatch evidence, and acceptance below are retained.

Original report and acceptance (retained verbatim)

The original Matrix class now reaches ordinary execution after UnitTestBot/jacodb#448, but a fixed ordinary access produces an unreported semantic mismatch. Found during #575, linked to the real-class two-phase PBT/USVM plan #345.

Exact source: the unchanged frozen cases/matrix/target.ts from pbt-large-class-rerun-20261016. SHA-256: 3c4f790554d52f6da1913e2d3826e04b3de45e6271bc11e3bb21c15b96d7396c. Probe wrapper:

import { Matrix } from './target';
export function scenario(): number {
    return new Matrix([[1, 2], [3, 4]]).get(1, 0);
}

USVM base e56cea501, official immutable JacoDB snapshot 0fac32c8c5 (full source commit 0fac32c8c511c6228c3d6897262dc0924635af44), plus narrow marker-aware consumer changes from #575. Native TypeScript execution returns 3. Symbolic execution returns one terminal TsUndefined, EXHAUSTED, engineFailed=false, unsupportedCall=false, and empty unsupportedPaths.

Observed entered statements by method: %dflt.scenario=23, Matrix.constructor=23, Matrix.%instInit=4, Matrix.get=11, Matrix.isValidIndex=13. The source defines get rows(), get cols(), and get data() backed by _rows, _cols, and _data; none of those getter bodies is entered. Property reads currently follow ordinary field storage and fail to invoke the declared accessor. This silently changes isValidIndex and consequently the return from get.

Bounded fix: preserve the declared getter identity through frontend/IR, invoke the getter with the actual receiver on property reads, and preserve getter effects, thrown exceptions, and inherited lookup. Preserve existing setter behavior and explicitly reject unsupported accessor forms. Do not treat the missing accessor as an ordinary missing field. A general Symbol implementation is outside this issue.

Validation should include a minimal class getter source regression plus native replay for receiver/effects/inheritance, and the unchanged Matrix scenario above. Evidence artifact in this task: pbt-computed-members-preflight-20261016/{source-hashes.json,comparison.json,symbolic-results.json,native-results.json}.

Activity

  1. CaelmBleidd commented on Oct 16, 2026

    @CaelmBleidd
    MemberAuthor

    Allocated class-instance reads can currently treat a declared getter as an ordinary storage field and return undefined without reporting a limitation. The frozen Matrix example reproduces this: new Matrix([[1,2],[3,4]]).get(1,0) reads the rows getter, producing symbolic undefined versus native 3.

    EtsIR currently does not distinguish accessor declarations from ordinary prototype methods. A small shared boundary now rejects allocated instance property reads and writes when the runtime class/ancestor declares a same-name method-like member but no real data field. The existing unsupported-path mechanism retains the reason and does not report an engine failure. A previous write does not bypass this boundary: assigning a getter without a setter can throw or be ignored natively, so a modeled write must not fabricate an own property. Ordinary method calls, inherited calls, declared own data fields (including field shadowing), ordinary data-field writes, and callable data fields retain their behavior. Method-value reads and ambiguous method-overwriting assignments conservatively stop.

    A narrow grammar proof preserves already supported method-overwriting calls: a TypeScript getter has zero parameters and a setter has one parameter with a void return. More than one parameter, or one parameter with an explicit Boolean/number/string return, proves an ordinary method. Only writes use that exception; unknown/ambiguous signatures remain guarded. The original CapturedMethodCalls source and assertions are unchanged.

    Validation: full :usvm-ts:test passes with 1786 tests: 1647 passed, 139 skipped, zero failures/errors; detektMain, detektTest, and git diff --check pass. The 11 focused tests include the original method-capture controls and source/native accessor boundary tests. Before the guard, the new getter regression failed because analysis returned a state instead of an unsupported result. Native controls distinguish function-valued reads, own data, safe method-capture order, setter effects, and the TypeError from getter assignment inside a class method. Setter writes remain explicitly unsupported instead of discarding their effect.

    Original frozen sources remain unchanged: Matrix retains 44 methods, stops explicitly on rows with zero terminal states and no engine failure; SegmentTree retains 21 methods and its constructor/query still returns one symbolic 3, matching native execution with no unsupported paths. This is a safety boundary, not getter execution. General accessor effects, setter execution, and complete method-value semantics remain in #577.

    Refs #577. Depends on #578.

    Implementation: #595 at 371a4659321831999ecacb4835c90785b0a23af6. #577 remains open for full accessor semantics.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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