[PR #1517] [MERGED] fix(javascript): expand an escaped receiver's methods once (#1495) #2937

Closed
opened 2026-10-17 21:12:01 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/morluto/rea/pull/1517
Author: @kaoru0822-kitauji
Created: 10/10/2026
Status: ✅ Merged
Merged: 10/10/2026
Merged by: @kaoru0822-kitauji

Base: main ← Head: fix/escaped-receiver-method-results


📝 Commits (1)

  • ed479f9 fix(javascript): expand an escaped receiver's methods once (#1495)

📊 Changes

2 files changed (+112 additions, -13 deletions)

View changed files

📝 src/domain/javascript/javascriptSemanticAnalysis.member_mutations.test.ts (+38 -0)
📝 src/domain/javascript/javascriptSemanticMemberMutations.ts (+74 -13)

📄 Description

Summary

Fixes #1495.

#1471 made an escaped object escape its methods' results as well. objectReferencedValues now returns ObjectMethod nodes, and referencedValues(…, "escape") maps a function to its return and yield expressions. Every call box.mI() escapes box as the receiver (markReceiver). Each of those escapes started a fresh markValue walk: the escaped binding's initializer object, then all N methods, then N return shared identifiers, all resolving to the same shared binding. With N such calls that is N × N work. A CPU profile at N = 4,000 showed markValue and referencedValues dominating, at 12.2 s of 12.2 s.

The escape site has to stay part of each record: escapedPaths entries are compared against capture points (semanticEscapeFollowsCapture). Dropping later sites would lose uncertainty. What does not depend on the site is the syntax below a reference. So for escape effects only:

  • escapedReferenceLeaves(expression, path) flattens the expansion of a non-reference expression down to identifier, member and iterable leaves. Literals, objects, arrays, functions, classes, conditionals and new are all expanded through the existing referencedValues. The result is memoized per expression node and path.
  • Identifier leaves that resolve to the same binding (or the same unresolved name) at the same path collapse to one representative. markValue processes such identifiers identically, because everything after binding resolution keys on bindingId, path, effect, site and origin, and those last three come from the current item.
  • The arrayIterationUnknown check that ran before referencedValues now runs on each expanded node (expandedValues), so it still covers nested expansions.
  • Parser-recovered argument holes still expand to nothing.

Write effects are unchanged.

Measured effect

Issue shape, in-process analyzeJavaScriptSemantics:

N main this branch
1,000 792 ms 124 ms
2,000 2,992 ms 253 ms
4,000 12,016 ms 638 ms

rea analyze-javascript-application <dir> --artifact-format directory wall time, which includes about 0.8 s of startup: N = 2,000 went from 4.99 s to 2.08 s, and N = 4,000 runs in 3.26 s.

Same results

The semantic IR (analyzeJavaScriptSemantics, serialized) is byte-identical between main and this branch on 50 sources:

  • the 26 writes cases in tests/acceptance/applications/javascriptReturnedReferences.test.ts;
  • the issue shape at N = 300;
  • 23 real JavaScript files from the installed Visual Studio Code and Obsidian apps.

Verification

New tests in javascriptSemanticAnalysis.member_mutations.test.ts:

  • Methods returning different objects on an escaped receiver keep each result uncertain, and an unrelated object stays literal. This checks that collapsing never merges distinct bindings.
  • The #1495 shape at N = 2,000 finishes within 2 s with shared.token unknown. This follows the precedent of 1ec490f3. On main's implementation it fails (3.2 s); here the whole file runs in about 0.4 s.

Other checks:

  • npx vitest run src/domain/javascript src/application/javascript passes (56 files, 994 tests).
  • npm run test:focused on tests/acceptance/applications/{javascript,application}*.test.ts and tests/boundary/mcp/application*.test.ts passes (12 files, 49 tests), including javascriptReturnedReferences.
  • npm run check:fast and npm run docs:check pass. The lint warnings in this file match main (17).

🤖 Generated with Claude Code


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/morluto/rea/pull/1517 **Author:** [@kaoru0822-kitauji](https://github.com/kaoru0822-kitauji) **Created:** 10/10/2026 **Status:** ✅ Merged **Merged:** 10/10/2026 **Merged by:** [@kaoru0822-kitauji](https://github.com/kaoru0822-kitauji) **Base:** `main` ← **Head:** `fix/escaped-receiver-method-results` --- ### 📝 Commits (1) - [`ed479f9`](https://github.com/morluto/rea/commit/ed479f97ac001d4960e04775e8b970c3132ae33f) fix(javascript): expand an escaped receiver's methods once (#1495) ### 📊 Changes **2 files changed** (+112 additions, -13 deletions) <details> <summary>View changed files</summary> 📝 `src/domain/javascript/javascriptSemanticAnalysis.member_mutations.test.ts` (+38 -0) 📝 `src/domain/javascript/javascriptSemanticMemberMutations.ts` (+74 -13) </details> ### 📄 Description ## Summary Fixes #1495. #1471 made an escaped object escape its methods' results as well. `objectReferencedValues` now returns `ObjectMethod` nodes, and `referencedValues(…, "escape")` maps a function to its return and yield expressions. Every call `box.mI()` escapes `box` as the receiver (`markReceiver`). Each of those escapes started a fresh `markValue` walk: the escaped binding's initializer object, then all N methods, then N `return shared` identifiers, all resolving to the same `shared` binding. With N such calls that is N × N work. A CPU profile at N = 4,000 showed `markValue` and `referencedValues` dominating, at 12.2 s of 12.2 s. The escape *site* has to stay part of each record: `escapedPaths` entries are compared against capture points (`semanticEscapeFollowsCapture`). Dropping later sites would lose uncertainty. What does not depend on the site is the syntax below a reference. So for escape effects only: - `escapedReferenceLeaves(expression, path)` flattens the expansion of a non-reference expression down to identifier, member and iterable leaves. Literals, objects, arrays, functions, classes, conditionals and `new` are all expanded through the existing `referencedValues`. The result is memoized per expression node and path. - Identifier leaves that resolve to the same binding (or the same unresolved name) at the same path collapse to one representative. `markValue` processes such identifiers identically, because everything after binding resolution keys on `bindingId`, path, effect, site and origin, and those last three come from the current item. - The `arrayIterationUnknown` check that ran before `referencedValues` now runs on each expanded node (`expandedValues`), so it still covers nested expansions. - Parser-recovered argument holes still expand to nothing. Write effects are unchanged. ## Measured effect Issue shape, in-process `analyzeJavaScriptSemantics`: | N | main | this branch | |---|---|---| | 1,000 | 792 ms | 124 ms | | 2,000 | 2,992 ms | 253 ms | | 4,000 | 12,016 ms | 638 ms | `rea analyze-javascript-application <dir> --artifact-format directory` wall time, which includes about 0.8 s of startup: N = 2,000 went from 4.99 s to 2.08 s, and N = 4,000 runs in 3.26 s. ## Same results The semantic IR (`analyzeJavaScriptSemantics`, serialized) is byte-identical between `main` and this branch on 50 sources: - the 26 `writes` cases in `tests/acceptance/applications/javascriptReturnedReferences.test.ts`; - the issue shape at N = 300; - 23 real JavaScript files from the installed Visual Studio Code and Obsidian apps. ## Verification New tests in `javascriptSemanticAnalysis.member_mutations.test.ts`: - Methods returning *different* objects on an escaped receiver keep each result uncertain, and an unrelated object stays literal. This checks that collapsing never merges distinct bindings. - The #1495 shape at N = 2,000 finishes within 2 s with `shared.token` unknown. This follows the precedent of 1ec490f3. On `main`'s implementation it fails (3.2 s); here the whole file runs in about 0.4 s. Other checks: - `npx vitest run src/domain/javascript src/application/javascript` passes (56 files, 994 tests). - `npm run test:focused` on `tests/acceptance/applications/{javascript,application}*.test.ts` and `tests/boundary/mcp/application*.test.ts` passes (12 files, 49 tests), including `javascriptReturnedReferences`. - `npm run check:fast` and `npm run docs:check` pass. The lint warnings in this file match `main` (17). 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-10-17 21:12:01 +00:00
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#2937
No description provided.