mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-08 03:08:13 +00:00
refactor: Phase 2 architecture — shared helper, required params, decoupled type nodes
- Extract resolveIterableElementType shared helper in shared.ts implementing 3-strategy fallback (declarationTypeNodes → scopeEnv string → AST walk) - Refactor TS, Python, Go extractors to use shared helper (eliminates 3x duplication) - Make ForLoopExtractor params required (aligned with PatternBindingExtractor) - Update Java, Kotlin, C# extractor signatures to accept required params - Decouple declarationTypeNodes from scopeEnv — capture raw type annotation nodes BEFORE extractDeclaration for container types (User[], []User, List[User]) - Hybrid approach: direct name extraction + keysBefore fallback for multi-declarator - Document declarationTypeNodes invariant change (superset of scopeEnv)
This commit is contained in:
parent
96cbe8cdae
commit
1a90112495
8 changed files with 115 additions and 90 deletions
|
|
@ -3,7 +3,7 @@ import { FUNCTION_NODE_TYPES, extractFunctionName, CLASS_CONTAINER_TYPES } from
|
|||
import { SupportedLanguages } from '../../config/supported-languages.js';
|
||||
import { typeConfigs, TYPED_PARAMETER_TYPES } from './type-extractors/index.js';
|
||||
import type { ClassNameLookup } from './type-extractors/types.js';
|
||||
import { extractSimpleTypeName, stripNullable } from './type-extractors/shared.js';
|
||||
import { extractSimpleTypeName, extractVarName, stripNullable } from './type-extractors/shared.js';
|
||||
import type { SymbolTable } from './symbol-table.js';
|
||||
|
||||
/**
|
||||
|
|
@ -297,6 +297,9 @@ export const buildTypeEnv = (
|
|||
// Maps `scope\0varName` → the type annotation AST node from the original declaration.
|
||||
// Allows pattern extractors to navigate back to the declaration's generic type arguments
|
||||
// (e.g., to extract T from Result<T, E> for `if let Ok(x) = res`).
|
||||
// NOTE: This is a SUPERSET of scopeEnv — entries exist even when extractSimpleTypeName
|
||||
// returns undefined for container types (User[], []User, List[User]). This is intentional:
|
||||
// for-loop Strategy 1 needs the raw AST type node for exactly those container types.
|
||||
const declarationTypeNodes = new Map<string, SyntaxNode>();
|
||||
|
||||
/**
|
||||
|
|
@ -314,17 +317,19 @@ export const buildTypeEnv = (
|
|||
const extractTypeBinding = (node: SyntaxNode, scopeEnv: Map<string, string>, scope: string): void => {
|
||||
// This guard eliminates 90%+ of calls before any language dispatch.
|
||||
if (TYPED_PARAMETER_TYPES.has(node.type)) {
|
||||
const keysBefore = new Set(scopeEnv.keys());
|
||||
config.extractParameter(node, scopeEnv);
|
||||
// Capture the type node for newly introduced parameter bindings
|
||||
// Capture the raw type annotation BEFORE extractParameter — parameters
|
||||
// consistently expose 'name' and 'type' fields across all languages.
|
||||
const typeNode = node.childForFieldName('type');
|
||||
if (typeNode) {
|
||||
for (const varName of scopeEnv.keys()) {
|
||||
if (!keysBefore.has(varName)) {
|
||||
const nameNode = node.childForFieldName('name');
|
||||
if (nameNode) {
|
||||
const varName = extractVarName(nameNode);
|
||||
if (varName && !declarationTypeNodes.has(`${scope}\0${varName}`)) {
|
||||
declarationTypeNodes.set(`${scope}\0${varName}`, typeNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
config.extractParameter(node, scopeEnv);
|
||||
return;
|
||||
}
|
||||
// For-each loop variable bindings (Java/C#/Kotlin): explicit element types in the AST.
|
||||
|
|
@ -334,15 +339,31 @@ export const buildTypeEnv = (
|
|||
return;
|
||||
}
|
||||
if (config.declarationNodeTypes.has(node.type)) {
|
||||
const keysBefore = new Set(scopeEnv.keys());
|
||||
config.extractDeclaration(node, scopeEnv);
|
||||
// Capture the type annotation AST node for newly introduced bindings.
|
||||
// Only declarations with an explicit 'type' field are recorded — constructor
|
||||
// inferences (Tier 1) don't have a type annotation node to preserve.
|
||||
// Capture the raw type annotation AST node BEFORE extractDeclaration.
|
||||
// This decouples type node capture from scopeEnv success — container types
|
||||
// (User[], []User, List[User]) that fail extractSimpleTypeName still get
|
||||
// their AST type node recorded for Strategy 1 for-loop resolution.
|
||||
// Try direct extraction first (works for Go var_spec, Python assignment, Rust let_declaration).
|
||||
const typeNode = node.childForFieldName('type');
|
||||
if (typeNode) {
|
||||
const nameNode = node.childForFieldName('name')
|
||||
?? node.childForFieldName('left')
|
||||
?? node.childForFieldName('pattern');
|
||||
if (nameNode) {
|
||||
const varName = extractVarName(nameNode);
|
||||
if (varName && !declarationTypeNodes.has(`${scope}\0${varName}`)) {
|
||||
declarationTypeNodes.set(`${scope}\0${varName}`, typeNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Run the language-specific declaration extractor (may or may not add to scopeEnv).
|
||||
const keysBefore = new Set(scopeEnv.keys());
|
||||
config.extractDeclaration(node, scopeEnv);
|
||||
// Fallback: for multi-declarator languages (TS, C#, Java) where the type field
|
||||
// is on variable_declarator children, capture via keysBefore/keysAfter diff.
|
||||
if (typeNode) {
|
||||
for (const varName of scopeEnv.keys()) {
|
||||
if (!keysBefore.has(varName)) {
|
||||
if (!keysBefore.has(varName) && !declarationTypeNodes.has(`${scope}\0${varName}`)) {
|
||||
declarationTypeNodes.set(`${scope}\0${varName}`, typeNode);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -132,7 +132,12 @@ const FOR_LOOP_NODE_TYPES: ReadonlySet<string> = new Set([
|
|||
]);
|
||||
|
||||
/** C#: foreach (User user in users) — extract loop variable binding */
|
||||
const extractForLoopBinding: ForLoopExtractor = (node: SyntaxNode, scopeEnv: Map<string, string>): void => {
|
||||
const extractForLoopBinding: ForLoopExtractor = (
|
||||
node: SyntaxNode,
|
||||
scopeEnv: Map<string, 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');
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import type { SyntaxNode } from '../utils.js';
|
||||
import type { ConstructorBindingScanner, ForLoopExtractor, LanguageTypeConfig, ParameterExtractor, TypeBindingExtractor, PendingAssignmentExtractor } from './types.js';
|
||||
import { extractSimpleTypeName, extractVarName, extractElementTypeFromString, findChildByType } from './shared.js';
|
||||
import { extractSimpleTypeName, extractVarName, extractElementTypeFromString, findChildByType, resolveIterableElementType } from './shared.js';
|
||||
|
||||
const DECLARATION_NODE_TYPES: ReadonlySet<string> = new Set([
|
||||
'var_declaration',
|
||||
|
|
@ -283,8 +283,8 @@ const findGoParamElementType = (iterableName: string, startNode: SyntaxNode): st
|
|||
const extractForLoopBinding: ForLoopExtractor = (
|
||||
node: SyntaxNode,
|
||||
scopeEnv: Map<string, string>,
|
||||
declarationTypeNodes?: ReadonlyMap<string, SyntaxNode>,
|
||||
scope?: string,
|
||||
declarationTypeNodes: ReadonlyMap<string, SyntaxNode>,
|
||||
scope: string,
|
||||
): void => {
|
||||
if (node.type !== 'for_statement') return;
|
||||
|
||||
|
|
@ -304,27 +304,10 @@ const extractForLoopBinding: ForLoopExtractor = (
|
|||
if (!rightNode || rightNode.type !== 'identifier') return;
|
||||
const iterableName = rightNode.text;
|
||||
|
||||
let elementType: string | undefined;
|
||||
|
||||
// Strategy 1: declarationTypeNodes — raw type annotation node (covers var decls with known types)
|
||||
if (!elementType && declarationTypeNodes && scope) {
|
||||
const typeAnnotationNode = declarationTypeNodes.get(`${scope}\0${iterableName}`);
|
||||
if (typeAnnotationNode) {
|
||||
elementType = extractGoElementTypeFromTypeNode(typeAnnotationNode);
|
||||
}
|
||||
}
|
||||
|
||||
// Strategy 2: scopeEnv string — for locally declared vars where the type was stored
|
||||
if (!elementType) {
|
||||
const iterableType = scopeEnv.get(iterableName);
|
||||
if (iterableType) elementType = extractElementTypeFromString(iterableType);
|
||||
}
|
||||
|
||||
// Strategy 3: AST walk — for []User parameters where extractSimpleTypeName returned undefined
|
||||
if (!elementType) {
|
||||
elementType = findGoParamElementType(iterableName, node);
|
||||
}
|
||||
|
||||
const elementType = resolveIterableElementType(
|
||||
iterableName, node, scopeEnv, declarationTypeNodes, scope,
|
||||
extractGoElementTypeFromTypeNode, findGoParamElementType,
|
||||
);
|
||||
if (!elementType) return;
|
||||
|
||||
// The loop variable(s) are in the `left` field.
|
||||
|
|
|
|||
|
|
@ -90,7 +90,12 @@ const JAVA_FOR_LOOP_NODE_TYPES: ReadonlySet<string> = new Set([
|
|||
]);
|
||||
|
||||
/** Java: for (User user : users) — extract loop variable binding */
|
||||
const extractJavaForLoopBinding: ForLoopExtractor = (node: SyntaxNode, scopeEnv: Map<string, string>): void => {
|
||||
const extractJavaForLoopBinding: ForLoopExtractor = (
|
||||
node: SyntaxNode,
|
||||
scopeEnv: Map<string, string>,
|
||||
_declarationTypeNodes: ReadonlyMap<string, SyntaxNode>,
|
||||
_scope: string,
|
||||
): void => {
|
||||
const typeNode = node.childForFieldName('type');
|
||||
const nameNode = node.childForFieldName('name');
|
||||
if (!typeNode || !nameNode) return;
|
||||
|
|
@ -281,7 +286,12 @@ const KOTLIN_FOR_LOOP_NODE_TYPES: ReadonlySet<string> = new Set([
|
|||
]);
|
||||
|
||||
/** Kotlin: for (user: User in users) — extract loop variable binding when explicit type annotation exists */
|
||||
const extractKotlinForLoopBinding: ForLoopExtractor = (node: SyntaxNode, scopeEnv: Map<string, string>): void => {
|
||||
const extractKotlinForLoopBinding: ForLoopExtractor = (
|
||||
node: SyntaxNode,
|
||||
scopeEnv: Map<string, string>,
|
||||
_declarationTypeNodes: ReadonlyMap<string, SyntaxNode>,
|
||||
_scope: string,
|
||||
): void => {
|
||||
// Kotlin loop variable: variable_declaration child with optional user_type annotation
|
||||
const varDecl = findChildByType(node, 'variable_declaration');
|
||||
if (!varDecl) return;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import type { SyntaxNode } from '../utils.js';
|
||||
import type { LanguageTypeConfig, ParameterExtractor, TypeBindingExtractor, InitializerExtractor, ClassNameLookup, ConstructorBindingScanner, PendingAssignmentExtractor, PatternBindingExtractor, ForLoopExtractor } from './types.js';
|
||||
import { extractSimpleTypeName, extractVarName, extractElementTypeFromString, extractGenericTypeArgs } from './shared.js';
|
||||
import { extractSimpleTypeName, extractVarName, extractElementTypeFromString, extractGenericTypeArgs, resolveIterableElementType } from './shared.js';
|
||||
|
||||
const DECLARATION_NODE_TYPES: ReadonlySet<string> = new Set([
|
||||
'assignment',
|
||||
|
|
@ -215,8 +215,8 @@ const findPyParamElementType = (iterableName: string, startNode: SyntaxNode): st
|
|||
const extractForLoopBinding: ForLoopExtractor = (
|
||||
node: SyntaxNode,
|
||||
scopeEnv: Map<string, string>,
|
||||
declarationTypeNodes?: ReadonlyMap<string, SyntaxNode>,
|
||||
scope?: string,
|
||||
declarationTypeNodes: ReadonlyMap<string, SyntaxNode>,
|
||||
scope: string,
|
||||
): void => {
|
||||
if (node.type !== 'for_statement') return;
|
||||
|
||||
|
|
@ -225,27 +225,10 @@ const extractForLoopBinding: ForLoopExtractor = (
|
|||
if (!rightNode || rightNode.type !== 'identifier') return;
|
||||
const iterableName = rightNode.text;
|
||||
|
||||
let elementType: string | undefined;
|
||||
|
||||
// Strategy 1: declarationTypeNodes — raw type annotation node
|
||||
if (!elementType && declarationTypeNodes && scope) {
|
||||
const typeAnnotationNode = declarationTypeNodes.get(`${scope}\0${iterableName}`);
|
||||
if (typeAnnotationNode) {
|
||||
elementType = extractPyElementTypeFromAnnotation(typeAnnotationNode);
|
||||
}
|
||||
}
|
||||
|
||||
// Strategy 2: scopeEnv string — for locally declared vars with container type strings
|
||||
if (!elementType) {
|
||||
const iterableType = scopeEnv.get(iterableName);
|
||||
if (iterableType) elementType = extractElementTypeFromString(iterableType);
|
||||
}
|
||||
|
||||
// Strategy 3: AST walk — for List[User] parameters where extractSimpleTypeName returned undefined
|
||||
if (!elementType) {
|
||||
elementType = findPyParamElementType(iterableName, node);
|
||||
}
|
||||
|
||||
const elementType = resolveIterableElementType(
|
||||
iterableName, node, scopeEnv, declarationTypeNodes, scope,
|
||||
extractPyElementTypeFromAnnotation, findPyParamElementType,
|
||||
);
|
||||
if (!elementType) return;
|
||||
|
||||
// The loop variable is the `left` field — a plain identifier.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,43 @@
|
|||
import type { SyntaxNode } from '../utils.js';
|
||||
|
||||
/**
|
||||
* Shared 3-strategy fallback for resolving the element type of a container variable.
|
||||
* Used by all for-loop extractors to resolve the loop variable's type from the iterable.
|
||||
*
|
||||
* Strategy 1: declarationTypeNodes — raw AST type annotation node (handles container types
|
||||
* where extractSimpleTypeName returned undefined, e.g., User[], List[User])
|
||||
* Strategy 2: scopeEnv string — extractElementTypeFromString on the stored type string
|
||||
* Strategy 3: AST walk — language-specific upward walk to enclosing function parameters
|
||||
*
|
||||
* @param extractFromTypeNode Language-specific function to extract element type from AST node
|
||||
* @param findParamElementType Optional language-specific AST walk to find parameter type
|
||||
*/
|
||||
export function resolveIterableElementType(
|
||||
iterableName: string,
|
||||
node: SyntaxNode,
|
||||
scopeEnv: ReadonlyMap<string, string>,
|
||||
declarationTypeNodes: ReadonlyMap<string, SyntaxNode>,
|
||||
scope: string,
|
||||
extractFromTypeNode: (typeNode: SyntaxNode) => string | undefined,
|
||||
findParamElementType?: (name: string, startNode: SyntaxNode) => string | undefined,
|
||||
): string | undefined {
|
||||
// Strategy 1: declarationTypeNodes AST node
|
||||
const typeNode = declarationTypeNodes.get(`${scope}\0${iterableName}`);
|
||||
if (typeNode) {
|
||||
const t = extractFromTypeNode(typeNode);
|
||||
if (t) return t;
|
||||
}
|
||||
// Strategy 2: scopeEnv string → extractElementTypeFromString
|
||||
const iterableType = scopeEnv.get(iterableName);
|
||||
if (iterableType) {
|
||||
const el = extractElementTypeFromString(iterableType);
|
||||
if (el) return el;
|
||||
}
|
||||
// Strategy 3: AST walk to function parameters
|
||||
if (findParamElementType) return findParamElementType(iterableName, node);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/** Known single-arg nullable wrapper types that unwrap to their inner type
|
||||
* for receiver resolution. Optional<User> → "User", Option<User> → "User".
|
||||
* Only nullable wrappers — NOT containers (List, Vec) or async wrappers (Promise, Future).
|
||||
|
|
|
|||
|
|
@ -24,12 +24,14 @@ export type ConstructorBindingScanner = (node: SyntaxNode) => { varName: string;
|
|||
* rather than in AST fields. Returns undefined if no return type can be determined. */
|
||||
export type ReturnTypeExtractor = (node: SyntaxNode) => string | undefined;
|
||||
|
||||
/** Extracts loop variable type binding from a for-each statement. */
|
||||
/** Extracts loop variable type binding from a for-each statement.
|
||||
* All parameters are required (aligned with PatternBindingExtractor convention)
|
||||
* to prevent new extractors from silently ignoring declarationTypeNodes/scope. */
|
||||
export type ForLoopExtractor = (
|
||||
node: SyntaxNode,
|
||||
scopeEnv: Map<string, string>,
|
||||
declarationTypeNodes?: ReadonlyMap<string, SyntaxNode>,
|
||||
scope?: string,
|
||||
declarationTypeNodes: ReadonlyMap<string, SyntaxNode>,
|
||||
scope: string,
|
||||
) => void;
|
||||
|
||||
/** Extracts a plain-identifier assignment for Tier 2 propagation.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import type { SyntaxNode } from '../utils.js';
|
||||
import type { LanguageTypeConfig, ParameterExtractor, TypeBindingExtractor, InitializerExtractor, ClassNameLookup, ConstructorBindingScanner, ReturnTypeExtractor, PendingAssignmentExtractor, ForLoopExtractor } from './types.js';
|
||||
import { extractSimpleTypeName, extractVarName, hasTypeAnnotation, unwrapAwait, extractCalleeName, extractElementTypeFromString, extractGenericTypeArgs } from './shared.js';
|
||||
import { extractSimpleTypeName, extractVarName, hasTypeAnnotation, unwrapAwait, extractCalleeName, extractElementTypeFromString, extractGenericTypeArgs, resolveIterableElementType } from './shared.js';
|
||||
|
||||
const DECLARATION_NODE_TYPES: ReadonlySet<string> = new Set([
|
||||
'lexical_declaration',
|
||||
|
|
@ -314,8 +314,8 @@ const findTsIterableElementType = (iterableName: string, startNode: SyntaxNode):
|
|||
const extractForLoopBinding: ForLoopExtractor = (
|
||||
node: SyntaxNode,
|
||||
scopeEnv: Map<string, string>,
|
||||
declarationTypeNodes?: ReadonlyMap<string, SyntaxNode>,
|
||||
scope?: string,
|
||||
declarationTypeNodes: ReadonlyMap<string, SyntaxNode>,
|
||||
scope: string,
|
||||
): void => {
|
||||
if (node.type !== 'for_in_statement') return;
|
||||
|
||||
|
|
@ -335,27 +335,10 @@ const extractForLoopBinding: ForLoopExtractor = (
|
|||
if (!rightNode || rightNode.type !== 'identifier') return;
|
||||
const iterableName = rightNode.text;
|
||||
|
||||
let elementType: string | undefined;
|
||||
|
||||
// Strategy 1: declarationTypeNodes — raw type annotation node (avoids extractSimpleTypeName stripping)
|
||||
if (!elementType && declarationTypeNodes && scope) {
|
||||
const typeAnnotationNode = declarationTypeNodes.get(`${scope}\0${iterableName}`);
|
||||
if (typeAnnotationNode) {
|
||||
elementType = extractTsElementTypeFromAnnotation(typeAnnotationNode);
|
||||
}
|
||||
}
|
||||
|
||||
// Strategy 2: scopeEnv string — for locally declared vars with container types like Array<User>
|
||||
if (!elementType) {
|
||||
const iterableType = scopeEnv.get(iterableName);
|
||||
if (iterableType) elementType = extractElementTypeFromString(iterableType);
|
||||
}
|
||||
|
||||
// Strategy 3: AST walk — for User[] parameters/locals where extractSimpleTypeName returned undefined
|
||||
if (!elementType) {
|
||||
elementType = findTsIterableElementType(iterableName, node);
|
||||
}
|
||||
|
||||
const elementType = resolveIterableElementType(
|
||||
iterableName, node, scopeEnv, declarationTypeNodes, scope,
|
||||
extractTsElementTypeFromAnnotation, findTsIterableElementType,
|
||||
);
|
||||
if (!elementType) return;
|
||||
|
||||
// The loop variable is the `left` field. It may be wrapped in a variable_declarator.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue