mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-08 03:08:13 +00:00
feat: Phase 3 partial — Rust for-loop + C# var foreach Tier 1c
- Rust: add extractForLoopBinding with for_expression support - Handles &users, &mut users via reference_expression unwrapping - extractRustElementTypeFromTypeNode: generic_type, reference_type, slice/array - findRustParamElementType: AST walk with reference/mut pattern unwrapping - 4 unit tests (Vec<User>, &[User], range expr negative, no-annotation negative) - C#: upgrade foreach to handle var (implicit_type) via Tier 1c - extractCSharpElementTypeFromTypeNode: generic_name, array_type, nullable_type - findCSharpParamElementType: AST walk to method_declaration parameters - 3 unit tests (var foreach, explicit type regression, no-annotation negative)
This commit is contained in:
parent
1a90112495
commit
d526ee927c
3 changed files with 270 additions and 11 deletions
|
|
@ -1,6 +1,6 @@
|
|||
import type { SyntaxNode } from '../utils.js';
|
||||
import type { ConstructorBindingScanner, ForLoopExtractor, LanguageTypeConfig, ParameterExtractor, TypeBindingExtractor, PendingAssignmentExtractor, PatternBindingExtractor } from './types.js';
|
||||
import { extractSimpleTypeName, extractVarName, findChildByType, unwrapAwait } from './shared.js';
|
||||
import { extractSimpleTypeName, extractVarName, findChildByType, unwrapAwait, extractGenericTypeArgs, resolveIterableElementType } from './shared.js';
|
||||
|
||||
const DECLARATION_NODE_TYPES: ReadonlySet<string> = new Set([
|
||||
'local_declaration_statement',
|
||||
|
|
@ -131,22 +131,84 @@ const FOR_LOOP_NODE_TYPES: ReadonlySet<string> = new Set([
|
|||
'foreach_statement',
|
||||
]);
|
||||
|
||||
/** C#: foreach (User user in users) — extract loop variable binding */
|
||||
/** Extract element type from a C# type annotation AST node.
|
||||
* Handles generic_name (List<User>), array_type (User[]), nullable_type (?). */
|
||||
const extractCSharpElementTypeFromTypeNode = (typeNode: SyntaxNode): string | undefined => {
|
||||
// generic_name: List<User>, IEnumerable<User> — C# uses generic_name (not generic_type)
|
||||
if (typeNode.type === 'generic_name') {
|
||||
const argList = findChildByType(typeNode, 'type_argument_list');
|
||||
if (argList && argList.namedChildCount >= 1) {
|
||||
const firstArg = argList.namedChild(0);
|
||||
if (firstArg) return extractSimpleTypeName(firstArg);
|
||||
}
|
||||
}
|
||||
// array_type: User[]
|
||||
if (typeNode.type === 'array_type') {
|
||||
const elemNode = typeNode.firstNamedChild;
|
||||
if (elemNode) return extractSimpleTypeName(elemNode);
|
||||
}
|
||||
// nullable_type: unwrap and recurse (List<User>? → List<User> → User)
|
||||
if (typeNode.type === 'nullable_type') {
|
||||
const inner = typeNode.firstNamedChild;
|
||||
if (inner) return extractCSharpElementTypeFromTypeNode(inner);
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
/** Walk up from a foreach to the enclosing method and search parameters. */
|
||||
const findCSharpParamElementType = (iterableName: string, startNode: SyntaxNode): string | undefined => {
|
||||
let current: SyntaxNode | null = startNode.parent;
|
||||
while (current) {
|
||||
if (current.type === 'method_declaration' || current.type === 'local_function_statement') {
|
||||
const paramsNode = current.childForFieldName('parameters');
|
||||
if (paramsNode) {
|
||||
for (let i = 0; i < paramsNode.namedChildCount; i++) {
|
||||
const param = paramsNode.namedChild(i);
|
||||
if (!param || param.type !== 'parameter') continue;
|
||||
const nameNode = param.childForFieldName('name');
|
||||
if (nameNode?.text !== iterableName) continue;
|
||||
const typeNode = param.childForFieldName('type');
|
||||
if (typeNode) return extractCSharpElementTypeFromTypeNode(typeNode);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
current = current.parent;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
/** C#: foreach (User user in users) — extract loop variable binding.
|
||||
* Tier 1c: for `foreach (var user in users)`, resolves element type from iterable. */
|
||||
const extractForLoopBinding: ForLoopExtractor = (
|
||||
node: SyntaxNode,
|
||||
scopeEnv: Map<string, string>,
|
||||
_declarationTypeNodes: ReadonlyMap<string, SyntaxNode>,
|
||||
_scope: string,
|
||||
declarationTypeNodes: ReadonlyMap<string, SyntaxNode>,
|
||||
scope: string,
|
||||
): void => {
|
||||
const typeNode = node.childForFieldName('type');
|
||||
// The loop variable name is in the 'left' field in tree-sitter-c-sharp
|
||||
const nameNode = node.childForFieldName('left');
|
||||
if (!typeNode || !nameNode) return;
|
||||
// Skip 'var' — type would need to be inferred from the collection element type
|
||||
if (typeNode.type === 'implicit_type' && typeNode.text === 'var') return;
|
||||
const typeName = extractSimpleTypeName(typeNode);
|
||||
const varName = extractVarName(nameNode);
|
||||
if (typeName && varName) scopeEnv.set(varName, typeName);
|
||||
if (!varName) return;
|
||||
|
||||
// Explicit type (existing behavior): foreach (User user in users)
|
||||
if (!(typeNode.type === 'implicit_type' && typeNode.text === 'var')) {
|
||||
const typeName = extractSimpleTypeName(typeNode);
|
||||
if (typeName) scopeEnv.set(varName, typeName);
|
||||
return;
|
||||
}
|
||||
|
||||
// Tier 1c: implicit type (var) — resolve from iterable's container type
|
||||
const rightNode = node.childForFieldName('right');
|
||||
if (!rightNode || rightNode.type !== 'identifier') return;
|
||||
const iterableName = rightNode.text;
|
||||
|
||||
const elementType = resolveIterableElementType(
|
||||
iterableName, node, scopeEnv, declarationTypeNodes, scope,
|
||||
extractCSharpElementTypeFromTypeNode, findCSharpParamElementType,
|
||||
);
|
||||
if (elementType) scopeEnv.set(varName, elementType);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import type { SyntaxNode } from '../utils.js';
|
||||
import type { LanguageTypeConfig, ParameterExtractor, TypeBindingExtractor, InitializerExtractor, ClassNameLookup, ConstructorBindingScanner, PendingAssignmentExtractor, PatternBindingExtractor } from './types.js';
|
||||
import { extractSimpleTypeName, extractVarName, hasTypeAnnotation, unwrapAwait, extractGenericTypeArgs } from './shared.js';
|
||||
import type { LanguageTypeConfig, ParameterExtractor, TypeBindingExtractor, InitializerExtractor, ClassNameLookup, ConstructorBindingScanner, PendingAssignmentExtractor, PatternBindingExtractor, ForLoopExtractor } from './types.js';
|
||||
import { extractSimpleTypeName, extractVarName, hasTypeAnnotation, unwrapAwait, extractGenericTypeArgs, resolveIterableElementType } from './shared.js';
|
||||
|
||||
const DECLARATION_NODE_TYPES: ReadonlySet<string> = new Set([
|
||||
'let_declaration',
|
||||
|
|
@ -269,13 +269,113 @@ const extractPatternBinding: PatternBindingExtractor = (
|
|||
return { varName: innerVar, typeName: typeArgs[argIndex] };
|
||||
};
|
||||
|
||||
// --- For-loop Tier 1c ---
|
||||
|
||||
const FOR_LOOP_NODE_TYPES: ReadonlySet<string> = new Set(['for_expression']);
|
||||
|
||||
/** Extract element type from a Rust type annotation AST node.
|
||||
* Handles: generic_type (Vec<User>), reference_type (&[User]), array_type ([User; N]),
|
||||
* slice_type ([User]). For call-graph purposes, strips references (&User → User). */
|
||||
const extractRustElementTypeFromTypeNode = (typeNode: SyntaxNode): string | undefined => {
|
||||
// generic_type: Vec<User>, HashSet<User> — extract first type argument
|
||||
if (typeNode.type === 'generic_type') {
|
||||
const args = extractGenericTypeArgs(typeNode);
|
||||
if (args.length >= 1) return args[0];
|
||||
}
|
||||
// reference_type: &[User] or &Vec<User> — unwrap the reference and recurse
|
||||
if (typeNode.type === 'reference_type') {
|
||||
const inner = typeNode.lastNamedChild;
|
||||
if (inner) return extractRustElementTypeFromTypeNode(inner);
|
||||
}
|
||||
// array_type: [User; N] — element is the first child
|
||||
if (typeNode.type === 'array_type') {
|
||||
const elemNode = typeNode.firstNamedChild;
|
||||
if (elemNode) return extractSimpleTypeName(elemNode);
|
||||
}
|
||||
// slice_type: [User] — element is the first child
|
||||
if (typeNode.type === 'slice_type') {
|
||||
const elemNode = typeNode.firstNamedChild;
|
||||
if (elemNode) return extractSimpleTypeName(elemNode);
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
/** Walk up from a for-loop to the enclosing function_item and search parameters
|
||||
* for one named `iterableName`. Returns the element type from its annotation. */
|
||||
const findRustParamElementType = (iterableName: string, startNode: SyntaxNode): string | undefined => {
|
||||
let current: SyntaxNode | null = startNode.parent;
|
||||
while (current) {
|
||||
if (current.type === 'function_item') {
|
||||
const paramsNode = current.childForFieldName('parameters');
|
||||
if (paramsNode) {
|
||||
for (let i = 0; i < paramsNode.namedChildCount; i++) {
|
||||
const param = paramsNode.namedChild(i);
|
||||
if (!param || param.type !== 'parameter') continue;
|
||||
const nameNode = param.childForFieldName('pattern');
|
||||
if (!nameNode) continue;
|
||||
// Unwrap reference patterns: &users, &mut users
|
||||
let identNode = nameNode;
|
||||
if (identNode.type === 'reference_pattern') {
|
||||
identNode = identNode.lastNamedChild ?? identNode;
|
||||
}
|
||||
if (identNode.type === 'mut_pattern') {
|
||||
identNode = identNode.firstNamedChild ?? identNode;
|
||||
}
|
||||
if (identNode.text !== iterableName) continue;
|
||||
const typeNode = param.childForFieldName('type');
|
||||
if (typeNode) return extractRustElementTypeFromTypeNode(typeNode);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
current = current.parent;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
/** Rust: for user in &users where users has a known container type.
|
||||
* Unwraps reference_expression (&users, &mut users) to get the iterable name. */
|
||||
const extractForLoopBinding: ForLoopExtractor = (
|
||||
node: SyntaxNode,
|
||||
scopeEnv: Map<string, string>,
|
||||
declarationTypeNodes: ReadonlyMap<string, SyntaxNode>,
|
||||
scope: string,
|
||||
): void => {
|
||||
if (node.type !== 'for_expression') return;
|
||||
|
||||
const patternNode = node.childForFieldName('pattern');
|
||||
const valueNode = node.childForFieldName('value');
|
||||
if (!patternNode || !valueNode) return;
|
||||
|
||||
// Extract iterable name — may be &users, &mut users, or plain users
|
||||
let iterableName: string | undefined;
|
||||
if (valueNode.type === 'reference_expression') {
|
||||
const inner = valueNode.lastNamedChild;
|
||||
if (inner?.type === 'identifier') iterableName = inner.text;
|
||||
} else if (valueNode.type === 'identifier') {
|
||||
iterableName = valueNode.text;
|
||||
}
|
||||
if (!iterableName) return;
|
||||
|
||||
const elementType = resolveIterableElementType(
|
||||
iterableName, node, scopeEnv, declarationTypeNodes, scope,
|
||||
extractRustElementTypeFromTypeNode, findRustParamElementType,
|
||||
);
|
||||
if (!elementType) return;
|
||||
|
||||
const loopVarName = extractVarName(patternNode);
|
||||
if (loopVarName) scopeEnv.set(loopVarName, elementType);
|
||||
};
|
||||
|
||||
export const typeConfig: LanguageTypeConfig = {
|
||||
declarationNodeTypes: DECLARATION_NODE_TYPES,
|
||||
forLoopNodeTypes: FOR_LOOP_NODE_TYPES,
|
||||
patternBindingNodeTypes: new Set(['let_condition']),
|
||||
extractDeclaration,
|
||||
extractInitializer,
|
||||
extractParameter,
|
||||
scanConstructorBinding,
|
||||
extractForLoopBinding,
|
||||
extractPendingAssignment,
|
||||
extractPatternBinding,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2748,4 +2748,101 @@ func process() {
|
|||
expect(flatGet(env, 'user')).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('for-loop element type inference (Tier 1c) — Rust', () => {
|
||||
it('infers loop variable from Vec<User> parameter (for user in &users)', () => {
|
||||
const tree = parse(`
|
||||
fn process(users: Vec<User>) {
|
||||
for user in &users {
|
||||
user.save();
|
||||
}
|
||||
}
|
||||
`, Rust);
|
||||
const { env } = buildTypeEnv(tree, 'rust');
|
||||
expect(flatGet(env, 'user')).toBe('User');
|
||||
});
|
||||
|
||||
it('infers loop variable from &[User] slice parameter', () => {
|
||||
const tree = parse(`
|
||||
fn process(users: &[User]) {
|
||||
for user in users {
|
||||
user.save();
|
||||
}
|
||||
}
|
||||
`, Rust);
|
||||
const { env } = buildTypeEnv(tree, 'rust');
|
||||
expect(flatGet(env, 'user')).toBe('User');
|
||||
});
|
||||
|
||||
it('does not infer type for range expression (0..10)', () => {
|
||||
const tree = parse(`
|
||||
fn process() {
|
||||
for i in 0..10 {
|
||||
println!("{}", i);
|
||||
}
|
||||
}
|
||||
`, Rust);
|
||||
const { env } = buildTypeEnv(tree, 'rust');
|
||||
expect(flatGet(env, 'i')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('does not infer type when iterable has no annotation', () => {
|
||||
const tree = parse(`
|
||||
fn process() {
|
||||
let users = get_users();
|
||||
for user in &users {
|
||||
user.save();
|
||||
}
|
||||
}
|
||||
`, Rust);
|
||||
const { env } = buildTypeEnv(tree, 'rust');
|
||||
expect(flatGet(env, 'user')).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('for-loop element type inference (Tier 1c) — C#', () => {
|
||||
it('infers loop variable from var foreach with List<User> parameter', () => {
|
||||
const tree = parse(`
|
||||
using System.Collections.Generic;
|
||||
class Foo {
|
||||
void Process(List<User> users) {
|
||||
foreach (var user in users) {
|
||||
user.Save();
|
||||
}
|
||||
}
|
||||
}
|
||||
`, CSharp);
|
||||
const { env } = buildTypeEnv(tree, 'csharp');
|
||||
expect(flatGet(env, 'user')).toBe('User');
|
||||
});
|
||||
|
||||
it('still resolves explicit type foreach (regression)', () => {
|
||||
const tree = parse(`
|
||||
class Foo {
|
||||
void Process(List<User> users) {
|
||||
foreach (User user in users) {
|
||||
user.Save();
|
||||
}
|
||||
}
|
||||
}
|
||||
`, CSharp);
|
||||
const { env } = buildTypeEnv(tree, 'csharp');
|
||||
expect(flatGet(env, 'user')).toBe('User');
|
||||
});
|
||||
|
||||
it('does not infer type when iterable has no annotation', () => {
|
||||
const tree = parse(`
|
||||
class Foo {
|
||||
void Process() {
|
||||
var users = GetUsers();
|
||||
foreach (var user in users) {
|
||||
user.Save();
|
||||
}
|
||||
}
|
||||
}
|
||||
`, CSharp);
|
||||
const { env } = buildTypeEnv(tree, 'csharp');
|
||||
expect(flatGet(env, 'user')).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue