feat(javascript): link assignment right-hand sides to their writes (#6206)
* feat(javascript): link assignment right-hand sides to their writes A declaration carries its value forward, but a later assignment to a variable, a property or through a compound operator stopped the forward trace at the right-hand-side read. The semantic IR now records one syntactic assignment flow per simple assignment target (ranges, operator, owning callable), and the graph projection connects each retained read of the right-hand side to the write occurrence through a defines relation. Compound assignments also consume the previous value of the target; the written value itself stays unknown, keeping the boundary of #4342 intact. Fixes #6193 * fix(javascript): preserve computed key assignment ownership * test(javascript): retain nested callback assignment ownership * docs(javascript): record assignment influence verification * test(mcp): isolate mocks and acknowledge cancellation boundaries * test(javascript): retain CLI diagnostics and current-main verification --------- Co-authored-by: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
This commit is contained in:
1 parent
2968d1e932
commit
9df94a62cd
17 files changed
+702
-35
No files matched your search
@@ -0,0 +1,63 @@
|
||||
import type { SemanticFlowProjectionContext } from "./JavaScriptSemanticGraphFlowProjection.js";
|
||||
import { createSemanticNodeRangeLookup } from "./JavaScriptSemanticGraphProjection.js";
|
||||
import { sourceRangesEqual } from "../../domain/javascript/javascriptStaticAnalysisHelpers.js";
|
||||
import {
|
||||
addSemanticGraphRelation,
|
||||
addSemanticGraphUnknown,
|
||||
} from "./JavaScriptSemanticGraphConstruction.js";
|
||||
import { unknownSemanticEvidence } from "./JavaScriptSemanticGraphEvidence.js";
|
||||
|
||||
/** Connect retained reads to writes without claiming assignment values or order. */
|
||||
export function* projectSemanticAssignments(
|
||||
context: SemanticFlowProjectionContext,
|
||||
): Generator<void, void> {
|
||||
const readsWithin = createSemanticNodeRangeLookup(context.readNodes);
|
||||
const writesWithin = createSemanticNodeRangeLookup(context.writeNodes);
|
||||
for (const [index, flow] of context.ir.assignmentFlows.entries()) {
|
||||
if (index % 64 === 0) yield;
|
||||
const owner =
|
||||
flow.ownerCallableId === null
|
||||
? null
|
||||
: context.callableNodes.get(flow.ownerCallableId)?.node_id;
|
||||
const writes = writesWithin(flow.target).filter((node) =>
|
||||
sourceRangesEqual(node.identity.source_range, flow.target),
|
||||
);
|
||||
const reads = readsWithin(flow.source).filter(
|
||||
(node) => node.function_node_id === owner,
|
||||
);
|
||||
// Compound assignments consume the previous value as well as the RHS.
|
||||
if (flow.operator !== "=")
|
||||
reads.push(
|
||||
...readsWithin(flow.target).filter((node) =>
|
||||
sourceRangesEqual(node.identity.source_range, flow.target),
|
||||
),
|
||||
);
|
||||
for (const write of writes) {
|
||||
for (const read of reads)
|
||||
addSemanticGraphRelation(context.state, {
|
||||
source: read,
|
||||
target: write,
|
||||
relation: "defines",
|
||||
resolution:
|
||||
sourceRangesEqual(read.identity.source_range, flow.target) ||
|
||||
(flow.sourceIsReference &&
|
||||
sourceRangesEqual(read.identity.source_range, flow.source) &&
|
||||
!["&&=", "||=", "??="].includes(flow.operator))
|
||||
? "resolved"
|
||||
: "candidate",
|
||||
properties: { assignment_operator: flow.operator },
|
||||
});
|
||||
if (reads.length > 0)
|
||||
addSemanticGraphUnknown(context.state, {
|
||||
node_id: write.node_id,
|
||||
family: "data-flow",
|
||||
relation_kinds: ["defines"],
|
||||
reason: "unknown-value",
|
||||
detail:
|
||||
"Assignment inputs may influence this write; execution order, branch selection and the resulting value are not established.",
|
||||
candidate_node_ids: [],
|
||||
evidence: unknownSemanticEvidence(context.file, flow.target),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user