[GH-ISSUE #2570] fix(javascript): TypeScript this annotation shifts local call argument flows #887

Closed
opened 2026-10-15 18:29:18 +00:00 by bsakaguchi · 1 comment
Owner

Originally created by @coygeek on GitHub (Oct 13, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/2570

Summary

When REA analyzes a directly called local TypeScript function with an explicit this annotation, it attributes the first positional argument to a binding named this. The first runtime parameter receives no flow for that argument. This produces incorrect argument-to-parameter evidence for otherwise valid source, including a function with a rest parameter.

Steps to reproduce

  1. Use the source checkout identified under Runtime or environment with its locked dependencies installed.
  2. Create src/domain/javascript/javascriptSemanticAnalysis.thisParameter.test.ts with the following focused test. The fixture strings are input to static analysis; REA does not execute them.
import { expect, it } from "vitest";
import { analyzeJavaScriptSemantics } from "./javascriptSemanticAnalysis.js";

const parameterFlows = (source: string) => {
  const ir = analyzeJavaScriptSemantics(source);
  expect(ir.callSites).toHaveLength(1);
  expect(ir.callSites[0]?.resolution).toBe("exact");
  return ir.argumentFlows.map(({ argumentIndex, parameterBindingId }) => [
    argumentIndex,
    ir.bindings.find(({ bindingId }) => bindingId === parameterBindingId)?.name,
  ]);
};

it("maps the first argument to the first runtime parameter", () => {
  expect(parameterFlows(
    "function f(this: any, a: number) { return a; } f(1);",
  )).toEqual([[0, "a"]]);
});

it("maps both arguments to the runtime rest parameter", () => {
  expect(parameterFlows(
    "function f(this: any, ...rest: number[]) { return rest; } f(1, 2);",
  )).toEqual([[0, "rest"], [1, "rest"]]);
});

it("preserves ordinary destructured parameter positions", () => {
  expect(parameterFlows(
    "function f({ a }: { a: number }, b: number) { return a + b; } f({ a: 1 }, 2);",
  )).toEqual([[0, "a"], [1, "b"]]);
});
  1. Run the focused test.
npm run test:focused -- src/domain/javascript/javascriptSemanticAnalysis.thisParameter.test.ts
  1. Compare the returned argument-index/parameter-name pairs with the assertions. The first two expectations fail; the destructuring control passes. This procedure was executed locally with the same fixtures and assertions, plus checks that the inputs parse without errors.

Expected behavior

An explicit TypeScript this annotation describes the receiver type and occupies no runtime positional parameter slot. For the first fixture, calling f(1) should produce [[0,"a"]]. For the rest fixture, calling f(1, 2) should produce [[0,"rest"],[1,"rest"]]. Neither call should attribute a positional argument to this.

Actual behavior

The local focused run returned [[0,"this"]] for the first fixture and [[0,"this"],[1,"rest"]] for the rest fixture. The call targets resolved exactly, and both inputs parsed without errors. The run exited 1 with two failed expectations and one passed control. The rest parameter loses its first argument flow; its second argument flow still targets rest.

Evidence

  • Expected source: src/domain/javascript/javascriptSemanticIr.ts:127 defines positional argument initialization of local parameter bindings, and src/domain/javascript/javascriptSemanticAnalysis.calls.test.ts:10 verifies exact local-call parameter mapping. docs/javascript-artifact-reconstruction.md:200 documents TypeScript-source support; src/domain/javascript/javascriptSourceParser.ts:27 enables its parser plugin.
  • Failure source: The focused analyzer observations above. src/domain/javascript/javascriptSemanticAnalysis.ts:1024 registers every AST parameter, including this, while src/domain/javascript/javascriptSemanticCallResolution.ts:189 matches argument indexes against that unfiltered parameter array. src/domain/javascript/javascriptSemanticCalls.ts:227 emits the resulting incorrect flows.
  • Evidence provenance: observed
  • Local verification: reproduced
  • Reproduction completeness: complete

An independent execution of these simple fixtures through Node's TypeScript type erasure returned a runtime parameter count of 1 and a result of 1 for the first function, and [1,2] for the rest function. This confirms that the annotation does not consume an argument. The analyzer test itself remained static and did not execute the input functions.

Source inspection also shows that the application graph projects the IR flows into argument-to-parameter relations. No CLI/MCP or graph-query reproduction was run; the observed failure is the analyzer's returned IR.

Restoration check

The same two typed fixtures should return the expected argument-index/parameter-name pairs, with no positional flow to this, while retaining exact local-call resolution. The ordinary destructuring control must continue returning [[0,"a"],[1,"b"]]. Passing by dropping all flows or treating these local calls as unresolved does not restore the contract.

Runtime or environment

  • Failure was verified at a2d8b94dd5 and is still present at 8b181716ac by source recheck
  • Node.js v26.10.0, npm 11.19.1, and the repository's locked Vitest 5.0.3 dependencies.
  • The probe used only local static analysis of inline TypeScript source. No native provider was required.

Triage assessment

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: Valid directly resolved local calls publish incorrect argument-to-parameter facts for TypeScript functions with a receiver-type annotation. The reproduced failure is bounded to those inputs and can mislead analysis that consumes the flows; production incidence and broader callable coverage were not measured.
  • Workaround status: unverified
  • Workaround basis: No workaround preserving the original TypeScript source and correct returned flow facts was validated. Removing the annotation changes the analyzed input, and consumer-side correction was not tested.
Originally created by @coygeek on GitHub (Oct 13, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/2570 ## Summary When REA analyzes a directly called local TypeScript function with an explicit `this` annotation, it attributes the first positional argument to a binding named `this`. The first runtime parameter receives no flow for that argument. This produces incorrect argument-to-parameter evidence for otherwise valid source, including a function with a rest parameter. ## Steps to reproduce 1. Use the source checkout identified under Runtime or environment with its locked dependencies installed. 2. Create `src/domain/javascript/javascriptSemanticAnalysis.thisParameter.test.ts` with the following focused test. The fixture strings are input to static analysis; REA does not execute them. ```ts import { expect, it } from "vitest"; import { analyzeJavaScriptSemantics } from "./javascriptSemanticAnalysis.js"; const parameterFlows = (source: string) => { const ir = analyzeJavaScriptSemantics(source); expect(ir.callSites).toHaveLength(1); expect(ir.callSites[0]?.resolution).toBe("exact"); return ir.argumentFlows.map(({ argumentIndex, parameterBindingId }) => [ argumentIndex, ir.bindings.find(({ bindingId }) => bindingId === parameterBindingId)?.name, ]); }; it("maps the first argument to the first runtime parameter", () => { expect(parameterFlows( "function f(this: any, a: number) { return a; } f(1);", )).toEqual([[0, "a"]]); }); it("maps both arguments to the runtime rest parameter", () => { expect(parameterFlows( "function f(this: any, ...rest: number[]) { return rest; } f(1, 2);", )).toEqual([[0, "rest"], [1, "rest"]]); }); it("preserves ordinary destructured parameter positions", () => { expect(parameterFlows( "function f({ a }: { a: number }, b: number) { return a + b; } f({ a: 1 }, 2);", )).toEqual([[0, "a"], [1, "b"]]); }); ``` 3. Run the focused test. ```sh npm run test:focused -- src/domain/javascript/javascriptSemanticAnalysis.thisParameter.test.ts ``` 4. Compare the returned argument-index/parameter-name pairs with the assertions. The first two expectations fail; the destructuring control passes. This procedure was executed locally with the same fixtures and assertions, plus checks that the inputs parse without errors. ## Expected behavior An explicit TypeScript `this` annotation describes the receiver type and occupies no runtime positional parameter slot. For the first fixture, calling `f(1)` should produce `[[0,"a"]]`. For the rest fixture, calling `f(1, 2)` should produce `[[0,"rest"],[1,"rest"]]`. Neither call should attribute a positional argument to `this`. ## Actual behavior The local focused run returned `[[0,"this"]]` for the first fixture and `[[0,"this"],[1,"rest"]]` for the rest fixture. The call targets resolved exactly, and both inputs parsed without errors. The run exited 1 with two failed expectations and one passed control. The rest parameter loses its first argument flow; its second argument flow still targets `rest`. ## Evidence - Expected source: `src/domain/javascript/javascriptSemanticIr.ts:127` defines positional argument initialization of local parameter bindings, and `src/domain/javascript/javascriptSemanticAnalysis.calls.test.ts:10` verifies exact local-call parameter mapping. `docs/javascript-artifact-reconstruction.md:200` documents TypeScript-source support; `src/domain/javascript/javascriptSourceParser.ts:27` enables its parser plugin. - Failure source: The focused analyzer observations above. `src/domain/javascript/javascriptSemanticAnalysis.ts:1024` registers every AST parameter, including `this`, while `src/domain/javascript/javascriptSemanticCallResolution.ts:189` matches argument indexes against that unfiltered parameter array. `src/domain/javascript/javascriptSemanticCalls.ts:227` emits the resulting incorrect flows. - Evidence provenance: observed - Local verification: reproduced - Reproduction completeness: complete An independent execution of these simple fixtures through Node's TypeScript type erasure returned a runtime parameter count of 1 and a result of 1 for the first function, and `[1,2]` for the rest function. This confirms that the annotation does not consume an argument. The analyzer test itself remained static and did not execute the input functions. Source inspection also shows that the application graph projects the IR flows into argument-to-parameter relations. No CLI/MCP or graph-query reproduction was run; the observed failure is the analyzer's returned IR. ## Restoration check The same two typed fixtures should return the expected argument-index/parameter-name pairs, with no positional flow to `this`, while retaining exact local-call resolution. The ordinary destructuring control must continue returning `[[0,"a"],[1,"b"]]`. Passing by dropping all flows or treating these local calls as unresolved does not restore the contract. ## Runtime or environment - Failure was verified at a2d8b94dd50dab8d966239dd63ab84718aa8b4b4 and is still present at 8b181716aca2cb467be499cba226abfb5ae19141 by source recheck - Node.js v26.10.0, npm 11.19.1, and the repository's locked Vitest 5.0.3 dependencies. - The probe used only local static analysis of inline TypeScript source. No native provider was required. ## Triage assessment - Impact level: P2 - Assessment status: supported - Impact basis: Valid directly resolved local calls publish incorrect argument-to-parameter facts for TypeScript functions with a receiver-type annotation. The reproduced failure is bounded to those inputs and can mislead analysis that consumes the flows; production incidence and broader callable coverage were not measured. - Workaround status: unverified - Workaround basis: No workaround preserving the original TypeScript source and correct returned flow facts was validated. Removing the annotation changes the analyzed input, and consumer-side correction was not tested.
bsakaguchi 2026-10-15 18:29:18 +00:00
  • closed this issue
  • added the
    bug
    label
Author
Owner

@xu91102 commented on GitHub (Oct 13, 2026):

Reproduced on main d13c9f840d36a321a56917e75c48afcb8d72920e through both the compiled CLI and production stdio MCP.

A test-owned .mts program with first(this: void, value: number), rest(this: void, ...values: number[]), and an ordinary destructuring control runs successfully under Node type erasure, returning [7,[1,2],7,1,0]. Both public graph regressions then fail because the graph invents two runtime parameter nodes named this.

The regression also checks every argument-to-parameter target and each callable's parameter-arity fingerprint. I am fixing the shared runtime-parameter interpretation rather than shifting argument indexes downstream; source AST/type annotations remain intact.

<!-- gh-comment-id:6143307984 --> @xu91102 commented on GitHub (Oct 13, 2026): Reproduced on main `d13c9f840d36a321a56917e75c48afcb8d72920e` through both the compiled CLI and production stdio MCP. A test-owned `.mts` program with `first(this: void, value: number)`, `rest(this: void, ...values: number[])`, and an ordinary destructuring control runs successfully under Node type erasure, returning `[7,[1,2],7,1,0]`. Both public graph regressions then fail because the graph invents two runtime parameter nodes named `this`. The regression also checks every argument-to-parameter target and each callable's parameter-arity fingerprint. I am fixing the shared runtime-parameter interpretation rather than shifting argument indexes downstream; source AST/type annotations remain intact.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#887
No description provided.