fix(javascript): bound static getter reference expansion

Replace recursive getter histories with a query-owned worklist and memo. Bound
precise expansion at 512 states and widen growing receiver cycles; retain
escaped roots and constructed children rather than silently dropping suffixes.
Carry the resource limit through semantic coverage, worker transport and graph
Evidence while preserving direct writes, imports and unrelated literal facts.

The small source probe reduced 65/326 paths to one widened reference for five
and six getters. The original large Claude artifact was not rerun. Validate
receiver/alias/mutation controls and compiled CLI/MCP consumers.

Refs #3839, #3848

Co-authored-by: Yi Han <yhan606@aucklanduni.ac.nz>
This commit is contained in:
morlutoandYi Han committed 2026-10-16 06:06:00 +08:00
1 parent 461eb16128
commit 880b3a510b
17 files changed
+1308 -524

No files matched your search

@@ -34,8 +34,16 @@ import {
import { projectSemanticResources } from "./JavaScriptSemanticGraphResourceProjection.js";
import { projectSemanticObjects } from "./JavaScriptSemanticGraphObjectProjection.js";
import { projectSemanticValues } from "./JavaScriptSemanticGraphValueProjection.js";
import { inferredSemanticEvidenceAt } from "./JavaScriptSemanticGraphEvidence.js";
import { observedSemanticEvidence } from "./JavaScriptSemanticGraphEvidence.js";
import {
inferredSemanticEvidenceAt,
observedSemanticEvidence,
unknownSemanticEvidence,
} from "./JavaScriptSemanticGraphEvidence.js";
import {
semanticCoverageResourceLimits,
semanticResourceLimitCoverage,
} from "../../domain/javascript/javascriptSemanticCoverage.js";
import { semanticResourceLimitReason } from "../../domain/javascript/javascriptSemanticResourceLimits.js";
import {
partialApplicationCoverage,
type ApplicationCoverage,
@@ -465,6 +473,7 @@ function* projectFileSteps(
});
if (moduleNode === null) return [];
state.roots.add(moduleNode.node_id);
projectFileResourceLimits({ file, ir, state, moduleNode });
yield* projectElectronSemanticCallPaths(file, paths, state);
// Node creation keeps source order, which the per-file node budget relies
// on; yields between nodes do not change it.
@@ -553,6 +562,36 @@ function* projectFileSteps(
);
}
const projectFileResourceLimits = (
context: Pick<FileContext, "file" | "ir" | "state" | "moduleNode">,
): void => {
// Getter limits describe query effects without tagged values; other limits
// already project from binding or return-site values and must not be duplicated.
for (const limit of semanticCoverageResourceLimits(context.ir.coverage)) {
if (limit !== "getter-expansion") continue;
const unknown = createJavaScriptSemanticGraphUnknown(
{
node_id: context.moduleNode.node_id,
family: "data-flow",
relation_kinds: ["defines"],
reason: "resource-limit",
detail: semanticResourceLimitReason(limit),
candidate_node_ids: [],
evidence: {
...unknownSemanticEvidence(context.file, null),
coverage: partialApplicationCoverage(
semanticResourceLimitCoverage([limit]),
null,
),
limitations: [semanticResourceLimitReason(limit)],
},
},
context.state.evidenceContexts,
);
context.state.unknowns.set(unknown.unknown_id, unknown);
}
};
function* createReturnSiteNodes(
file: JavaScriptArtifactFile,
ir: JavaScriptSemanticIr,