diff --git a/gitnexus/src/core/ingestion/cfg/visitors/swift-harvest.ts b/gitnexus/src/core/ingestion/cfg/visitors/swift-harvest.ts index d0fdf5480..3e96f3770 100644 --- a/gitnexus/src/core/ingestion/cfg/visitors/swift-harvest.ts +++ b/gitnexus/src/core/ingestion/cfg/visitors/swift-harvest.ts @@ -196,6 +196,13 @@ export class SwiftHarvester { if (err) this.declarePattern(err); break; } + case 'switch_pattern': { + // `case let n` / `case (let a, let b)` / `case .some(let v)` — declare + // the value binding(s) so a body use resolves to a real local. + const pat = node.namedChildren.find((c) => c.type === 'pattern'); + if (pat) this.declarePattern(pat); + break; + } default: // Optional binding (`if let` / `while let` / `guard let`): a // `value_binding_pattern` condition followed by a `bound_identifier`. @@ -264,6 +271,19 @@ export class SwiftHarvester { return acc.finish(); } + /** + * MAY-def facts for a `switch_pattern`'s value bindings (`case let n` / + * `case .some(let v)`). The binding only takes effect when the case matches, + * so it is a may-def on the dispatch block — propagated into the case body + * where the bound name is read. + */ + switchPatternFacts(switchPattern: SyntaxNode): StatementFacts { + const acc = new FactAccumulator(switchPattern.startPosition.row + 1); + const pat = switchPattern.namedChildren.find((c) => c.type === 'pattern'); + if (pat) this.conditional(() => this.defPattern(pat, acc)); + return acc.finish(); + } + /** * Facts for a `for item in COLLECTION` head: the loop pattern's leaves are * defs, the iterated collection a use. The `where` guard (if any) is harvested diff --git a/gitnexus/src/core/ingestion/cfg/visitors/swift.ts b/gitnexus/src/core/ingestion/cfg/visitors/swift.ts index 24a4652a2..19bb85a32 100644 --- a/gitnexus/src/core/ingestion/cfg/visitors/swift.ts +++ b/gitnexus/src/core/ingestion/cfg/visitors/swift.ts @@ -616,6 +616,10 @@ class SwiftCfgWalk { // uses onto the dispatch block (a later case tests only when earlier ones // didn't match; any def there is a may-def). for (const entry of entries) { + // `case let n` value bindings are may-defs on the dispatch block (the case + // may not match) — propagated into the body where the name is read. + const pat = this.entryPattern(entry); + if (pat) this.builder.attachFacts(dispatch, this.harvest.switchPatternFacts(pat)); const guard = this.entryGuard(entry); if (guard) this.builder.attachFacts(dispatch, this.harvest.factsConditional(guard)); } @@ -650,6 +654,11 @@ class SwiftCfgWalk { return { entry: dispatch, exits: [switchExit] }; } + /** The `switch_pattern` of a `switch_entry` (absent for `default:`). */ + private entryPattern(entry: SyntaxNode): SyntaxNode | undefined { + return entry.namedChildren.find((c) => c.type === 'switch_pattern'); + } + /** The `where` guard expression of a `switch_entry`, if any. */ private entryGuard(entry: SyntaxNode): SyntaxNode | undefined { let sawWhere = false; diff --git a/gitnexus/test/unit/cfg/swift-visitor.test.ts b/gitnexus/test/unit/cfg/swift-visitor.test.ts index a553eddc7..3944875e7 100644 --- a/gitnexus/test/unit/cfg/swift-visitor.test.ts +++ b/gitnexus/test/unit/cfg/swift-visitor.test.ts @@ -292,6 +292,21 @@ describe('Swift CfgVisitor — def/use harvest', () => { expect(cfgs.length).toBeGreaterThanOrEqual(2); for (const cfg of cfgs) expect(reaches(cfg, cfg.entryIndex, cfg.exitIndex)).toBe(true); }); + + it('switch `case let n` binds n as a (may-)def, not a synthetic global (#2195 P2)', () => { + const cfg = swift.cfgOf( + `func f(x: Int) { switch x { case let n where n > 0: use(n); default: break } }`, + ); + const n = bindingIdx(cfg, 'n'); + // The case value-binding defines n (a may-def — the case may not match); + // the bug left n a synthetic module binding with no def at all. + const defined = cfg.blocks.some((bl) => + bl.statements?.some((s) => s.defs.includes(n) || (s.mayDefs ?? []).includes(n)), + ); + expect(defined).toBe(true); + // use(n) (and the `where n > 0` guard) read n. + expect(cfg.blocks.some((bl) => bl.statements?.some((s) => s.uses.includes(n)))).toBe(true); + }); }); describe('Swift CfgVisitor — functionStartColumn', () => {