mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-09-05 08:06:02 +00:00
19 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
a94d6ef80b
|
Extract registries into model/ module with SemanticModel interface (#786)
* Initial plan * feat(SM-20): extract registries into model/ module with SemanticModel interface - Create model/type-registry.ts — TypeRegistry interface + factory - Create model/method-registry.ts — MethodRegistry interface + factory - Create model/field-registry.ts — FieldRegistry interface + factory - Create model/semantic-model.ts — SemanticModel interface + factory - Create model/heritage-map.ts — re-export HeritageMap types - Create model/binding-accumulator.ts — re-export BindingAccumulator types - Create model/resolve.ts — move lookupMethodByOwnerWithMRO from call-processor - Update symbol-table.ts — delegate to SemanticModel for registry ops - Update call-processor.ts — re-export lookupMethodByOwnerWithMRO from model/resolve No circular dependencies: model/resolve.ts does NOT import resolution-context.ts. All 775 related unit tests pass with no regressions. Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/27ad2975-1a31-4f50-815b-178ee8a95277 * fix: clarify re-export comment per code review feedback Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/27ad2975-1a31-4f50-815b-178ee8a95277 * refactor(SM-20): wire up SemanticModel as first-class resolution input PR #786 extracted TypeRegistry/MethodRegistry/FieldRegistry into model/ behind SemanticModel, but consumers still routed through SymbolTable delegates. This change completes Phase 6 of the fuzzy-lookup elimination roadmap by making call-processor, resolution-context, type-env, and heritage-map query the model directly via `table.model.{types,methods,fields}`. Also absorbs the open PR #786 review findings so the branch lands clean: - Removed duplicate JSDoc block on lookupMethodByOwner (symbol-table.ts) - Added model/index.ts barrel for the public model/ surface - Fixed O(n) buildParentMapFromHeritage BFS via head-pointer queue - Clarified re-export facade framing on binding-accumulator.ts and heritage-map.ts inside model/ - Refined @internal JSDoc on lookupMethodByOwnerWithMRO Changes: - symbol-table.ts: expose `readonly model: SemanticModel` on the SymbolTable interface. SymbolTable delegate wrappers (lookupClassByName etc.) stay as thin pass-throughs for backward compat; deletion is a follow-up once all internal callers are migrated. - model/resolve.ts: lookupMethodByOwnerWithMRO now takes SemanticModel instead of SymbolTable, removing the last SymbolTable import from the model/ module. Preserves circular-dependency firewall. - call-processor.ts: 6 call sites in D0 member resolution, field resolution, ctor override, and ctor disambiguation migrated to model.types/methods/fields. - resolution-context.ts: tier 3 class+impl lookup migrated. - type-env.ts: 5 sites across lookupClassDefsByName, resolveFieldType, and resolveMethodReturnType migrated. - heritage-map.ts: parent/child class-name resolution migrated. Tests: - symbol-table.test.ts: +10 parity and feeding-audit tests covering every model.{types,methods,fields} path (Class, Method, Property, Impl, Function-with-ownerId, Property-without-ownerId skip, arity filtering, clear cascade). - call-processor.test.ts: classLookupSpy now targets ctx.symbols.model.types since the wrapper is bypassed. - type-env.test.ts: createMockSymbolTable and the destructured-call makeSymbolTable helpers gained a model shim that forwards to the (possibly overridden) top-level lookup stubs. Validation: full suite 5603 passed / 159 skipped, resolver integration suite (19 files, 1766 tests) clean, tsc --noEmit clean. * refactor(SM-21): invert ownership — SemanticModel contains SymbolTable Follow-up to SM-20. Previously SymbolTable owned a `model` subfield; this commit turns the ownership direction around so the SemanticModel is the top-level container and SymbolTable is nested as `.symbols`: SemanticModel (top-level, passed everywhere) ├── types (TypeRegistry) ├── methods (MethodRegistry) ├── fields (FieldRegistry) └── symbols (SymbolTable — file-indexed + callable-name index) The owner-scoped registries live directly on the model; file and callable-name lookups go through `.symbols`. Consumers receive a `SemanticModel` and reach into the appropriate field — no more `table.model.types.X` double-hop. Core changes: - symbol-table.ts: createSymbolTable now takes injected TypeRegistry/MethodRegistry/FieldRegistry via a SymbolTableDeps argument. When omitted (test fallback), it creates standalone registries locally and clears them in clear() — production callers always inject. The five registry convenience delegates (lookupClassByName, lookupMethodByOwner, lookupFieldByOwner, lookupClassByQualifiedName, lookupImplByName) remain as thin forwards to the injected registries so standalone SymbolTable use (chiefly tests) stays ergonomic. - model/semantic-model.ts: createSemanticModel() now creates the three registries AND a SymbolTable wired to them, exposing the SymbolTable as `.symbols`. clear() cascades through all four. - resolution-context.ts: `readonly symbols: SymbolTable` field is replaced with `readonly model: SemanticModel`. Internal factory builds a SemanticModel and keeps a local `symbols` alias for backward-compatible inner body. Consumer migrations (src/): - call-processor.ts: ctx.symbols.add/.lookupExactAll/ .lookupCallableByName → ctx.model.symbols.*; ctx.symbols.model.X → ctx.model.X. buildTypeEnv option key renamed symbolTable → model. - type-env.ts: symbolTable parameter renamed model (type SemanticModel), all internal call sites rewritten to use model.types.*, model.methods.*, model.fields.*, model.symbols.lookupExactAll / .lookupCallableByName. - heritage-map.ts: 2 class-lookup sites migrated. - pipeline.ts: ctx.symbols → ctx.model.symbols throughout. Test migrations: - symbol-table.test.ts: parity tests (which validated the old table.model.X hop) replaced with direct SemanticModel coverage via createSemanticModel(). New tests exercise types/methods/fields/ symbols feeding end-to-end. - type-env.test.ts: createMockSymbolTable rebuilt as a SemanticModel-shaped mock that still accepts the legacy flat override bag for backward compat; inline `makeSymbolTable` helpers for destructured-call and importedReturnTypes suites rewritten to match the new shape; buildTypeEnv options `symbolTable: X` and `{ symbolTable }` shorthand renamed to `model:`; one real createSymbolTable-based test rewritten to use createSemanticModel. - call-processor.test.ts, heritage-map.test.ts, heritage-processor.test.ts, symbol-resolver.test.ts: bulk sed `ctx.symbols.` → `ctx.model.symbols.`. call-processor.test.ts spy updated to target `ctx.model.types.lookupClassByName`. Validation: full test suite 5589 passed / 169 skipped / 0 failed; tsc --noEmit clean; pre-commit eslint + prettier + typecheck all green. CLAUDE.md / AGENTS.md stats bumped from an earlier `npx gitnexus analyze` refresh (3965 symbols / 10012 edges / 243 flows). * refactor(SM-22/SM-23): dispatch table + DAG rearchitecture SM-22: Extract registration dispatch table into model/registration-table.ts. Replaces the if/else ladder inside SymbolTable.add() with an O(1) Map<NodeLabel, RoutingDecision> fan-out. SemanticModel wires the table per-instance so hooks close over the correct registries. SM-23: DAG rearchitecture. symbol-table.ts is now a pure 2-index leaf (fileIndex + callableByName) with zero imports from model/. All type/method/field routing lives in the model/ layer. Tests migrated to createSemanticModel() + model.symbols access pattern. Tests: 5632 passed, 0 failures. * refactor: delete dead code (skipCallableIndex + model/ facades) Removes the unused skipCallableIndex flag from the registration dispatch table and deletes two facade files that had zero consumers. skipCallableIndex was declared on RoutingDecision and populated for all 10 entries but never read at runtime — semantic-model.ts explicitly documented that the flag was NOT consulted. The callable-index gate lives inside SymbolTable.add() via CALLABLE_TYPES.has(type), which is the single source of truth. Deleting the flag keeps SymbolTable as the sole decision point and removes documentation-as-data. model/binding-accumulator.ts and model/heritage-map.ts were facade pass-throughs of their parent-directory counterparts. Grep confirms no consumer imports either from the model/ path — all usage goes through ../binding-accumulator.js and ../heritage-map.js directly. model/index.ts was the only "user" and re-exported them with a note about unifying the import boundary, but that boundary has no actual consumers today. Resolves review findings M-01 and M-03 from .context/compound-engineering/ce-review/20260411-144641-59605d93/maintainability.json Tests: 5631 passed, 0 failures (1 less than pre-Unit-1: the skipCallableIndex-specific assertion was removed). * refactor: remove lookupMethodByOwnerWithMRO backward-compat shim call-processor.ts re-exported lookupMethodByOwnerWithMRO from ./model/resolve.js as a backward-compat shim for symbol-table.test.ts. The function already lives in model/resolve.ts and is re-exported properly from model/index.ts (the barrel) — the call-processor shim was a duplicate export path with no durable reason to exist. Migrated the test import from call-processor.js to model/index.js (the canonical barrel). Deleted the re-export statement and the stale "re-exported for backward compatibility" comment block. Hoisted the remaining import to the top of the file with the other imports; the bottom-of-file position was a relic of the shim pattern. Resolves review finding M-02 from .context/compound-engineering/ce-review/20260411-144641-59605d93/maintainability.json Tests: 5631 passed, 0 failures. * refactor: harden registration dispatch runtime safety Two hardening changes in semantic-model.ts, both closing silent-failure paths in the SM-series dispatcher-bypass failure mode. 1. model.symbols.clear() now cascades to the owner-scoped registries. Previously, the SymbolTable facade exposed rawSymbols.clear directly, which only emptied fileIndex + callableByName — the types/methods/ fields registries stayed populated. Any caller holding a SymbolTable reference that invoked .clear() left the model in a split state where subsequent .add() calls double-registered in the registries. No current caller exercises this path, but it was a latent phantom- resolution risk that didn't belong in a public API. Extracted the cascade into a single cascadeClear closure wired into both model.clear() and the facade's clear field. 2. runExhaustivenessGuard now throws instead of console.warn on drift. The production short-circuit via NODE_ENV === 'production' is preserved, so real users never see the throw — but CI and dev runs now fail loudly if a NodeLabel is added to gitnexus-shared without being placed in one of the three registration-table allowlists. The previous warn-only behavior was silent in test output volume; SM-19 already documented dispatcher-bypass as the dominant silent-failure mode in this codebase. Test-first: added test/unit/model/semantic-model.test.ts covering model.symbols.clear() cascade (4 registries × clear = 4 tests), the existing model.clear() cascade (regression guard), and a happy-path construction test that verifies the current allowlists have zero drift. Resolves correctness P2 finding (symbols.clear() partial clear), correctness P3 (exhaustiveness warn-only), and kieran-typescript KT-03 (same exhaustiveness finding, agreement boost). Tests: 5638 passed (+7 new), 0 failures. * docs: fix stale JSDoc references in resolveStaticCall call-processor.ts:2215-2216 referenced SymbolTable.lookupClassByName and SymbolTable.lookupMethodByOwner via {@link}. Both methods were removed from SymbolTable during SM-20 — they now live on TypeRegistry and MethodRegistry respectively, accessible via model.types and model.methods. Other SymbolTable.* references in the codebase (lookupExactFull, add, lookupCallableByName in call-processor.ts:593, symbol-table.ts:86, type-extractors/types.ts:57) target methods that are still on SymbolTable and remain valid. Resolves correctness P3 and kieran-typescript KT-02 (same finding, agreement boost). * refactor: deduplicate ALL_NODE_LABELS constant ALL_NODE_LABELS was private in semantic-model.ts and duplicated verbatim in registration-table.test.ts. Two hardcoded lists meant a new NodeLabel added to gitnexus-shared could land in one copy but not the other, silently drifting the exhaustiveness invariant. Exported ALL_NODE_LABELS from semantic-model.ts, re-exported through model/index.ts for barrel consistency, and switched the test to import it instead of redeclaring. The explanatory comment now describes the single-source-of-truth contract. Resolves maintainability M-04. Tests: 5638 passed, 0 failures. * refactor: add compile-time NodeLabel exhaustiveness check The runtime exhaustiveness guard in semantic-model.ts caught drift at test time. Added a type-level check in registration-table.ts that catches drift at BUILD time — if a new NodeLabel is added to gitnexus-shared without being classified into one of the three allowlists, TypeScript fails the _exhaustiveCheck assignment and names the missing label. The runtime guard stays as belt-and-suspenders: if a future contributor bypasses the type check with @ts-ignore, the runtime guard still fires in dev/test. Implementation: converted the three allowlist Set<NodeLabel> initializers to use `as const` tuples, then derived a union type from the tuples and asserted `Exclude<NodeLabel, union> extends never`. Zero runtime impact — the exported Sets are unchanged, Map.get hot-path performance is unchanged, the test API is unchanged. Resolves kieran-typescript KT-04. Tests: 21/21 registration-table tests pass with zero modifications. * refactor(test): restore type safety to createMockSymbolTable createMockSymbolTable was widened to (overrides: any = {}): any with an eslint-disable-next-line, and every buildTypeEnv call site passed the mock as `model: mockSymbolTable as any`. The widening masked silent false-green tests: buildTypeEnv accesses model.types/methods/fields, and a flat any-typed override could silently return undefined from a path that TypeScript should have caught at compile time. Defined LegacyMockOverrides interface with typed stubs for each method the mock can override (SymbolTable reads + TypeRegistry/MethodRegistry/ FieldRegistry lookups). Return type is now SemanticModel, so the mock object is compile-checked against the real interface — a missing registry method is a type error, not a silent runtime undefined. Removed the eslint-disable and all 9 `as any` casts at call sites (lines 1287, 1300, 1307, 2124, 2138, 5823, 5835, 5850, 5870). The mock's return value now flows through buildTypeEnv's typed `model` option without coercion. Resolves kieran-typescript KT-01 and testing gap TG-02. This was the highest-value cleanup in the plan — the only finding representing real hidden test weakness. Tests: 360 passed | 7 skipped (type-env.test.ts), typecheck clean. * test: close coverage gaps in model/ registries Added direct unit tests for the three owner-scoped registries that previously had only transitive coverage via symbol-table.test.ts and registration-table.test.ts. These new tests pin behaviors that were flagged by the testing reviewer as untested or undertested. method-registry.test.ts (14 tests): - T-01: arity-fallback branch — when argCount matches no overload, fall back to the full pool so fuzzy resolution still has candidates. Previously untested and would have returned undefined instead of a valid candidate if the branch regressed. - T-02: requiredParameterCount range filtering — methods with default parameters accept any argCount in [requiredParameterCount, parameterCount]. Previously untested at the registry level. - Variadic fallback (parameterCount=undefined is retained during arity narrowing, bypassing range check). - Return-type dedup paths: shared returnType → first wins, differing returnTypes → undefined, firstReturnType=undefined → undefined, single-overload skips dedup entirely. type-registry.test.ts (9 tests): - classByName homonym accumulation (two User classes in different packages both returned). - classByQualifiedName disambiguation — same simple name, different FQNs resolve independently. - Partial classes with identical simple + qualified name accumulate in both indexes. - registerImpl stores Rust impl blocks separately from classes. - Multiple impl blocks per type accumulate. field-registry.test.ts (6 tests): - register/lookup round-trip, owner-scope isolation, last-wins on duplicate key (flat map, not overload list). - clear + re-register round-trip. Extended symbol-table.test.ts cascade test (renamed from "both registries" to "all three registries and the nested symbol table") to also assert model.methods and model.fields are cleared — the test name previously implied full coverage but only asserted types + symbols. Resolves testing findings T-01, T-02, T-03, T-05. Tests: 5667 passed (+29 new), 0 failures. * refactor(test): replace brittle reference-equality tests + add intent comments Two cleanups flagged as low-severity P3 by the testing reviewer: 1. registration-table.test.ts: Replaced three reference-equality tests (hook identity via toBe) with behavioral tests that survive a future refactor to per-label closures. The new "class-like behavior group" describe iterates Class/Struct/Interface/Enum/Record/Trait and verifies each one writes to types.registerClass. Same pattern for Method/Constructor. A separate "behavior group isolation" describe verifies class-like hooks don't leak into methods/fields and Impl never pollutes registerClass. Strictly more coverage than the reference-equality tests provided and implementation-independent. 2. symbol-resolver.test.ts: Added a comment above the lookupExactFull and SM-16: getFiles() describes explaining why they intentionally use createSymbolTable() directly instead of createSemanticModel(). The DAG leaf-only behaviors they test do not involve registries, so testing the bare SymbolTable keeps the unit isolated. Prevents a future reader from "fixing" the inconsistency. 3. qualified-class-lookups.test.ts: Added a comment above `const symbolTable = model.symbols` explaining that processParsing writes still reach the owner-scoped registries via SemanticModel's fan-out — the alias is convenience, not a leaf in isolation. Resolves testing T-04, kieran-typescript KT-05, kieran-typescript KT-06. Tests: affected files all green (112 passed in registration-table + symbol-resolver + qualified-class-lookups). * refactor(model): collapse RoutingDecision wrapper and trim barrel surface Two cleanups against the advanced-review findings on post-Unit-9 state: S2 (cross-reviewer agreement — architecture-strategist + code-simplicity): Delete the RoutingDecision single-field wrapper interface. Post-Unit-1 it held exactly one field (hook: RegistrationHook) and added pure ceremony at every call site — `dispatchTable.get(key)!.hook(name, def)` vs the now-direct `dispatchTable.get(key)!(name, def)`. Change the Map type from Map<NodeLabel, RoutingDecision> to Map<NodeLabel, RegistrationHook>, drop the interface, and update 17 test call sites. A3 (architecture-strategist): Trim model/index.ts barrel surface. createRegistrationTable, RegistrationHook, and RegistrationTableDeps were re-exported from the barrel despite having zero legitimate consumers outside model/ itself. The only callers (semantic-model.ts and registration-table.test.ts) import directly from ./registration-table.js. Barrel exposure invited external callers to construct orphan dispatch tables with independent registries, weakening the SM-21 ownership inversion where SemanticModel is the composition root. Kept CALLABLE_ONLY_LABELS, INERT_LABELS, DISPATCH_LABELS exported since those remain useful for downstream resolution logic and have no construction risk. Resolves review findings: - S2 (code-simplicity P3, 0.85) + architecture-strategist residual - A3 (architecture-strategist P3, 0.82) Tests: 5674 passed, 0 failures. Typecheck clean. * refactor(model): replace runtime exhaustiveness guard with compile-time bijection Replace the three-layer drift protection (hardcoded ALL_NODE_LABELS array + 3 tuple consts + _ExhaustiveLabelCheck type + runExhaustivenessGuard runtime + CI taxonomy test) with a single Record<NodeLabel, LabelBehavior> map that structurally proves every invariant at compile time. ## Before - ALL_NODE_LABELS hardcoded in semantic-model.ts (36 entries, could drift) - DISPATCH_LABELS_TUPLE / CALLABLE_ONLY_LABELS_TUPLE / INERT_LABELS_TUPLE private tuples (36 more entries total, could overlap or miss) - _ClassifiedLabel / _UncoveredLabel type-level check (caught missing labels but NOT duplicates across tuples) - runExhaustivenessGuard runtime throw (only defense against duplicates) - NodeLabel taxonomy coverage test in CI (same check as runtime guard) Four defenses for invariants that the type system can express directly. ## After ```ts type LabelBehavior = 'dispatch' | 'callable-only' | 'inert'; const LABEL_BEHAVIOR = { Class: 'dispatch', // ...36 entries... Tool: 'inert', } as const satisfies Record<NodeLabel, LabelBehavior>; ``` The `as const satisfies Record<NodeLabel, LabelBehavior>` combo enforces: 1. **Every NodeLabel must be a key** — Record requires all K keys. Adding a NodeLabel to gitnexus-shared without classifying it here fails with "Property 'X' is missing in type ..." naming the drifted label. 2. **No non-NodeLabel keys allowed** — `satisfies` with object literals triggers excess-property checking. A typo'd key fails to compile. 3. **No duplicate classification** — impossible by construction; object keys are unique at the source level. 4. **Valid category** — LabelBehavior is a narrow union, typos caught. `ALL_NODE_LABELS`, `DISPATCH_LABELS`, `CALLABLE_ONLY_LABELS`, and `INERT_LABELS` are now derived via `Object.keys(LABEL_BEHAVIOR)` and `filter(l => LABEL_BEHAVIOR[l] === ...)` — single source of truth, structurally impossible to drift. ## Deleted - runExhaustivenessGuard() function in semantic-model.ts (~18 lines) - ALL_NODE_LABELS hardcoded array in semantic-model.ts (~38 lines) - DISPATCH_LABELS_TUPLE / CALLABLE_ONLY_LABELS_TUPLE / INERT_LABELS_TUPLE private consts in registration-table.ts (~30 lines) - _ClassifiedLabel / _UncoveredLabel / _exhaustiveCheck type machinery (~20 lines) ## Kept named proofs: none The `as const satisfies` on the object literal already catches all four drift modes. Named type-level proofs (_MissingFromMap / _ExtraKeysInMap) are pure duplication and were removed per review. ## Also in this commit - S6: trim wrappedAdd narration comments in semantic-model.ts (Step 1/2/3 block comments removed; kept the Function+ownerId WHY note) - A3: tighten model/index.ts barrel — createRegistrationTable, RegistrationHook, RegistrationTableDeps remain direct-imports only; ALL_NODE_LABELS and LabelBehavior re-exported from the new home in registration-table.ts ## Resolves - Advanced-review S4 (runtime guard per-call cost) — guard no longer exists - Advanced-review S1 (tuple three-defenses indirection) — single Record replaces all tuples - Correctness P3 (exhaustiveness warns-only) — structurally impossible to drift - Unit 6 type-level check — subsumed by the Record type - Unit 3 runtime throw — no longer needed Tests: 5674 passed, 0 failures. Typecheck clean. * test(model): delete duplicate closure-isolation spy tests S5 (code-simplicity P3): The 'closure isolation — each hook can only write to its registry' describe block duplicated the 'behavior group isolation' block's coverage via a different mechanism. Behavioral tests (lines 151-174, kept): table.get('Class')!('User', def); expect(deps.methods.lookupMethodByOwner('unrelated', 'User')).toBeUndefined(); expect(deps.fields.lookupFieldByOwner('unrelated', 'User')).toBeUndefined(); Spy tests (deleted, ~55 lines): vi.spyOn(deps.methods, 'register') table.get('Class')!('User', def); expect(methodsSpy).not.toHaveBeenCalled(); Both assert the same invariant — classHook does not touch the methods or fields registries. The behavioral form observes the END STATE of the registry (lookup returns undefined), which is the actual contract. The spy form asserts the IMPLEMENTATION (a specific method was not called), which couples to internal wiring — a refactor to a different register function name would break the spy test while the behavioral test would still pass. Also dropped the now-unused `vi` import from vitest. Tests: 24/24 registration-table.test.ts pass (-4 from spy deletion). * refactor(model): compile-time cross-invariant between CLASS_TYPES and dispatch classHook A1 (architecture-strategist P2, 0.90): CLASS_TYPES in symbol-table.ts and the class-like entries of the dispatch table were two independent hardcoded sets. Adding a new class-like label (e.g. Swift 'Extension') to one but not the other would silently degrade qualifiedName population — the symptom is subtle (partial qualified-name lookups) and no test asserted the co-extensive invariant. Fixed with a single source of truth and a two-layer compile-time enforcement: ## symbol-table.ts - Add `CLASS_TYPES_TUPLE` as `readonly [...] as const satisfies readonly NodeLabel[]`. The `satisfies` forces every tuple entry to be a valid NodeLabel at compile time. - Export derived type `ClassLikeLabel = typeof CLASS_TYPES_TUPLE[number]`. - Derive `CLASS_TYPES` Set from the tuple — same runtime shape as before, now typed `ReadonlySet<NodeLabel>`. ## registration-table.ts - Import `CLASS_TYPES_TUPLE` and `ClassLikeLabel` from symbol-table.ts. - Narrow the `satisfies` on `LABEL_BEHAVIOR` via intersection: Record<NodeLabel, LabelBehavior> & Record<ClassLikeLabel, 'dispatch'> This forces every class-like label to have value 'dispatch' at compile time. Adding a label to CLASS_TYPES_TUPLE without classifying it as dispatch in LABEL_BEHAVIOR fails to compile with a type error naming the drifted label. - Build the class-like entries of the dispatch Map by iterating `CLASS_TYPES_TUPLE` at factory time. Adding a label to the tuple automatically wires it to classHook — no second place to update. ## What the design prevents 1. Drift scenario A (A1 original): 'Extension' added to CLASS_TYPES_TUPLE but not to LABEL_BEHAVIOR → compile error on LABEL_BEHAVIOR's satisfies. 2. Drift scenario B: 'Extension' added to CLASS_TYPES_TUPLE but not wired to classHook → impossible because the Map is derived from the tuple. 3. Drift scenario C: class-like label classified as something other than 'dispatch' in LABEL_BEHAVIOR → compile error on the narrowed intersection. Runtime behavior unchanged: same 6 labels in CLASS_TYPES, same 6 class-like entries in the dispatch Map. Tests pin the behavior via the existing behavior-group tests in registration-table.test.ts. DAG unchanged: registration-table.ts already imported from symbol-table.ts (the allowed upward direction). symbol-table.ts still imports nothing from model/. Tests: 5670 passed, 0 failures. Typecheck clean. * test(field-extraction): use SemanticModel facade instead of raw SymbolTable A6 (architecture-strategist P3, 0.85): field-extraction.test.ts created its FieldExtractorContext fixture with `symbolTable: createSymbolTable()` — a raw SymbolTable leaf, not the facade. In production, the context's symbolTable field is always `model.symbols` (the SemanticModel-wrapped facade where .add() dispatches through the owner-scoped registries). The current field extractors don't call symbolTable.add() at all, so this change is behavior-neutral today. The value is architectural consistency — matching the test fixture to the production shape prevents silent drift if a future field extractor starts registering dynamically-discovered properties via the context. Without the fix, such writes would hit the raw leaf and skip the fan-out, and tests would pass even though the symptom (empty FieldRegistry) would manifest in production. Tests: 50/50 field-extraction.test.ts pass. Production tsc --noEmit clean. Test-tsconfig error count unchanged (634 pre-existing errors in unrelated test files, out of scope). * refactor(A5): decouple model/resolve.ts from language registry Move the MroStrategy type into gitnexus-shared and replace the language: SupportedLanguages parameter on lookupMethodByOwnerWithMRO with a direct mroStrategy: MroStrategy literal. Callers derive the strategy from their language provider before invoking the resolver. model/resolve.ts no longer imports from ../languages/index.js, so the model/ layer is free of cross-layer coupling with the language registry — this closes finding A5 from the SM-20/21/22/23 advanced review (plan 006). * feat(A4): add MethodRegistry.lookupMethodByName flat-by-name index Add a secondary `methodsByName: Map<string, SymbolDefinition[]>` index on MethodRegistry that returns every method with a given unqualified name, accumulated across owners and overloads. The new index shares SymbolDefinition references with methodByOwner — no duplication. This is step 1 of the A4 double-index removal (plan 006). Tier 3 global resolution will switch to this index in Unit 3 so Method and Constructor can be removed from CALLABLE_TYPES in Unit 4. * refactor(A4): extend Tier 3 + memberCallByFile to consult method registry Add model.methods.lookupMethodByName to Tier 3 global resolution in resolution-context.ts and to the callable-pool build in call-processor.ts (resolveMemberCallByFile + D2 widen path). Intentionally behavior-preserving: Method and Constructor are still in CALLABLE_TYPES so the new lookup returns identical candidates that already reach Tier 3 through callableByName. Both paths dedup by nodeId during this intermediate state — Unit 4 shrinks CALLABLE_TYPES and the dedup is removed. Part of plan 006 A4 step 2. * refactor(A4): shrink CALLABLE_TYPES to free callables only CALLABLE_TYPES = {Function, Macro, Delegate}. Method and Constructor are no longer double-indexed in callableByName — they reach resolvers through model.methods.lookupMethodByName instead. Companion changes: - Introduce CALL_TARGET_TYPES = CALLABLE_TYPES ∪ {Method, Constructor} for the resolver's kind filter (filterCallableCandidates, countCallableCandidates). Separates registration semantics (narrow) from the resolver's acceptable-target set (wide). - type-env.ts for-loop return-type inference consults both indexes, treating the union as the authoritative call pool. - resolveMemberCallByFile + D2 widen path keep the nodeId dedup in place: Python/Rust/Kotlin class methods emitted as Function+ownerId still land in both indexes until Unit 5 unblocks the normalization. - Tier 3 global resolution (resolution-context.ts) keeps the same dedup for the same reason. Test updates reflect the new contract: Method/Constructor live in methodsByName, not callableByName. Orphan Method-without-ownerId now lives only in the file index (no registry coverage). Part of plan 006 — closes A4 for strictly-labeled methods. Python/ Rust/Kotlin Function+ownerId normalization is tracked as Unit 5 (blocked). * refactor: rename CALLABLE_TYPES → FREE_CALLABLE_TYPES Pure rename. The constant's meaning changed in Unit 4 (free callables only — no methods, no constructors) so the name now reflects that scope: "callables that have no owner scope". Updates the constant declaration and every consumer in src/ and test/. Closes plan 006 Unit 6. * refactor(A2): strict SymbolTableReader (pure reads) + SymbolTableWriter (+add) Split the SymbolTable interface into three strictly layered surfaces: - SymbolTableReader: lookups + iteration. NO add, NO clear. Holders cannot mutate the table in any way. - SymbolTableWriter extends Reader: + add. NO clear. Holders can register new symbols but cannot trigger a leaf-index reset. - InternalSymbolTable (private, not exported): + clear. The cascading reset capability is reachable only through createSymbolTable's return type, held exclusively by SemanticModel.rawSymbols. SemanticModel.symbols is now typed as SymbolTableWriter — external consumers (workers, processors, pipelines) can register symbols and query them, but cannot reach .clear(). The A2 LSP fix holds: callers holding any public reference cannot desync the leaf indexes from the owner-scoped registries. Delete the transitional `type SymbolTable = SymbolTableReader` alias and migrate every consumer (src + test) to the explicit names: - Field and parameter annotations use SymbolTableReader by default; only code that calls .add() uses SymbolTableWriter. - parsing-processor (workers + sequential paths) takes SymbolTableWriter so it can register extracted symbols. - field-types, call-processor, named-binding-processor, workers/parse-worker: use SymbolTableReader (query-only). - Tests: drop the stale `clear` fields from mock factories and migrate the semantic-model cascade tests from the removed model.symbols.clear() path to model.clear(). Closes plan 006 Unit 7. Industry sources: TypeScript compiler API builder pattern, Salsa ParallelDatabase, .NET IReadOnlyList. See the a2-lsp-clear-contract-research artifact for full citations. * feat(A2): add SemanticModel.resetFileIndex() partial-reset entry point Add a named method that clears only the leaf file and callable indexes without cascading to the three owner-scoped registries (types, methods, fields). Replaces the rare partial-reset use case that was previously reachable via the now-removed symbols.clear() path from A2 (plan 006 Unit 7). JSDoc makes the semantic difference with model.clear() explicit so future readers don't have to guess which method to call for a given reingestion scenario. Test-first: three scenarios cover the partial-vs-full semantics, re-add after reset, and idempotency. Closes plan 006 Unit 8. * docs(S7): trim registration-table module JSDoc Remove the ~24 lines of design-provenance citations from the module JSDoc. The rust-analyzer, TypeScript-compiler, and Fowler references are preserved in git history via the original SM-22 commits and in plan 006 Unit 9. Keep the ownership diagram, behavior-group table, and the 'How to add a new NodeLabel' checklist — those are load-bearing for future contributors. Closes plan 006 Unit 9 (S7 advanced-review finding). * test(S3): migrate type-env.test.ts off LegacyMockOverrides Replace the createMockSymbolTable bridge and LegacyMockOverrides interface with real createSemanticModel() + add() calls across all 14 call sites. Where a test needs a specific registry lookup that can't be pre-populated cleanly, use vi.spyOn on the real registry instead. Pattern breakdown: - Pattern A (pre-populate via model.symbols.add): 13 sites - Pattern B (vi.spyOn on registry lookup): 1 site Deletes LegacyMockOverrides + createMockSymbolTable entirely. The real MethodRegistry arity/returnType semantics match the hand-rolled mock behavior in every migrated case, and no 'as any' casts remain in the file. Closes plan 006 Unit 10 (S3 advanced-review finding). * refactor: remove unused MroStrategy type exports from language-provider and resolve modules * refactor: relocate symbol-table, heritage-map, resolution-context into model/ Use git mv so blame and history follow each file: - gitnexus/src/core/ingestion/symbol-table.ts → model/symbol-table.ts - gitnexus/src/core/ingestion/heritage-map.ts → model/heritage-map.ts - gitnexus/src/core/ingestion/resolution-context.ts → model/resolution-context.ts These three files are part of the SemanticModel layer (file/callable indexes, heritage parent map, tiered resolver) and now sit alongside the registries they collaborate with. Updates every consumer import path across src/ and test/ to the new locations. * refactor(model): enforce pure-leaf DAG + delete legacy re-exports model/ is now a pure leaf: zero upward imports and zero compat shims in its parent processors. Completes the DAG cleanup started in the previous commit. 1. walkBindingChain — moved into model/resolution-context.ts; named-binding-processor.ts deleted. 2. NamedImportMap + NamedImportBinding + isFileInPackageDir — moved into model/resolution-context.ts. Every consumer now imports from the canonical location directly. Legacy re-exports in import-processor.ts deleted. 3. c3Linearize + gatherAncestors — moved into model/resolve.ts. mro-processor.ts imports them back for computeMRO. Legacy c3Linearize re-export from mro-processor.ts deleted. 4. ExtractedHeritage type — moved into model/heritage-map.ts. call-processor.ts, parsing-processor.ts, pipeline.ts, heritage-processor.ts, and the test files now import it from the canonical location. Legacy re-exports in parse-worker.ts and heritage-processor.ts deleted. 5. resolveExtendsType — rewritten in model/heritage-map.ts to take an explicit HeritageResolutionStrategy (A5-style DI). buildHeritageMap accepts an optional getHeritageStrategy callback; production uses getHeritageStrategyForLanguage from heritage-processor.ts. Legacy resolveExtendsType re-export from heritage-processor.ts deleted. Verified: - grep 'from "..' gitnexus/src/core/ingestion/model → empty - grep 'Re-export for legacy' gitnexus/src/core/ingestion → empty - npx tsc --noEmit → clean - npx vitest run → 5686 passing * docs(model): strip phase/plan references from module comments Remove SM-20/21/22/23, A2/A4/A5, plan 006, Unit N labels and historical phrasing ("previously", "legacy", "model-leaf DAG cleanup") from all 10 files in src/core/ingestion/model/. Preserve domain vocabulary (Tier 1/2/3), invariants, and caveats — only the plan archaeology is gone. * refactor(model): tighten interface segregation + compile-time invariants Apply four gated findings from branch-wide code review: - SemanticModel.symbols now typed as SymbolTableReader; MutableSemanticModel widens it back to SymbolTableWriter. ResolutionContext.model is typed as MutableSemanticModel since it owns the lifecycle. Resolvers that only query symbols can annotate their own fields as SemanticModel to drop write access at the type level. - Lookup methods (lookupExactAll, lookupCallableByName, lookupClassByName, lookupClassByQualifiedName, lookupImplByName) now return readonly SymbolDefinition[]. The returned arrays are live views into the internal indexes; the readonly marker prevents accidental caller mutation. walkBindingChain return type narrowed to match. - FREE_CALLABLE_TUPLE + FreeCallableLabel exported from symbol-table.ts as the single source of truth for free-callable labels. LABEL_BEHAVIOR now satisfies Record<FreeCallableLabel, 'callable-only'> as a second cross-invariant alongside Record<ClassLikeLabel, 'dispatch'>. Adding a label to the tuple without classifying it as 'callable-only' fails at build time. CALLABLE_ONLY_LABELS is now a re-export alias of FREE_CALLABLE_TYPES so the two sets cannot drift. - walkBindingChain fast-exits before allocating its cycle-detection Set when the caller's file has no named bindings. Skips ~200k transient Set allocations per large-repo resolution pass. Also fixes five stale comments flagged by the review: duplicate JSDoc block on RegistrationHook merged; resolve.ts "delegates to mro-processor" direction corrected; RegistrationTableDeps JSDoc names createRegistrationTable (not createSymbolTable); mro-processor.ts "re-exported at top" stale comment removed; gatherAncestors export comment matches reality. tsc --noEmit clean, full test suite green (5786 tests). * refactor(model): resolve four deferred P2 review findings Address the four gated items from the branch-wide review that needed design decisions before applying: F#3 — Method/Constructor without ownerId fallback to callable index. The dispatch hook silently skips owner-scoped labels that lack an owner (an extractor contract violation — AST-degraded parse, or a buggy language extractor). Pre-dispatch-table code let such defs fall through to callableByName and stay reachable at Tier 3 global resolution. This restores that fallback in SymbolTable.add so orphaned Methods and Constructors don't silently vanish. Property deliberately does NOT participate in the fallback to avoid polluting common names like id / name / type. F#4 — Delete MutableSemanticModel.resetFileIndex. The method had zero production callers (only three tests), documented a "rare partial- reingestion flow" that was never implemented, and contained the adversarial-reviewer's double-populate trap: calling resetFileIndex followed by re-adding the same class symbol would push a duplicate SymbolDefinition into TypeRegistry.classByName without ever clearing the first one. If incremental reingestion is ever needed, it can be designed properly with per-file TypeRegistry invalidation. For now, deleting the footgun is safer than documenting it. F#5 — Compile-time dispatch-table completeness check. `LABEL_BEHAVIOR` already enforces "every NodeLabel is classified" via `Record<NodeLabel, LabelBehavior>`, but the dispatch-table factory populated its Map with manual `table.set(...)` calls that TypeScript could not correlate back to the `'dispatch'` classification. Add a type-level `DispatchLabel` extracted from `LABEL_BEHAVIOR` via a conditional mapped type, and build the table from an object literal that satisfies `Record<DispatchLabel, RegistrationHook>`. Adding a new dispatch-classified label without wiring it to a hook now fails the build with a named-key error — no more silent no-op hooks. F#7 — Tier 3 dedup fast-path via MethodRegistry.hasFunctionMethods. The Set-based dedup between callableDefs and methodDefs is only needed when a Python/Rust/Kotlin class method (emitted as Function+ownerId by the worker) lands in both indexes. For TS/Java/C#/C++/Ruby-only repos — where the two indexes are disjoint by construction — the dedup was pure overhead on every global-tier hit. MethodRegistry now tracks whether any Function-typed def was ever registered, and resolution- context branches Tier 3 into a concat-only fast path when that flag is false. Slow path with dedup survives unchanged for mixed-language repos. New tests pin the invariants: hasFunctionMethods flag transitions, Method/Constructor orphan fallback, Property non-fallback, and the MethodRegistry clear() reset. Full test suite green (5756 tests). * refactor(model): close remaining P3 review findings + coverage gaps Address the remaining review items in one batch. Production refactors: - Rename classHook → classLikeHook (M05). The hook handles Class / Struct / Interface / Enum / Record / Trait; the vocabulary used in surrounding docs and the behavior-group table is "class-like". The rename makes the code match the taxonomy without forcing readers through a mental glossary. - Extract MAX_BINDING_CHAIN_DEPTH constant in resolution-context.ts and document it as a known silent false-negative source (ADV-003). Five hops cover the common TypeScript monorepo pattern; raising the cap is a one-line change if a real repo exceeds it. walkBindingChain consumes the constant so the 5 magic number no longer floats free. - Replace defs.filter() allocation in MethodRegistry.lookupMethodByOwner with a two-pass streaming count + conditional materialization (PERF-04). Pure-match and pure-reject arity paths now skip the filtered-array allocation entirely; only the discriminating case (at least one match AND at least one rejection) pays it. - Rewrite NOOP_SYMBOL_TABLE in parse-worker.ts and NOOP_SYMBOL_TABLE_SEQ in parsing-processor.ts to implement all six SymbolTableReader methods (ADV-005). The `as unknown as SymbolTableReader` cast is removed in favor of a direct SymbolTableReader annotation, so future additions to the interface surface as compile errors on the stubs instead of silently falling through. - type-env.ts getCallableUnionCount and getFirstCallable now take `model: SemanticModel` as an explicit argument instead of reaching into the enclosing `model!` non-null assertion (KT-003). Callers enter via an `if (model)` guard and pass the narrowed reference, so the non-null precondition is visible at the type level and the closures cannot be accidentally extracted into a context without the guard. - Tier 3 dedup in resolution-context.ts now covers all four index reads (classDefs, implDefs, callableDefs, methodDefs) via a pushUnique helper (C-03). Previously classDefs and implDefs were spread directly without dedup; any theoretical nodeId collision would have produced duplicates in globalDefs. Test infrastructure: - Extract makeDef / makeMethod factory helpers into test/unit/model/helpers.ts (T-07). The four registry/table test files now import the shared helper and specialize with overrides, removing ~25 lines of duplicated boilerplate and creating a single point of maintenance. New test coverage: - T-01: c3 BFS fallback — cyclic Python hierarchy that fails c3 linearization and must fall back to heritageMap.getAncestors() BFS order. Added to the lookupMethodByOwnerWithMRO describe block. - T-02: Tier 2a-named precedence — verifies the binding chain walker fires before Tier 2a import-scoped when an aliased import `import { User as U } from B` competes with a raw same-name Tier 2a hit. Also pins Tier 1 same-file precedence over Tier 2a-named. - T-03: Tier 3 Function+ownerId dedup — end-to-end test that a Python class method emitted as `Function + ownerId` yields exactly ONE Tier 3 candidate (not two). Companion test pins the fast-path branch for hasFunctionMethods === false repos. - T-06: walkBindingChain guards — circular re-export detection, depth-cap exceeded drop, and boundary case at exactly MAX_BINDING_CHAIN_DEPTH hops resolving successfully. All tests added to a new test/unit/model/resolution-context.test.ts dedicated to ResolutionContext.resolve() tier-precedence invariants. Full suite: 5708 passing (minus the known Windows LBUG lock flake that passes in isolation). --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Gergo Magyar <gergomagyar@icloud.com> |
||
|
|
d9960c62bf
|
SM-19: Delete resolveCallTarget — replace with thin dispatcher (#770)
* Initial plan
* SM-19: Replace resolveCallTarget with thin dispatcher
Delete the monolithic resolveCallTarget function (~200 lines) and replace it
with a 15-line thin dispatcher that routes to resolveMemberCall,
resolveStaticCall, or resolveFreeCall. Extract module-alias resolution and
file-based member-call fallback into dedicated helper functions.
- resolveCallTarget body reduced from ~200 lines to ~15 lines
- Extract resolveModuleAliasedCall helper (Python/Ruby module imports)
- Extract resolveMemberCallByFile helper (trait dispatch, overload disambiguation)
- Extract singleCandidate helper (constructor alias fallback, name-based fallback)
- Update unit tests for new dispatcher semantics
- Update doc comments referencing deleted D0-D4 paths
Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/469eac38-b0c0-4a26-a2ff-3eb06299730b
Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com>
* SM-19: Add singleCandidate tail fallback for member calls with unresolvable receiver type
Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/469eac38-b0c0-4a26-a2ff-3eb06299730b
Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com>
* fix(SM-19): address all PR #770 review findings + fix CI
Fixes all 5 test failures (2 unit + 3 integration) and addresses 10
review findings from comment 4225312416.
Critical fix — singleCandidate null-route guard
The SM-19 dispatcher chained singleCandidate as an unconditional tail
fallback for member calls with receiverTypeName. This bypassed the
SM-10 R3 null-route contract: when the receiver type IS in the index
but file/owner filtering produced zero matches, the old code returned
null (genuine miss), but the new code fell through to singleCandidate
(false-positive CALLS edge).
Root cause: resolveMemberCallByFile returns null for two semantically
different reasons — (1) type not found in the index at all, and
(2) type found but no candidate matched after narrowing. The dispatcher
treated both as "try the next fallback." The old resolveCallTarget
exited the entire function on case 2.
Fix: after the scoped resolvers both return null, check whether the
receiver type resolves in the index. If it does (case 2), null-route
— the scoped resolvers made the right decision. If it doesn't (case 1,
e.g. PHP 'mixed', dynamic types), singleCandidate is the correct last
resort. ctx.resolve is cached so the check is free.
This fixes:
- Unit: no heritageMap null-route test (was getting 1 edge, expects 0)
- Integration: Rust c.trait_only() negative test
- Integration: 3 PHP heritage + alias tests (singleCandidate correctly
fires when the receiver type is not in the index)
Performance (findings #1, #2, #3)
- Thread pre-computed tiered result into resolveModuleAliasedCall via
new tieredOverride parameter — eliminates the duplicate ctx.resolve
call on every module-alias path.
- Add countCallableCandidates helper that short-circuits at threshold
without allocating an intermediate array — replaces the
filterCallableCandidates(...).length > 1 allocation in skipMember.
- resolveMemberCallByFile lookupCallableByName caching deferred to a
follow-up (finding #2) — the fix requires threading widenCache
through the file-scoped resolver which is a larger change.
Code quality (findings #4, #5)
- Remove dead code: redundant conditional in resolveMemberCallByFile
where both branches returned null.
- Move WidenCache type declaration from mid-file (between JSDoc blocks)
to adjacent to CONSTRUCTOR_TARGET_TYPES with other type declarations.
Formatting
- Applied prettier to call-processor.ts (CI format check was failing).
Verification
- tsc --noEmit clean
- 3188 unit tests pass (0 skipped real tests)
- 1766 resolver integration tests pass
- Zero regressions — all PHP, Rust, and no-heritageMap tests green
Review: https://github.com/abhigyanpatwari/GitNexus/pull/770#issuecomment-4225312416
* fix(SM-19): restore module-alias narrowing and constructor disambiguation
Codex adversarial review on PR #770 surfaced two silent regressions in the
SM-19 thin dispatcher:
Finding 1 [high] — Typed member calls bypassed module-alias narrowing.
When two homonym receiver types are both imported by the caller, the
import-scoped tier no longer narrows and the owner/file resolvers see
genuine ambiguity. The dispatcher null-routed silently, dropping valid
CALLS edges. Fix: consult `resolveModuleAliasedCall` at the top of the
typed-member branch so an active alias on `call.receiverName` picks the
aliased file before the generic resolvers run.
Finding 2 [medium] — Constructor dispatch lost overload disambiguation.
When `resolveStaticCall` bails (ambiguous or ownerless Constructor pool)
and the caller supplied `overloadHints` / `preComputedArgTypes`, the
branch fell straight through to `singleCandidate` — which also bails on
multiple same-arity survivors. Fix: between `resolveStaticCall` and
`singleCandidate`, run constructor-filtered overload disambiguation on
the tiered pool. Only engages when a narrowing signal is present;
preserves SM-10 R3 null-route for genuinely ambiguous cases.
Tests:
- call-processor.test.ts: 3 new dispatcher-level regression tests
covering real-homonym alias narrowing, constructor overload
disambiguation with `argTypes`, and null-route control
- symbol-table.test.ts: update `module alias homonyms` test which
previously codified the Finding 1 regression; now asserts resolution
to the aliased file's method
Verification: 3191 unit + 2398 integration tests pass; tsc --noEmit
clean; prettier clean.
* refactor(SM-19): address code review findings with clean-code pass
Code review on commit
|
||
|
|
100858f8c8
|
feat(SM-18): Delete lookupFuzzy, lookupFuzzyCallable, globalIndex, callableIndex (#769)
* Initial plan
* Update test files for SymbolTable interface changes
Remove lookupFuzzy, lookupFuzzyCallable, globalIndex, and callableIndex
references from all test files. Replace lookupFuzzyCallable with
lookupCallableByName. Update getStats assertions to only expect
{ fileCount }. Remove tests that exclusively tested removed methods.
Files updated:
- symbol-table.test.ts: Remove lookupFuzzy describe block and all
globalIndex/callableIndex tests, update callable method references
- symbol-resolver.test.ts: Remove SM-16 lookupFuzzy test block,
update Tier 3 describe title
- type-env.test.ts: Update all mock SymbolTable objects and spy
variable names
- call-form.test.ts: Update ownerId propagation test
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat(SM-18): Remove lookupFuzzy, lookupFuzzyCallable, globalIndex, callableIndex
Remove from SymbolTable interface and implementation:
- lookupFuzzy method
- lookupFuzzyCallable method
- globalIndex Map
- callableIndex Map (renamed to callableByName, backing lookupCallableByName)
Add lookupCallableByName as the targeted replacement for fuzzy callable
lookups. Migrate all production callers:
- resolution-context.ts: lookupFuzzyCallable → lookupCallableByName
- type-env.ts: lookupFuzzyCallable → lookupCallableByName
- call-processor.ts: lookupFuzzy → lookupCallableByName (D2 widen paths)
Remove fuzzyCallCount/fuzzyCallableCallCount stats and globalSymbolCount
from getStats(). Update pipeline.ts logging accordingly.
Memory savings: globalIndex stored every non-Property symbol (typically
the largest index by entry count). Removing it eliminates one Map plus
all its per-name arrays — net savings proportional to unique symbol
count in the project.
Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/4a658c69-41a9-4d57-8527-50ca544ca967
* fix(SM-18): address all PR #769 review findings
1. type-env.test.ts mock: add missing lookupImplByName + getFiles methods.
2. Macro/Delegate tests: 2 new tests confirm C/C++ Macro and C# Delegate
are indexed in callableByName.
3. D2 widen path test: module-alias scenario verifying lookupCallableByName
resolves methods in aliased files that shadow same-file definitions.
4. CALLABLE_TYPES unified: exported from symbol-table.ts (single source of
truth), imported in call-processor.ts. Removed duplicate
CALLABLE_SYMBOL_TYPES constant.
5. getStats() observability restored: tier hit counters (tierSameFile,
tierImportScoped, tierGlobal, tierMiss) replace the removed
fuzzyCallCount diagnostic.
* chore(SM-18): remove unnecessary `as any` casts on valid NodeLabel types
Macro, Delegate, TypeAlias, Const, and Variable are all valid NodeLabel
values in gitnexus-shared. The casts suppressed type checking without
purpose and signaled false uncertainty.
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Gergo Magyar <gergomagyar@icloud.com>
|
||
|
|
ab956f113c
|
feat(SM-15): Wire BindingAccumulator into processCallsFromExtracted for cross-file return type propagation (#763)
* Initial plan * Initial setup - Phase 9 BindingAccumulator cross-file return type wiring Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/7cee6490-090d-4714-8cb5-a704168ff47a * feat(SM-15): wire BindingAccumulator into processCallsFromExtracted for Phase 9 cross-file return type propagation Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/7cee6490-090d-4714-8cb5-a704168ff47a * fix(SM-15): address all PR #763 review findings Performance (R1) - Changed _fileScopeByFile from Map<string, [string,string][]> to Map<string, Map<string,string>>. fileScopeGet(filePath, name) is now O(1) — replaces the O(n) linear scan + defensive-copy alloc that ran once per ConstructorBinding entry. fileScopeEntries() reconstructs tuples from Map.entries() for backward compat. - Updated finalize() dev-mode invariant to compare deduplicated Map size rather than raw array length (Map.set deduplicates same-name). Lifecycle (R2) - Documented that Phase 9 intentionally reads pre-finalize because finalize() cannot move before both the worker consumer (line 984) AND the sequential-path writer (line 1061). Pre-finalize reads are safe because finalize() is write-lock-only with no side effects. Replaced the ambiguous "populated but not yet finalized" comment with the full lifecycle ordering explanation. Sequential-path parity (R3) - Wired bindingAccumulator into processCalls at line 797 (sequential path) so verifyConstructorBindings gets the Phase 9 fallback. - Added bindingAccumulator parameter to processAssignmentsFromExtracted signature and wired it at the pipeline.ts call site (line 1026). - Both paths now produce identical Phase 9 behavior for the same code. Tracking comments (R4) - Added "Overlapping mechanism (N of 3)" cross-references at: 1. buildImportedReturnTypes (~line 109) 2. collectExportedBindings (~line 168) 3. Phase 9 fallback in verifyConstructorBindings (~line 563) Each links to the other two and notes future unification. Language coverage (R5) - Added 5 new Phase 9 integration test suites in cross-file-binding.test.ts: JavaScript, C++, C#, PHP, Ruby. Each uses the existing fixture directories and asserts getUser() → User → user.save() resolves. Total cross-file binding tests: 52 (was 37). Quality asymmetry (R6) - Added inline comment at the Phase 9 fallback noting worker-path entries are Tier 0/1 only and that binding accuracy is structurally lower for large repos where the worker path dominates. Tests (+21 new) - 6 fileScopeGet unit tests (happy path, unknown file/name, mixed scopes, post-dispose, duplicate varName last-write-wins) - 15 integration tests across 5 new language suites Verification - tsc --noEmit clean - 3147 unit tests pass (+6 new) - 52 cross-file binding integration tests pass (+15 new) - 1766 resolver integration tests pass - Zero regressions Plan: docs/plans/2026-04-10-001-fix-sm15-review-findings-plan.md Review: https://github.com/abhigyanpatwari/GitNexus/pull/763#issuecomment-4220354242 * fix(SM-15): gate accumulator fallback on resolution tier and fix sequential file-order dependency Two Codex adversarial reviews identified medium-severity bugs in the Phase 9 BindingAccumulator fallback: 1. Local-first violation: the fallback fired regardless of whether ctx.resolve() found same-file candidates, letting an imported callee shadow a local one and produce false CALLS edges. Fixed by gating on tiered.tier !== 'same-file' and callableDefs.length <= 1. 2. Sequential file-order dependency: processCalls flushed and verified per-file, so consumer files processed before their providers missed accumulator bindings. Fixed by splitting into a flush pre-pass (all files) then a resolution loop, mirroring the worker path's "all appends before any reads" pattern. Also adds 11 consumer-before-provider integration test fixtures (one per supported language) and 4 unit tests for tier gating edge cases. * refactor(SM-15): eliminate duplicated prepare logic in processCalls two-pass split Replace the duplicated pre-pass + legacy-path code (parse → query → heritage → TypeEnv → exports) with a single preparation loop followed by a resolution loop. Both paths now share the same preparation code — the only conditional is the accumulator flush. Side benefit: globalParentMap is now fully populated before any resolution runs, improving cross-file isSubclassOf accuracy regardless of file order. Net -118 lines (226 removed, 108 added). * fix(SM-15): address PR #763 third-pass review findings 1. Update stale dispose() JSDoc — remove forward-reference to Phase 9 wiring that is now complete; document actual consumers. 2. Add processAssignmentsFromExtracted Phase 9 unit test — verifies the accumulator fallback produces ACCESSES write edges when the SymbolTable has no returnType for the callee. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Gergo Magyar <gergomagyar@icloud.com> |
||
|
|
bb68cc1eb0
|
Extract resolveMemberCall from resolveCallTarget (SM-11) (#744)
* Initial plan * feat(SM-11): extract resolveMemberCall from resolveCallTarget - Create resolveMemberCall(ownerType, methodName, currentFile, ctx, heritageMap?) that uses owner-scoped + MRO resolution only (no fuzzy lookup) - resolveCallTarget delegates member calls (D0 path) to resolveMemberCall - walkMixedChain uses resolveMemberCall for owner-scoped member-call resolution - Add 7 unit tests for resolveMemberCall covering direct, inherited, MRO, null cases, and confidence tier assertions - Export resolveMemberCall for external use Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/3b7889a9-5f2f-4572-8904-45084210f10d * fix(SM-11): address PR #744 review Blocking fixes: - B1: Revert unrelated package-lock.json gitnexus-shared addition - B2: Document confidence-tier semantic change on resolveMemberCall Performance / coupling fixes: - S1: walkMixedChain now calls resolveMethodByOwner directly (hot path) to avoid throwaway ResolveResult allocation per chain step - S2: Thread tier from resolveMethodByOwner via { def, tier } tuple; eliminates double ctx.resolve Alignment with semantic-model plan (Phase 3 target): - resolveMethodByOwner now iterates ALL class-like candidates from ctx.resolve, deduplicating matches by nodeId. Absorbs D4's ownerId-filtering into the owner-scoped path. - Handles homonym classes (two Users in different files) without falling through to D1-D4 fuzzy widening - Shared-ancestor MRO walks automatically dedup (both homonyms walk to same base method) - Unified direct-vs-MRO lookup under a single canWalkMRO check Tests added: - T1: Three D0 skip-condition tests via new _resolveCallTargetForTesting internal export (overloadHints, preComputedArgTypes, hasActiveModuleAlias) - T2: Rust qualified-syntax null test (trait-inherited method) + direct impl control - T3: C++ leftmost-base diamond inheritance test - B2 lock-in: cross-file class tier assertion - Homonym disambiguation: only-one-owns-method, both-own-method ambiguity, shared-ancestor MRO convergence Verification: - tsc --noEmit: clean - vitest run test/unit/: 3014 passed - vitest run test/integration/resolvers/: 1746 passed * test(SM-11): address second PR #744 review round + per-language integration tests Review fixes (https://github.com/abhigyanpatwari/GitNexus/pull/744#issuecomment-4211877593): P1 (Performance): Replace Map allocation in resolveMethodByOwner with a firstDef+ambiguous flag pattern. Zero allocation for the common single-candidate case on the hot path — the previous Map approach allocated on every member call regardless of whether deduplication was needed. P2 (Test gap): Strengthen the module-alias D0 skip test with a homonym fixture (two Users in different files). Previously the test passed whether or not D0 was actually bypassed; the new version proves D0 must be skipped by showing that resolveMemberCall directly returns null (ambiguous) but D1-D4 with alias narrowing picks the right one. Also fixes the underlying D2-vs-alias widening interaction: when filteredCandidates was narrowed by module-alias disambiguation, D2 no longer widens back to the full fuzzy pool (introduces aliasNarrowed boolean flag). L1 (Language coverage): Add C# and Kotlin implements-split tests at the resolveMemberCall layer. L2 (Maintainability): Export OverloadHints as @internal so the test can use a direct cast instead of fragile Parameters<...> type inference. Per-language integration tests: - rust-child-extends-parent: Direct impl method resolution via D0 (with honest documentation of the trait-method-as-Function gap that is Phase 5 / SM-16 scope) - java-interface-default-method: User implements Validator with default method resolved via implements-split MRO - csharp-interface-default-method: Same pattern for C# 8.0+ default interface methods - kotlin-interface-default-method: Same pattern for Kotlin interfaces with default implementations - python-multi-level-mro: 3-level C3 linearization (Grandparent ← Parent ← Child) - cpp-diamond-inheritance: Classic diamond (Base ← A, B ← Derived) via leftmost-base MRO Verification: - tsc --noEmit: clean - vitest run test/unit/: 3015 passed - vitest run test/integration/resolvers/: 1763 passed (+17 new per-language tests) * fix(SM-11): Codex adversarial review corrections + deeper D0 fixes Addresses the three high-severity findings from the Codex adversarial review of PR #744 (https://github.com/abhigyanpatwari/GitNexus/pull/744#issuecomment-4212075120), plus four deeper fixes discovered during regression triage. All discovered issues are now addressed end-to-end rather than papered over with tail-return fallbacks. Codex review findings: R1 (C++ diamond): The cpp-diamond-inheritance fixture used non-virtual inheritance, which is genuinely ambiguous in real C++ (two Base subobjects). Changed A and B to use 'virtual public Base' so there's a single shared Base subobject and d.method() is an unambiguous call that the leftmost-base MRO walk correctly resolves. R2 (C# default-interface): The csharp-interface-default-method fixture called user.Validate() via a User-typed variable, but C# does not inherit default interface methods as callable class members — the call is only valid through an interface-typed variable. Changed App.cs to 'IValidator user = new User(...)' which is the idiomatic dispatch pattern. R3 (resolveCallTarget tail-return): When D1-D4 receiver filtering produced zero file-matched and zero owner-matched candidates for a member call, the function fell through to the permissive single-candidate tail return — silently emitting CALLS edges for methods that don't belong to the receiver. Added an explicit null-route inside the D1-D4 block that fires only when both filters yielded 0. R4 (Rust negative assertion): Added the c.trait_only() negative integration test in rust.test.ts demonstrating that direct member calls on Rust structs do not walk trait ancestry. The test now passes because of R3 (previously fell through to the tail return). Regression triage discoveries: 1. D0 was dead code on the sequential pipeline. The sequential path sets overloadHints for every call regardless of whether the method is overloaded, and the original D0 skip condition '!overloadHints && !preComputedArgTypes' was therefore always false. The Java/C#/C++ SM-9/SM-10 inheritance tests were passing ONLY via the tail-return fallback. Fix: narrow the skip to 'overloadHints && filteredCandidates.length > 1' — skip D0 only when there are actually multiple candidates that need overload disambiguation. 2. lookupMethodByOwner couldn't disambiguate arity-differing overloads (e.g. C++ greet() vs greet(string)). With D0 now firing on the sequential path, same-name/different-arity overloads would collapse to an arbitrary first pick. Fix: added an optional argCount parameter to lookupMethodByOwner + lookupMethodByOwnerWithMRO that filters the overload set by parameterCount/requiredParameterCount before the returnType dedup. 3. Python and Rust class methods are captured as Function nodes (not Method) with ownerId set to the class. The methodByOwner index only accepted 'Method' and 'Constructor' types, so Python class methods and Rust trait methods were invisible to D0. Fix: extended the methodByOwner indexing condition to include 'Function' when ownerId is set. This also unlocks the Rust trait-method negative assertion by ensuring the qualified-syntax MRO strategy has something to return null for. 4. D0 was being skipped when a local variable shadowed an imported module name (Python 'from models.c import C; c = C()' creates both a module alias 'c → models/c.py' AND a typed local 'c'). Fix: the D0 skip now gates on 'aliasNarrowed' (a new boolean tracking whether the alias block actually narrowed filteredCandidates) instead of 'hasActiveModuleAlias'. If the method isn't in the aliased module, the receiver is a typed local variable and D0 should run. 5. PHP trait walk missed the HasTimestamps trait because lookupClassByName did not include 'Trait' type. buildHeritageMap uses lookupClassByName to resolve parent names, so 'BaseModel use HasTimestamps' was failing to register an ancestor edge for BaseModel → HasTimestamps. Fix: added 'Trait' to CLASS_TYPES. The trait is now a valid class-like type for heritage resolution (PHP use, Rust impl Trait for Struct, Scala traits). Test updates: - Updated the 'no heritageMap' unit test in call-processor.test.ts to assert the correct null-route behavior instead of the old tail-return fallback. - Added a new unit test asserting Trait inclusion in the class set. - Updated the 'does NOT include other type-like labels' test to remove Trait from its rejection set. Verification: - tsc --noEmit: clean - vitest run test/unit/: 3016 passed (+1 new Trait inclusion test) - vitest run test/integration/resolvers/: 1764 passed (+1 new Rust negative assertion) - Zero regressions --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Gergo Magyar <magyargergo@users.noreply.github.com> Co-authored-by: Gergo Magyar <gergomagyar@icloud.com> |
||
|
|
d9ba9aa998
|
SM-10: Add MRO fast path before D2 fuzzy widening in resolveCallTarget (#741)
* Initial plan * Add MRO fast path before D2 fuzzy widening in resolveCallTarget When receiverTypeName is known, try resolveMethodByOwner (owner-scoped + MRO lookup) before falling back to the expensive lookupFuzzy in D2. This short-circuits cross-file member call resolution for the common non-overloaded case. The fast path is skipped when overload disambiguation hints are available (overloadHints or preComputedArgTypes) to avoid picking the wrong overload for same-return-type overloaded methods. Passes heritageMap to resolveCallTarget from all 4 call sites: - Language seed path (processCalls) - Sequential path (processCalls) - walkMixedChain fallback - Worker path (processCallsFromExtracted) Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/9e49521f-2472-47bc-96e9-be4a46b073f0 Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> * fix(SM-10): address PR #741 review Correctness: - Module-alias guard for D0. When call.receiverName matches an active entry in ctx.moduleAliasMap for the current file, D0 is now skipped and resolution falls through to D1-D4 which respects the alias-narrowed candidate pool. Prevents a homonymous class in a different file from being picked by ctx.resolve(receiverTypeName) inside resolveMethodByOwner. New unit test pins the contract. Unit tests (call-processor.test.ts — 3 new): - D0 hit: child.parentMethod() resolves via MRO walk when heritageMap is provided. - D0 skipped: same scenario still resolves via D1-D4 when heritageMap is undefined (backward-compat guard). - Module-alias guard: two files both define class User with a save() method; 'import auth_mod as auth' in app.py must resolve auth.user.save() to auth_mod.py, not user_mod.py. Integration language coverage (+3 fixtures/tests): - swift-child-extends-parent — first-wins, gated on swiftAvailable. - ruby-child-extends-parent — first-wins. - php-child-extends-parent — first-wins (uses ParentClass since 'Parent' is a PHP reserved word). * test(SM-10): address second PR #741 review round Unit tests (call-processor.test.ts, +2 new): - overloadHints guard: Java source with two same-return-type overloads method(int) and method(String), int added first so lookupMethodByOwner would return it. processCalls auto-generates overloadHints for Java, forcing D0 to be skipped. o.method("hello") must resolve to method(String) via literal-inferred disambiguation. - preComputedArgTypes guard: worker-path equivalent via processCallsFromExtracted with ExtractedCall.argTypes=['String']. Same two overloads, same correctness guarantee. Integration tests (+2 fixtures + test blocks): - go-child-extends-parent — struct embedding, first-wins (Go structs are labeled 'Struct' not 'Class' in GitNexus). - dart-child-extends-parent — extends, first-wins, gated on dartAvailable like other Dart tests. Documentation: - Expanded the fallthrough comment in resolveMethodByOwner to clarify that unknown-extension paths land on plain lookupMethodByOwner without an ancestor walk, and that D1-D4 still runs on D0 miss. * test(SM-10): D0 miss with heritageMap present falls through to D1-D4 Closes the last remaining gap from PR #741 review round 3. The existing 'D0 skipped' test only covered the heritageMap=undefined case, leaving the miss-with-heritageMap path implicitly covered by integration tests only. This adds a focused unit test where: - Class Obj has a method doWork findable via tiered resolution (import-scoped) but intentionally NOT registered in methodByOwner (no ownerId), so lookupMethodByOwner misses. - heritageMap is provided but built from an empty heritage array, so getAncestors(class:Obj) returns []. The MRO walk yields no parents. - lookupMethodByOwnerWithMRO therefore returns undefined → D0 miss. - D1 resolves the receiver type; D2 widens via lookupFuzzy; D3 file-filter picks the single matching candidate. - A CALLS edge must still be emitted — D0 miss must not swallow the call. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> Co-authored-by: Gergo Magyar <gergomagyar@icloud.com> |
||
|
|
b75e76d44a
|
feat(SM-8): Build HeritageMap from accumulated ExtractedHeritage[] (#739)
* Initial plan * feat(SM-8): add HeritageMap with MRO-aware parent/ancestor lookup - New heritage-map.ts: HeritageMap interface with getParents() and getAncestors() - buildHeritageMap() consumes ExtractedHeritage[], resolves names via lookupClassByName - Cycle protection and bounded depth (MAX_ANCESTOR_DEPTH=32) in getAncestors - Worker path: HeritageMap built from deferredWorkerHeritage, threaded into processCallsFromExtracted - Sequential path: Heritage accumulated across chunks, HeritageMap built after all chunks, passed to processCalls - 18 unit tests covering parent lookup, multi-level, diamond, cycles, missing parent, bounded depth Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/c413e0a3-5d63-4ddb-8ece-02fe6ed99efd Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> * test: rename cycle test for clarity per code review Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/c413e0a3-5d63-4ddb-8ece-02fe6ed99efd Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> * refactor(SM-8): merge implementor map into heritage map - Add `getImplementorFiles(interfaceName)` to HeritageMap interface - Build implementor index (interface name → file paths) alongside parent lookup in `buildHeritageMap`, using same `resolveExtendsType` logic - Remove `ImplementorMap` type, `buildImplementorMap`, `mergeImplementorMaps` from call-processor.ts - Update `findInterfaceDispatchTargets`, `processCalls`, and `processCallsFromExtracted` to use HeritageMap for both parent lookup and implementor dispatch - Pipeline: single `buildHeritageMap` call replaces separate buildImplementorMap + buildHeritageMap for both worker and sequential paths - Migrate implementor tests from call-processor.test.ts to heritage-map.test.ts (4 new getImplementorFiles tests) - Update interface dispatch test to use buildHeritageMap instead of hand-constructed ImplementorMap Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/085dffb4-b31e-4aa5-9aa3-4314bc0010e7 Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> * test: rename implementor test for clarity per code review Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/085dffb4-b31e-4aa5-9aa3-4314bc0010e7 Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> * fix(SM-8): address PR #739 review comments - pipeline.ts: cache chunk file contents from Pass 1 to eliminate double-read of sequential chunks in Pass 2. Peak memory drains incrementally as Pass 2 processes each chunk. - heritage-map.ts: document Rust trait-impl omission from implementor index and the interface-name collision limitation. - heritage-map.test.ts: add six tests covering the extends->IMPLEMENTS path across C# (interfaceNamePattern), Swift (heritageDefaultEdge), Java (symbol-table Interface lookup), Kotlin, PHP, and the Rust trait-impl omission. - pipeline.ts: comment why the heritage accumulation uses a manual push loop instead of spread (ref #650). * test(SM-8): address second PR #739 review pass - Add TypeScript implements test to getImplementorFiles (closes the .ts coverage gap flagged by the bot reviewer). - Tighten deep-chain boundary assertion from toBeLessThanOrEqual(32) to toBe(32) so a future regression returning fewer ancestors fails loudly. Added an ancestors[31] === 'class:Level32' check to pin the upper boundary. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> Co-authored-by: Gergo Magyar <gergomagyar@icloud.com> |
||
|
|
3388ae16d7
|
[cli] Replace Phase P class checks with class lookup index (#734)
* refactor(call-processor): use class lookup index in phase p * test(call-processor): cover class lookup fallback --------- Co-authored-by: 许恩宁 <xuenning@qiyi.com> |
||
|
|
e2de9271fc
|
feat(java): method references, worker overload disambiguation, interface dispatch (#540)
* feat(java): method references + worker overload disambiguation (TypeEnv + argTypes) Fix two Java gaps: (1) method references (obj::method) via tree-sitter @call + parseJavaMethodReference wired through extractLanguageCallSiteSeed for parse-worker and call-processor; (2) overloaded calls with typed non-literal args by extending OverloadHints with TypeEnv for identifiers and adding ExtractedCall.argTypes from extractCallArgTypes on the worker path with matchCandidatesByArgTypes (inferJvmLiteralType remains for literals). * test(csharp): expect interface-dispatch edge for IRepository.Save in heritage fixture * refactor(ingestion): move parseJavaMethodReference to call-sites/java.ts * refactor(ingestion): defer worker call resolution until implementor map is complete * style: prettier + remove unused import for CI quality checks Made-with: Cursor * fix(ingestion): implementor map for C# base_list + sequential pipeline path - buildImplementorMap: treat extends rows as implements when resolveExtendsType says IMPLEMENTS (worker heritage mirrors parse-worker, all base_list as extends) - Worker path: pass ctx into buildImplementorMap(deferredWorkerHeritage, ctx) - Sequential path: extract heritage before processCalls and pass implementor map so small repos get interface-dispatch CALLS (fixes csharp-proj integration test) Made-with: Cursor * perf(pipeline): accumulate sequential implementor map without O(E) per chunk - Merge buildImplementorMap(chunk heritage) into one map each sequential chunk so work is O(heritage) per chunk and interface dispatch sees prior chunks (worker parity) - Drop unused globalImplementorMap + redundant merge after worker pass Made-with: Cursor |
||
|
|
bf09eab95b
|
feat: configure prettier with pre-commit hook (#563)
* feat: configure prettier with pre-commit hook integration Add prettier, lint-staged, and prettier-plugin-tailwindcss at the repo root with husky pre-commit hook integration. Moves husky from gitnexus/ to root package.json for reliable hook installation. - Root package.json with prepare/format/format:check scripts - .prettierrc with endOfLine:lf and tailwindStylesheet for TW v4 - .prettierignore excluding fixtures, vendor, generated, *.d.ts, *.md - .gitattributes enforcing LF line endings for Windows consistency - Pre-commit hook uses direct node_modules/.bin/ paths (no npx) * style: apply prettier formatting to entire codebase One-time bulk format. No logic changes. Use .git-blame-ignore-revs to skip this commit in git blame. * chore: add .git-blame-ignore-revs for prettier format commit * perf: pre-commit hook runs only tests related to staged files Use vitest --related to scope test execution to tests that import the changed files, instead of running the full suite on every commit. * perf: remove vitest from pre-commit hook, keep in CI only Pre-commit now runs lint-staged + tsc only. Tests run in CI (ci-tests.yml) where they belong — keeps commits fast. * ci: add prettier format check to quality workflow PRs will now fail if code isn't formatted with prettier. |
||
|
|
c437acf6bb
|
feat: deep flow detection — consumer access tracking, middleware chains, error shapes, api_impact tool (#482) | ||
|
|
fff716dd92 |
feat(type-resolution): Phase 14 enhancements — single-pass seeding, Tarjan's SCC, cross-file return types
E0: Fix AST cache thrashing in re-resolution loop (was creating size-1 cache per file), batch file reads per topological level, add MAX_CROSS_FILE_REPROCESS=2000 cap for adversarial repos. E1: seedCrossFileReceiverTypes() — enrich ExtractedCall.receiverTypeName from ExportedTypeMap+namedImportMap in O(1) Map lookups, eliminating re-parse for ~80-90% of single-hop cross-file receiver types. E2: computeImportCycleSCCs() — iterative Tarjan's SCC on cycle subgraph from Kahn's output. Dev-mode diagnostic logging of individual import cycle components. E3: buildImportedReturnTypes() + ReturnTypeLookup extension — cross-file return type propagation with corrected local-first priority (SymbolTable checked first, cross-file fallback only on 0 matches, ambiguous 2+ returns undefined). E4: PARALLEL_RE_RESOLUTION_THRESHOLD constant, timing metrics, worker parallelization design comments (deferred implementation). 24 new tests (6 E1 + 6 E2 + 7 E3 unit + 5 E3 integration). All 3478 tests pass. |
||
|
|
604b575e4b
|
feat: Phase 7 type resolution — return-aware loop inference & PHP class-property iterables (#341)
* feat(type-resolution): Phase 7.1+7.2 foundation — ReturnTypeLookup, context object, pendingCallResults - Move extractReturnTypeName + helpers from call-processor.ts to type-extractors/shared.ts (breaks circular import risk: call-processor → type-env → type-extractors → call-processor) - Add SymbolTable.lookupFuzzyCallable(name) — lazy callable-only index, O(1) per call, invalidated on add(); avoids per-call .filter() on lookupFuzzy results - Add ReturnTypeLookup interface (conservative: undefined when 0 or 2+ callables match) - Add ForLoopExtractorContext interface — replaces 4 positional params with context object; update all 10 language extractor implementations (go, ts, py, jvm×2, cs, rs, rb, php, c-cpp) - Add PendingAssignment discriminated union (kind: 'copy' | 'callResult'); update PendingAssignmentExtractor in all 9 language extractors that implement it - Wire buildTypeEnv: build ReturnTypeLookup from optional symbolTable; split pendingAssignments into pendingCopies + pendingCallResults; add Tier 2b call-result propagation loop - Update call-processor.test.ts to import extractReturnTypeName from shared.ts * feat(type-resolution): Phase 7.3 — call_expression iterables in for-loop extractors (7 languages) Extends for-loop type extraction in all 7 typed-iteration languages to resolve element types when the iterable is a direct function call. **New capability**: `for (var u : getUsers())` in Java, `for u in get_users()` in Python, `for user in getUsers()` in TypeScript, etc. now resolve `u`/`user` to the callee's return element type via lookupRawReturnType + extractElementTypeFromString. Changes per language: - types.ts: extend ReturnTypeLookup with lookupRawReturnType (raw return string for container-type extraction); update ForLoopExtractorContext with returnTypeLookup field - type-env.ts: implement lookupRawReturnType on the concrete ReturnTypeLookup built in buildTypeEnv (same guards as lookupReturnType, no extractReturnTypeName) - go.ts: call_expression branch in range_clause — identifier func or selector_expression method; existing isChannelType guards updated - typescript.ts: identifier fn branch inside call_expression handler - python.ts: identifier fn branch inside call handler - jvm.ts (Java): method_invocation without object field in enhanced_for_statement - jvm.ts (Kotlin): simple_identifier callee branch in call_expression node - csharp.ts: identifier fn branch in invocation_expression handler - rust.ts: identifier func branch in call_expression handler (alongside existing field_expression/method-call path) All branches follow the same conservative pattern: lookupRawReturnType(callee) → extractElementTypeFromString → bind loop var * feat(type-resolution): Phase 7.4 — PHP \$this->property iterable via @var class property scan Adds Strategy C to PHP's extractForLoopBinding for the pattern: foreach (\$this->property as \$item) when Strategy A (resolveIterableElementType) and Strategy B (scopeEnv lookup) both fail to find the element type. Strategy C: when the iterable is a member_access_expression with object '$this', walk up the AST to the enclosing class_declaration, scan its declaration_list for a property_declaration whose variable_name matches the property, and extract the element type from: 1. PHPDoc @var annotation on a preceding comment sibling (/** @var User[] */) 2. PHP 7.4+ native type field (e.g. UserRepo \$repo — skips generic 'array') This eliminates the @param workaround that was previously required in the php-foreach-member-access fixture (which used @param User[] \$users on the method to populate the method's scopeEnv with a \$users binding). New helpers in php.ts: - PHPDOC_VAR_RE: regex for @var extraction - extractClassPropertyElementType: reads @var or native type from a property_declaration - findClassPropertyElementType: scans class body for a named property Tests added (type-env.test.ts): - PHP: resolves from @var User[] without @param workaround - PHP: conservative — no binding for unknown property - PHP: multi-class file — both classes resolve independently Fixture updated (php-foreach-member-access/App.php): - Removed the @param User[] \$users workaround from processMembers() - Test now validates the natural class-property-based resolution path * docs: mark Phase 7 complete in type-resolution-roadmap.md Records that 7A (call_expression iterables, 7 languages), 7B (PHP $this->property via @var scan), and 7C (ReturnTypeLookup + context object) are all shipped. Adds implementation notes and strikethroughs on resolved language-specific gaps. * fix(docs): update project references to feat-phase7-type-resolution in AGENTS.md and CLAUDE.md * feat(type-resolution): Phase 7.5 — PHP call_expression foreach + integration tests for 7 languages Add integration test coverage for Phase 7.3's call_expression iterable resolution across all 7 languages (Go, TypeScript, Python, Java, Kotlin, PHP, Rust). Each test creates a fixture with competing User/Repo classes that both define save(), then verifies for-loop iteration over a function call's return value resolves to the correct class. PHP was missing function_call_expression support in its for-loop extractor. Three changes fix this: - php.ts extractForLoopBinding: handle function_call_expression and member_call_expression iterables via returnTypeLookup - php.ts normalizePhpReturnType: preserve array notation (User[]) in SymbolTable so lookupRawReturnType returns useful container types - parse-worker.ts + parsing-processor.ts: upgrade uninformative AST return types (array, iterable) with PHPDoc @return annotations 35 new integration tests (5 per language), 2525 total tests passing. * fix(type-resolution): address PR #341 review findings — PHP asymmetry + dormant infrastructure docs - Replace normalizePhpType with extractElementTypeFromString in PHP call-expression foreach paths, aligning with all 6 other language extractors and preventing incorrect binding of bare non-container types like User - Add NOTE comments clarifying pendingCallResults Tier 2b is infrastructure-ready but no extractor populates it yet - Expand Go channel-type comments explaining why non-channel assumption is safe * fix(type-resolution): address verification review — docs accuracy + PHP fallback guard - Roadmap lines 86/100: correct pendingCallResults from "active" to "dormant infrastructure (Phase 9)" - type-resolution-system.md line 363: update to reflect Phase 7.3 loop inference is delivered - type-resolution-system.md line 409: clarify for-loop call-expression resolution (done) vs general assignment propagation (pending) - php.ts:127: add declaration_list type guard on fallback to prevent silent wrong results |
||
|
|
f0132c1077
|
feat: Phase 6 type resolution — for-loop Tier 1c, pattern matching, container descriptors, 10-language coverage (#318)
* feat: Phase 6 type resolution — pattern matching, for-loop Tier 1c, coverage completion
- Add patternBindingNodeTypes gate to LanguageTypeConfig for 50% perf improvement
- Expand ForLoopExtractor signature with optional declarationTypeNodes + scope
- Add extractElementTypeFromString shared utility for container type parsing
- Python match/case: extractPatternBinding for `case User() as u:` pattern
- C# refactor: move is_pattern_expression from extractDeclaration to extractPatternBinding
- Ruby: add extractPendingAssignment for assignment chain propagation
- TS/JS: add for-loop Tier 1c for `for (const user of users)` with User[] inference
- Python: add for-loop Tier 1c for `for user in users:` with type annotation inference
- Go: add for-loop Tier 1c for `for _, user := range users` with []User inference
- Fix 'Property' as any stale cast in call-processor.ts
- Add dual return-type string length cap (2048 pre-cap, 512 post-cap)
- Add chain call integration tests for C#, Go, Rust, Python, JS, C++
- Add Python match/case integration test fixtures
- 27 new extractElementTypeFromString unit tests
- 3 for-loop edge cases skipped (declarationTypeNodes scope key lookup)
* fix: address code review findings for Phase 6
- Add missing patternBindingNodeTypes to C# typeConfig (perf gate)
- Add 2048-char input length guard to extractElementTypeFromString
- Skip Python match/case integration tests (call extraction needs query updates)
* reorganise
* fix: Phase 1 bug fixes — Go range semantics, typed_parameter, bracket depth
- Go single-var range correctly returns early for slices/maps (index, not element)
- Go single-var range on channels correctly resolves element type
- Added map_type and channel_type to extractGoElementTypeFromTypeNode
- Added isChannelType helper for channel detection before skip decision
- Added 'typed_parameter' to TYPED_PARAMETER_TYPES for Python annotated params
- Fixed bracket depth tracking in extractElementTypeFromString — only match
selected closeChar at depth 0, return undefined for mismatched brackets
- Un-skipped 3 prematurely skipped tests (TS local const, Python List/Sequence)
- Added tests for map range, single-var range semantics, bracket edge cases
* 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)
* 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)
* feat: Phase 3 complete — all language gaps + pattern matching
Kotlin Tier 1c:
- Unannotated for-loop resolves via shared helper
- extractKotlinElementTypeFromTypeNode handles type_projection unwrapping
- findKotlinParamElementType walks to function_declaration
Java Tier 1c:
- var foreach resolves via shared helper
- extractJavaElementTypeFromTypeNode handles generic_type, array_type
- findJavaParamElementType walks to method_declaration
TypeScript:
- readonly User[] unwrapped via readonly_type → array_type recursion
C# switch patterns:
- declaration_pattern added to patternBindingNodeTypes
- extractPatternBinding handles standalone declaration_pattern (switch case/expr)
Rust match arms:
- match_arm added to patternBindingNodeTypes
- extractPatternBinding extended with match_arm → match_expression parent traversal
Python:
- as_pattern tries childForFieldName('alias') before positional fallback
Tests: 237 pass (was 224), 13 new tests added
* 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
* test: integration tests for all Phase 6 language gaps + fix Rust param pattern field
Integration test fixtures and tests (30 new tests, all with exact match + negative):
Rust for-loop (5 tests):
- for user in &users with Vec<User> → User#save, negative Repo#save
- for repo in &repos with Vec<Repo> → Repo#save, negative User#save
Rust match arm (5 tests):
- match opt { Some(user) => user.save() } → User#save, negative Repo#save
- if let Ok(repo) = res → Repo#save, negative User#save
C# var foreach (5 tests):
- foreach (var user in users) with List<User> → User#Save, negative Repo#Save
- foreach (var repo in repos) with List<Repo> → Repo#Save
C# switch pattern (4 tests):
- is User user → User#Save, case Repo repo → Repo#Save
Kotlin unannotated for (4 tests):
- for (user in users) with List<User> → user.save, negative repo.save
Go map range (3 tests):
- for _, user := range userMap with map[string]User → User#Save, negative
TypeScript readonly (4 tests):
- for (const user of users) with readonly User[] → user.save, negative
Bug fix: type-env.ts parameter branch now falls back to childForFieldName('pattern')
for Rust parameters (Rust uses 'pattern' not 'name' for parameter names)
* test: add assertion bodies to known limitation skip tests
Convert empty skip test stubs to proper tests with parse/buildTypeEnv/expect
assertions following the codebase convention (e.g., call-processor.test.ts:319).
Each skip test now documents the exact expected behavior, so removing .skip
will cause a meaningful failure when the limitation is eventually fixed.
Also clarify Python integration skip tests as call-extraction issues (not
type-env) and Swift integration skips as build-dep issues (self/super
resolution code already exists in type-env.ts).
* feat: resolve 4 known limitation skip tests + method-aware type arg selection
Unskip 4 of 5 type-env known limitations with full integration test coverage:
1. TS destructured for-of: handle array_pattern by binding last named child
to element type. Fix Map<K,V> to return last generic arg (value type).
2. Python dict.items() loop: handle `call` iterables + `pattern_list` left
side. Fix dict[K,V] extraction via type_parameter with last-arg heuristic.
Unwrap `type` wrapper in extractPyElementTypeFromAnnotation.
3. TS instanceof narrowing: add extractPatternBinding for binary_expression
with positional child access. First-writer-wins (not block-scoped).
4. Rust .iter() for-loops: handle call_expression in for_expression value
node by extracting receiver from field_expression.
Method-aware type arg resolution:
- Add TypeArgPosition ('first'|'last') to resolveIterableElementType
- .keys()/.keySet()/.Keys → first type arg (key); all else → last (value)
- Thread position through all 3 strategy callbacks in TS/Rust/Python
- Add predefined_type to extractSimpleTypeName for TS primitives (string etc)
New fixtures: rust-iter-for-loop, typescript-destructured-for-of,
typescript-instanceof-narrowing, python-dict-items-loop.
248 unit tests pass (6 new), 1 skip (Ruby block params).
* feat: container descriptor table for generic type arg resolution
Replace simple KEY_METHODS heuristic with CONTAINER_DESCRIPTORS table
that maps 30+ container types across all languages to their type parameter
semantics per access method.
Key improvements:
- Container-aware resolution: HashMap.iter() correctly yields V (arity 2),
while Vec.iter() yields T (arity 1) — same method, different semantics
- Cross-language coverage: Map/HashMap/BTreeMap/dict/Dict/Dictionary/
ConcurrentHashMap + List/Vec/Set/HashSet/Queue/Deque/Stack etc.
- Method categorization: keyMethods (keys/keySet/Keys) vs valueMethods
(values/get/pop/iter/first/last) per container type
- Fallback for unknown containers: still uses method name heuristic,
so MyCache<K,V>.keys() correctly returns first arg
- Exported getContainerDescriptor() for future heritage-chain lookups
Each language extractor now passes containerTypeName from scopeEnv to
methodToTypeArgPosition for descriptor-aware resolution.
252 unit tests pass (4 new descriptor tests), 1 skip (Ruby).
* feat: method-aware for-loop extractors + integration tests for all languages
Upgrade 4 existing extractors + create 3 new ones for full cross-language
coverage of call_expression iterables and container descriptor resolution:
Upgraded (add call expr iterable + methodToTypeArgPosition):
- Java: method_invocation (data.keySet(), data.values())
- Kotlin: navigation_expression + call_expression (data.keys, data.values())
- C#: member_access_expression + invocation_expression (data.Keys, data.Values)
- Go: TypeArgPosition threading for Go 1.18+ generics
New for-loop extractors:
- C++: for_range_loop with auto& unwrapping, template_type + qualified_identifier
(std::vector<User>) extraction, explicit vs auto type handling
- PHP: foreach_statement with simple/key-value/by-reference forms, PHPDoc
@param priority over AST array type
- Ruby: for-in with YARD @param type resolution via comment parsing
Integration test fixtures + tests for all 6 languages:
- java-map-keys-values (Map.values() + List iteration)
- kotlin-map-keys-values (HashMap.values + List iteration)
- csharp-dictionary-keys-values (Dictionary.Values foreach)
- cpp-range-for (auto& + const auto& range-based for)
- php-foreach-loop (foreach with PHPDoc @param User[])
- ruby-for-in-loop (for-in with YARD @param Array<User>)
Bugs fixed during integration testing:
- C++: qualified_identifier (std::vector) not unwrapped to template_type
- PHP: extractParameter overwrote PHPDoc-derived types with bare 'array'
252 unit tests pass, 201 integration tests pass across 6 languages.
* fix: update extractElementTypeFromString tests for last-arg default
TypeArgPosition change (default 'last') broke 5 existing tests expecting
first arg from multi-arg generics. Updated expectations and added explicit
pos='first' tests for key type extraction.
* fix: rename C++ fixture files to correct case for case-sensitive CI
On case-sensitive filesystems (Linux/macOS CI), git tracked both the old
lowercase files (app.cpp, user.h) and the new uppercase files (App.cpp,
User.h) as separate files. The pipeline processed both, causing the old
app.cpp (with explicit User& type) to interfere with the new auto& test.
Removes old lowercase entries and re-adds with uppercase casing to match
the #include directives in the fixture.
* feat: PR #318 review findings — pattern bindings, member access iterables, structured bindings
Address all 7 genuine gaps identified in PR #318 deep code review:
- Kotlin: add extractKotlinPatternBinding for when/is (type_test AST node)
with allowPatternBindingOverwrite for smart-cast semantics
- Java: add type_pattern branch for Java 17+ switch pattern variables
- TypeScript: explicit object_pattern skip in for-of (no false bindings)
- Cross-language: member access iterables (self.users, this.users, repo.users)
across all 10 language extractors
- C++: structured_binding_declarator handling in range-for (last-child heuristic)
- Rust: closure_parameter added to TYPED_PARAMETER_TYPES
- PHP: normalizePhpType handles angle-bracket generics (Collection<User>)
Code review fixes applied:
- Remove 4 debug console.log statements (c-cpp.ts, call-processor.ts)
- Hoist KNOWN_CONTAINER_PROPS to module scope (csharp.ts)
- Guard keysBefore allocation behind typeNode check (type-env.ts)
- Add depth limits (50) to 7 recursive type extraction functions
- Add 2048-char length cap to extractSimpleTypeName
- Fix PHP/Ruby missing typeArgPos parameter in resolveIterableElementType
Integration test fixtures: kotlin-when-pattern, java-switch-pattern,
cpp-structured-binding, typescript-member-access-for-loop,
python-member-access-for-loop
* fix: position-indexed when/is bindings, Kotlin param extraction, HashMap.values for-loop
Three root causes for failing Kotlin integration tests:
1. When/is multi-arm resolution: flat scopeEnv stored only the last arm's
type (last-writer-wins). Added PatternOverrides with AST range indexing
so each when arm resolves to its narrowed type independently.
2. HashMap.values for-loop: navigation_expression without call_suffix was
classified as bare property access (iterableName='values' instead of
'data'). Now tries object-as-iterable + property-as-method first, with
fallback to property-as-iterable for this.users patterns.
3. Kotlin parameter extraction: tree-sitter-kotlin parameter nodes use
positional children (simple_identifier, user_type) not named fields
(name, type). Added fallback to findChildByType in both
extractKotlinParameter and extractTypeBinding.
Integration tests added for .keys/.values/Set/MutableMap iteration,
3-arm when/is, multi-call within arms, and when+else branch.
* feat: enhance PHP type resolution for generics and member access in foreach loops
* feat: Phase 6.1 type resolution gap closure — container descriptors, recursive_pattern, class fields
Add 13 missing container type descriptors (Collection, MutableMap, Stream, SortedSet, etc.)
to CONTAINER_DESCRIPTORS for correct element type extraction across C#, Kotlin, and Java.
Extend C# pattern binding to handle recursive_pattern (obj is User { Name: "Alice" } u)
in both is-expression and switch expression contexts.
Add TypeScript class field declaration support (public_field_definition) so for-loop
iteration over this.fieldName resolves element types from class field type annotations.
Includes file-scope fallback in resolveIterableElementType and nested member_expression
handling for this.field.method() patterns.
* docs: add type resolution system documentation with roadmap
Covers the full architecture, resolution tiers (0-2), scope model,
language feature matrix, container descriptors, pipeline integration,
and the Phase 7-9 roadmap for cross-scope propagation, field-type
resolution, and return-type-aware binding.
* feat: Phase 6.2 review findings — C# nested member foreach, C++ deref range-for, Java field_access
Close two gaps found during fourth-pass review of PR #318:
- C# foreach (var user in this.data.Values): nested member_access_expression
now extracts intermediate property name for scopeEnv lookup
- C++ for (auto& user : *ptr): pointer_expression dereference now recognized
as range-for iterable
Root causes fixed in shared infrastructure:
- extractSimpleTypeName: add template_type (C++) and generic_name (C#)
- extractGenericTypeArgs: add generic_name for consistency
- type-env.ts: unwrap variable_declaration wrapper in field_declaration
for declarationTypeNodes capture (zero-allocation manual loop)
Additional review findings addressed:
- Java: add field_access handler for this.data.values() in method_invocation
- C++ pointer_expression: document limitation (*identifier only)
- TypeScript: fix stale comment about property_identifier
All 525 tests pass (278 unit + 247 integration).
* perf: optimize type resolution pipeline — worker threshold, skip graph phases, AST pruning
- Skip worker pool creation for small repos (<15 files or <512KB) — saves 100-400ms
- Add skipGraphPhases option to runPipelineFromRepo to skip MRO/community/process phases
- Add conservative SKIP_SUBTREE_TYPES for leaf-only AST nodes (string, comment, number)
- Pre-compute interestingNodeTypes set — single Set.has() replaces 3 checks per node
- Add fastStripNullable — skip full stripNullable for simple identifiers (90%+ case)
- Replace .children?.find() with manual for loops in extractFunctionName (no array alloc)
- Add hookTimeout: 120000 to vitest.config.ts for CI beforeAll hooks
* fix: review findings — remove template_string from SKIP_SUBTREE_TYPES, handle bare nullable keywords
- Remove template_string and concatenated_string from SKIP_SUBTREE_TYPES
(template literals contain interpolated expressions with typed code)
- Add FAST_NULLABLE_KEYWORDS check to fastStripNullable for behavioral
parity with stripNullable on bare null/undefined/void/None/nil
- Add explanatory comment on extractPendingAssignment scopeEnv guard
* feat: add type resolution system and roadmap documentation
|
||
|
|
f2d3df48f6
|
feat: Phase 5 type resolution — chained calls, pattern matching, class-as-receiver (#315)
* feat: Phase 5 type resolution — chained calls, pattern matching, class-as-receiver, code review fixes Phase 5.1: Chained method call resolution (depth-capped at 3) - resolveChainedReceiver() resolves a.getUser().save() by walking the chain and looking up intermediate return types from the SymbolTable - extractReceiverNode() + extractCallChain() shared in utils.ts - receiverCallChain on ExtractedCall for worker path parity - MAX_CHAIN_DEPTH=3 enforced in both extraction and resolution Phase 5.2: Pattern matching binding extractors - PatternBindingExtractor type added to LanguageTypeConfig - declarationTypeNodes map tracks original type AST nodes for generic unwrapping - Rust: if let Some(x)/Ok(x) unwrapping with extractGenericTypeArgs - Java: instanceof pattern variables (Java 16+) - C#: is-pattern disambiguation fixture (already working via extractDeclaration) Phase 5.5d: Python standalone type annotations (name: str) - expression_statement with type child now captured in DECLARATION_NODE_TYPES Phase 5.5e: ReceiverKey collision fix for overloaded methods - receiverKey preserves @startIndex to prevent same-name method collisions - lookupReceiverType does prefix scan with ambiguity refusal Class-as-receiver for static method calls (#289) - UserService.find_user() now resolves via ctx.resolve() tiered lookup - Respects import scoping — no false positives from unrelated packages Code review fixes: - Extracted CALL_EXPRESSION_TYPES + extractCallChain to utils.ts (eliminated duplication) - Converted resolveChainedReceiver from recursion to loop (no exposed depth param) - Added depth cap to extractReturnTypeName (defense against nested wrapper types) - Replaced lookupFuzzy with ctx.resolve for class-as-receiver (architecturally consistent) Closes #289 Test coverage: 6 new fixtures, 12+ new unit tests, 7 new integration test suites * fix: Ruby chain calls, Rust Err(x) unwrap, Enum class-as-receiver (#315) Address three per-language gaps identified in Phase 5 code review: - Ruby: add `method`/`receiver` field fallbacks to extractCallChain (tree-sitter-ruby uses different field names than other grammars) - Rust: handle `Err(e)` pattern binding via typeArgs[1] from Result<T,E> - Enum: include Enum type in class-as-receiver filter (both paths) Integration tests added for all three fixes. * fix: chain base type resolution parity between serial and worker paths (#315) - Worker path: add typeEnv.lookup for chain base receiver after extraction (typed parameters like `fn process(svc: &UserService)` were silently lost) - Serial path: add ctx.resolve class-as-receiver fallback for chain base (class-name chains like `UserService.find_user().save()` failed) - Fix misleading comment in parse-worker.ts that described unimplemented logic - Integration tests: typed-parameter chain, static class-name chain * fix: Kotlin chain call extraction, createClassNameLookup Enum/Struct (#315) - Kotlin: extractCallChain now handles navigation_expression → navigation_suffix AST structure (Kotlin's call_expression has no 'function' field) - createClassNameLookup: include Enum and Struct alongside Class for consistent constructor recognition in extractInitializer - Integration test: kotlin-chain-call fixture verifying svc.getUser().save() |
||
|
|
6c18ae08f7
|
feat: return type inference, doc-comment parsing, and per-language type extractors (#284)
* feat: Phase 3 — return type inference, generic args extraction, Ruby YARD type extractor
Three architectural improvements to the type resolution system:
1. Return type inference — wire extractMethodSignature returnType through
SymbolDefinition into call-processor. When var = callee() and callee
has a known return type, bind var to that type. Handles Promise<T>
unwrapping, nullable stripping, pointer/reference removal.
2. Generic type argument extraction — new extractGenericTypeArgs() utility
that extracts type parameters from List<User> → ['User']. Handles
TS/Java/Kotlin/C#/Rust generic syntax. Building block for for-loop
variable typing.
3. Ruby dedicated type extractor — replaces the stub with YARD annotation
parsing (@param name [Type]), handling qualified types, nullable types,
and singleton methods. Ruby now has real type resolution.
Unit tests: 127 → 192+ (type-env) + 65 (symbol-table, call-processor) + 18 (generics)
Integration tests: 8+ new test cases with fixtures across TS/Python/Go/Java/Ruby
* fix: Phase 3 gaps — WRAPPER_GENERICS correctness, Ruby :: qualifier, namespaced constructors
- Remove collection types (List, Array, Vec, Set) from WRAPPER_GENERICS to prevent
false CALLS edges (e.g. List<User> no longer unwraps to User)
- Add :: qualifier handling in extractReturnTypeName for Ruby/C++/Rust namespaced types
- Add Ruby `constant` and `scope_resolution` node types to shared extractors
- Extract shared extractRubyConstructorAssignment helper (dedup type-env.ts + ruby.ts)
- Add integration tests for return type inference: Python, TypeScript, Go, Java, Ruby
- Add Ruby namespaced constructor fixture (Models::UserService.new)
- Add unit tests for collection reclassification and :: qualifiers
* feat: Phase 4 — CONSTRUCTOR_BINDING_SCANNERS for all languages + return type inference tests
Add CONSTRUCTOR_BINDING_SCANNERS for 6 missing languages, completing
return type inference coverage across all 11 supported languages:
- TypeScript/JS: variable_declarator with call_expression, unwraps await
- Go: short_var_declaration single-assignment (skips multi-return, new/make)
- Java: local_variable_declaration with `var` type + method_invocation
- C#: variable_declaration with implicit_type (var) + invocation_expression
- Rust: let_declaration without type annotation, handles mut_pattern
- PHP: assignment_expression with function_call_expression
Also adds property_identifier to extractSimpleTypeName for qualified
member calls (repo.getUser → getUser), fixing namespaced constructor
inference that was previously a known limitation.
Integration tests added for all 11 languages with correct label
assertions (Function vs Method per language's tree-sitter queries).
* refactor: merge CONSTRUCTOR_BINDING_SCANNERS into per-language LanguageTypeConfig
Eliminates the parallel dispatch map in type-env.ts by moving all 11
constructor binding scanners into their respective type-extractors/*.ts
files as `scanConstructorBinding` on LanguageTypeConfig.
- Add ConstructorBindingScanner type to types.ts
- Add shared helpers: hasTypeAnnotation, unwrapAwait, extractCalleeName
- Move scanners to typescript.ts, jvm.ts, python.ts, php.ts, go.ts,
rust.ts, swift.ts, c-cpp.ts, csharp.ts, ruby.ts
- Fix `any` types in C# scanner → SyntaxNode | null
- Delete ~300 lines from type-env.ts (CONSTRUCTOR_BINDING_SCANNERS map)
- Update buildTypeEnv to use config.scanConstructorBinding
All 143 type-env unit tests and all 10 language integration suites pass.
* fix: remove unused import, fix any type in Java scanner, update stale comment
- Remove unused extractCalleeName import from jvm.ts
- Fix (c: any) → (c: SyntaxNode) in Java scanner
- Update stale CONSTRUCTOR_BINDING_SCANNERS reference in ruby.ts comment
* fix: C# and PHP return type inference — scanner fixes, method signature extraction, and cross-file resolution
Addresses code review findings on PR #284:
C# scanner (csharp.ts):
- Fix type node lookup: iterate children instead of childForFieldName('type')
which returns undefined in tree-sitter-c-sharp
- Fix initializer lookup: handle direct invocation_expression children
(no equals_value_clause wrapper in tree-sitter-c-sharp)
C# return type extraction (utils.ts):
- Add 'returns' field check to extractMethodSignature — tree-sitter-c-sharp
uses 'returns', not 'type', for method return types
C# cross-file resolution (call-processor.ts + fixture):
- Add constructor binding verification to sequential processCalls path
(was only in the worker processCallsFromExtracted path)
- Add ReturnType.csproj to csharp-return-type fixture
- Update fixture namespaces to use ReturnType.Models/ReturnType.Services
prefix (matches real C# project conventions)
PHP scanner (php.ts):
- Extend scanConstructorBinding to handle member_call_expression
($this->getUser() patterns), not just function_call_expression
Shared (shared.ts):
- Add member_access_expression to extractSimpleTypeName qualified-names
block (C# method calls like svc.GetUser())
Tests:
- Add Repo.cs/Repo.php disambiguation fixtures (two Save methods)
- Strengthen C# and PHP return type tests with hard disambiguation assertions
- Add C# scanner unit tests and return type extraction test
* feat: per-language ReturnTypeExtractor + doc-comment @param parsing for PHP, JS, Ruby
Add ReturnTypeExtractor to LanguageTypeConfig interface with implementations
for Ruby (YARD @return), PHP (PHPDoc @return), and JS/TS (JSDoc @returns).
The fallback is wired in both parsing-processor and parse-worker paths,
activating only when extractMethodSignature finds no AST-based return type.
Also add doc-comment @param type extraction for PHP and JS/TS, following
Ruby's existing collectYardParams pattern. This enables parameter.method()
resolution in loosely-typed codebases using PHPDoc @param or JSDoc @param.
Additional fixes from PR #284 code review:
- Go: add selector_expression + field_identifier to extractSimpleTypeName
(enables package-qualified factory calls like models.NewUser())
- Ruby: broaden scanConstructorBinding to capture plain call assignments
(user = get_user()) in addition to Class.new patterns
- Ruby: harden return-type fixture with disambiguation (two save methods)
Test coverage: +14 new integration tests across Go, Ruby, PHP, JS/TS
* fix: JSDoc async return type, PHP attribute walkers, and $this receiver disambiguation
Three fixes from fourth-pass code review on PR #284:
1. JSDoc `@returns {Promise<User>}` no longer stripped to `Promise` — extractReturnType
now uses sanitizeReturnType (preserves generics) instead of normalizeJsDocType
(which stripped them before extractReturnTypeName could unwrap WRAPPER_GENERICS).
2. PHP 8+ `#[Attribute]` and JS `@decorator` nodes no longer break doc-comment walkers.
Both extractReturnType and collect*Params functions now skip attribute_list/decorator
nodes instead of breaking on them as named siblings.
3. PHP `$this->method()` now provides receiverClassName for disambiguation.
When two classes define the same method, the enclosing class narrows candidates
via ownerId matching in call-processor, preventing false no-binding results.
* fix: sanitizeReturnType dot corruption, JS test assertions, Ruby constant receiver
- Remove redundant dot-path stripping from sanitizeReturnType that corrupted
qualified names inside generics (e.g. Promise<models.User> → User>)
- Split JS async fixture into separate files and add negative assertions
to properly verify disambiguation (mirroring PHP test pattern)
- Accept 'constant' node type in Ruby scanConstructorBinding for factory
call assignments (SERVICE = build_service())
- Add 'constant' to SIMPLE_RECEIVER_TYPES so extractReceiverName handles
Ruby constant receivers (SERVICE.process)
* fix: nested generic arg splitting, JS/Ruby test false positives
- Replace naive comma split in extractReturnTypeName with bracket-balanced
extractFirstGenericArg so nested types like Future<Result<User, Error>>
unwrap correctly instead of producing malformed "Result<User"
- Add CompletableFuture to WRAPPER_GENERICS for Java async unwrapping
- Split js-jsdoc-return-type fixture models.js into user.js/repo.js and
add negative assertions to prove disambiguation (not just file match)
- Split ruby-constant-factory-call fixture into separate service files
and add negative assertions against AdminService resolution
* fix: review findings — receiverClassName parity, Rust wrappers, Go multi-return, Kotlin/Swift qualified calls
P1: Sequential path now includes receiverClassName narrowing for PHP
$this->method() disambiguation (was missing vs worker path).
P2: Added Rc/Arc/Weak/MutexGuard/Cow + 6 more Rust Deref types to
WRAPPER_GENERICS (Box excluded — Java Swing collision). Extended
Kotlin/Swift scanners to handle navigation_expression callees.
Added Go multi-return support (user, err := f()) with blank/_/err/ok
guard + AST-level first-return extraction in extractMethodSignature.
P3: Extracted shared verifyConstructorBindings() eliminating 60 lines
of duplication between sequential and worker paths. Added return-type
inference integration tests for C++, Rust, Swift with competing
methods and negative disambiguation assertions.
* fix: Swift navigation_suffix unwrapping, Rust lifetime skipping, Kotlin disambiguation tests
- Swift scanConstructorBinding: handle tree-sitter wrapping qualified
identifiers in navigation_suffix nodes
- Add extractFirstTypeArg to skip Rust lifetime parameters ('a, '_)
when unwrapping wrapper generics like Ref<'_, User>
- Kotlin tests: add Repo class fixture with competing save() methods
to prove disambiguation; assert no spurious edges on known gap
- Remove tree-sitter-kotlin from optionalDependencies (now regular dep)
* fix: C# null-conditional calls, Ruby YARD bracket-balanced split, PHPDoc alternate order, escapeValue hardening
- Add C# null-conditional call support (user?.Save()): tree-sitter query for
conditional_access_expression, member_binding_expression in MEMBER_ACCESS_NODE_TYPES,
receiver extraction via conditional_access_expression parent walk
- Fix Ruby YARD type parsing for nested generics (Hash<Symbol, User>): replace
naive split(',') with bracket-balanced splitter respecting <> depth
- Add alternate YARD format (@param [Type] name) alongside standard (@param name [Type])
- Add alternate PHPDoc format (@param $name Type) alongside standard (@param Type $name)
- Harden escapeValue in kuzu-adapter.ts: escape \n and \r to prevent Cypher injection
- Integration tests: C# null-conditional fixture (5 tests), Ruby YARD generics fixture (6 tests)
- Unit tests: PHPDoc alternate order (2 tests), C# null-conditional call-form (updated)
* test: add Python static/classmethod integration tests (issue #289)
Verifies that classes using only @staticmethod/@classmethod have HAS_METHOD
edges connecting them to their child methods. This was the root cause of
issue #289 where context() and impact() returned empty for such classes.
Tests cover: HAS_METHOD edge emission, unique static method resolution
(create_user, delete_user), and ambiguous same-named method handling
(find_user on both UserService and AdminService — safely refused).
* fix: lbug batch escapeValue newline hardening, Rust ::default() scanner exclusion
- Apply \n/\r escaping to batch upsert escapeValue in lbug-adapter.ts:429
(missed instance of the CREATE-path fix from
|
||
|
|
62242d5f44
|
feat: TypeEnvironment API with constructor inference, self/this/super resolution (#274)
* feat(type-env): constructor-call type inference for TypeEnv (Phase 1)
Add extractInitializer as a Tier 1 fallback in buildTypeEnv: when a
declaration node has no explicit type annotation, infer the type from
constructor-call patterns (new X(), X::new(), X::default(), $x = new X()).
Languages covered: TypeScript/JS, Java (var), Rust, PHP, C++ (auto).
Python/Kotlin/Swift deferred — need symbol-table access to distinguish
class constructors from function calls.
Adds 20 new unit tests covering constructor inference, annotation
precedence, and known limitations across all supported languages.
* fix(type-env): class-aware constructor resolution, multi-declarator fix
- Add collectClassNames pre-scan: walks AST to build Set<string> of
class/struct names defined in the file
- C++ extractInitializer uses classNames.has() to verify identifier is
a known class before inferring (auto x = User() resolves, auto x =
getUser() does not — no false positives)
- Add InitializerExtractor type that receives classNames parameter
- Fix env.size gating: always call extractInitializer when available,
so mixed declarators like const a: A = x, b = new B() resolve both
- Add env.has() guard in Java extractInitializer to skip already-bound vars
- Document Rust new/default whitelist rationale
- Pin all test assertions, add mixed multi-declarator test case
* fix(type-env): resolve Self/self/static/parent to actual type names
- Rust: Self::new()/Self::default() resolves to enclosing impl type
- PHP: new self()/static() resolves to enclosing class, parent() to superclass
- Rust: Tier 0 annotation guard prevents overwrite by constructor inference
- Rust: mut_pattern handling in extractVarName for let mut bindings
- TS: fix misleading comment in extractInitializer
- 58 tests passing (3 new Self/self resolution tests)
* perf(type-env): single-pass AST walk with closure-scoped state
Refactors buildTypeEnv to use closures instead of passing mutable state
as parameters. classNames, env, and config are captured by the inner
walk and extractTypeBinding functions — no parameter mutation.
- Eliminates separate collectClassNames pre-scan (O(2n) → O(n))
- config looked up once per file instead of per-node
- 29 fewer lines
* feat(type-env): constructor-inferred type resolution for all languages
Add cross-file constructor type inference to the ingestion pipeline,
enabling receiver-type disambiguation for member calls like
`user.save()` when the variable is assigned from a constructor without
explicit type annotations.
Pipeline changes:
- Add extractInitializer to Python and Swift type extractors
- Add CONSTRUCTOR_BINDING_SCANNERS for Python, Swift, C/C++ in type-env
- Wire constructorBindings through parse-worker → parsing-processor →
pipeline → processCallsFromExtracted
- Rewrite resolveCallTarget receiver-type filtering (step D) to use
tiered import resolution (same-file → import-scoped → global) before
falling back to fuzzy ownerId matching
- Use collectTieredCandidates for constructor binding verification
instead of raw lookupFuzzy
Bug fixes:
- Fix C++ inline method query: @definition.method was captured on
field_declaration_list instead of function_definition, causing wrong
parameterCount for all inline class methods
- Fix parse-worker accumulated/flush results missing constructorBindings
CI changes:
- Add swift.test.ts to ci-integration pipeline group and coverage job
- Update ci-report to fetch base branch (main) coverage for delta
reporting instead of showing config thresholds
- Add per-suite timing breakdown table (unit/integration/total)
- Add expandable skipped test details section
Tests: 288 passed, 4 skipped (swift — macOS only) across 10 languages
- 36 new constructor-inferred integration tests (4 per language)
- 10 fixture directories with cross-file constructor patterns
- TypeScript, JavaScript, Java, Kotlin, Python, PHP, Rust, Go, C++, Swift
* fix(type-extractors): add type assertion for LanguageTypeConfig
* feat(ruby): constructor-inferred type resolution and self-receiver mapping
Add Ruby User.new constructor binding scanner to type-env, enabling
receiver-type disambiguation for member calls like user.save vs repo.save.
Add self/this → enclosing class resolution in lookupTypeEnv so self.method()
calls resolve to the correct class even when the method name is ambiguous.
* docs: update README with constructor inference and self/this resolution details
* refactor(ingestion): unified ResolutionContext replaces fragmented map passing
Introduce createResolutionContext() as the single resolution API for all
processors. Eliminates duplicated tier-selection logic, fixes heritage
namedImportMap bug, and adds per-file resolution caching.
- NEW resolution-context.ts: closure-factory with resolve(), per-file cache,
TIER_CONFIDENCE constant, and shared ResolutionTier type
- DELETE symbol-resolver.ts: zero production importers, logic now in
resolution-context.ts
- call-processor: all functions take ctx instead of 6 separate maps,
collectTieredCandidates removed (ctx.resolve replaces it),
D4 redundant re-resolve eliminated
- heritage-processor: takes ctx, resolveHeritageId helper extracts
repeated 14-line fallback pattern, namedImportMap now included
- import-processor: takes ctx, dead createImportMap/createPackageMap/
createNamedImportMap factories removed
- pipeline: creates single ctx, wires onProgress to all processors,
logs cache hit rate in dev mode
- Tier renamed: unique-global → global (honest about returning all candidates)
- Tests migrated: 1178 unit + 84 integration passing
* feat(type-env): self/this/super resolution, TypeEnvironment API, and review fixes
Add cross-language receiver keyword resolution:
- self/this/$this → enclosing class name via AST walk
- super/base/parent → parent class name via heritage AST extraction
(8 grammar variants: TS/JS, Java, Python, Ruby, C#, PHP, Kotlin, C++, Swift)
- D-phase widening in resolveCallTarget for super→parent method dispatch
Introduce TypeEnvironment API replacing loose TypeEnvResult + lookupTypeEnv:
- buildTypeEnv() returns TypeEnvironment with .lookup() method
- Single-pass AST walk merges constructor binding scan (was separate traversal)
- ClassNameLookup type replaces over-broad ReadonlySet<string> facade
- Memoized class name lookups to avoid redundant SymbolTable scans
Code review fixes (6 agents, 11 findings):
- Replace ctx.resolve(name, '') hack with direct symbols.lookupFuzzy()
- Extract scope key helpers (extractFuncNameFromScope, receiverKey)
- Simplify D-phase from 5 steps to 4 with deduped typeNodeIds
- Remove C from CONSTRUCTOR_BINDING_SCANNERS (YAGNI — C has no constructors)
- Cache Map reuse in ResolutionContext to reduce GC pressure
- Remove unused TieredCandidates import
Integration tests for self/this, parent, and super resolution across all
12 supported languages with per-language fixture directories.
* fix(type-env): generic parent resolution, TS cast inference, C++ brace-init
Fix generic parent class breaking super resolution:
- extractParentClassFromNode now uses extractSimpleTypeName to strip
generic params (Base<T> → Base) and qualified names (models.Model → Model)
- Affects TS, Java, Python, C# heritage extraction
Fix TypeScript new X() as T / new X()! missed inference:
- Unwrap as_expression and non_null_expression before checking for
new_expression in extractInitializer
Fix C++ brace-init User{} missed inference:
- Handle compound_literal_expression with type_identifier child
in extractInitializer
Clean up deprecated lookupTypeEnv:
- Remove standalone lookupTypeEnv export, migrate all callers to
TypeEnvironment.lookup() method
- Update all 80+ test assertions to use the new API
Integration test fixtures added:
- typescript-cast-constructor-inference (new X() as T, new X()!)
- typescript/java/csharp/kotlin-generic-parent-resolution
- cpp-brace-init-inference (auto x = User{})
* fix(type-extractors): Go &User{}, TS double-cast, Swift .init inference
Fix Go pointer-to-struct literal not inferred:
- Unwrap unary_expression (address-of &) before composite_literal check
- user := &User{} now correctly infers type User
Fix TypeScript double-cast only unwrapping one level:
- Change if to while loop for nested as_expression/non_null_expression
- new User() as unknown as Admin now correctly infers type User
Fix Swift User.init(name:) explicit init call missed:
- Handle navigation_expression callee with .init suffix in extractInitializer
Integration test fixtures:
- go-pointer-constructor-inference (&User{}, &Repo{})
- typescript-double-cast-inference (as unknown as T)
* feat: Rust struct literal, Python qualified ctor, Go new(), Swift .init scanner
- Rust: handle struct_expression in extractInitializer (User { name: "alice" })
- Python: support attribute nodes in extractInitializer (models.User("alice"))
and the cross-file scanner — extractSimpleTypeName handles qualified names
- Go: handle new(User) built-in in extractGoShortVarDeclaration
- Swift: extend CONSTRUCTOR_BINDING_SCANNERS to handle navigation_expression
callee for User.init(name:) cross-file resolution
Unit tests: 87 → 96 (Rust struct literal, Go new(), Python qualified ctor,
Python scanner qualified, plus edge cases)
Integration tests: 4 new describe blocks with fixtures
* fix: Rust Self{} resolution, C++ scoped brace-init, PHP promotion params, Ruby constants
- Rust: resolve Self {} struct literal to enclosing impl type (was stored as "Self")
- C++: replace type_identifier guard with extractSimpleTypeName for compound_literal_expression,
enabling ns::User{} scoped brace-init (closes previously deferred gap)
- PHP: add property_promotion_parameter to TYPED_PARAMETER_TYPES for PHP 8.0+
constructor property promotion (__construct(private Foo $x))
- Ruby: extend extractRubyConstructorBinding to accept constant left-hand side
(REPO = Repo.new)
Unit tests: 96 → 101 (+5: Rust Self{} ×2, C++ ns::User{} ×1, PHP promotion ×1,
Ruby constant ×1)
Integration tests: 4 new describe blocks with fixtures
* feat: Phase 1 type resolution gaps — walrus, PHP properties, nullable, Go make/assert
Phase 1 quick wins from the type resolution gap analysis:
1. Python walrus operator := (named_expression) — extractInitializer + scanner
2. PHP 7.4+ typed class properties — property_declaration in extractDeclaration
3. Nullable union unwrapping — User | null → User in extractSimpleTypeName
4. Go make() builtin — slice/map element type extraction
5. Go type assertions — iface.(User) type extraction
Also: PHP primitive_type handling in extractSimpleTypeName (string, int, etc.)
Unit tests: 101 → 114 (+13)
Integration tests: 8 new describe blocks with fixtures
* feat: Phase 2 type resolution gaps — C++ range-for, Rust if-let, C# pattern matching, Python class annotations
Phase 2 medium-effort improvements:
1. C++ range-for with explicit type — for (User& u : vec) binds u: User
2. Rust if-let/while-let captured_pattern — user @ User { .. } binds user: User
3. C# is-pattern matching — if (obj is User user) binds user: User
4. Python class-level annotations — confirmed already working, added tests
Unit tests: 114 → 127 (+13)
Integration tests: 11 new test cases with fixtures
|
||
|
|
1afe9166aa
|
feat: language-aware code intelligence — symbol resolution, MRO, constructor discrimination (#238)
* feat: add Method Resolution Order (MRO) with language-specific rules
Implement full MRO computation for multi-language inheritance hierarchies:
- HAS_METHOD edges: Class→Method ownership edges emitted during parsing
(both worker pool and sequential fallback paths)
- Method signatures: extract parameterCount and returnType from AST nodes
- C# heritage fix: distinguish EXTENDS vs IMPLEMENTS for base_list captures
using symbol table lookup + I[A-Z] naming heuristic fallback
- MRO processor (Phase 4.5): walks inheritance DAG, detects method-name
collisions across parents, applies language-specific resolution:
- C++: leftmost base class in declaration order wins
- C#/Java: class method wins over interface default
- Python: C3 linearization with cycle detection
- Rust: no auto-resolution (requires qualified syntax)
- Default: first definition in BFS order wins
- OVERRIDES edges emitted for resolved method collisions
- KuzuDB schema: Method table extended with parameterCount/returnType;
dedicated CSV writer and COPY query for 10-column Method rows
- MCP tools: updated Cypher examples for HAS_METHOD, OVERRIDES, diamond
72 tests across 5 test files covering MRO resolution, HAS_METHOD edges,
method signature extraction, C# heritage resolution, and integration
tests across C#/Rust/Python/TS/Java/C++.
* feat: add scope-based symbol resolution replacing raw lookupFuzzy
Introduces a shared 3-tier resolveSymbol function used by both
heritage-processor and call-processor:
1. Same-file (lookupExactFull — authoritative)
2. Import-scoped (filtered by ImportMap — high confidence)
3. Global fuzzy (first match — low confidence fallback)
Adds lookupExactFull to SymbolTable returning full SymbolDefinition
with type info needed for heritage Class/Interface disambiguation.
* refactor: tighten symbol resolution — Tier 3 refuses ambiguous matches
- lookupExactFull now O(1) via direct SymbolDefinition storage in fileIndex
(shared object references with globalIndex — zero additional memory)
- Added resolveSymbolInternal() preserving { definition, tier, candidateCount }
for test assertions and logging
- Tier 3 now returns null when multiple global candidates exist instead of
arbitrary allDefs[0] — a wrong edge is worse than no edge
- call-processor: renamed fuzzy-global → unique-global, removed dead branch
- 12 new tests: tier assertions, ambiguous refusal per language family,
heritage false-positive guard, O(1) shared reference verification
* fix: critical language support bugs in import resolution and MRO
Phase 5 critical fixes from all-language analysis:
- Python: add relative_import query capture (PEP 328) — `.models`, `..utils`
were silently dropped, producing zero ImportMap entries
- Rust: extract prefix from grouped imports `crate::module::{A, B}` — brace
groups previously failed resolution entirely
- Swift: use normalizedFileList for Windows path compatibility in module
import resolution (matches Go's resolveGoPackage pattern)
- MRO: fix c_sharp → csharp language name mismatch (enum is 'csharp'),
add Kotlin to C#/Java resolution rules (class method wins over interface)
* feat: add strict multi-language integration tests + fix C/C++ import resolution
Add 32 integration tests across 6 language fixtures (TypeScript, C#, C++,
Java, Python, Rust) with exact toBe/toEqual assertions validating heritage
edges, import resolution, and trait implementations.
Fix C/C++ import resolution bug where dot-to-slash conversion mangled
include paths (e.g. "animal.h" became "animal/h"). Now skips conversion
for C/C++ languages which use actual file paths in #include directives.
* fix: language-gate heritage heuristic, add Swift extension heritage, handle Rust grouped imports
- Gate I[A-Z] naming heuristic to C#/Java only (was firing for all languages)
- Swift unresolved types default to IMPLEMENTS (protocol conformance is the norm)
- Add tree-sitter query for Swift extension protocol conformance (extension Foo: Protocol)
- Handle Rust top-level grouped imports (use {crate::a, crate::b}) in both import loops
- Add 4 new heritage-processor tests (TypeScript refusal, Swift default, Swift Tier 1)
* feat: add Go struct embedding heritage + PackageMap optimization
Add Go struct embedding detection (anonymous fields → EXTENDS edges) via
new tree-sitter heritage query with named-field filtering in both
parse-worker and heritage-processor paths.
Implement PackageMap optimization for Go cross-package resolution:
replace O(N) file-level ImportMap expansion with directory-level suffix
matching (Tier 2b in symbol resolver). Graph IMPORTS edges are preserved
via addImportGraphEdge split.
Remove overly broad @definition.type from GO_QUERIES that was
double-matching structs/interfaces as TypeAlias nodes, breaking Tier 3
unique-global resolution.
Add Go fixture (go-pkg) with Admin→User embedding, cross-package calls,
and 7 integration tests covering structs, functions, imports, calls,
and heritage edges.
* test: add Kotlin heritage integration tests
Adds a kotlin-heritage fixture and 7 integration tests validating
class inheritance, interface implementation, JVM-style import
resolution, and symbol-table-driven EXTENDS/IMPLEMENTS disambiguation
via Kotlin delegation specifiers.
* feat: extract resolvers, add PHP tests, ambiguous tests for all languages
- Extract language-specific resolvers from import-processor.ts into
resolvers/ directory (P7): jvm, go, csharp, php, rust, standard, utils
- import-processor.ts reduced from 1412 to 711 lines (50% reduction)
- Add comprehensive PHP integration tests: PSR-4 imports, traits, enums,
heritage edges, method calls, MRO overrides
- Add ambiguous symbol resolution tests for all 9 languages verifying
correct disambiguation via import chains
- Split monolithic lang-resolution.test.ts (1080 lines) into 9 per-language
files under test/integration/resolvers/ with shared helpers
* feat: update integration tests to include resolver tests for multiple languages
* fix: address code review — schema gap, Rust impl name, Property OVERRIDES
Bugs fixed:
- Add 13 missing FROM/TO pairs in RELATION_SCHEMA for HAS_METHOD edges
(Class/Interface/Struct/Trait/Impl/Record to Method/Constructor/Property)
- Fix findEnclosingClassId to pick implementing type for Rust
impl Trait for Struct blocks (was picking trait name)
- Exclude Property nodes from MRO OVERRIDES collision detection
- Change MRO language fallback from typescript to unknown
Tests added:
- Unit: Property OVERRIDES exclusion (2 tests), Rust impl Trait for
Struct name resolution (2 tests), schema HAS_METHOD pair coverage
- Integration: no OVERRIDES targets Property nodes across all 9 languages
- PHP fixture: added shared $status property to both traits to create
real collision scenario for Property OVERRIDES exclusion test
Documentation:
- OVERRIDES edge direction (Class to Method), Go return type gap,
BFS first-reach heuristic limitation
* feat: harden CALLS-edge resolution — Phase 0 validation
- Fix same-file confidence (0.85 → 0.95) to correctly outrank import-scoped (0.9)
- Fix Tier 1 overload preservation: use globalIndex filter instead of fileIndex lookup
- Add callable-kind guard: refuse CALLS edges to Interface and Enum symbols
- Fix Kotlin countCallArguments: handle call_suffix → value_arguments nesting
- Fix Kotlin extractFunctionName: add simple_identifier to fallback search
- Strictly type findParameterList and countCallArguments (remove all `any`)
- Add arity-based call resolution integration tests for 9 languages
- Add unit regression tests for Interface/Enum CALLS refusal
* chore: remove C# build artifacts from fixtures
* feat: add call-form discrimination and ownerId to symbol table (Phase 1)
Add inferCallForm() and extractReceiverName() to distinguish free/member/constructor
calls at the AST level across all 9 languages. Add ownerId field to SymbolDefinition
linking Method/Constructor/Property to their owning class. Includes 36 unit tests
and member-call integration tests for all 9 languages (132 tests, 0 failures).
* feat: constructor/struct-literal resolution across all languages (Phase 2)
Add constructor discrimination to CALLS-edge resolution: new Foo(),
User{...} struct literals, and C# primary constructors now resolve to
Constructor/Class/Struct/Record nodes instead of being filtered out.
Queries: new_expression (C++), object_creation_expression (PHP),
composite_literal (Go), struct_expression (Rust), primary constructor
and implicit_object_creation_expression (C#).
Relaxes global tier in collectTieredCandidates to pass all candidates
through filterCallableCandidates, allowing kind/arity narrowing to
disambiguate at lower confidence.
* feat: receiver-constrained resolution with integration tests for all 9 languages
Add receiver-type filtering (Phase 3): when a member call like `user.save()`
has a known receiver type from TypeEnv, filter candidates by ownerId to
disambiguate methods with the same name across different classes.
Key changes:
- call-processor: build per-file TypeEnv, pass receiverTypeName to resolveCallTarget
- parse-worker: extract receiverTypeName from TypeEnv in worker thread
- resolveCallTarget: new step D filters by ownerId matching receiver type
- utils: extractReceiverName supports C++ field_expression (argument field)
- utils: findEnclosingClassId extracts Go method receiver types
- type-env: handle Go qualified_type, Kotlin user_type/variable_declaration
- parse-worker + parsing-processor: Function added to needsOwner for
Kotlin/Rust/Python class methods captured as Function nodes
Integration tests added for receiver-constrained resolution across all 9
languages: TypeScript, Java, Python, Go, Rust, C++, C#, Kotlin, PHP.
* feat: NamedImportMap, scoped TypeEnv, broadened signatures + TS rest-param variadic fix
Address all 4 PR #238 review items:
1. Remove redundant lookupFuzzy in processRoutesFromExtracted
2. Add NamedImportMap for TS/Python symbol-level import tracking (Tier 2a)
3. Make TypeEnv scope-aware (Map<scopeKey, Map<varName, type>>) to fix
non-deterministic receiver resolution across functions
4. Broaden extractMethodSignature: Go/Rust/C++ return types, variadic
detection for Go/Java/Python/C++/Kotlin/TypeScript rest params
Discovered and fixed: TS rest params (...args) were not detected as
variadic — added rest_pattern detection inside required_parameter nodes.
Integration tests added: scoped receiver, named import disambiguation,
and variadic call resolution for both TypeScript and Python.
* fix: alias import resolution, Go multi-assign TypeEnv, dead code removal
- NamedImportMap now stores {sourcePath, exportedName} so aliased imports
(import { User as U }) resolve U → User in the source file
- Named binding check moved before empty-allDefs early return in both
call-processor and symbol-resolver, fixing constructor calls via aliases
- Go extractFromGoShortVarDeclaration iterates all LHS/RHS pairs for
multi-assignment (user, repo := User{}, Repo{}) instead of only first
- Remove unused TYPED_DECLARATION_TYPES set (TYPED_PARAMETER_TYPES kept)
- Integration tests for both fixes (go-multi-assign, typescript-alias-imports)
* feat: alias import extraction for Kotlin, Rust, PHP, C# + integration tests
Add named import alias extraction to both pipeline paths
(import-processor.ts and parse-worker.ts) for Kotlin, Rust, PHP,
and C#. Add integration test fixtures and tests for all 5 languages
(Python alias extraction already worked, just needed the test).
Each test verifies: class detection, member call resolution through
aliases to correct target files, and IMPORTS edge emission.
* refactor: use SupportedLanguages enum everywhere instead of raw strings
Replace all raw language string literals and `language: string` types
with the SupportedLanguages enum across 10 files. This ensures
compile-time safety for language dispatch and eliminates dead
`language === 'tsx'` checks (tsx maps to TypeScript in the enum).
* fix: tier-ordering bug, re-export chains, PHP grouped imports, Java named imports
- Fix collectTieredCandidates tier-ordering: same-file now checked before
named bindings, preventing imports from shadowing local definitions
(matches resolveSymbolInternal priority order)
- Add re-export chain resolution for TypeScript/JavaScript barrel files:
export { X } from './base' and export type { X } from './base' now
followed up to 5 hops through NamedImportMap
- Fix PHP grouped import alias extraction: use App\Models\{User, Repo as R}
now correctly handled in both parse-worker and import-processor
- Add Java NamedImportMap support: import com.example.models.User now
records User as a named binding for precise disambiguation
- Add 16 new integration tests across TypeScript, PHP, and Java resolvers
(220 total resolver tests, all passing)
* refactor: consolidate alias extraction + add variadic/constructor/shadow integration tests
- Extract shared named-binding-extraction.ts from duplicate logic in
import-processor.ts and parse-worker.ts (net -200 lines)
- Deduplicate appendKotlinWildcard (now imported from resolvers/index.ts)
- Add integration tests: constructor calls (Kotlin, Python), variadic
resolution (Go, Java, C#, C++, Kotlin), re-export chains (Python),
local definition shadowing (Python, Go)
- Add TODO(stack-graph) for TypeEnv scope key collision
- 225 integration tests passing (was 223)
* fix: PHP non-aliased imports, Python node identity, re-export chain dedup + local-shadow tests
- PHP flat non-aliased imports (use App\Models\User) now stored in NamedImportMap
- PHP grouped non-aliased imports ({User} in {User, Repo as R}) now stored in NamedImportMap
- Python: replace non-public child.id with child.startIndex for node identity
- Extract shared walkBindingChain() from symbol-resolver and call-processor
- Add PHP variadic resolution fixture + test (variadic_parameter already covers PHP)
- Add local-shadow integration tests for Java, C#, Kotlin, Rust, PHP, C++ (6 languages)
* feat: Rust non-aliased use bindings, Kotlin non-aliased imports, re-export chain resolution
Extend NamedImportMap coverage for Rust and Kotlin non-aliased imports:
- Rust: rename collectUseAsClauses → collectRustBindings, extract terminal
scoped_identifier (use crate::models::User) and identifier in use_list
(use crate::models::{User, Repo}) into NamedImportMap. This also enables
pub use re-export chain following via walkBindingChain.
- Kotlin: extend extractKotlinNamedBindings to handle non-aliased imports
(import com.example.User), skipping wildcard imports.
- Add rust-reexport-chain fixture + 3 integration tests verifying Handler{}
resolves through mod.rs pub use to handler.rs.
- Add Kotlin heritage + constructor-calls reason assertions for non-aliased
import-resolved resolution.
- Add C# heritage test documenting namespace import tier behavior.
* fix: skip Kotlin lowercase member imports in NamedImportMap
Member imports like `import util.OneArg.writeAudit` (lowercase last
segment) must not populate NamedImportMap — same-named function imports
from different classes collide, breaking arity-based disambiguation.
Apply the same guard Java already uses: skip lowercase last segments.
* fix: skip spurious path-prefix bindings in Rust grouped imports
collectRustBindings was extracting the path segment (e.g. "models") from
`use crate::models::{User, Repo}` as a spurious NamedImportMap entry.
Skip scoped_identifier nodes that are direct children of scoped_use_list
since they are path prefixes, not importable symbols.
Adds rust-grouped-imports fixture and 4 integration tests verifying both
symbols resolve correctly and no spurious binding leaks through.
* fix: use startIndex in TypeEnv scope key to prevent same-name method collision
Two methods named identically in different classes within the same file
previously shared a scope key, causing non-deterministic type resolution.
Now keys use funcName@startIndex for uniqueness.
Also adds tests documenting destructuring assignment extraction gap.
* test: document C# namespace-level import limitation in named binding extraction
* test: document same-arity overload discrimination limitation in call processor
* perf: parallelize calls/heritage/routes processing in worker path
Worker path now runs processCallsFromExtracted, processHeritageFromExtracted,
and processRoutesFromExtracted via Promise.all instead of sequentially.
Safe because all three only read shared state and write via addRelationship's
dedup guard. Sequential fallback path stays sequential (shared LRU astCache).
Also fixes Rust collectRustBindings spurious path-prefix bindings for 3+ level
grouped imports, and adds @param JSDoc for walkBindingChain's allDefs invariant.
* docs: improve Promise.all safety comment and walkBindingChain JSDoc
Clarify that the parallelization safety comes from disjoint relationship
types + idempotent id-keyed Maps, not from lack of shared state (the
graph is shared). Strengthen allDefs JSDoc to describe silent-miss
consequence of passing pre-filtered results.
* refactor: extract language-specific processing into modular dispatch tables
Phase 1: Extract type binding logic from type-env.ts (635→125 LOC) into
type-extractors/ directory with per-language files and Record<SupportedLanguages,
LanguageTypeConfig> + satisfies dispatch.
Phase 2: Extract 5 config loaders from import-processor.ts into
language-config.ts (removed ~196 LOC of inline loaders).
Phase 3: Convert export-detection.ts switch/case to exhaustive
Record<SupportedLanguages, ExportChecker> + satisfies dispatch table,
fix node: any → SyntaxNode.
Also adds language feature matrix to README.
All 1146 unit tests and 433 integration tests pass.
* refactor: extract type binding logic into type-extractors/ directory (Phase 1)
Extract per-language type extraction from type-env.ts (635→125 LOC) into
type-extractors/ with Record<SupportedLanguages, LanguageTypeConfig> + satisfies
dispatch. 9 per-language files, shared helpers, and barrel index.
* refactor: extract config loaders to language-config.ts (Phase 2)
Move 5 language-specific config loaders and their type interfaces from
import-processor.ts into standalone language-config.ts module.
|
||
|
|
8a100a76d3 |
test: add test suite with vitest (unit + integration + fixtures)
- 59 test files covering unit and integration tests - vitest config with coverage thresholds and fork pooling - Test fixtures (mini-repo + multi-language sample code) - Add vitest + coverage-v8 to devDependencies - Add test scripts (test, test:integration, test:all, test:watch, test:coverage) - Move typescript to devDependencies where it belongs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> |