feat: Phase 4 — known limitation tests, match arm fix, final verification

- Fix Rust match_arm pattern extraction: unwrap match_pattern to get
  tuple_struct_pattern inside (tree-sitter-rust wraps in match_pattern node)
- Add first-writer-wins regression test for match arm scope leakage
- Add 5 documented skip tests for known limitations:
  - TS destructured for-of (tuple destructuring)
  - Python tuple unpacking in for-loops
  - TS instanceof narrowing (block-level scoping)
  - Rust for with .iter() (method call iterable)
  - Ruby block parameters (closure param inference)

Final: 238 passed, 5 skipped (documented limitations), tsc clean
This commit is contained in:
Gergo Magyar 2026-03-16 22:06:27 +00:00
parent 104f9cd311
commit caa3310714
2 changed files with 45 additions and 2 deletions

View file

@ -222,9 +222,13 @@ const extractPatternBinding: PatternBindingExtractor = (
patternNode = node.childForFieldName('pattern');
valueNode = node.childForFieldName('value');
} else if (node.type === 'match_arm') {
// match_arm → pattern is in the 'pattern' field
// match_arm → pattern field is match_pattern wrapping the actual pattern
const matchPatternNode = node.childForFieldName('pattern');
// Unwrap match_pattern to get the tuple_struct_pattern inside
patternNode = matchPatternNode?.type === 'match_pattern'
? matchPatternNode.firstNamedChild
: matchPatternNode;
// source variable is in the parent match_expression's 'value' field
patternNode = node.childForFieldName('pattern');
const matchExpr = node.parent?.parent; // match_arm → match_block → match_expression
if (matchExpr?.type === 'match_expression') {
valueNode = matchExpr.childForFieldName('value');

View file

@ -2927,4 +2927,43 @@ class Foo {
expect(flatGet(env, 'user')).toBeUndefined();
});
});
describe('known limitations (documented skip tests)', () => {
it.skip('TS destructured for-of: for (const [k, v] of entries) — requires tuple destructuring', () => {
// extractVarName returns undefined for array_pattern
});
it.skip('Python tuple unpacking: for key, value in dict.items() — requires pattern_list', () => {
// left is pattern_list, not identifier
});
it.skip('TS instanceof narrowing: if (x instanceof User) { x.save() } — needs block-level scoping', () => {
// Narrows existing variable, does not introduce new one
});
it.skip('Rust for with .iter(): for user in users.iter() — needs method call iterable resolution', () => {
// Iterable is a call_expression, not an identifier
});
it.skip('Ruby block parameter: users.each { |user| } — closure param inference, different feature', () => {
// Not a for-loop; requires block/closure parameter inference
});
});
describe('match arm scoping — first-writer-wins regression', () => {
it('Rust: first match arm binding wins, later arms do not overwrite', () => {
const tree = parse(`
fn process(opt: Option<User>) {
match opt {
Some(user) => user.save(),
None => {},
}
}
`, Rust);
const { env } = buildTypeEnv(tree, 'rust');
// user should be typed from the first arm (Some unwrap)
// Known limitation: binding leaks across arms (first-writer-wins)
expect(flatGet(env, 'user')).toBe('User');
});
});
});