[GH-ISSUE #6192] [Feature] Model objects assigned to properties after declaration #3772

Open
opened 2026-10-31 00:43:55 +00:00 by bsakaguchi · 3 comments
Owner

Originally created by @jacobmalibiran on GitHub (Oct 22, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/6192

Before submitting

  • I searched the open and closed issues and pull requests for a related proposal.
  • I understand the contribution scope must be reviewed before integration; open questions are welcome here.
  • This request does not include credentials, private samples, or sensitive target data.

Area

Evidence, artifacts, or reconstruction

Current limitation

An object literal assigned to a property after declaration isn't modelled as that
property's value. For Game.config = { ACCEL: 0.001 }, where Game is either a
function or an empty object, REA creates slots config and config.ACCEL with
value status unknown and no literal node for 0.001. A literal seed for
0.001 then matches nothing. The same object written in the declaration's
initializer is fully modelled.

Slot values come from each binding's initializer
(JavaScriptSemanticGraphObjectProjection.ts:25-26),
so later property assignments don't contribute values.

Use cases

Configuration and constants attached after declaration, common in ES5 code
(Runner.config = {…}, Obstacle.types = […]) and namespace objects
(const App = {}; App.settings = {…}). Example: wayou's T-Rex runner index.js
(https://wayou.github.io/t-rex-runner/) defines its speed constants this way
(Runner.config = { ACCELERATION: 0.001, MAX_SPEED: 13, SPEED: 6, … }), so none
of those values can be traced. Frequency across real targets: not measured.

Desired outcome and success criteria

For the function and assigned fixtures below, slot config.ACCEL has value
status literal with a 0.001 literal node, and a literal seed for 0.001
reaches read:/config/ACCEL, matching the initializer control. When an
assignment's ordering or reachability is uncertain (conditional, inside a
function, multiple writers), the value stays a candidate with unknowns.

Expected interaction with targets

Read existing files or captured data

Target and scope details (optional)

Static analysis of local JavaScript only; no execution. Scope question: should
this cover only unconditional top-level assignments first, leaving assignments in
functions or branches as candidates?

Verification available so far

I have a prototype or observed results

Verification details (optional)

Observed on main at 2451e187b (REA 6.7.0, Node v24.22.0, macOS arm64). Run from
the repository root after npm run build:cached:

R=$(mktemp -d); mkdir -p "$R/function" "$R/assigned" "$R/initializer"

cat > "$R/function/game.js" <<'JS'
function Game() {}
Game.config = { ACCEL: 0.001 };
export function accel() { return Game.config.ACCEL; }
JS
cat > "$R/assigned/game.js" <<'JS'
const Game = {};
Game.config = { ACCEL: 0.001 };
export function accel() { return Game.config.ACCEL; }
JS
cat > "$R/initializer/game.js" <<'JS'
const Game = { config: { ACCEL: 0.001 } };
export function accel() { return Game.config.ACCEL; }
JS

for v in function assigned initializer; do
  node scripts/rea.mjs analyze-javascript-application "$R/$v" \
    --artifact-format directory --format json > "$R/$v.json" 2>/dev/null
  jq -c '{application: ., query: {seed: {kind: "literal", value: 0.001}, direction: "forward-influence"}}' \
    "$R/$v.json" > "$R/query.json"
  printf '%-12s ' "$v"
  jq -c '.normalized_result.semantic_graph | {
    slots: [.nodes[] | select(.kind == "property-slot") | "\(.properties.property_path | join(".")):\(.properties.value_status)"] | sort,
    literals: [.nodes[] | select(.kind == "literal") | .properties.value]}' "$R/$v.json" | tr -d '\n'
  printf ' trace(0.001)='
  node scripts/rea.mjs trace-javascript-semantics "$R/query.json" --format json |
    jq -c '.normalized_result | {status, reached: [.nodes[].label] | unique}'
done

Output:

function     {"slots":["config.ACCEL:unknown","config:unknown"],"literals":[]} trace(0.001)={"status":"partial","reached":[]}
assigned     {"slots":["config.ACCEL:unknown","config:unknown"],"literals":[]} trace(0.001)={"status":"partial","reached":[]}
initializer  {"slots":["config.ACCEL:literal","config:object"],"literals":[0.001]} trace(0.001)={"status":"found","reached":["0.001","ACCEL","read:/config/ACCEL","return"]}

Alternatives considered

  • literal and property seeds: neither reaches the value today.
  • Reading raw source by hand.

Possible direction

Proposal only: treat a dominating, unconditional X.p = <object literal> as
defining slot p on X's container, the same way an initializer property does,
and keep other assignments as candidate writes with unknowns.

References

  • #6189: found while tracing the T-Rex runner's speed rule
  • Related: #6191 (this receivers), #6193 (assignment right-hand-side flow)
Originally created by @jacobmalibiran on GitHub (Oct 22, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/6192 ## Before submitting - [x] I searched the open and closed issues and pull requests for a related proposal. - [x] I understand the [contribution scope](https://github.com/morluto/rea/blob/main/CONTRIBUTING.md#contribution-scope) must be reviewed before integration; open questions are welcome here. - [x] This request does not include credentials, private samples, or sensitive target data. ## Area Evidence, artifacts, or reconstruction ## Current limitation An object literal assigned to a property after declaration isn't modelled as that property's value. For `Game.config = { ACCEL: 0.001 }`, where `Game` is either a function or an empty object, REA creates slots `config` and `config.ACCEL` with value status `unknown` and no literal node for `0.001`. A `literal` seed for `0.001` then matches nothing. The same object written in the declaration's initializer is fully modelled. Slot values come from each binding's initializer ([`JavaScriptSemanticGraphObjectProjection.ts:25-26`](https://github.com/morluto/rea/blob/2451e187beab43a811bb6149dfd3af3ad8178327/src/application/javascript/JavaScriptSemanticGraphObjectProjection.ts#L25-L26)), so later property assignments don't contribute values. ## Use cases Configuration and constants attached after declaration, common in ES5 code (`Runner.config = {…}`, `Obstacle.types = […]`) and namespace objects (`const App = {}; App.settings = {…}`). Example: wayou's T-Rex runner `index.js` (<https://wayou.github.io/t-rex-runner/>) defines its speed constants this way (`Runner.config = { ACCELERATION: 0.001, MAX_SPEED: 13, SPEED: 6, … }`), so none of those values can be traced. Frequency across real targets: not measured. ## Desired outcome and success criteria For the `function` and `assigned` fixtures below, slot `config.ACCEL` has value status `literal` with a `0.001` literal node, and a `literal` seed for `0.001` reaches `read:/config/ACCEL`, matching the `initializer` control. When an assignment's ordering or reachability is uncertain (conditional, inside a function, multiple writers), the value stays a candidate with unknowns. ## Expected interaction with targets Read existing files or captured data ## Target and scope details (optional) Static analysis of local JavaScript only; no execution. Scope question: should this cover only unconditional top-level assignments first, leaving assignments in functions or branches as candidates? ## Verification available so far I have a prototype or observed results ## Verification details (optional) Observed on `main` at `2451e187b` (REA 6.7.0, Node v24.22.0, macOS arm64). Run from the repository root after `npm run build:cached`: ```bash R=$(mktemp -d); mkdir -p "$R/function" "$R/assigned" "$R/initializer" cat > "$R/function/game.js" <<'JS' function Game() {} Game.config = { ACCEL: 0.001 }; export function accel() { return Game.config.ACCEL; } JS cat > "$R/assigned/game.js" <<'JS' const Game = {}; Game.config = { ACCEL: 0.001 }; export function accel() { return Game.config.ACCEL; } JS cat > "$R/initializer/game.js" <<'JS' const Game = { config: { ACCEL: 0.001 } }; export function accel() { return Game.config.ACCEL; } JS for v in function assigned initializer; do node scripts/rea.mjs analyze-javascript-application "$R/$v" \ --artifact-format directory --format json > "$R/$v.json" 2>/dev/null jq -c '{application: ., query: {seed: {kind: "literal", value: 0.001}, direction: "forward-influence"}}' \ "$R/$v.json" > "$R/query.json" printf '%-12s ' "$v" jq -c '.normalized_result.semantic_graph | { slots: [.nodes[] | select(.kind == "property-slot") | "\(.properties.property_path | join(".")):\(.properties.value_status)"] | sort, literals: [.nodes[] | select(.kind == "literal") | .properties.value]}' "$R/$v.json" | tr -d '\n' printf ' trace(0.001)=' node scripts/rea.mjs trace-javascript-semantics "$R/query.json" --format json | jq -c '.normalized_result | {status, reached: [.nodes[].label] | unique}' done ``` Output: ```text function {"slots":["config.ACCEL:unknown","config:unknown"],"literals":[]} trace(0.001)={"status":"partial","reached":[]} assigned {"slots":["config.ACCEL:unknown","config:unknown"],"literals":[]} trace(0.001)={"status":"partial","reached":[]} initializer {"slots":["config.ACCEL:literal","config:object"],"literals":[0.001]} trace(0.001)={"status":"found","reached":["0.001","ACCEL","read:/config/ACCEL","return"]} ``` ## Alternatives considered - `literal` and `property` seeds: neither reaches the value today. - Reading raw source by hand. ## Possible direction Proposal only: treat a dominating, unconditional `X.p = <object literal>` as defining slot `p` on `X`'s container, the same way an initializer property does, and keep other assignments as candidate writes with unknowns. ## References - #6189: found while tracing the T-Rex runner's speed rule - Related: #6191 (`this` receivers), #6193 (assignment right-hand-side flow)
Author
Owner

@XinwangTao commented on GitHub (Oct 22, 2026):

赞同这个方向,而且它和 #6193 是互补的两半:一个是值建模(字面量挂到 slot),一个是依赖边(赋值 RHS 连到写入目标),共同根因都是投影只从 binding 的 initializer 取值。建议范围里也把数组字面量算进去——ES5 里 Obstacle.types = [...]、Runner.sprites = {…} 这类后置配置跟对象字面量一样普遍,漏掉的话 trace 仍会断。另外这条依赖 #6191 的接收者解析:只有先 resolve 出 Game.config 指向哪个对象,字面量才能挂到正确的 slot 而不是空对象上,三个 issue 的 fixture 建议按 #6191 → #6192 → #6193 的顺序串起来验证。

<!-- gh-comment-id:6330324489 --> @XinwangTao commented on GitHub (Oct 22, 2026): 赞同这个方向,而且它和 #6193 是互补的两半:一个是值建模(字面量挂到 slot),一个是依赖边(赋值 RHS 连到写入目标),共同根因都是投影只从 binding 的 initializer 取值。建议范围里也把数组字面量算进去——ES5 里 `Obstacle.types = [...]`、`Runner.sprites = {…}` 这类后置配置跟对象字面量一样普遍,漏掉的话 trace 仍会断。另外这条依赖 #6191 的接收者解析:只有先 resolve 出 `Game.config` 指向哪个对象,字面量才能挂到正确的 slot 而不是空对象上,三个 issue 的 fixture 建议按 #6191 → #6192 → #6193 的顺序串起来验证。
Author
Owner

@IvenKooLab commented on GitHub (Oct 24, 2026):

Proposed in #6972, building on the assignment-flow IR from #6206. All fixtures from this issue now trace to read:/config/ACCEL and the return through the compiled CLI, matching the initializer control; conditional, in-function and non-literal writers keep the unknown semantics. On the scope question above: this increment folds only unconditional top-level assignments whose path has no other intersecting writer — everything else stays a candidate, so the uncertain cases never gain a value.

<!-- gh-comment-id:6381691810 --> @IvenKooLab commented on GitHub (Oct 24, 2026): Proposed in #6972, building on the assignment-flow IR from #6206. All fixtures from this issue now trace to `read:/config/ACCEL` and the return through the compiled CLI, matching the `initializer` control; conditional, in-function and non-literal writers keep the unknown semantics. On the scope question above: this increment folds only unconditional top-level assignments whose path has no other intersecting writer — everything else stays a candidate, so the uncertain cases never gain a value.
Author
Owner

@XinwangTao commented on GitHub (Oct 24, 2026):

感谢实现!想确认一下 #6972 对数组字面量是否同样覆盖:之前 #6192 的场景里 Obstacle.types = [...] 这类后置数组赋值与对象字面量一样常见,如果这次的 IR 只处理对象字面量,数组的 trace 仍然会断。另外条件分支写者的 candidate 语义保持不变这点很合理,正好和 #6193 的 RHS 依赖边互补。

<!-- gh-comment-id:6382001350 --> @XinwangTao commented on GitHub (Oct 24, 2026): 感谢实现!想确认一下 #6972 对数组字面量是否同样覆盖:之前 #6192 的场景里 Obstacle.types = [...] 这类后置数组赋值与对象字面量一样常见,如果这次的 IR 只处理对象字面量,数组的 trace 仍然会断。另外条件分支写者的 candidate 语义保持不变这点很合理,正好和 #6193 的 RHS 依赖边互补。
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#3772
No description provided.