mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-11 03:38:07 +00:00
fix(cfg): bind Swift switch-case value patterns (case let n) (#2195)
A switch value-binding (case let n where …, case .some(let v)) was never declared in prescan, so n/v resolved to a synthetic module binding and a body use(n) did not link to any def — a very common Swift idiom silently lost its data dependence. Declare the switch_pattern's bindings (prescan, reusing declarePattern) and emit them as MAY-defs on the dispatch block (a case may not match) via a new switchPatternFacts, propagated into the case body. swift suite 25 passed, tsc + grammar gate green. (The rare ?? / ternary-arm may-def — Swift assignment-as-expression — remains a separate follow-up.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
324bde0368
commit
c97d1b2dfc
3 changed files with 44 additions and 0 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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', () => {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue