mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-09 03:17:54 +00:00
fix(type-env): thread enclosingFunctionFinder through scope resolution, unskip Dart ACCESSES test
The type-env's findEnclosingScopeKey had the same Dart sibling problem as findEnclosingFunction — it walked parents but never found function_signature because the call lives inside function_body (a sibling). Instead of hardcoding a function_body check, thread the provider's enclosingFunctionFinder hook through BuildTypeEnvOptions → lookupInEnv → findEnclosingScopeKey. All three buildTypeEnv call sites (call-processor, parsing-processor, parse-worker) now pass the hook. This enables the type-env to resolve scoped parameter bindings for Dart (e.g., `user: User` in processUser), which lets the chain-resolution tier (Step 1c) walk `user.address` and emit ACCESSES edges. Dart integration test unskipped — 10/10 passing including ACCESSES. Reverted CHANGELOG.md to origin/main.
This commit is contained in:
parent
dddfd0d789
commit
fc49fd2e72
6 changed files with 32 additions and 30 deletions
|
|
@ -2,18 +2,6 @@
|
|||
|
||||
All notable changes to GitNexus will be documented in this file.
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
- Phase 8: Field and Property Type Resolution
|
||||
- Field-type maps for TypeScript class/interface declarations
|
||||
- Language-agnostic FieldExtractor interface and registry
|
||||
- Chained property resolution support
|
||||
- Phase 9: Return-Type-Aware Variable Binding
|
||||
- Call-result variable binding in TypeEnv
|
||||
- FQN-aware return type lookup
|
||||
- Enhanced pending assignment processing
|
||||
|
||||
## [1.4.8] - 2026-03-23
|
||||
|
||||
### Added
|
||||
|
|
|
|||
|
|
@ -395,7 +395,7 @@ export const processCalls = async (
|
|||
const importedBindings = importedBindingsMap?.get(file.path);
|
||||
const importedReturnTypes = importedReturnTypesMap?.get(file.path);
|
||||
const importedRawReturnTypes = importedRawReturnTypesMap?.get(file.path);
|
||||
const typeEnv = buildTypeEnv(tree, language, { symbolTable: ctx.symbols, parentMap, importedBindings, importedReturnTypes, importedRawReturnTypes });
|
||||
const typeEnv = buildTypeEnv(tree, language, { symbolTable: ctx.symbols, parentMap, importedBindings, importedReturnTypes, importedRawReturnTypes, enclosingFunctionFinder: provider?.enclosingFunctionFinder });
|
||||
if (typeEnv && exportedTypeMap) {
|
||||
const fileExports = collectExportedBindings(typeEnv, file.path, ctx.symbols, graph);
|
||||
if (fileExports) exportedTypeMap.set(file.path, fileExports);
|
||||
|
|
|
|||
|
|
@ -263,7 +263,7 @@ const processParsingSequential = async (
|
|||
}
|
||||
|
||||
// Build per-file type environment for FieldExtractor context (lightweight — skipped if no fieldExtractor)
|
||||
const typeEnv = provider.fieldExtractor ? buildTypeEnv(tree, language) : null;
|
||||
const typeEnv = provider.fieldExtractor ? buildTypeEnv(tree, language, { enclosingFunctionFinder: provider.enclosingFunctionFinder }) : null;
|
||||
|
||||
matches.forEach(match => {
|
||||
const captureMap: Record<string, any> = {};
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { getProvider } from './languages/index.js';
|
|||
import type { ClassNameLookup, ReturnTypeLookup, ForLoopExtractorContext, PendingAssignment } from './type-extractors/types.js';
|
||||
import { extractSimpleTypeName, extractVarName, stripNullable, extractReturnTypeName } from './type-extractors/shared.js';
|
||||
import type { SymbolTable } from './symbol-table.js';
|
||||
import type { NodeLabel } from '../graph/types.js';
|
||||
|
||||
/**
|
||||
* Per-file scoped type environment: maps (scope, variableName) → typeName.
|
||||
|
|
@ -115,6 +116,7 @@ const lookupInEnv = (
|
|||
varName: string,
|
||||
callNode: SyntaxNode,
|
||||
patternOverrides?: PatternOverrides,
|
||||
enclosingFunctionFinder?: (n: SyntaxNode) => { funcName: string; label: NodeLabel } | null,
|
||||
): string | undefined => {
|
||||
// Self/this receiver: resolve to enclosing class name via AST walk
|
||||
if (varName === 'self' || varName === 'this' || varName === '$this') {
|
||||
|
|
@ -128,7 +130,7 @@ const lookupInEnv = (
|
|||
}
|
||||
|
||||
// Determine the enclosing function scope for the call
|
||||
const scopeKey = findEnclosingScopeKey(callNode);
|
||||
const scopeKey = findEnclosingScopeKey(callNode, enclosingFunctionFinder);
|
||||
|
||||
// Check position-indexed pattern overrides first (e.g., Kotlin when/is smart casts).
|
||||
// These take priority over flat scopeEnv because they represent per-branch narrowing.
|
||||
|
|
@ -338,14 +340,30 @@ const extractParentClassFromNode = (classNode: SyntaxNode): string | undefined =
|
|||
return undefined;
|
||||
};
|
||||
|
||||
/** Find the enclosing function name for scope lookup. */
|
||||
const findEnclosingScopeKey = (node: SyntaxNode): string | undefined => {
|
||||
/** Find the enclosing function name for scope lookup.
|
||||
* When an `enclosingFunctionFinder` hook is provided (from the language provider),
|
||||
* it is consulted for each ancestor before the default FUNCTION_NODE_TYPES check.
|
||||
* This handles languages like Dart where the function body is a sibling of the
|
||||
* signature instead of a child. */
|
||||
const findEnclosingScopeKey = (
|
||||
node: SyntaxNode,
|
||||
enclosingFunctionFinder?: (n: SyntaxNode) => { funcName: string; label: NodeLabel } | null,
|
||||
): string | undefined => {
|
||||
let current = node.parent;
|
||||
while (current) {
|
||||
if (FUNCTION_NODE_TYPES.has(current.type)) {
|
||||
const { funcName } = extractFunctionName(current);
|
||||
if (funcName) return `${funcName}@${current.startIndex}`;
|
||||
}
|
||||
// Language-specific hook (e.g., Dart function_body → sibling function_signature)
|
||||
if (enclosingFunctionFinder) {
|
||||
const result = enclosingFunctionFinder(current);
|
||||
if (result) {
|
||||
const sigNode = current.previousSibling;
|
||||
const startIdx = sigNode?.startIndex ?? current.startIndex;
|
||||
return `${result.funcName}@${startIdx}`;
|
||||
}
|
||||
}
|
||||
current = current.parent;
|
||||
}
|
||||
return undefined;
|
||||
|
|
@ -683,6 +701,10 @@ export interface BuildTypeEnvOptions {
|
|||
* Stores raw declared return type strings (e.g., 'User[]', 'List<User>').
|
||||
* Used by lookupRawReturnType for for-loop element extraction. */
|
||||
importedRawReturnTypes?: ReadonlyMap<string, string>;
|
||||
/** Language-specific enclosing function resolver for scope key lookup.
|
||||
* Same hook as LanguageProvider.enclosingFunctionFinder — handles languages
|
||||
* where function_body is a sibling of the signature (e.g., Dart). */
|
||||
enclosingFunctionFinder?: (ancestorNode: SyntaxNode) => { funcName: string; label: NodeLabel } | null;
|
||||
}
|
||||
|
||||
/** Seed cross-file type bindings into the file scope.
|
||||
|
|
@ -1110,7 +1132,7 @@ export const buildTypeEnv = (
|
|||
}
|
||||
|
||||
return {
|
||||
lookup: (varName, callNode) => lookupInEnv(env, varName, callNode, patternOverrides),
|
||||
lookup: (varName, callNode) => lookupInEnv(env, varName, callNode, patternOverrides, options?.enclosingFunctionFinder),
|
||||
constructorBindings: bindings,
|
||||
fileScope: () => env.get(FILE_SCOPE) ?? EMPTY_FILE_SCOPE,
|
||||
allScopes: () => env as ReadonlyMap<string, ReadonlyMap<string, string>>,
|
||||
|
|
|
|||
|
|
@ -1044,8 +1044,8 @@ const processFileGroup = (
|
|||
// Build per-file type environment + constructor bindings in a single AST walk.
|
||||
// Constructor bindings are verified against the SymbolTable in processCallsFromExtracted.
|
||||
const parentMap: ReadonlyMap<string, readonly string[]> = fileParentMap;
|
||||
const typeEnv = buildTypeEnv(tree, language, { parentMap });
|
||||
const provider = getProvider(language);
|
||||
const typeEnv = buildTypeEnv(tree, language, { parentMap, enclosingFunctionFinder: provider?.enclosingFunctionFinder });
|
||||
const callRouter = provider.callRouter;
|
||||
|
||||
if (typeEnv.constructorBindings.length > 0) {
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@
|
|||
* Verifies that class fields are captured as Property nodes with HAS_PROPERTY
|
||||
* edges, and that calls (including chained and call-result-bound) are resolved.
|
||||
*
|
||||
* Remaining known Dart gaps (field-chain ACCESSES) are documented as
|
||||
* it.todo() tests to be filled when the pipeline is extended.
|
||||
* All Dart pipeline features are covered: Property nodes, HAS_PROPERTY edges,
|
||||
* CALLS chain resolution, IMPORTS, call attribution, and ACCESSES field reads.
|
||||
*/
|
||||
import { describe, it, expect, beforeAll } from 'vitest';
|
||||
import path from 'path';
|
||||
|
|
@ -78,15 +78,7 @@ describe.skipIf(!dartAvailable)('Dart field-type resolution', () => {
|
|||
expect(appImports.length).toBe(1);
|
||||
});
|
||||
|
||||
// Dart field-chain ACCESSES edges require the call-processor's chain-resolution
|
||||
// tier (Step 1c) to fire. This needs the type-env's scoped parameter binding
|
||||
// (processUser's `user: User`) to propagate to processCallsFromExtracted so
|
||||
// walkMixedChain can resolve User → address → Address and emit ACCESSES.
|
||||
// The chain extraction (extractMixedChain) and member detection
|
||||
// (MEMBER_ACCESS_NODE_TYPES) are wired, but the base receiver type lookup
|
||||
// from the type-env currently returns undefined for Dart function parameters
|
||||
// in the call-processor context. Tracked for follow-up.
|
||||
it.skip('emits ACCESSES edges for field reads in chains', () => {
|
||||
it('emits ACCESSES edges for field reads in chains', () => {
|
||||
const accesses = getRelationships(result, 'ACCESSES');
|
||||
const addressReads = accesses.filter(
|
||||
(e) => e.target === 'address' && e.rel.reason === 'read',
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue