mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-05 02:43:32 +00:00
feat(csharp-scope): parity Unit 6b — member-call dedup + handled-site fix
Closes 1 parity failure (9 → 8). Adds the missing legacy-parity behavior: collapse multiple member-call sites from the same caller to the same target into one CALLS edge. Changes: - `scope-resolution/contract/scope-resolver.ts`: new optional `collapseMemberCallsByCallerTarget` flag. Default false (preserves the per-site invariant); C# sets it true. - `scope-resolution/graph-bridge/edges.ts`: dedup key drops `line:col` when `collapseByCallerTarget` is on AND edgeType is `CALLS` (ACCESSES writes keep per-site granularity). - `scope-resolution/passes/receiver-bound-calls.ts`: plumbs `collapse` through every `tryEmitEdge` call, and crucially marks `handledSites.add(siteKey)` whenever a resolved def was found — not only when the edge was freshly emitted. Otherwise the site leaked through to `emitReferencesViaLookup` which re-emitted a per-site edge, defeating the collapse. - `languages/csharp/scope-resolver.ts`: opt in to the collapse. Python parity 204/204 on both flag paths; legacy C# 175/175 green; 8 C# parity failures remain.
This commit is contained in:
parent
2de104150e
commit
5e54fee523
4 changed files with 67 additions and 30 deletions
|
|
@ -71,6 +71,11 @@ const csharpScopeResolver: ScopeResolver = {
|
|||
// since signatures are authoritative.
|
||||
fieldFallbackOnMethodLookup: false,
|
||||
propagatesReturnTypesAcrossImports: true,
|
||||
|
||||
// C# matches legacy DAG by collapsing member-call CALLS edges to
|
||||
// `(caller, target)` — multiple `g.Greet(...)` sites from Main
|
||||
// yield ONE edge, not one per site.
|
||||
collapseMemberCallsByCallerTarget: true,
|
||||
};
|
||||
|
||||
export { csharpScopeResolver };
|
||||
|
|
|
|||
|
|
@ -210,6 +210,15 @@ export interface ScopeResolver {
|
|||
*/
|
||||
readonly fieldFallbackOnMethodLookup?: boolean;
|
||||
|
||||
/**
|
||||
* Collapse member-call CALLS edges by `(caller, target)` rather
|
||||
* than per-site. Default `false` (scope-resolution's contract
|
||||
* invariant is per-site dedup). C# enables this to match the
|
||||
* legacy DAG's member-call collapsing where multiple call sites
|
||||
* from the same caller to the same target yield one edge.
|
||||
*/
|
||||
readonly collapseMemberCallsByCallerTarget?: boolean;
|
||||
|
||||
/**
|
||||
* Optional post-finalize hook to inject cross-file bindings that
|
||||
* aren't modeled via explicit imports. C# uses this to make every
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@ export function tryEmitEdge(
|
|||
reason: string,
|
||||
seen: Set<string>,
|
||||
confidence = 0.85,
|
||||
collapseByCallerTarget = false,
|
||||
): boolean {
|
||||
const callerGraphId = resolveCallerGraphId(site.inScope, scopes, nodeLookup);
|
||||
const targetGraphId = resolveDefGraphId(targetDef.filePath, targetDef, nodeLookup);
|
||||
|
|
@ -76,7 +77,14 @@ export function tryEmitEdge(
|
|||
if (targetGraphId === undefined) return false;
|
||||
if (edgeType === undefined) return false;
|
||||
|
||||
const dedupKey = `${edgeType}:${callerGraphId}->${targetGraphId}:${site.atRange.startLine}:${site.atRange.startCol}`;
|
||||
// CALLS edges may collapse to `(caller, target)` granularity when
|
||||
// the provider opts in (C# matches legacy DAG behavior this way).
|
||||
// Write/read ACCESSES keep per-site dedup so multiple writes to the
|
||||
// same field on different lines produce distinct edges.
|
||||
const useCollapsed = collapseByCallerTarget && edgeType === 'CALLS';
|
||||
const dedupKey = useCollapsed
|
||||
? `${edgeType}:${callerGraphId}->${targetGraphId}`
|
||||
: `${edgeType}:${callerGraphId}->${targetGraphId}:${site.atRange.startLine}:${site.atRange.startCol}`;
|
||||
if (seen.has(dedupKey)) return false;
|
||||
seen.add(dedupKey);
|
||||
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ import { resolveCompoundReceiverClass } from '../passes/compound-receiver.js';
|
|||
* refactors lighter — callers only need to populate what we read. */
|
||||
type ReceiverBoundProviderSubset = Pick<
|
||||
ScopeResolver,
|
||||
'isSuperReceiver' | 'fieldFallbackOnMethodLookup'
|
||||
'isSuperReceiver' | 'fieldFallbackOnMethodLookup' | 'collapseMemberCallsByCallerTarget'
|
||||
>;
|
||||
|
||||
export function emitReceiverBoundCalls(
|
||||
|
|
@ -69,6 +69,7 @@ export function emitReceiverBoundCalls(
|
|||
// from the reference index — see Contract Invariant I5.
|
||||
const seen = new Set<string>();
|
||||
const fieldFallback = provider.fieldFallbackOnMethodLookup ?? true;
|
||||
const collapse = provider.collapseMemberCallsByCallerTarget === true;
|
||||
|
||||
for (const parsed of parsedFiles) {
|
||||
const namespaceTargets = collectNamespaceTargets(parsed, scopes);
|
||||
|
|
@ -100,11 +101,15 @@ export function emitReceiverBoundCalls(
|
|||
memberDef,
|
||||
'scope-resolution: super-receiver',
|
||||
seen,
|
||||
0.85,
|
||||
collapse,
|
||||
);
|
||||
if (ok) {
|
||||
emitted++;
|
||||
handledSites.add(siteKey);
|
||||
}
|
||||
if (ok) emitted++;
|
||||
// Always mark handled when the site was resolved, even
|
||||
// if the edge was deduplicated (collapse mode), so
|
||||
// `emitReferencesViaLookup` doesn't re-emit from the
|
||||
// reference index.
|
||||
handledSites.add(siteKey);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
|
@ -135,11 +140,15 @@ export function emitReceiverBoundCalls(
|
|||
memberDef,
|
||||
memberDef.filePath !== parsed.filePath ? 'import-resolved' : 'global',
|
||||
seen,
|
||||
0.85,
|
||||
collapse,
|
||||
);
|
||||
if (ok) {
|
||||
emitted++;
|
||||
handledSites.add(siteKey);
|
||||
}
|
||||
if (ok) emitted++;
|
||||
// Always mark handled when the site was resolved, even
|
||||
// if the edge was deduplicated (collapse mode), so
|
||||
// `emitReferencesViaLookup` doesn't re-emit from the
|
||||
// reference index.
|
||||
handledSites.add(siteKey);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
|
@ -158,11 +167,11 @@ export function emitReceiverBoundCalls(
|
|||
memberDef,
|
||||
memberDef.filePath !== parsed.filePath ? 'import-resolved' : 'global',
|
||||
seen,
|
||||
0.85,
|
||||
collapse,
|
||||
);
|
||||
if (ok) {
|
||||
emitted++;
|
||||
handledSites.add(siteKey);
|
||||
}
|
||||
if (ok) emitted++;
|
||||
handledSites.add(siteKey);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
|
@ -185,11 +194,11 @@ export function emitReceiverBoundCalls(
|
|||
memberDef,
|
||||
memberDef.filePath !== parsed.filePath ? 'import-resolved' : 'global',
|
||||
seen,
|
||||
0.85,
|
||||
collapse,
|
||||
);
|
||||
if (ok) {
|
||||
emitted++;
|
||||
handledSites.add(siteKey);
|
||||
}
|
||||
if (ok) emitted++;
|
||||
handledSites.add(siteKey);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
|
@ -254,11 +263,15 @@ export function emitReceiverBoundCalls(
|
|||
memberDef,
|
||||
memberDef.filePath !== parsed.filePath ? 'import-resolved' : 'global',
|
||||
seen,
|
||||
0.85,
|
||||
collapse,
|
||||
);
|
||||
if (ok) {
|
||||
emitted++;
|
||||
handledSites.add(siteKey);
|
||||
}
|
||||
if (ok) emitted++;
|
||||
// Always mark handled when the site was resolved, even
|
||||
// if the edge was deduplicated (collapse mode), so
|
||||
// `emitReferencesViaLookup` doesn't re-emit from the
|
||||
// reference index.
|
||||
handledSites.add(siteKey);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
|
@ -294,11 +307,14 @@ export function emitReceiverBoundCalls(
|
|||
reason,
|
||||
seen,
|
||||
confidence,
|
||||
collapse,
|
||||
);
|
||||
if (ok) {
|
||||
emitted++;
|
||||
handledSites.add(siteKey);
|
||||
}
|
||||
if (ok) emitted++;
|
||||
// Always mark handled when the site was resolved, even
|
||||
// if the edge was deduplicated (collapse mode), so
|
||||
// `emitReferencesViaLookup` doesn't re-emit from the
|
||||
// reference index.
|
||||
handledSites.add(siteKey);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
|
@ -336,11 +352,10 @@ export function emitReceiverBoundCalls(
|
|||
reason,
|
||||
seen,
|
||||
confidence,
|
||||
collapse,
|
||||
);
|
||||
if (ok) {
|
||||
emitted++;
|
||||
handledSites.add(siteKey);
|
||||
}
|
||||
if (ok) emitted++;
|
||||
handledSites.add(siteKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue