Skip to content

Commit 9dfc394

Browse files
committed
fix: resolve merge conflicts with main
2 parents 3b8c66a + 117ac5b commit 9dfc394

19 files changed

Lines changed: 504 additions & 18 deletions

File tree

package-lock.json

Lines changed: 13 additions & 13 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/domain/graph/builder/incremental.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -584,7 +584,11 @@ function buildCallEdges(
584584
}
585585
}
586586
if (targets.length === 0) {
587-
const sameFile = lookup.byNameAndFile(call.name, relPath);
587+
// Narrow to function/method kinds only to avoid matching unrelated
588+
// variables or classes that share a name in the same file.
589+
const sameFile = lookup
590+
.byNameAndFile(call.name, relPath)
591+
.filter((n) => n.kind === 'function' || n.kind === 'method');
588592
if (sameFile.length > 0) {
589593
targets = [...sameFile];
590594
}

src/domain/graph/builder/stages/build-edges.ts

Lines changed: 93 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import type {
1717
ClassRelation,
1818
Definition,
1919
ExtractorOutput,
20+
FnRefBinding,
2021
Import,
2122
NativeAddon,
2223
NodeRow,
@@ -709,6 +710,78 @@ function buildFnRefBindingsPtsPostPass(
709710
}
710711
}
711712

713+
/**
714+
* this-rebinding post-pass for the native call-edge path.
715+
*
716+
* When `fn.call(namedCtx, ...)` or `fn.apply(namedCtx, ...)` is extracted by the
717+
* WASM layer, `thisCallBindings` records `{ callee: 'fn', thisArg: 'namedCtx' }`.
718+
* The native Rust engine has no knowledge of these bindings, so `this()` calls
719+
* inside `fn` remain unresolved. This JS post-pass adds the missing edges by
720+
* resolving `this()` calls inside each `fn` that has a thisCallBinding.
721+
*/
722+
function buildThisCallBindingsPtsPostPass(
723+
ctx: PipelineContext,
724+
getNodeIdStmt: NodeIdStmt,
725+
allEdgeRows: EdgeRowTuple[],
726+
sharedLookup?: CallNodeLookup,
727+
): void {
728+
const filesWithBindings = [...ctx.fileSymbols].filter(
729+
([, symbols]) => symbols.thisCallBindings && symbols.thisCallBindings.length > 0,
730+
);
731+
if (filesWithBindings.length === 0) return;
732+
733+
const seenByPair = new Set<string>();
734+
for (const [srcId, tgtId] of allEdgeRows) {
735+
seenByPair.add(`${srcId}|${tgtId}`);
736+
}
737+
738+
const { barrelOnlyFiles, rootDir } = ctx;
739+
const lookup = sharedLookup ?? makeContextLookup(ctx, getNodeIdStmt);
740+
741+
for (const [relPath, symbols] of filesWithBindings) {
742+
if (barrelOnlyFiles.has(relPath)) continue;
743+
const fileNodeRow = getNodeIdStmt.get(relPath, 'file', relPath, 0);
744+
if (!fileNodeRow) continue;
745+
746+
const importedNames = buildImportedNamesMap(ctx, relPath, symbols, rootDir);
747+
const typeMap: Map<string, TypeMapEntry | string> = symbols.typeMap || new Map();
748+
const ptsMap = buildPointsToMapForFile(symbols, importedNames);
749+
if (!ptsMap) continue;
750+
751+
// Only process calls named 'this' (callee-not-receiver usage)
752+
for (const call of symbols.calls) {
753+
if (call.name !== 'this' || call.receiver) continue;
754+
755+
const caller = findCaller(lookup, call, symbols.definitions, relPath, fileNodeRow);
756+
if (caller.callerName == null) continue;
757+
758+
const scopedKey = `${caller.callerName}::this`;
759+
if (!ptsMap.has(scopedKey)) continue;
760+
761+
for (const alias of resolveViaPointsTo(scopedKey, ptsMap)) {
762+
const { targets: aliasTargets, importedFrom: aliasFrom } = resolveCallTargets(
763+
lookup,
764+
{ name: alias },
765+
relPath,
766+
importedNames,
767+
typeMap as Map<string, unknown>,
768+
);
769+
for (const t of aliasTargets) {
770+
const edgeKey = `${caller.id}|${t.id}`;
771+
if (t.id !== caller.id && !seenByPair.has(edgeKey)) {
772+
const conf =
773+
computeConfidence(relPath, t.file, aliasFrom ?? null) - PROPAGATION_HOP_PENALTY;
774+
if (conf > 0) {
775+
seenByPair.add(edgeKey);
776+
allEdgeRows.push([caller.id, t.id, 'calls', conf, 0, 'points-to']);
777+
}
778+
}
779+
}
780+
}
781+
}
782+
}
783+
}
784+
712785
/**
713786
* Phase 8.3f post-pass for the native call-edge path.
714787
*
@@ -1144,6 +1217,7 @@ function buildPointsToMapForFile(
11441217
symbols: ExtractorOutput,
11451218
importedNames: Map<string, string>,
11461219
): PointsToMap | null {
1220+
const hasThisCallBindings = !!symbols.thisCallBindings?.length;
11471221
if (
11481222
!symbols.fnRefBindings?.length &&
11491223
!symbols.paramBindings?.length &&
@@ -1152,7 +1226,8 @@ function buildPointsToMapForFile(
11521226
!symbols.forOfBindings?.length &&
11531227
!symbols.arrayCallbackBindings?.length &&
11541228
!symbols.objectRestParamBindings?.length &&
1155-
!symbols.objectPropBindings?.length
1229+
!symbols.objectPropBindings?.length &&
1230+
!hasThisCallBindings
11561231
)
11571232
return null;
11581233
const defNames = new Set(
@@ -1161,8 +1236,21 @@ function buildPointsToMapForFile(
11611236
.map((d) => d.name),
11621237
);
11631238
const definitionParams = buildDefinitionParamsMap(symbols.definitions);
1239+
1240+
// Convert thisCallBindings into scoped fnRefBindings: `fn::this → namedCtx`.
1241+
// The scoped key `fn::this` is looked up when `this()` calls are resolved inside
1242+
// function `fn` — caller.callerName='fn', call.name='this' → scopedPtsKey='fn::this'.
1243+
let allFnRefBindings: readonly FnRefBinding[] = symbols.fnRefBindings ?? [];
1244+
if (hasThisCallBindings) {
1245+
const extra: FnRefBinding[] = (symbols.thisCallBindings ?? []).map((b) => ({
1246+
lhs: `${b.callee}::this`,
1247+
rhs: b.thisArg,
1248+
}));
1249+
allFnRefBindings = [...allFnRefBindings, ...extra];
1250+
}
1251+
11641252
return buildPointsToMap(
1165-
symbols.fnRefBindings ?? [],
1253+
allFnRefBindings,
11661254
defNames,
11671255
importedNames,
11681256
symbols.paramBindings,
@@ -1835,6 +1923,9 @@ export async function buildEdges(ctx: PipelineContext): Promise<void> {
18351923
// (e.g. `const f = fn.bind(ctx)`), so calls to bind-created aliases are
18361924
// not resolved to their original function on the native path.
18371925
buildFnRefBindingsPtsPostPass(ctx, getNodeIdStmt, allEdgeRows, sharedLookup);
1926+
// this-rebinding post-pass: resolve `this()` calls inside functions that
1927+
// were invoked via `.call(namedCtx, ...)` / `.apply(namedCtx, ...)`.
1928+
buildThisCallBindingsPtsPostPass(ctx, getNodeIdStmt, allEdgeRows, sharedLookup);
18381929
// Phase 8.3f post-pass: augment native call edges with object rest-param
18391930
// receiver resolution — typeMap[restName] → argName → typeMap[argName.method].
18401931
buildObjectRestParamPostPass(ctx, getNodeIdStmt, allEdgeRows, sharedLookup);

0 commit comments

Comments
 (0)