mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-09-06 08:16:02 +00:00
fix(zig): gate bare literal branches; AND the flag over deduplicated free-call sites
PR #3161 review, two findings: 1. `stampZigStaticGating` returned early when the file declared no boolean constants, but `collectZigStaticGatedRanges` also folds bare literals, so `if (false) { foo(); }` in a constant-free file went unstamped. The early return is gone; the range walk runs for every file. 2. `emitFreeCallFallback` deduplicates CALLS edges per (caller, callee) and wrote `staticGated` from whichever site it met first, so a callee reached from one live site and one dead site was gated or not by traversal order. Emission is now deferred to the end of each file's sites and the flag is the AND over every site that collapsed into the edge: one live site keeps the edge live. The other emit path keys its dedup on the site range and was not affected; `collapseByCallerTarget` in the generic bridge would have the same shape but no language that sets the marker opts into it. Fixture + tests: `gated_bare_literal`, `live_and_gated_same_callee` (live site first) and `gated_then_live_same_callee` (dead site first) in zig-static-gating.test.ts. All 70 resolver suites (3,603 tests) pass with the shared emitter change. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015ciQ3MTjpQXkCoNntZR9zG
This commit is contained in:
parent
3ec546be62
commit
f495496383
4 changed files with 80 additions and 16 deletions
|
|
@ -34,8 +34,9 @@ const NO_IMPORT_ALIASES: ZigImportAliasMap = new Map();
|
|||
* cross-file alias walk in `zig-static-gating.ts` needs the repo file list,
|
||||
* which the capture layer does not see. */
|
||||
function stampZigStaticGating(out: readonly CaptureMatch[], root: SyntaxNode): CaptureMatch[] {
|
||||
// No early return on an empty constant table: `collectZigStaticGatedRanges`
|
||||
// also folds bare literals (`if (false) { ... }`), which need no constants.
|
||||
const bools = buildZigBoolConstMap(root);
|
||||
if (bools.size === 0) return [...out];
|
||||
const ranges = collectZigStaticGatedRanges(root, bools, NO_IMPORT_ALIASES, () => undefined);
|
||||
if (ranges.length === 0) return [...out];
|
||||
return out.map((m) => {
|
||||
|
|
|
|||
|
|
@ -183,6 +183,8 @@ export function emitFreeCallFallback(
|
|||
};
|
||||
|
||||
for (const parsed of parsedFiles) {
|
||||
type PendingRel = { rel: Parameters<KnowledgeGraph['addRelationship']>[0]; gatedAll: boolean };
|
||||
const pending = new Map<string, PendingRel>();
|
||||
const bindingCandidatesByScope =
|
||||
options.freeCallsRequireInstanceOwnership === true
|
||||
? new Map<ScopeId, Map<string, readonly CallableBindingCandidate[]>>()
|
||||
|
|
@ -616,26 +618,40 @@ export function emitFreeCallFallback(
|
|||
tgtGraphId,
|
||||
);
|
||||
const relId = `rel:CALLS:${callerGraphId}->${tgtGraphId}`;
|
||||
// One edge per (caller, callee): `staticGated` is the AND over every site
|
||||
// that collapses into it, so a callee reached from one live site and one
|
||||
// dead site stays live whichever site the walk meets first. Emission is
|
||||
// deferred to the end of this file's sites for that reason.
|
||||
const pendingRel = pending.get(relId);
|
||||
if (pendingRel !== undefined) {
|
||||
if (site.staticGated !== true) pendingRel.gatedAll = false;
|
||||
continue;
|
||||
}
|
||||
if (seen.has(relId)) continue;
|
||||
seen.add(relId);
|
||||
graph.addRelationship({
|
||||
id: relId,
|
||||
sourceId: callerGraphId,
|
||||
targetId: tgtGraphId,
|
||||
type: 'CALLS',
|
||||
confidence: 0.85,
|
||||
// Match legacy DAG's reason convention so consumers that
|
||||
// assert `reason === 'import-resolved'` keep working. The
|
||||
// construction-site marker is opt-in for the same reason.
|
||||
reason: constructionSiteReason(
|
||||
fnDef.filePath !== parsed.filePath ? 'import-resolved' : 'local-call',
|
||||
site,
|
||||
options.markConstructionSites,
|
||||
),
|
||||
...(site.staticGated === true ? { staticGated: true } : {}),
|
||||
pending.set(relId, {
|
||||
gatedAll: site.staticGated === true,
|
||||
rel: {
|
||||
id: relId,
|
||||
sourceId: callerGraphId,
|
||||
targetId: tgtGraphId,
|
||||
type: 'CALLS',
|
||||
confidence: 0.85,
|
||||
// Match legacy DAG's reason convention so consumers that
|
||||
// assert `reason === 'import-resolved'` keep working. The
|
||||
// construction-site marker is opt-in for the same reason.
|
||||
reason: constructionSiteReason(
|
||||
fnDef.filePath !== parsed.filePath ? 'import-resolved' : 'local-call',
|
||||
site,
|
||||
options.markConstructionSites,
|
||||
),
|
||||
},
|
||||
});
|
||||
emitted++;
|
||||
}
|
||||
for (const { rel, gatedAll } of pending.values()) {
|
||||
graph.addRelationship(gatedAll ? { ...rel, staticGated: true } : rel);
|
||||
}
|
||||
}
|
||||
return emitted;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -162,6 +162,22 @@ pub fn run() void {
|
|||
if (cfg.NOT_A_BOOL != 0) {
|
||||
live_cross_file_not_bool();
|
||||
}
|
||||
|
||||
// Bare literal gate: no constant table involved, must still be gated
|
||||
// (PR #3161 review, finding 1).
|
||||
if (false) {
|
||||
gated_bare_literal();
|
||||
}
|
||||
|
||||
// Same callee reached from a LIVE site and a GATED site in one caller: the
|
||||
// free-call edge is deduplicated per (caller, callee), so the flag must be
|
||||
// the AND over all sites, never whichever site was visited first
|
||||
// (PR #3161 review, finding 2). Live first here, gated first in
|
||||
// `run_gated_first` below.
|
||||
live_and_gated_same_callee();
|
||||
if (UPGRADERS_ENABLED) {
|
||||
live_and_gated_same_callee();
|
||||
}
|
||||
}
|
||||
|
||||
fn live_unconditional() void {
|
||||
|
|
@ -295,3 +311,22 @@ fn live_cross_file_not_bool() void {
|
|||
fn some_runtime_flag() bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
pub fn run_gated_first() void {
|
||||
if (UPGRADERS_ENABLED) {
|
||||
gated_then_live_same_callee();
|
||||
}
|
||||
gated_then_live_same_callee();
|
||||
}
|
||||
|
||||
fn gated_bare_literal() void {
|
||||
_ = 1;
|
||||
}
|
||||
|
||||
fn live_and_gated_same_callee() void {
|
||||
_ = 1;
|
||||
}
|
||||
|
||||
fn gated_then_live_same_callee() void {
|
||||
_ = 1;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,6 +40,18 @@ describe('Zig static-gated edges', () => {
|
|||
expect(isGated('gated_or_both_false')).toBe(true);
|
||||
});
|
||||
|
||||
it('tags a bare `if (false)` gate even when the file declares no bool constants', () => {
|
||||
expect(isGated('gated_bare_literal')).toBe(true);
|
||||
});
|
||||
|
||||
it('keeps a deduplicated edge LIVE when a live site precedes a gated site', () => {
|
||||
expect(isGated('live_and_gated_same_callee')).toBe(false);
|
||||
});
|
||||
|
||||
it('keeps a deduplicated edge LIVE when a gated site precedes a live site', () => {
|
||||
expect(isGated('gated_then_live_same_callee')).toBe(false);
|
||||
});
|
||||
|
||||
it('does NOT tag unconditional calls', () => {
|
||||
expect(isGated('live_unconditional')).toBe(false);
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue