mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-09-07 08:26:11 +00:00
35 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
368049576b
|
fix(python): make multi-segment suffix fallback deterministic (#1253) | ||
|
|
59acfb2261
|
fix(python): walk ancestors for multi-segment dotted imports (#1241)
* fix(python): walk ancestors for multi-segment dotted imports (#1240) Single-segment Python imports (`from middleware import X`) already get an ancestor-directory walk in `resolvePythonImportInternal`, so they resolve correctly when the importer and the imported module share a parent directory (e.g. both under `backend/`). Multi-segment dotted imports (`from services.sync import X`) were only resolved against the workspace root. In a `backend/`-prefixed repo, `from services.sync import X` from `backend/routers/cron.py` would not resolve because `services/sync.py` does not exist at the workspace root — only `backend/services/sync.py` does. The IMPORTS edge was dropped, the imported names were never bound, and downstream CALLS edges to those names were silently lost. The fix mirrors the single-segment ancestor walk for multi-segment paths in `resolveAbsoluteFromFiles`, and widens `hasRepoCandidate` to accept nested `/segment/` matches so it does not bail before the walk runs. Includes a new fixture and 5 integration tests covering: - IMPORTS resolution for `from services.sync`, `from services.alerts`, `from routers.alerts` from `backend/routers/cron.py`. - CALLS edge counts for every multi-segment-imported callee. - Regression check: single-segment ancestor walk (`from auth_utils import …`) still resolves correctly. The django-app-imports regression suite (which prevents `accounts.apps` from spuriously matching a local `apps.py`) continues to pass — the new nested-namespace check in `hasRepoCandidate` is bounded by an explicit `/segment/` substring, and the workspace-root candidate check still runs first. * fix(python): scope hasRepoCandidate widening to importer ancestors + tighten ancestor-walk loop Address review findings from PR #1241: 1. hasRepoCandidate's nested check now requires the matching directory to sit on an ancestor of the importer. Previously any nested /SEGMENT/ path satisfied the gate, which would let a vendored copy of an external package (e.g. vendor/django/urls.py) gate-pass an external import like 'from django.urls import path' issued from app/main.py. 2. Loop bound in resolveAbsoluteFromFiles tightened from 'i >= 0' to 'i > 0' to skip a redundant root-candidate recheck (the workspace-root direct check above already covers that case). 3. Doc-comment in resolveAbsoluteFromFiles now states the precedence order explicitly: workspace root > closest ancestor > suffix fallback. Tests added: - Vendored-external false-positive guard (vendor/django/urls.py must not resolve from app/main.py). - Workspace-root vs ancestor precedence (root services/sync.py wins over backend/services/sync.py for a backend/routers/cron.py importer). 215/215 python integration tests pass (+4 from this change). tsc --noEmit green. |
||
|
|
7c3fa5853f
|
fix(ingestion): classify Python class methods as Method (#1102)
* fix(ingestion): classify Python class methods as Method * fix(test): align Python large-buffer assertion with Method labels --------- Co-authored-by: gergo <gergo@Galahad.localdomain> |
||
|
|
98ee665889
|
fix(ingestion): two-channel binding lifecycle (closes #1066) + scope-resolution I8 hardening (#1082)
Some checks are pending
CI / quality (push) Waiting to run
CI / tests (push) Waiting to run
CI / e2e (push) Waiting to run
CI / scope-parity (push) Waiting to run
CI / Save PR Metadata (push) Blocked by required conditions
CI / CI Gate (push) Blocked by required conditions
Release Candidate / Check if release candidate should run (push) Waiting to run
Release Candidate / ci (push) Blocked by required conditions
Release Candidate / Publish release candidate to npm (push) Blocked by required conditions
Release Candidate / Build & Push RC Docker images (push) Blocked by required conditions
* fix(csharp): adaptive tree-sitter buffer + frozen-bucket clone for cross-namespace siblings (#1066) Two coupled regressions surfaced when analyzing real-world C# repos with large source files (issue #1066): 1. Tree-sitter `parser.parse()` is hard-coded to a 32 KB buffer by default. Any file exceeding that threshold throws `Invalid argument` on the worker re-parse path of `populateCsharpNamespaceSiblings` (and the analogous Python / TypeScript captures fallbacks). 2. After the buffer fix unblocks the AST walk, the hook tries to `push()` onto the inner `BindingRef[]` array fetched from `indexes.bindings` — but `materializeBindings` froze that array via `Object.freeze(refs.slice())`. Result: `Cannot add property N, object is not extensible`. Fixes: - `csharp/captures.ts`, `python/captures.ts`, `typescript/captures.ts`: pass `bufferSize: getTreeSitterBufferSize(sourceText.length)` to `parser.parse()` on the cache-miss path so multi-MB files parse. - `csharp/namespace-siblings.ts`: introduce `cloneBindingBucket` to copy the frozen array before mutating, then `set()` the new array back. This is a working but architecturally compromised workaround (#1050 follow-up will replace it with an explicit augmentation channel — see docs/plans/2026-04-26-001 plan). Tests: - New `csharp-large-cache-miss-resolution` fixture (Models/Services/ Other layout, ~77 KB padded UserService.cs) drives the buffer-size failure end-to-end through worker mode. - `csharp.test.ts`: 4 new regression assertions covering both the parse-time buffer-size failure and the freeze workaround. - Per-language captures unit tests gain "large cache-miss file uses adaptive buffer" coverage (TS, Python, C#). - `csharp-hooks.test.ts`: in-memory freeze regression test that reproduces the `Cannot add property` crash without invoking the C# parser at all. Made-with: Cursor * refactor(scope-resolution): add bindingAugmentations channel to indexes Step 1 of the binding-augmentation-channel refactor (issue #1066 follow-up). Pure shape change — no consumers yet. Adds a new `readonly bindingAugmentations` field to `ScopeResolutionIndexes` initialized as an empty `Map` by `finalizeScopeModel`. The new channel is the dedicated post-finalize write target for hooks like `populateCsharpNamespaceSiblings`, so `indexes.bindings` can stay frozen and finalize-owned. Behavior unchanged: nothing reads or writes the new field yet. tsc and the full unit suite remain green. Plan: docs/plans/2026-04-26-001-binding-augmentation-channel.md (local only — `docs/plans/` is gitignored). Made-with: Cursor * feat(scope-resolution): add lookupBindingsAt dual-source helper Step 2 of the binding-augmentation-channel refactor. Introduces a single primitive every walker uses to read both the finalize-owned `indexes.bindings` channel and the post-finalize `indexes.bindingAugmentations` channel. Contract: - Finalized refs come first (preserves existing precedence). - Augmented refs append, deduped by `def.nodeId`. - Empty input on both channels returns a shared frozen empty array. - Single-channel hits return the bucket by reference (no allocation). No consumers are wired yet — Step 3 routes the existing walker primitives through this helper. Augmentations remain empty for every language; behavior of the full suite is unchanged. 8 unit tests pin precedence, dedup, identity for single-channel hits, and the shared-empty-frozen-array sentinel. Made-with: Cursor * refactor(scope-resolution): route binding lookups through lookupBindingsAt Step 3 of the binding-augmentation-channel refactor. Every direct `indexes.bindings.get(...)` consumer in the post-finalize phase is now routed through `lookupBindingsAt` (per-name) or `namesAtScope` + `lookupBindingsAt` (bulk iteration). Routed sites: - `findClassBindingInScope` (walkers.ts) — class-receiver lookups. - `findCallableBindingInScope` (walkers.ts) — free-call lookups. - `findExportedDefByName` (walkers.ts) — module-scope-fallback callable lookups. - `propagateImportedReturnTypes` (passes/imported-return-types.ts) — bulk iteration over an importer's binding entries; switched to `namesAtScope` + per-name `lookupBindingsAt` so post-finalize augmentations are visible to import-derived typeBinding mirrors. Behavior unchanged: augmentations are empty across the suite (Step 4 populates them for C# `populateNamespaceSiblings`). 587 scope-resolution unit tests + 50 integration resolver suites green (4 pre-existing Swift method-implements failures unrelated to this work). Adds `namesAtScope` companion helper for the bulk-iteration callers. Made-with: Cursor * refactor(csharp): write namespace siblings to bindingAugmentations channel Step 4 of the binding-augmentation-channel refactor. The C# `populateNamespaceSiblings` hook is the only consumer that needed to inject cross-file bindings post-finalize, and prior to this change it cloned the (frozen) finalized `BindingRef[]` arrays through a `cloneBindingBucket` helper, then `set()`-back the new array — a workaround for the `Object.freeze` applied by `finalize-algorithm.ts` (issue #1066 root cause). Architecturally that violated `ScopeResolver` Invariant I8 (which permits post-finalize modifications but not in-place mutation of finalized buckets). It also forced read-side consumers to be aware of the workaround. This change: * Switches the three C# write sites to append into `indexes.bindingAugmentations` via `getAugmentationBucket`. The augmentation channel was added in Step 1 and is mutable by contract: inner `BindingRef[]` arrays here are NEVER frozen. * Deletes `cloneBindingBucket` and `getMutableScopeBindings` (workaround helpers no longer needed). * `lookupBindingsAt` (Step 2) merges the two channels transparently for every walker (Step 3), so behavior is unchanged for callers. * Updates the unit test to assert against both channels: finalized bucket stays frozen and untouched, cross-file siblings show up in augmentations only. Renamed the test accordingly. Validation: * `npx tsc --noEmit` clean. * csharp hooks unit + walkers-augmentations unit + csharp integration resolver suite all green (236/236). * Wider `test/unit/scope-resolution test/integration/resolvers` suite: 2507 pass, only 4 pre-existing Swift METHOD_IMPLEMENTS failures remain (unrelated to this work, present on baseline). Refs: issue #1066, ADR-pending binding-augmentation-channel. Made-with: Cursor * feat(scope-resolution): tighten I8 + add validateBindingsImmutability dev guard Step 5 of the binding-augmentation-channel refactor. Captures the new two-channel binding lifecycle in the contract docs and adds a dev-mode runtime validator so a future hook cannot silently drift back into mutating `indexes.bindings`. Contract changes: * `contract/scope-resolver.ts` — rewrote Invariant I8 to describe the two channels (`indexes.bindings` is finalize-output and immutable post-finalize; `indexes.bindingAugmentations` is the append-only post-finalize channel populated by hooks like `populateNamespaceSiblings`). Documented `lookupBindingsAt` as the read-side merger and pointed at the new validator as the enforcement mechanism. * `gitnexus-shared/src/scope-resolution/types.ts` — extended the module-header lifecycle contract to call out `bindingAugmentations` alongside `ReferenceIndex` as the two structures populated after the freeze. Validator: * New `pipeline/validate-bindings-immutability.ts` mirrors the shape of `validateOwnershipParity` (#909): runs only when `NODE_ENV !== 'production' && VALIDATE_SEMANTIC_MODEL !== '0'`, emits via `onWarn`, never throws. Asserts (a) every inner `BindingRef[]` in `indexes.bindings` is `Object.isFrozen`, and (b) every inner array in `indexes.bindingAugmentations` is NOT frozen. * Wired into `pipeline/run.ts` after both `populateNamespaceSiblings` and `propagateImportedReturnTypes`, before `resolveReferenceSites`. One sweep covers the full post-finalize surface. Tests: * `validate-bindings-immutability.test.ts` — 6 cases pinning happy path, both drift directions, multi-violation accumulation, and both production no-op gates. All scope-resolution + csharp resolver tests green (242/242 in the focused run; matches the wider Step 4 baseline). Made-with: Cursor * fix(ingestion): size tree-sitter buffers from UTF-8 bytes Tree-sitter buffer sizing is byte-based, so computing adaptive buffers from JavaScript string length under-sized UTF-8-heavy files. Make getTreeSitterBufferSize accept source text directly and compute Buffer.byteLength internally, then update all parse call sites and max-buffer skip checks to use byte length. Add multibyte cache-miss and cap regressions for C#, Python, TypeScript, and the C# namespace-sibling fallback parse path. Made-with: Cursor * test(scope-resolution): pin augmentation read paths Add focused unit coverage for augmented-only binding reads across the routed walker helpers and imported-return-type propagation path. Clarify I8 wording around lexical Scope.bindings versus post-finalize index channels, and document the intentional local-only behavior of findExportedDef. Also switch the immutability validator tests to Vitest env stubs, document one intentional validator blind spot, and split C# namespace-sibling tests so UTF-8 parsing and augmentation-channel behavior are asserted independently. Made-with: Cursor * test(scope-resolution): avoid slow parser stress fixtures Replace high-cardinality large-file capture fixtures with large padding plus a trailing declaration. This still proves adaptive tree-sitter buffers parse beyond large ASCII and UTF-8-heavy input, without making query matching process thousands of declarations and risking timeouts. Made-with: Cursor * test(scope-resolution): add python and typescript cache-miss resolver regressions Add worker-mode resolver integration coverage mirroring the C# #1066 scenario for Python and TypeScript. Each test builds a temp fixture with large ASCII and UTF-8-heavy source padding, then asserts trailing declarations and call edges still resolve after scope-resolution cache-miss reparsing. Made-with: Cursor * refactor(scope-resolution): gate I8 validator and fast-path namesAtScope Addresses SPARC reviewer feedback on the binding-augmentation channel: - Validator gate is now opt-in outside development. Extract isSemanticModelValidatorEnabled() in utils/env.ts as the single predicate; both validateBindingsImmutability and phase.ts's warn handler share it. Default CLI runs no longer pay the O(binding-buckets) scan, and explicit VALIDATE_SEMANTIC_MODEL=1 now emits warnings even when NODE_ENV is unset. - namesAtScope returns Iterable<string> and zero-allocates when at most one channel is populated (returns Map.keys() directly), only materializing a Set when both channels carry names. The caller-side branching and EMPTY_NAMES escape hatch in propagateImportedReturnTypes are gone -- both helpers handle the empty-augmentation case internally. - C# namespace-siblings header/JSDoc, model JSDoc, I8 contract prose, and the #1066 integration-test header rewritten to say post-finalize fanout appends only to bindingAugmentations; finalized refs come first and win duplicate def.nodeId metadata; local lexical Scope.bindings remains the first-tier shadowing channel. Validator unit-test setup deduplicated via beforeEach and extended with default-CLI no-op + explicit-opt-in cases. Made-with: Cursor |
||
|
|
a7b3fa1b81
|
feat(csharp): migrate C# to registry-primary scope-resolution (Closes #934) (#1019)
* feat(csharp-scope): unit 1 — scope query + captures orchestrator First slice of the C# scope-resolution migration (issue #934, RFC #909 Ring 3). Closes `Unit 1` of docs/plans/2026-04-21-004-feat-csharp-scope-resolution-plan.md. Adds: - src/core/ingestion/languages/csharp/query.ts — tree-sitter scope query covering compilation_unit, namespace (block + file-scoped), class-like (class/interface/struct/record/enum), method-like (method/constructor/destructor/local_function/operator), property and field declarations, using directives, type bindings (parameter annotations, local variable annotations, constructor inference, invocation alias), and references (free call, member call including null-conditional, constructor call, member write). - src/core/ingestion/languages/csharp/captures.ts — pass-through orchestrator mirroring python/captures.ts. Import decomposition (Unit 2), receiver-type-binding synthesis (Unit 3), and arity metadata synthesis (Unit 5) stub out for future units. - src/core/ingestion/languages/csharp/cache-stats.ts — PROF instrumentation mirror of python/cache-stats.ts. Design notes: - Return-type / field-type / property-type captures deferred. tree-sitter-c-sharp does not expose these under a clean named field that pattern-matches. When Unit 7 parity gate surfaces a gap, add positional patterns or a post-hoc extractor lookup. - object_creation_expression with qualified_name type — the qualified name itself is the reference text; captured as a whole via a dedicated tag so interpretation in later units can split namespace + name. - Null-conditional calls use positional descendant patterns because tree-sitter-c-sharp's member_binding_expression and conditional_access_expression don't expose named fields. Coverage: - 23/23 new unit tests in test/unit/scope-resolution/csharp/csharp-captures.test.ts cover every capture tag. Confirmed against tree-sitter-c-sharp via the probe-script loop during development; grammar drift would surface as a capture-shape assertion failure. - tsc --noEmit clean. No changes to shared infrastructure. Resolver wiring + registration land in Unit 6. * fix(csharp-scope): capture null-conditional receiver + operator decls Adversarial review surfaced two Unit 1 bugs that would silently corrupt the graph once C# is flipped on the scope-resolution path: - `obj?.Save()` only emitted @reference.name, so receiver-bound resolution downgraded to the free-call fallback and could mis-link to an imported `Save`. Capture the conditional_access_expression receiver under @reference.receiver. - `operator_declaration` had @scope.function but no @declaration.method owner, so calls inside operator bodies were attributed to the enclosing class and the operator itself disappeared from method lookup. Capture the operator token as @declaration.name (downstream csharpMethodConfig normalizes to op_Addition etc.). - `conversion_operator_declaration` was missing from both scope and declaration sets. Added with the target type as the name anchor. Arity metadata for overload resolution remains deferred to Unit 5 and gated behind Unit 7's parity flip, as documented in captures.ts. * chore(scope-resolution): drop unused python/scopes.scm sibling The file was documentation-only — the authoritative scope query is the embedded `PYTHON_SCOPE_QUERY` constant in `python/query.ts`. Nothing loaded the `.scm` at runtime, so it drifted from the code. Remove it and update the four doc comments that pointed at it: - language-provider.ts: "scopes.scm query" → "scope query (embedded in each language's query.ts)". - languages/python.ts: capture-vocabulary pointer → query.ts. - python/query.ts header: drop the "edit both together" note. - python/receiver-binding.ts: "keeps the .scm declarative" → "keeps the embedded scope query declarative". - scope/walkers.ts: "Python's scopes.scm" → "Python's scope query". Historical plan docs under docs/plans/ still reference scopes.scm but are frozen artifacts, not living documentation. C# never had a .scm sibling, so no action needed there. * feat(csharp-scope): Unit 2 — import interpret + target resolver Adds the three files Unit 2 of the C# scope-resolution plan calls for: - `import-decomposer.ts` — inspects each `using_directive` node and synthesizes `@import.kind/source/name/alias` markers. Kinds: `namespace` — `using X;` / `using X.Y.Z;` `alias` — `using Alias = X.Y.Z;` (generics stripped) `static` — `using static X.Y;` `global using` maps to namespace (plan's deferred decision); the `global::` qualifier is stripped before emitting. - `interpret.ts` — reads the markers and builds `ParsedImport`. Static using maps to `kind: 'wildcard'` since it brings members into unqualified scope; Unit 4's merge-bindings tiers wildcards lowest. Also provides `interpretCsharpTypeBinding` with nullable/single-arg generic/qualifier stripping so receiver-typed resolution sees the concrete class name. - `import-target.ts` — suffix-match adapter returning a single primary file. Cross-file partial-class aggregation runs later at graph-bridge time (Unit 6). The csproj-based `resolveCSharpImportInternal` stays on the legacy path until Unit 7's parity gate surfaces a gap. - `captures.ts` routes `@import.statement` matches through the decomposer so the interpreter sees the markers it needs. Tests cover every using flavor + resolution edge cases. 38/38 scope- resolution C# unit tests pass; tsc clean. * feat(csharp-scope): Unit 3 — simple hooks (binding/import/receiver) Adds simple-hooks.ts mirroring Python's pattern: - `csharpBindingScopeFor` — delegates to innermost (block scope is already captured by @scope.block in the query). - `csharpImportOwningScope` — binds `using` inside a namespace to that namespace's scope so imports don't leak into sibling namespaces. File-level using delegates to module. Function-body using (not legal C# but possible from malformed input) attaches to the function. - `csharpReceiverBinding` — looks up `this` / `base` in the function scope's type bindings; returns null for statics, free functions, and non-Function scopes. `this` / `base` synthesis itself is deferred to a follow-up (matches Python's receiver-binding.ts pattern). 9 new tests pin delegation semantics. 47/47 C# scope-resolution unit tests pass; tsc clean. * feat(csharp-scope): Unit 4 — mergeBindings (using precedence) Three-tier shadowing, same shape as Python's LEGB merge: 0: local — class members, locals, parameters 1: using — namespace / named / reexport (equal tier; compiler requires explicit qualifier if two using collide) 2: wildcard — `using static X.Y;` static-member imports Within the surviving tier, de-dup by DefId (last-write-wins) so a re-declared `using` cleanly replaces its earlier binding. Explicit interface implementations bind under their qualified name in the extractor layer, so they don't collide with plain simple names here. 7 new tests pin precedence + dedup semantics. 54/54 C# scope-resolution unit tests pass. * feat(csharp-scope): Unit 5 — arity metadata synthesis + compatibility Adversarial review flagged overload narrowing as a blocker for the Unit 7 flip. This lands the declaration-side metadata; callsite-side arity synthesis is a separate gap we'll address if the parity gate surfaces overload misresolution. - `arity-metadata.ts` — reads `csharpMethodConfig.extractParameters` and produces `{ parameterCount, requiredParameterCount, parameterTypes }`. `params` variadic collapses parameterCount to undefined (matches Python's `*args` treatment) and appends a literal `'params'` marker to parameterTypes so the compatibility hook can detect it without re-reading the AST. Default-valued parameters contribute to optionalCount → requiredParameterCount = total − optional. - `arity.ts` — `csharpArityCompatibility(def, callsite)` returns compatible / incompatible / unknown. Mirrors Python's three-verdict shape so the central registry's arity filter works without adapter logic per-verdict. - `captures.ts` — on every @declaration.method / @declaration.constructor / @declaration.function match, synthesize @declaration.parameter-count, @declaration.required-parameter-count, and @declaration.parameter-types captures. Covers method_declaration, constructor_declaration, destructor_declaration, operator_declaration, conversion_operator_declaration, and local_function_statement. 12 new tests: 5 on captures-side synthesis (method + params + types + variadic + constructor + local function), 7 on the compatibility hook. 66/66 C# scope-resolution unit tests pass; tsc clean. * feat(csharp-scope): Unit 6 — wire csharpScopeResolver + register Creates the public barrel (index.ts) and ScopeResolver (scope-resolver.ts) and plumbs them into the provider + registry: - `languages/csharp/index.ts` — re-exports the hook entry points and documents the 8 known limitations of the registry-primary path (csproj-driven namespace resolution, multi-file namespace expansion, type-based overload resolution, nested generics, dynamic, preprocessor branches, cross-file global using, expression-bodied members). - `languages/csharp/scope-resolver.ts` — ScopeResolver shape mirroring Python's. `isSuperReceiver` matches the literal `base` keyword. `fieldFallbackOnMethodLookup: false` since C# is statically typed — the type-binding layer already produces precise owner types; `propagatesReturnTypesAcrossImports: true` since signatures are authoritative. - `languages/csharp.ts` — adds the 9 hook entry points to the provider (emitScopeCaptures, interpretImport, interpretTypeBinding, four simple hooks, mergeBindings, arityCompatibility, resolveImportTarget). - `scope-resolution/pipeline/registry.ts` — registers csharpScopeResolver alongside the Python entry. MIGRATED_LANGUAGES stays at {Python} — the resolver sits idle until Unit 7's parity gate confirms ≥99% fixture parity. 368/368 scope-resolution unit tests pass; tsc clean. * feat(csharp-scope): parity Unit 1 — this/base receiver-binding synthesis Closes 3 parity failures (51 → 48). Target bucket: Category C from the parity plan. Changes: - `languages/csharp/receiver-binding.ts` (new): walks up from a function node to the enclosing class/struct/record/interface, synthesizes `@type-binding.self` captures with boundName `'this'` (and `'base'` when the enclosing type is a class/record with an explicit base_list entry). Skips static methods and interface / struct `base` cases. Anchors to the method's `body` block so the scope-extractor's positionIndex places the binding inside the function scope (not the enclosing class scope). - `languages/csharp/captures.ts`: route `@scope.function` matches through the synth, emitting the receiver captures as separate matches. - `languages/csharp/interpret.ts`: map `@type-binding.self` to `source: 'self'` (parity with Python). - `languages/csharp/query.ts`: explicit patterns for `this.X()`, `base.X()`, and `this.X = ...` / `base.X = ...` assignment writes. `this` and `base` are anonymous tokens in tree-sitter-c-sharp so the existing `expression: (_)` pattern (named-only) didn't match. Tests: - 8 new unit tests for receiver-binding synthesis edge cases (class/struct/record/interface, static, nested, constructor, local function inside method). - Parity: 48 failed | 127 passed (175) under REGISTRY_PRIMARY_CSHARP=1; legacy path 175/175 green. * feat(csharp-scope): parity Unit 2a — foreach + pattern + field captures Closes 11 parity failures (48 → 37). Partial Unit 2 progress. Adds type-binding captures for every shape the parity suite exercises whose resolution path is in-file: - Typed foreach `foreach (User u in xs)` — @type-binding.annotation with bindingName `u` and type `User`. - Var foreach `foreach (var u in xs)` — @type-binding.alias so the generic-stripper unwraps `List<User>` / `Dictionary<K,V>.Values` to the element type at chain-follow time. Matches Python's for-loop alias pattern. - `is` pattern `if (obj is User u)` — @type-binding.annotation with scope narrowing simplified to function scope (matches Python's match-case treatment since we don't emit @scope.block). - `switch_section > declaration_pattern` (`case User u:`) — no case_pattern_switch_label wrapper in tree-sitter-c-sharp. - `recursive_pattern` (`is User { Age: 1 } u` / `case User { ... } u:`) — named binding via type+name fields on the pattern node. - Field declaration `private City _city;` — @type-binding.annotation attached to the class scope for `this._city.X` resolution. - Property declaration `public User Owner { get; set; }` — same. - Assignment rebind `alias = Factory()` / `alias = new User()` — @type-binding.alias / @type-binding.constructor so reassignment propagates type info to later receiver-typed resolution. Closed tests: foreach (3), var foreach Tier 1c (2), is-pattern (1), switch pattern (2), recursive_pattern (3). Remaining 37 include tests that need cross-file same-namespace visibility (field chains, assignment chain, cross-file return-type propagation) — deferred to Unit 5 where the IMPORTS/cross-file work lives. 74/74 scope-resolution unit tests pass; legacy path 175/175 green. * feat(csharp-scope): parity Unit 2b — same-namespace cross-file visibility Closes 3 parity failures (37 → 34). Adds the C#-specific implicit import that has no syntactic counterpart: every type declared in `namespace X` is visible to every other file also declaring `namespace X`, without any `using` directive. Changes: - `scope-resolution/contract/scope-resolver.ts` — new optional hook `populateNamespaceSiblings(parsedFiles, indexes, { fileContents })`. Most languages leave it undefined; Python / TypeScript / Java need explicit imports so there's no analogous pass. - `scope-resolution/pipeline/run.ts` — invoke the hook after `buildWorkspaceResolutionIndex` and before `propagateImportedReturnTypes` so the return-type pass sees cross-file sibling class bindings. - `languages/csharp/namespace-siblings.ts` (new) — groups top-level class-like defs by namespace name (extracted from source via regex since `file_scoped_namespace_declaration` scope range covers only the declaration line, not the rest of the file). Injects sibling classes into each file's Module AND Namespace scope bindings with origin='namespace'. Local declarations shadow cross-file siblings via mergeBindings tier precedence. - `languages/csharp/scope-resolver.ts` — wire the hook. 74/74 scope-resolution unit tests pass; legacy path 175/175 green; 34 parity failures remain (was 37) under REGISTRY_PRIMARY_CSHARP=1. * feat(csharp-scope): parity Unit 2c — alias/await/return-type captures Closes 7 parity failures (34 → 27). Adds the remaining type-binding shapes the parity suite exercises: - `var alias = u;` / `alias = u;` — identifier-to-identifier alias. The resolver's chain-follow walks alias → u → u's declared type. - `var u = svc.GetUser();` — chained method call alias. Anchors on the method_access_expression's `name` field; chain-follow picks up GetUser's return type. - `var u = await Factory();` / `await svc.Get();` — await propagation. Strips the `await_expression` wrapper; interpret layer's `stripGeneric` handles `Task<T>` / `ValueTask<T>` unwrapping. - `public User GetUser() { ... }` — method return-type annotation via `@type-binding.return`. Required for `propagateImportedReturnTypes` to see the return type in later cross-file passes. Covers identifier, generic_name, qualified_name, and nullable_type return shapes. 74/74 scope-resolution unit tests pass; legacy path 175/175 green; 27 parity failures remain under REGISTRY_PRIMARY_CSHARP=1. * feat(csharp-scope): parity Unit 3a — cross-namespace `using` binding Closes 2 parity failures (27 → 25). Extends the namespace-siblings pass to resolve `using X;` directives against known namespace buckets: for each `using` that targets a namespace declared somewhere in the workspace, inject that namespace's classes into the importer's module scope with origin='namespace'. This is the scope-resolution analog of legacy's csproj-driven directory↔namespace mapping. Without it, `new User()` in `Services/UserService.cs` (namespace MyApp.Services) can't see the User class in `Models/User.cs` (namespace MyApp.Models) even with `using MyApp.Models;` — the scope-resolver layer doesn't have csproj metadata to translate the dotted namespace path into a directory lookup. Legacy 175/175 green; 25 parity failures remain. * feat(csharp-scope): parity Unit 3b — constructor CALLS emission Closes 3 parity failures (25 → 22). Adds constructor-form CALLS edge emission + C# 12 primary constructor synthesis. Changes: - `scope-resolution/passes/free-call-fallback.ts`: when a site's callForm === 'constructor', look up the class def (not a callable) and pick its explicit Constructor def via workspaceIndex's memberByOwner — or fall back to the Class def itself for implicit constructors. Matches legacy behavior (targetLabel === 'Constructor' when explicit, 'Class' when implicit). - `scope-resolution/pipeline/run.ts`: pass workspaceIndex to the free-call fallback. - `languages/csharp/captures.ts`: synthesize @declaration.constructor for C# 12 primary constructors — `class User(string name, int age)` / `record Person(string First, string Last)`. The parameter_list is a named child of the class_declaration / record_declaration (not a separate constructor_declaration node). Skip the synthesis when the type already has an explicit constructor to avoid duplicates. Emits @declaration.parameter-count + required-parameter-count alongside. Legacy 175/175 green; 376/376 scope-resolution unit tests pass; 22 parity failures remain. * feat(csharp-scope): parity Unit 3c — static call + default-namespace Closes 2 parity failures (22 → 21). - `receiver-bound-calls.ts`: add Case 5 for class-as-receiver. When `Animal.Classify()` has an identifier receiver that resolves to a Class binding (rather than a variable with a typeBinding), look up the member on the class's MRO chain. Covers C#-style static calls and any type-qualified member access. Python doesn't hit this because `ClassName.method()` is syntactically identical to a free call there. - `namespace-siblings.ts`: treat files with no `namespace X;` declaration as living in the default (empty-name) bucket, so types declared in no-namespace files share cross-file visibility. Required for fixtures without explicit namespaces (e.g. the method-enrichment fixture's Animal/App/Dog classes). Legacy 175/175 green; 21 parity failures remain. * feat(csharp-scope): parity Unit 4 — callsite arity synthesis (infra) Synthesize @reference.arity on every invocation_expression and object_creation_expression by counting `argument` named children of the backing `argument_list`. Wires the capture-to-Callsite pipeline shared extractor already consumes (`scope-extractor.ts:878`). No parity-count movement: the remaining arity-adjacent failures (overload disambiguation, optional-parameter dedup, variadic resolution) need type-based argument inference or member-call dedup, both explicitly deferred in the plan's Known Limitations section. This commit is infrastructure — future work lands on top of it. Legacy 175/175 green; 21 parity failures remain. * feat(csharp-scope): parity Unit 5a — IMPORTS edge + static-using mapping Closes 1 parity failure (21 → 20). Fixes cross-file IMPORTS edge emission for C#: - `languages/csharp/interpret.ts`: map `using static X.Y;` to `kind: 'namespace'` rather than `'wildcard'`. The File→File IMPORTS edge needs a non-wildcard kind to survive finalize's Phase 4 (wildcard-expanded edges drop to empty when the provider doesn't implement `expandsWildcardTo`). Unqualified static-member access is a deferred limitation — covered by the namespace-siblings cross-namespace pass for type lookups, and documented under the module's Known Limitations. - `languages/csharp/import-target.ts`: progressive prefix stripping. `using CrossFile.Models;` in a repo laid out `Models/User.cs` (no `CrossFile/` directory) works because the legacy resolver consults csproj; the scope-resolver tries each suffix of the dotted path against `.cs` files. Also handles `using static NS.Type;` by stripping leading segments until a direct match lands. - `test/unit/scope-resolution/csharp/csharp-imports.test.ts`: update the `using static` test to the new namespace-kind shape. 376/376 scope-resolution unit tests pass; legacy 175/175 green; 20 parity failures remain. * feat(csharp-scope): parity Unit 5b — return-type module hoist + chain fallback Closes 1 parity failure (20 → 19) and lays groundwork for Unit 6. Based on investigation-agent findings, addresses cluster of 7 cross-file + chain tests whose return-type bindings were stuck at Class scope and invisible to the chain-follow and propagation passes. Changes: - `languages/csharp/simple-hooks.ts::csharpBindingScopeFor`: when the declaration is a `@type-binding.return`, hoist the binding all the way to the Module scope. The central extractor's auto-hoist only promotes one level (Function → Class); for C# methods the parent is always a Class, so without this override the return binding never reaches Module where chain-follow and cross-file `propagateImportedReturnTypes` read from. - `scope-resolution/passes/compound-receiver.ts`: when the class-scope typeBindings lookup at `objClass.typeBindings.get( methodName)` misses, walk up from the class scope through the parent chain (→ Module) for a return-type binding. Preserves the existing class-scope fast-path while restoring owner-chain lookup for languages that hoist to Module. Python parity suite stays 204/204 green on both flag paths; legacy C# 175/175 green; 19 C# parity failures remain. * feat(csharp-scope): parity Unit 5c — switch-expr + reasons + ACCESSES 1.0 Closes 4 parity failures (19 → 15). - `languages/csharp/query.ts`: add captures for `switch_expression_arm` with `declaration_pattern` and `recursive_pattern`. C# expression- switch (`obj switch { User u => ..., Repo { Name: "x" } r => ... }`) uses a different AST node from classic `switch_statement`'s `switch_section` — needed separate query patterns. - `scope-resolution/passes/receiver-bound-calls.ts`: replace the self-describing `'scope-resolution: *-receiver'` reason strings (which fail legacy-parity consumer filters) with the legacy convention: `'import-resolved'` when the resolved member lives in a different file, `'global'` otherwise. Mirrors `free-call-fallback.ts`'s existing reason logic. - `scope-resolution/passes/receiver-bound-calls.ts`: pass `confidence: 1.0` to `tryEmitEdge` for write/read ACCESSES edges, matching legacy DAG behavior (default 0.85 was legacy-CALLS). Python parity 204/204 on both flag paths; legacy C# 175/175; 15 C# parity failures remain. * feat(csharp-scope): parity Unit 5d — cross-file typeBinding mirror Closes 3 parity failures (15 → 12). `languages/csharp/namespace-siblings.ts`: extend the pass to mirror method return-type bindings from accessible sibling files' Module scopes into the importer's Module scope. "Accessible" = same-namespace siblings + `using namespace X;` targets. Without this mirror, `var u = svc.GetUser()` in App.cs couldn't chain-follow to User even after Unit 5b's module-scope hoist: `GetUser → User` lived on User.cs's Module scope, which isn't on the ancestor chain of App.cs's function scope, and `propagateImportedReturnTypes` only mirrors across explicit ImportEdge targets (not same-namespace implicit visibility). Closes: var-invocation return type, async/await u.Save (ambient namespace), cross-file return-type propagation (via u.Save / u.GetName in Program.cs). Python parity 204/204 on both flag paths; legacy C# 175/175; 12 C# parity failures remain. * feat(csharp-scope): parity Unit 5e — namespace-prefix bucket matching Closes 2 parity failures (12 → 10). `languages/csharp/namespace-siblings.ts`: when matching accessible namespaces against class buckets, also probe every dotted prefix. `using static CrossFile.Models.UserFactory;` parses into the importer's accessible-namespace set as the full type path, but the matching bucket is keyed on the containing namespace (`CrossFile.Models`). Walking back through the dotted segments ensures the static-using importer sees the containing namespace's sibling files' return-type bindings. Legacy 175/175 green; 10 C# parity failures remain. * feat(csharp-scope): parity Unit 6a — class-like owner extension Closes 1 parity failure (10 → 9). Extends `populateClassOwnedMembers` to recognize Interface / Struct / Record / Enum / Trait as class-like owners, not just Class. The C# scope query collapses interface_declaration / struct_declaration / record_declaration / enum_declaration to @scope.class (they share body-scope semantics), but the declaration-side tags produce defs of type Interface / Struct / Record / Enum. `populateClassOwnedMembers` previously only looked for Class-typed defs in class scopes, so interface members (including C# 8+ default methods) never got ownerIds — making them invisible to `findOwnedMember` via `memberByOwner`. With this fix, `user.Validate()` on a variable typed as `IValidator` resolves correctly: receiver-bound-calls Case 4 finds IValidator via findClassBindingInScope (which already accepted Interface), walks the chain, and findOwnedMember locates Validate now that the interface default has a proper ownerId. Legacy C# 175/175 green; Python parity 204/204 on both flag paths; 9 C# parity failures remain. * feat(csharp-scope): parity Unit 6b — member-call dedup + handled-site fix Closes 1 parity failure (9 → 8). Adds the missing legacy-parity behavior: collapse multiple member-call sites from the same caller to the same target into one CALLS edge. Changes: - `scope-resolution/contract/scope-resolver.ts`: new optional `collapseMemberCallsByCallerTarget` flag. Default false (preserves the per-site invariant); C# sets it true. - `scope-resolution/graph-bridge/edges.ts`: dedup key drops `line:col` when `collapseByCallerTarget` is on AND edgeType is `CALLS` (ACCESSES writes keep per-site granularity). - `scope-resolution/passes/receiver-bound-calls.ts`: plumbs `collapse` through every `tryEmitEdge` call, and crucially marks `handledSites.add(siteKey)` whenever a resolved def was found — not only when the edge was freshly emitted. Otherwise the site leaked through to `emitReferencesViaLookup` which re-emitted a per-site edge, defeating the collapse. - `languages/csharp/scope-resolver.ts`: opt in to the collapse. Python parity 204/204 on both flag paths; legacy C# 175/175 green; 8 C# parity failures remain. * feat(csharp-scope): parity Unit 6c — Dictionary.Values / .Keys unwrap Closes 2 parity failures (8 → 6). Dictionary<K,V>.Values in a foreach binds the element to V; .Keys binds to K. Without this, `foreach (var user in data.Values)` where `data: Dictionary<string, User>` couldn't propagate user's type to User, and `user.Save()` stayed unresolved. Changes: - `languages/csharp/interpret.ts`: don't strip the qualifier when the final dotted segment is a known collection accessor (`Values` / `Keys`). Preserves the dotted form so downstream resolvers can unwrap the receiver's generic type based on the suffix. - `scope-resolution/passes/compound-receiver.ts`: new `extractDictionaryArgs` helper splits `Dictionary<K, V>` at the top-level comma. In the dotted-access walk, detect trailing `.Values` / `.Keys` and return V/K via findClassBindingInScope instead of the normal class-walk (Dictionary itself isn't a local class def). - Handles nested cases: `this.data.Values` walks `this.data` recursively (resolving `data` as a field on `this`'s class) before applying the unwrap. - `scope-resolution/passes/receiver-bound-calls.ts` Case 3b: when the typeRef's trailing segment is an accessor, pass the raw dotted path to `resolveCompoundReceiverClass` without appending `()` — the extra parens would misroute to the call-expression branch. Python parity 204/204 on both flag paths; legacy C# 175/175 green; 6 C# parity failures remain. * feat(csharp-scope): parity Unit 6d — using-static member injection Closes 2 parity failures (6 → 4). `using static X.Y.Z;` now injects every public static method of class Z into the importer's module scope, so `Record("hi")` (without `Logger.` qualifier) resolves to `Logger.Record` as a free call. `languages/csharp/namespace-siblings.ts`: regex-scan each file's source for `using static X.Y.Z;` directives. For each, look up the class Z in the `X.Y` namespace bucket, walk its owning file's localDefs for method/function members with `ownerId === Z.nodeId`, and inject them as `origin: 'import'` bindings in the importer's module-scope finalized bindings map. `findCallableBindingInScope` then picks them up via its imported-bindings check. Closes: variadic `Record(params string[])` + heritage arity narrowing `WriteAudit`. Python parity 204/204 on both flag paths; legacy C# 175/175 green; 4 C# parity failures remain (interface-dispatch pass + type-based overload disambiguation). * feat(csharp-scope): parity Unit 6e — overload disambig + interface dispatch + FLAG FLIP Closes the final 4 parity failures (4 → 0). C# now runs the registry-primary scope-resolution path by default — added to MIGRATED_LANGUAGES. Changes: - `scope-resolution/scope/walkers.ts`: was already extended in Unit 6a to recognize Interface/Struct/Record/Enum as class-like owners (interface default methods get ownerIds). - `scope-resolution/passes/receiver-bound-calls.ts`: build IMPLEMENTS edge index → emit secondary `interface-dispatch` CALLS edges to every implementor's same-named member when the primary receiver-typed edge targets an Interface method (closes heritage CreateUser CALLS-count test). - `scope-resolution/passes/receiver-bound-calls.ts`: new `pickOverload` helper narrows multi-valued `membersByOwner.get(owner).get(name)` candidates by arity then argument types. Replaces the first-seen `findOwnedMember` lookup in Case 4 so receiver-typed overloaded calls pick the right def. - `scope-resolution/passes/free-call-fallback.ts`: new `pickImplicitThisOverload` walks up to the enclosing class scope and applies the same arity + argument-type narrowing for free calls inside a class body (`Lookup("alice")` → `Lookup(string)`). - `scope-resolution/workspace-index.ts`: new `membersByOwner` multi-valued index (`Map<owner, Map<name, Def[]>>`) preserves every overload alongside the existing first-seen `memberByOwner`. - `scope-resolution/graph-bridge/node-lookup.ts` + `scope-resolution/graph-bridge/ids.ts`: include parameter-types suffix in the qualified lookup key for Method nodes. Legacy parse-phase encodes the type tag into the node id (`Method:f.cs: UserService.Lookup#1~int`); without this two same-arity overloads collapsed to one lookup entry and routed to the wrong graph node. - `scope-resolution/contract/scope-resolver.ts`: new `collapseMemberCallsByCallerTarget` opt-in flag (was added in Unit 6b for member-call dedup; documented here). - `gitnexus-shared/src/scope-resolution/reference-site.ts`: new `argumentTypes` field carrying inferred per-arg types. - `scope-extractor.ts`: read @reference.parameter-types capture into `site.argumentTypes` and add it + the declaration-arity tags to KNOWN_SUB_TAGS so the anchor-detection picks the right anchor. - `languages/csharp/captures.ts`: synthesize @reference.parameter-types by inferring arg types from literal AST nodes (integer_literal → 'int', string_literal → 'string', constructor_expression → type-name, etc). - `languages/csharp/scope-resolver.ts`: opt in to `collapseMemberCallsByCallerTarget`. - `registry-primary-flag.ts`: **add CSharp to MIGRATED_LANGUAGES**. Final state: - C# parity: 175/175 green on flag-on AND flag-off. - Python parity: 204/204 green on both flag paths (no regression). - TypeScript clean. 51 → 0 failures across 18 commits on `feat/csharp-scope-resolution`. * refactor(scope-resolution): extract language-specific accessor unwrap to provider hook Optimizer pass: move C# Dictionary-family `.Values`/`.Keys` handling out of the shared `compound-receiver.ts` (where it had hardcoded regex + accessor names) into a provider-level `unwrapCollectionAccessor` hook. The shared pass now takes an arbitrary language-specific unwrap function; C# supplies its Dictionary implementation in `languages/csharp/accessor-unwrap.ts`. Related cleanup in `receiver-bound-calls.ts` Case 3b: replace the hardcoded `tail === 'Values' || tail === 'Keys'` accessor check with a try-dotted-walk-first / fall-back-to-call-form strategy. This removes the last C#-specific branch in the shared pass and makes the logic generalize cleanly to other languages that use property-style accessors for collection views (Kotlin `.size`, future languages). Changes: - `scope-resolution/contract/scope-resolver.ts`: new optional `unwrapCollectionAccessor(receiverType, accessor) => string | undefined` hook. Documented as language-specific with examples. - `scope-resolution/passes/compound-receiver.ts`: delete `extractDictionaryArgs`, accept `unwrapCollectionAccessor` via options, call it for trailing accessor segments. - `scope-resolution/passes/receiver-bound-calls.ts`: plumb the hook through to `resolveCompoundReceiverClass`, remove the C#-hardcoded Case 3b accessor check. - `languages/csharp/accessor-unwrap.ts` (new): C# Dictionary-family regex + element-type extraction. - `languages/csharp/scope-resolver.ts`: opt in. Audit outcome: everything else added across the 19 C# migration commits is either correctly scoped to `languages/csharp/` (query, captures, namespace-siblings, receiver-binding, interpret, imports) or correctly generic in shared paths (argumentTypes field, collapseMemberCallsByCallerTarget flag, overload narrowing via parameterTypes, interface-dispatch via IMPLEMENTS edges, class-like owner extension for Interface/Struct/Record/Enum, type-tagged node IDs, module-scope return-type lookup fallback). 175/175 C# green on both flag paths; 204/204 Python green on both flag paths; TypeScript clean. * refactor(scope-resolution): gate module-scope typeBinding walk-up on hook Add optional `hoistTypeBindingsToModule` to the ScopeResolver contract and gate the Module-scope walk-up in `resolveCompoundReceiverClass` on it. Only providers that hoist method return-type bindings to Module scope (C#) opt in; Python and other providers no longer traverse that fallback path. Closes the architectural leak flagged in the production-readiness review: the walk-up was unconditional and therefore widened Python's code path despite existing only for C#. No behavior change for C# (hook=true restores the prior lookup). No behavior change for Python (hook undefined = walk-up skipped, matching pre-PR behavior). Verified: - npx tsc --noEmit clean - C# unit suite 74/74 passing - C# + Python integration 388/388 passing * refactor(csharp-scope): remove as-unknown-as double casts in scope-resolver Tighten three type boundaries that were previously papered over with `as unknown as` casts: * `CsharpResolveContext.allFilePaths`: `Set<string>` → `ReadonlySet<string>`. The orchestrator only hands out a read-only view; drop the widening cast at the resolver-adapter site. * `resolveCsharpImportTarget`: call passes the narrow context directly. `WorkspaceIndex` is `unknown` in the shared contract, so the `as unknown as WorkspaceIndex` cast was gratuitous — structural assignability covers it. * `csharpMergeBindings`: drop unused `_scope: Scope` parameter. The implementation never read it; the cast chain in `scope-resolver.ts` existed only to satisfy an unused slot. LanguageProvider.mergeBindings now wraps with a tiny arrow adapter; ScopeResolver.mergeBindings passes through directly. No runtime behavior change. `grep 'as unknown as' csharp/scope-resolver.ts` returns zero matches. Verified: - npx tsc --noEmit clean - C# unit + integration 462/462 passing (incl. Python integration) * test(csharp-scope): integration fixtures for Units 6c/6d/6e runtime behavior Close the integration-coverage gap flagged in the production-readiness review. Units 6c (collection-accessor unwrap), 6d (using-static member injection), and 6e (overload disambig + interface dispatch) previously had only hook-level unit tests; the end-to-end wiring was exercised only by the parity harness. Three minimal fixtures + four new it() blocks: * csharp-collection-accessor — RenderAll iterates Dictionary<string, Widget>.Values and calls .Render(); asserts the CALLS edge lands on Widget.Render. * csharp-using-static — `using static Helpers.MathUtils;` makes Square(int) a free-callable in the consumer; asserts the CALLS edge lands on MathUtils.Square. * csharp-overload-interface — three assertions: 1. Run → Log binds to the 2-arg overload only (arity narrowing); verified via target Method node's parameterTypes.length === 2. 2. Run → Greet emits one primary edge to IGreeter.Greet plus two reason='interface-dispatch' siblings to En/FrGreeter.Greet. 3. Interface-dispatch fan-out excludes the primary target. Verified: - csharp integration 189/189 passing * docs(scope-resolution): de-c#-ify optional-hook doc-comments on contract Rewrite the doc-comments on four optional hooks so they describe the behavior and when a provider would enable it, rather than naming C# as the sole consumer. Hook names were already generic — only the comments had baked in one-language framing, which risked discouraging future reuse. Affected hooks: * unwrapCollectionAccessor * collapseMemberCallsByCallerTarget * populateNamespaceSiblings * hoistTypeBindingsToModule Language-specific rationale stays where it belongs — next to the hook assignment in `languages/csharp/scope-resolver.ts`. Zero-match grep for `C#|csharp|CSharp` in the contract file confirms the separation. No code change. * docs(csharp-scope): justify regex-based namespace-sibling detection Record why `namespace-siblings.ts` uses regex over AST walks and enumerate the known misses so the next reader has ground to stand on: * `global using static X.Y;` — no plain `using static` token. * Aliased `using static X = Y.Z;` — `=` breaks the pattern. * Attributed namespace declarations between `]` and `{`. * Multi-namespace files — first-wins attribution. * Preprocessor-gated namespace declarations — textual branch only. Rationale: the pass is file-path-driven and the tree-sitter tree isn't available at its call site (the orchestrator feeds raw fileContents); re-parsing to count namespaces would cost more than the regex walk. Refactor to AST-driven detection is deferred to a separate PR. Mirrored the known-miss list into `csharp/index.ts`'s limitations ledger so the operator-visible surface and the in-code justification stay in sync. No code change. * refactor(csharp-scope): AST-driven namespace detection with treeCache reuse Replace regex-over-source-content with tree-sitter AST walks in namespace-siblings.ts; thread the orchestrator's treeCache through the populateNamespaceSiblings hook so the pass reuses the same parse trees `extractParsedFile` already consumed (single-source-of-truth for the AST — no double-parse). Behavior gains (no longer "known misses"): * `global using static X.Y;` is now detected. * Aliased `using static X = Y.Z;` is now detected. * Attributed namespace declarations (`[attr] namespace X`) parse correctly because tree-sitter sees them as one node. * Preprocessor-gated namespace declarations parse via the grammar. Contract change (additive, optional): * `populateNamespaceSiblings` ctx now carries an optional `treeCache?: { get(filePath): unknown }`. Existing providers that don't set it on `RunScopeResolutionInput` see undefined, and the hook falls back to a fresh parse (current behavior preserved on cache miss). Limitation ledger updated in csharp/index.ts: the AST-based detection removes 4 of the 5 prior known misses; only "first-wins multi-namespace file attribution" remains. Verified: - npx tsc --noEmit clean - C# + Python integration 393/393 passing * refactor(python-scope): remove as-unknown-as casts in scope-resolver (mirrors Unit 2) Replay the C# scope-resolver cleanup on the Python side so both providers share a single clean pattern: * Drop `ws as unknown as WorkspaceIndex` — `WorkspaceIndex` is `unknown` in the shared contract, so the narrow context assigns structurally without a cast. * Drop `{ id: scopeId } as unknown as Scope` — `pythonMergeBindings` never read the scope (the parameter was `_scope`), so the stub was a type-only ghost. Signature is now `(bindings)` and the LanguageProvider slot wraps with an arrow adapter. * Drop `allFilePaths as Set<string>` — the orchestrator hands a `ReadonlySet<string>`; we copy it into a `Set` at the resolver adapter so the legacy downstream `resolvePythonImportInternal` chain (typed for mutable `Set<string>`) keeps working. The copy is O(N) once per import, trivial cost. Left intact on purpose: the `(callsite, def) → (def, callsite)` arrow wrapper on `arityCompatibility`. That's a documented shape difference between `LanguageProvider.arityCompatibility(def, callsite)` and `ScopeResolver.arityCompatibility(callsite, def)`; both providers (Python + C#) carry the same wrapper. Reconciling is a separate refactor across both contracts. No runtime behavior change. Verified: - npx tsc --noEmit clean - Python + C# unit + integration suites 529/529 passing * docs(scope-resolution): document I1-I8 invariants, source-of-truth, and same-graph guarantee Promote contract knowledge that was implicit in code into the canonical docs so future migrations and the next reviewer don't have to reverse-engineer it. contract/scope-resolver.ts: * Migration cookbook lists every optional hook (was: only the two booleans), with one-line guidance per hook including when to enable `hoistTypeBindingsToModule`. * Contract Invariants I1-I7 are now spelled out in full (was: only I1/I3/I5 summarized with a pointer to a plan file). Added new I8 "post-finalize hooks may mutate Scope.typeBindings and indexes.bindings; consumers must not freeze or snapshot before all post-finalize hooks have run". * New "Semantic-model source of truth" section: ParsedFile is the single semantic model; passes that need AST-level facts must reuse the orchestrator's treeCache rather than re-parse. * New "Same-graph guarantee" section: legacy DAG and scope-resolution emit indistinguishable edges (node identity, edge vocabulary, confidence). CI parity workflow enforces this. gitnexus-shared/src/scope-resolution/parsed-file.ts: * Added "Source-of-truth invariant" pointer paragraph. ARCHITECTURE.md (Coexistence section): * Updated migrated-language list (Python + C#). * Added "Same-graph guarantee" subsection. * Added "Semantic-model source of truth" subsection. * Filled in the ScopeResolver hook table with the five optional hooks that landed in this branch (unwrapCollectionAccessor, collapseMemberCallsByCallerTarget, populateNamespaceSiblings, hoistTypeBindingsToModule, fieldFallbackOnMethodLookup). * Added C# rows to the code-references table. Verified: - npx tsc --noEmit clean - C# + Python integration 393/393 passing * refactor(scope-resolution): consume SemanticModel as single authoritative store Unify scope-resolution and legacy parse into one symbol index per the industry pattern (Roslyn / tsc / rust-analyzer). Scope-resolution passes now consume `SemanticModel.methods` / `SemanticModel.fields` / `SemanticModel.symbols` for all symbol-keyed lookups. The legacy DAG already read from these; the drift — two parallel owner-keyed indexes populated by two writers with divergent ownerId semantics — is closed. Changes: * `MethodRegistry.lookupAllByOwner(owner, name)`: new API returning every overload without arity narrowing. Powers `findOwnedMember` / `pickOverload`. * `pipeline/run.ts` reconciliation pass: after `provider.populateOwners(parsed)`, iterate `parsed.localDefs[i]` and register methods/fields into the SemanticModel under the corrected ownerId. Idempotent — skips defs already present under `(ownerId, simple)` by nodeId, so unmigrated languages whose legacy extractor already set ownerId (C#) don't double-register. Closes the Python gap where class-body methods were invisible to `MethodRegistry` because the legacy Python method extractor couldn't resolve `enclosingClassId` at parse time. * `WorkspaceResolutionIndex` slimmed to Scope-valued maps only (`classScopeByDefId`, `moduleScopeByFile`). Dropped `memberByOwner`, `membersByOwner`, `defsByFileAndName`, `callablesBySimpleName` — all symbol-keyed duplicates of SemanticModel indexes. * Walker helpers now consume SemanticModel: - `findOwnedMember(owner, name, model)` → methods then fields fallback (ACCESSES writes target Property/Variable defs too). - `findExportedDefByName` fallback walks every Module scope's `origin === 'local'` bindings via `index.moduleScopeByFile` (preserves the module-export-visibility filter that SymbolTable.fileIndex can't cheaply encode). - `findExportedDef` reads `moduleScope.bindings` directly. * `pickOverload` in receiver-bound-calls.ts falls back to `model.fields.lookupFieldByOwner` when method lookup returns empty, fixing ACCESSES write edges that receive a Property target. * `phase.ts` threads `resolutionContext.model` into `RunScopeResolutionInput`. Boundary rule, enforced by file placement: - symbol-indexed lookups (key = nodeId / name / filePath) → `SemanticModel` - Scope-valued lookups (value = `Scope`) → `WorkspaceResolutionIndex` Research synthesized from web-researcher + Explore + best-practices + system-architect agents; canonical references: Roslyn Overview, rust-analyzer architecture, stack-graphs paper. Verified: - npx tsc --noEmit clean - C# + Python integration 393/393 passing * docs(scope-resolution): refresh comments after dropping duplicated indexes Replace references to the now-deleted `memberByOwner` / `callablesBySimpleName` index fields with comments that describe the actual lookup path (`SemanticModel` registries + scope-tied module bindings). Pure doc cleanup; no behavior change. * feat(scope-resolution): extract reconciliation pass + add parity validator Extract the SemanticModel reconciliation pass (previously inline in `pipeline/run.ts`) into a dedicated module with: * `reconcileOwnership(parsedFiles, model)` — pure function returning stats (methodsRegistered / fieldsRegistered / skippedAlreadyPresent). Idempotent; safe to re-run. * `validateOwnershipParity(parsedFiles, model, onWarn)` — dev-mode runtime validator for Contract Invariant I9. Walks every def with an `ownerId` and asserts it is reachable via `model.methods.lookupAllByOwner` or `model.fields.lookupFieldByOwner`. Soft-fails via `onWarn`; never throws. Validator is gated on both `NODE_ENV !== 'production'` and `VALIDATE_SEMANTIC_MODEL !== '0'` so production incurs zero cost but development surfaces any drift between `parsed.localDefs` ownership and the registries. 12 new unit tests cover: * happy path: method, property, Variable registration * edge case: defs without ownerId are skipped * idempotency: second call is a no-op * coexistence: defs the legacy extractor already registered (via `model.symbols.add`) are skipped on reconcile * overloads: multiple methods under the same (owner, name) * validator: no warnings after reconciliation * validator: warns on drift * validator: no-op under NODE_ENV=production * validator: no-op when VALIDATE_SEMANTIC_MODEL=0 * validator: warns on missing Property same as missing Method Verified: - npx tsc --noEmit clean - reconcile-ownership unit tests 12/12 passing - C# + Python integration 393/393 passing * refactor(scope-resolution): narrow handles + tighten required params Two small hygiene fixes that fell out of the unified-model work: * Introduce `readonlyModel: SemanticModel` in `runScopeResolution` immediately after reconciliation so the write/read phase boundary is explicit at the code level. Downstream passes (receiver-bound, free-call) receive the narrowed `SemanticModel` rather than the `MutableSemanticModel` that only the reconciliation pass needs. The type system now rejects accidental writes in the read phase. * Make `emitFreeCallFallback`'s `workspaceIndex` parameter required. It's now always passed (every caller threads it through), and the `workspaceIndex?` guard was dead code. Also drops the `| undefined` branch from `pickConstructorOrClass` which no caller can hit. No behavior change. * docs(semantic-model): document unified single-source-of-truth invariant (I9) Add Contract Invariant I9 to the ScopeResolver contract and write the single-source-of-truth + write/read phase contract into both the SemanticModel file-head and ARCHITECTURE.md. Three landing points so the rule is reachable from every entry: * contract/scope-resolver.ts — new I9 entry in the Contract Invariants list: scope-resolution passes consult SemanticModel exclusively for symbol-keyed lookups; WorkspaceResolutionIndex is reserved for Scope-valued maps. Documents the two-phase write (legacy parse + reconcileOwnership) and the narrowed-handle read posture. Calls out the reconciliation shim as transitional. * model/semantic-model.ts — new "Single-source-of-truth invariant" and "Write / read phase contract" sections in the file-head. Three ordered write phases (parse → reconcile → attachScopeIndexes), then frozen for readers. * ARCHITECTURE.md § "Semantic-model source of truth" — expanded subsection covering both invariants (ParsedFile = AST truth, SemanticModel = symbol truth), the write/read phase diagram, and the reconciliation-shim rationale. No code change. * test(scope-resolution): rewrite workspace-index test for slimmed index The test file previously asserted on \`defsByFileAndName\`, \`callablesBySimpleName\`, and \`memberByOwner\` — fields removed when symbol-keyed lookups moved to \`SemanticModel\`. Rewrite so the same invariants are asserted via the authoritative consumers: * New WorkspaceResolutionIndex shape test (scope-only maps). * \`findExportedDef\` module-export visibility tests: - keeps top-level class and function defs. - excludes class-body Variable defs (MAX_USERS = 100). - excludes class methods from module-export lookup. * \`findExportedDefByName\` fallback excludes class methods when a same-named module function exists. * \`findOwnedMember\` via the reconciled SemanticModel finds Python class methods after populateOwners + reconcileOwnership. Total assertions preserved: every invariant from the old test file is still pinned; the assertion surface shifted from the index shape to the walker helpers. Verified: - workspace-index.test.ts 8/8 passing * fix(tests): update registry-primary-flag test for C# migration The "returns exactly the flipped languages" case expected `enabled.size === 1` after toggling Python off and Go on. After the C# migration lands C# in MIGRATED_LANGUAGES, C# is default-on too — so the size is now 2 (Go + C#) unless C# is also opted out. Turn off C# alongside Python in the test setup. Added a comment noting that future migrations must add their REGISTRY_PRIMARY_<LANG>='false' line here. * refactor(scope-resolution): address PR #1019 review findings Resolves all 5 findings from the automated review on feat/csharp-scope-resolution. Shared ingestion code stays language-agnostic; C# (and every class-like language) benefits. F1 [high] Broaden class-like predicate Hoist `isClassLike` in `scope/walkers.ts` to an exported top-level helper covering Class | Interface | Struct | Record | Enum | Trait. Use it in `findClassBindingInScope`, `findEnclosingClassDef`, and `buildWorkspaceResolutionIndex` so C# records, structs, interfaces, and enums participate in scope chains and receiver binding the same way Python classes do. F2 [medium] Remove stale comment in csharp simple-hooks `csharpReceiverBinding`'s doc claimed this/base synthesis was "planned for a follow-up"; synthesis has been implemented in receiver-binding.ts since the migration landed. Rewrite the doc to describe the actual behavior (non-null TypeRef on instance-method bodies, null on static/free functions). F3 [medium] O(1) reverse lookup for classScopeId -> classDefId Add `classScopeIdToDefId: ReadonlyMap<ScopeId, string>` to `WorkspaceResolutionIndex`, populated as the inverse of `classScopeByDefId`. Replace the O(C) linear scan in `pickImplicitThisOverload` (free-call-fallback.ts) with an O(1) `Map.get` — turns per-site reverse resolution from linear in class count to constant time for every free call. F4 [low] Extract narrowOverloadCandidates shared utility New `passes/overload-narrowing.ts` centralizes the arity + argument- type narrowing previously duplicated across `pickOverload` (receiver-bound-calls.ts) and `pickImplicitThisOverload` (free-call-fallback.ts). Both callsites now share identical narrowing semantics; variadic `params T` handling is preserved. Return type is `readonly SymbolDefinition[]` with no defensive spreads (allocations saved on the hot path). F5 [low] Merge unreachable Case 5 into Case 2 `Case 5` in `receiver-bound-calls.ts` was dead code — `Case 2` pre-empted it for every static/class-name receiver. Delete Case 5 and lift its kind-aware read/write ACCESSES reason/confidence logic into Case 2 so static-style member access (e.g. `Interface.Member`, `TypeName.StaticMember`) gets the correct edge metadata. Tests - New unit tests for `narrowOverloadCandidates` covering empty input, arity filtering, variadic params, type narrowing, and fallback semantics. - New unit tests for `classScopeIdToDefId` verifying inverse invariant and empty index behavior. - New C# integration fixtures and tests: * csharp-record-base — record inheritance + `base.Save()` * csharp-struct-overloads — struct with implicit-this overload narrowing (pinned exact edge count under registry-primary) * csharp-interface-receiver-static — interface-qualified static- style call exercises the merged Case 2. - Full runs green: * scope-resolution unit: 406/406 * csharp integration (registry-primary): 197/197 * csharp integration (legacy DAG): 197/197 * python integration (regression guard): 204/204 Chore - Add `.context/` to root `.gitignore` to prevent agent scratch files from being committed. Made-with: Cursor * test(csharp-scope-resolution): address adversarial review follow-ups on PR #1019 Applies the three actionable follow-ups from the post-commit adversarial review of |
||
|
|
ff4ae89aaa
|
feat(python): scope-based call resolution + registry-primary flip + perf + generalization (RFC #909 Ring 3) (#980)
* Initial plan
* plan: Python scope-based resolution migration
Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/0eee6c69-fc17-4df5-9ac6-358ab41f5740
Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com>
* feat(python): scope-based resolution provider hooks + 62 tests
Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/0eee6c69-fc17-4df5-9ac6-358ab41f5740
Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com>
* refactor(python): split scope-hooks monolith into focused modules
Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/db76e937-4b0e-4c4d-82b1-265a1fb3673d
Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com>
* test(python): integration-style scope-resolution tests + suffixResolve fallback
Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/db76e937-4b0e-4c4d-82b1-265a1fb3673d
Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com>
* wire python scope-based resolution end-to-end (initial pass)
Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/c474dc66-5cf7-445d-8eb4-76501c5e6d67
Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com>
* keep legacy IMPORTS for python (heritage needs importMap), scope phase owns CALLS only
Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/c474dc66-5cf7-445d-8eb4-76501c5e6d67
Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com>
* test(python): remove parallel scope-resolution integration test
The new test/integration/python-scope-resolution.test.ts duplicated coverage
the reviewer explicitly rejected. The existing
test/integration/resolvers/python.test.ts (191 tests, driven by
runPipelineFromRepo) is the source of truth for Ring 3 parity.
Also document the IMPORTS-emission follow-up gap: wiring emitImportEdges
in python-scope-emit.ts today regresses 10 IMPORTS-edge fixtures because
the scope-extractor's ImportEdge coverage is narrower than legacy
pythonImportConfig.importResolver. Tracked as a follow-up.
Baseline with REGISTRY_PRIMARY_PYTHON=1 is unchanged: 109/191 pass.
* feat(ingestion): scope-resolution phase owns Python IMPORTS edges (RFC #909 Ring 3)
When `REGISTRY_PRIMARY_PYTHON=1`, IMPORTS graph edges for Python files are now
emitted exclusively by the new scope-resolution path. The legacy
`import-processor` still runs — heritage resolution needs its importMap /
namedImportMap / moduleAliasMap population — but its graph edge emission is
gated per-language so Python no longer double-emits.
This closes the reviewer's second change request on PR #980: "the legacy path
must be turned off". Legacy IMPORTS edges for Python are now off by default
when the flag is enabled.
Three bugs were fixed to make the new path's coverage match legacy:
1. **Root-file bailout** (import-resolvers/python.ts): `resolvePythonImportInternal`
returned null immediately when the importer file lived at the repo root
(importerDir === ''). The ancestor directory walk further down already
handles this case correctly; the early return was the bug. Proximity check
now only runs when importerDir is non-empty, and the ancestor walk sees
root-level files for the first time.
2. **External dotted imports** (languages/python/import-target.ts): the new
path fell straight through to `suffixResolve` for multi-segment imports,
which happily matched `django.apps` to a local `accounts/apps.py`. Mirror
`pythonImportStrategy`'s `hasRepoCandidate` guard — suffix-match only when
the leading segment exists somewhere in-repo as a package, __init__.py,
or namespace directory.
3. **suffixResolve ambiguity** (languages/python/import-target.ts): the
shared `suffixResolve` helper requires a pre-built `SuffixIndex` to
disambiguate ties. Without one it falls back to an O(files) scan that
silently picks the first match when the last segment collides across
directories (e.g. `accounts.models` matching `billing/models.py`).
Replaced with `resolveAbsoluteFromFiles` — exact lookup first, then a
deterministic suffix match.
Validation:
- Flag OFF: 191/191 pass (no regression).
- Flag ON: 109/191 pass (82 fail — exact baseline match; remaining 82 are
unchanged CALLS-edge provider-feature gaps tracked as Phase B follow-ups).
- `tsc --noEmit`: clean.
The 82 CALLS failures cluster into 44 describe blocks covering type-inference
features (assignment chains, walrus, class-level annotations, constructor
inference, C3 MRO, overload dispatch, return-type inference) that need
dedicated Ring 3 follow-up work. Each cluster is tracked against the RFC #909
shadow-parity gate (>=99% fixtures / >=98% corpus) in the per-language ticket.
* ci(scope-resolution): automatic parity gate driven by MIGRATED_LANGUAGES
Adds the Ring 3 parity gate the RFC §6.4 requires: when a language's
scope-resolution migration is marked complete, CI runs its resolver
integration test twice on every PR (once with the legacy DAG, once with
the registry-primary path) and both must pass.
The "is this language migrated" signal is a single TypeScript constant:
// gitnexus/src/core/ingestion/registry-primary-flag.ts
export const MIGRATED_LANGUAGES: ReadonlySet<SupportedLanguages> =
new Set([ /* SupportedLanguages.Python when ready */ ]);
Adding a language here has three simultaneous effects:
1. `isRegistryPrimary(lang)` defaults to true for that language in
production (env-var override still wins if set explicitly).
2. `.github/workflows/ci-scope-parity.yml` auto-discovers the set via
`npx tsx scripts/ci-list-migrated-languages.ts`, builds a parity
matrix, and runs:
- `REGISTRY_PRIMARY_<LANG>=0 npx vitest run resolvers/<slug>.test.ts`
- `REGISTRY_PRIMARY_<LANG>=1 npx vitest run resolvers/<slug>.test.ts`
Both legs must pass for the job to succeed.
3. Legacy-path gating in call-processor.ts / import-processor.ts kicks
in automatically through the same `isRegistryPrimary` lookup.
No JSON registry, no manual workflow edit, no second source of truth —
contributors update the Set and CI picks it up. Empty Set = parity job
is a skipped matrix (workflow still reports success).
The new `scope-parity` reusable workflow is added to ci.yml's `needs`
graph and ci-status gate. Its result must be `success` (skipped would
mean upstream discover job failed and should block).
Validation (with empty MIGRATED_LANGUAGES set):
- flag OFF: 191/191 pass (no behavior change)
- flag ON (manual REGISTRY_PRIMARY_PYTHON=1): 82 fails = baseline exact match
- `npx tsc --noEmit`: clean
- concurrency-convention script: pass
- tsx discovery script: emits `[]` correctly
* ci(scope-resolution): keep MIGRATED_LANGUAGES empty; fix linter auto-uncomment
Previous commit's example entry got auto-uncommented (linter preferred a
type-checkable `SupportedLanguages.Python` over a commented-out reference).
That would have triggered the parity CI gate against Python, which today
has 82 known flag-on failures — unintended and would block the PR.
Use the explicit generic `new Set<SupportedLanguages>([])` so an empty set
still type-checks without needing an uncommented-out sample member.
Example in the comment now has `// SupportedLanguages.Python,` so it
remains illustrative without participating in the set.
* feat(python): capture constructor-inferred + annotated type bindings
Extends the Python scope-extractor with two new type-binding capture
patterns so receiver-typed method dispatch has concrete type bindings
to work from:
1. `u: User = ...` / `u: User` — variable annotations. `@type-binding.annotation`
anchor, `source: 'annotation'`.
2. `u = User("alice")` — assignment RHS is a bare-identifier call (Python
has no `new` keyword; constructor-shaped calls are syntactically
identical to function calls). `@type-binding.constructor` anchor,
`source: 'constructor-inferred'`.
The runtime query lives in `query.ts` (the `.scm` file is documentation
per the comment at its top); both are updated.
Fixes 19 failures across these resolver fixtures (flag-on 82 → 63):
- Python constructor-inferred type resolution (3)
- Python class-level annotation resolution (3)
- Python nullable receiver resolution (3)
- Python member-call / receiver-constrained / constructor-call (3)
- Python assignment chain propagation (2)
- Python walrus / match-case / chained method (3)
- Python member access iterable for-loop (2)
* feat(python): strip nullable unions + prefer annotations over inference
Two linked changes that together fix the 4 nullable-receiver tests:
1. `stripNullable` in Python's `interpretTypeBinding` unwraps `User | None`,
`None | User`, and `Optional[User]` to `User`, so receiver-typed
resolution treats nullable receivers identically to non-nullable ones.
Three-arm unions (`User | Error | None`) are left unchanged — truly
ambiguous for single-receiver inference.
2. Source-strength ordering in `pass4CollectTypeBindings`. When multiple
matches fire for the same bound name in the same scope — e.g. the
`u: User = find()` idiom where both the annotation and
constructor-inferred patterns match — the explicit annotation now
wins regardless of query-match arrival order. Rank:
explicit (annotation / parameter-annotation / return-annotation / self) > inferred
Also reorders the two Python patterns in query.ts / scopes.scm so the
constructor-inferred pattern appears first — a belt-and-braces fallback
that keeps behavior deterministic if the shared priority ranking is ever
revisited.
Fixes 4 failures (flag-on 63 → 59):
- Python nullable receiver resolution (4 tests)
Flag-off regression check: 191/191 still pass.
* feat(python): walrus, qualified-call, match-case type bindings
Extends the constructor-inferred family of captures with three more
assignment-shaped patterns that all bind a variable to a class-like type:
- Walrus: `(u := User(...))` → `u: User` via `(named_expression)`.
- Qualified call RHS: `u = models.User(...)` → `u: models.User` via
`(attribute)` node .text. Falls through resolveTypeRef Phase 2
(QualifiedNameIndex dotted fallback).
- Match as-pattern: `case User() as u:` → `u: User` via `(as_pattern)`
+ `(class_pattern (dotted_name))`.
Fixes 2 failures (flag-on 59 → 57):
- Python walrus operator type inference
- Python match/case as-pattern type binding
Qualified-call constructor tests still fail because they require
cross-module qualifiedName registration (models.User → models.py's User
class) which isn't yet wired in the Python extractor. Tracked as
follow-up alongside module-import CALLS (#337) resolution.
* feat(python): chain type bindings + strip list[T] generic for for-loop
Adds two capture patterns and a shared transitive-closure pass that
together handle Python's variable-aliasing and for-loop-over-typed-
iterable patterns:
1. `(assignment left: (identifier) right: (identifier))` — `alias = u`.
2. `(for_statement left: (identifier) right: (identifier))` — `for u in users`.
Both emit `@type-binding.alias` with the RHS identifier as rawName. The
shared `pass4CollectTypeBindings` now runs a final transitive-closure
walk that follows identifier-chain TypeRefs through the declaring scope
and its ancestors (depth-capped, cycle-guarded) so `alias` ultimately
points at the class type instead of another local variable name.
Generic stripping in `interpret.ts` unwraps single-arg collection
wrappers — `list[User]`, `set[User]`, `Iterable[User]`, etc. — to the
element type. Multi-arg generics (`dict[str, User]`, `Callable[...]`)
are left alone; their semantics aren't unambiguous.
Fixes 8 failures (flag-on 57 → 49):
- Python assignment chain propagation (4)
- Python nullable + assignment chain (2)
- Python walrus operator (:=) assignment chain (2)
Flag-off still 191/191.
* feat(python): namespace & class receiver resolution + file-level caller fallback
Adds a Python-specific post-resolution pass `emitReceiverBoundCalls`
that closes two receiver gaps the shared `MethodRegistry.lookup` doesn't
cover:
1. **Namespace receivers** — `import models; models.User()` /
`import models as m; m.User()`. The shared `lookupReceiverType` only
walks `scope.typeBindings`; namespace imports never land there
(they're filtered out of `scope.bindings` when the target module
has no self-named def, per `finalize-algorithm.ts:540`). The new
pass walks `indexes.imports` directly, builds a per-file
`localName → targetFilePath` map, and emits CALLS/ACCESSES edges
against the target file's `localDefs`.
2. **Class-name receivers** — `Dog.classify("dog")`. The shared resolver
requires typeBindings; class bindings in `scope.bindings` are never
consulted as receivers. The new pass checks class-kind bindings in
the call scope's chain and resolves members via `ownerId`.
Also fixes module-level call attribution: `resolveCallerGraphId` now
falls back to the File node id (`generateId('File', filePath)`) when no
enclosing function/method/class is found. Matches legacy DAG behavior
for module-scope calls like `u = models.User()` at the top of app.py.
Fixes 4 failures (flag-on 49 → 45):
- Python module import CALLS resolution (Issue #337) (4 of 7)
Flag-off still 191/191.
* feat(python): dotted-typebinding receiver resolution
Adds case 3 to `emitReceiverBoundCalls`: when a receiver's typeBinding
has a dotted rawName like `u: models.User` (the constructor-inferred
form fired by `u = models.User(...)`), walk the namespace map + target
file's defs to find the class, then look up the member via ownerId.
`resolveTypeRef`'s QualifiedNameIndex fallback can't cover this because
the target class's qualifiedName in models.py is just `"User"`, not
`"models.User"` — the dotted form only exists in the call-site file's
receiver expression. This pass bridges that gap without modifying the
shared registry.
Fixes 9 more failures (flag-on 45 → 36):
- Python qualified constructor inference (2)
- Python module import CALLS resolution (Issue #337) (3)
- (cluster overlap — several downstream tests in assignment/nullable/
walrus that propagate through qualified-ctor bindings also benefit)
Flag-off still 191/191.
* feat(python): consult finalized bindings for receiver resolution
`findClassBindingInScope` now walks BOTH:
1. `scope.bindings` — pre-finalize local declarations (origin: 'local')
2. `indexes.bindings` — post-finalize cross-file imports/namespaces
Without (2) we were blind to any class brought in via
`from models import Dog` at the call site's file, because the
scope-extractor's Pass 2 only populates local bindings and the
cross-file finalize produces a separate bindings map that never lands
on `scope.bindings`.
Case 2 (`Dog.classify()`) now walks MRO so inherited static/class
methods resolve — `Dog.classify()` where `classify` lives on `Animal`.
Case 4 (simple typeBinding like `u: U` from aliased import) now uses
`findClassBindingInScope` instead of the shared `resolveTypeRef`,
because `resolveTypeRef`'s `ctx.scopes` only sees pre-finalize local
bindings too.
Fixes 4 more failures (flag-on 36 → 32):
- Python method enrichment > Dog.classify static (1)
- Python static/classmethod class-as-receiver (2)
- Python alias import resolution (1)
Flag-off still 191/191.
* refactor(python-scope): extract language-agnostic emit-core/
Unit 1 of the python migration architectural plan
(docs/plans/2026-04-19-001-refactor-python-migration-architectural-plan.md).
Splits python-scope-emit.ts (~945 → 481 lines) by lifting 14 generic
graph-feeding primitives into emit-core/:
- graph-node-lookup, graph-id, emit-edge
- emit-references, emit-imports
- scope-walkers (findReceiverTypeBinding, findClassBindingInScope,
findOwnedMember, findExportedDef)
- namespace-targets, method-dispatch-bridge
Each file carries a "Next-consumer contract" JSDoc so future language
migrations (TS #927, JS #928, Java, Kotlin, Ruby) import from emit-core
rather than re-implementing. python-scope-emit.ts keeps only the four
Python-specific pieces: runPythonScopeResolution (orchestrator),
buildPythonMro, emitReceiverBoundCalls (4 cases), populateMethodOwnerIds
— these move to languages/python/emit/ in Unit 11.
Pure refactor, zero behavior change:
- flag-off: 191/191 python.test.ts pass (identical baseline).
- flag-on (REGISTRY_PRIMARY_PYTHON=1): 32 fail / 159 pass (identical
baseline — the refactor neither fixes nor regresses any test).
- tsc --noEmit clean.
* feat(python-scope): arity metadata + bind function decls in parent scope
Unit 2 of the python migration architectural plan
(docs/plans/2026-04-19-001-refactor-python-migration-architectural-plan.md).
Two changes that the registry-primary path needs before any of the
arity-sensitive failures can move:
1. Arity metadata on scope-extracted Function/Method defs.
- New helper `languages/python/arity-metadata.ts` reuses
`pythonMethodConfig.extractParameters` so self/cls stripping,
defaults, and *args/**kwargs detection match legacy semantics.
- `emit-captures.ts` synthesizes
`@declaration.parameter-count` /
`@declaration.required-parameter-count` /
`@declaration.parameter-types` captures on every
`@declaration.function` match.
- Generic `scope-extractor.ts buildDefFromDeclarationMatch` reads
the three optional captures into `SymbolDefinition`. Absence is
still the no-op default for non-Python providers.
2. Hoist function/class declaration bindings to the enclosing scope.
The "innermost scope containing the anchor" default placed
`def greet(...)` inside greet's OWN body — invisible to other
module-level callers, so every flag-on free-call resolved to
`unresolved`. The hoist condition (`anchor range == innermost
range`) only fires for scope-creating declarations, so variable /
for-loop captures whose anchor is a child identifier stay put.
Hooks can still override via `bindingScopeFor`.
Verification:
- Flag-off: 191/191 (identical baseline).
- Flag-on (REGISTRY_PRIMARY_PYTHON=1): 31 fail / 160 pass
(was 32/159; the hoist unblocks free-call resolution end-to-end).
- tsc --noEmit clean.
Per-(source,target) edge collapse for multi-call-site cases
(default-params, variadic) still pending — landing it without
regressing the static-method find_user fixture (which expects two
distinct edges through different targets) needs the ownership-aware
qualified-id work that lands with Unit 4 / Unit 11.
* feat(python-scope): capture function return-type annotations
Unit 3 of the python migration architectural plan
(docs/plans/2026-04-19-001-refactor-python-migration-architectural-plan.md).
Wires the `def get_user() -> User` return-type annotation into the
typeBindings stream so the existing constructor-inferred + transitive
chain machinery can resolve `u = get_user(); u.save()` to `User#save`
without any orchestrator change.
Changes:
- `query.ts` + `scopes.scm`: new `@type-binding.return` pattern keyed by
the function name (matches RFC §5.1 canonical vocabulary).
- `interpret.ts`: maps `@type-binding.return` to the existing
`'return-annotation'` source label (no shared change needed).
- `scope-extractor.ts pass4CollectTypeBindings`: extends the Pass 2
auto-hoist (anchor range == innermost scope range → bind in parent)
to type bindings as well — return-type bindings whose anchor IS the
function_definition land in the function's enclosing scope so
callers see them.
Same-file return-type inference is now end-to-end:
`def get_user() -> User: ...` + `u = get_user()` produces
`u: User (return-annotation)` in the caller's scope via
`followChainedRef`.
Verification:
- Flag-off: 191/191 (identical baseline).
- Flag-on: 31 fail / 160 pass (no change — every remaining
return-type test in this fixture set is *cross-file*; carrying
`get_user → User` across module boundaries lands with the
cross-file typeBinding propagation work in Unit 5/7).
- tsc --noEmit clean.
* feat(python-scope): resolve dotted receivers via class-scope field types
Unit 4 partial — the dotted-receiver case (`user.address.save()`).
Class-body annotations like `class User: address: Address` already
land in the class scope's typeBindings via the existing
`@type-binding.annotation` capture. This commit consumes that signal:
- Build a `Map<classDefId, Scope>` from every parsed file's class
scopes once per resolution pass.
- New Case 0 in `emitReceiverBoundCalls`: when the receiver's name
contains a dot, walk the chain — resolve the head's type, then for
each remaining segment look up that field's type in the owner
class's scope.typeBindings, then emit the call against the final
class with MRO walk.
- Cross-scope lookups use each TypeRef's `declaredAtScope` so an
imported `Address` resolves in the file that owns the field
declaration, not the file holding the call site.
Verification:
- Flag-off: 191/191 (identical baseline).
- Flag-on: 29 fail / 162 pass (was 31/160; both `Field type
resolution` fixtures now pass — same-file and cross-file disambig).
- tsc --noEmit clean.
Remaining Unit 4 work (write ACCESSES, `self.X` for-loop iteration)
needs Unit 6's tuple/iterable destructuring before it can land —
`for u in self.users` requires the iterable typing path.
* feat(python-scope): chain receiver via call-expression return types
Unit 5 — extends the compound-receiver case to handle call-expression
receivers (`svc.get_user().save()`).
`resolveCompoundReceiverClass` is the single recursive entry point for
all compound receivers. Three shapes:
- bare identifier — typeBinding chain
- dotted `obj.field[.field]…` — class-scope field types
- call `expr.method()` — recurse into expr, look up method's
return-type typeBinding on its class scope
Method return-type bindings auto-hoist to the parent (class) scope per
Unit 3, so `methodClassScope.typeBindings.get(methodName)` is the
canonical lookup. Free-call return types (`get_user()`) walk the
caller's scope chain.
Depth-capped at 4 hops to bound recursion.
Verification:
- Flag-off: 191/191 (identical baseline).
- Flag-on: 28 fail / 163 pass (was 29/162; `Python chained method
call resolution` now passes).
- tsc --noEmit clean.
Two related tests (`city.save() via method chain`, `c.greet().save()
depth-2 MRO`) still fail because the captures yield typeBindings
shaped like `city → user.get_city` (no trailing parens — the capture
grabs the attribute text). Resolving those needs a follow step that
detects the call-shape rawName and feeds it through the compound
recurser. Lands with the chain-typeBinding work in a follow-up.
* feat(python-scope): free-call fallback consults finalized bindings
Unit 7 — closes the cross-file free-call gap.
The shared `MethodRegistry.lookup` walks `scope.bindings` (pre-finalize
local-only) for free-call resolution. Cross-file imports land in
`indexes.bindings` (post-finalize). Without the dual-source lookup,
`from x import f; f()` resolves to "unresolved" and no CALLS edge is
emitted.
Two changes:
- `emit-core/scope-walkers.ts`: new `findCallableBindingInScope` —
same dual-source pattern as `findClassBindingInScope`, but accepts
Function/Method/Constructor. Promoted to emit-core because every
language with cross-file imports needs the same lookup.
- `python-scope-emit.ts emitFreeCallFallback`: post-pass that walks
every free-call reference site, looks up the callee with the new
helper, and emits via `tryEmitEdge`. Pre-seeds `seen` from the
shared resolver's emissions so we never double-count.
Verification:
- Flag-off: 191/191 (identical baseline).
- Flag-on: 22 fail / 169 pass (was 28/163; +6 tests including
the Python overload dispatch fixtures, ancestor-directory imports,
and same-name module-alias collision).
- tsc --noEmit clean.
* feat(python-scope): super() receiver dispatches up the MRO
Unit 8 — `super().method()` inside a class method walks the enclosing
class's MRO chain (skipping self) and resolves to the first ancestor
that owns the method.
New receiver branch in `emitReceiverBoundCalls` recognizes
`super(...)` syntactically (regex-cheap), finds the enclosing class
via a new `findEnclosingClassDef` scope-walk helper, then re-uses
`scopes.methodDispatch.mroFor` + `findOwnedMember` from the existing
class-receiver path. Handled before the compound-receiver case so
`super()` doesn't fall into the bare-identifier branch where `super`
isn't a binding.
Verification:
- Flag-off: 191/191 (identical baseline).
- Flag-on: 21 fail / 170 pass (was 22/169; `super().save() inside
User to BaseModel.save` now passes).
- tsc --noEmit clean.
* feat(python-scope): suppress shared resolver on member-call sites
Unit 9 — `app_metrics.get_metrics()` (namespace import alias) was
emitting two CALLS edges: a wrong self-call from the shared
resolver's free-call fallback, plus the correct namespace-receiver
edge from the Python post-pass.
Mechanism:
- `emit-core/emit-references.ts`: new optional `skipSites` parameter
(`Set<string>` of `${filePath}:${line}:${col}` keys). When supplied,
references at those positions are skipped — the provider has
already emitted (or chosen not to emit) for that site.
- `python-scope-emit.ts`: reorders Phase 4 — receiver-bound + free-
call fallback run FIRST, populating `handledSites`. The shared
`emitReferencesViaLookup` then runs with that set so the resolver's
fallback can't fight a precise per-receiver emission. Site keys are
added only on successful tryEmitEdge (not for sites the post-pass
saw but couldn't resolve — those still get a chance from the shared
path).
Verification:
- Flag-off: 191/191 (identical baseline).
- Flag-on: 20 fail / 171 pass (was 21/170; same-name module-alias
collision now resolves correctly).
- tsc --noEmit clean.
* feat(python-scope): propagate return-type bindings across imports
Closes the cross-file return-type propagation gap that left tests
like `u = get_user(); u.save()` (where get_user lives in another
file) with `u` typed as the function name instead of its return type.
The shared finalize pass copies callable bindings (`from x import f`
puts `f` in the importer's bindings) but typeBindings stay file-local
because they live on `Scope.typeBindings`, not on the index. Mutate
post-finalize:
- For each module-scope import binding (`origin: 'import'` or
`'reexport'`), look up the source file's module-scope typeBinding
for the def's simple name. If present (return-annotation source),
mirror it under the importer's local alias. Skip when the importer
already has its own typeBinding for the name (explicit local always
wins).
- After propagation, re-run a chain-follow on every scope's
typeBindings — pass-4 ran before propagation and missed any chain
whose terminal lived in a foreign file. Same algorithm as
`followChainedRef` in scope-extractor, but operates on the
finalized scopes so propagated entries are visible.
Mutating `Scope.typeBindings` is safe — `draftToScope` constructs a
plain `new Map(...)`, not a frozen one.
Verification:
- Flag-off: 191/191 (identical baseline).
- Flag-on: 16 fail / 175 pass (was 20/171; +4 — both cross-file
return-type tests, plus two related propagation cases).
- tsc --noEmit clean.
* feat(python-scope): for-loop call-iterable typeBinding
Adds `(for_statement left: (identifier) right: (call function:
(identifier)))` to the typeBinding capture set. Combined with Unit 3's
return-type capture and the cross-file return-type propagation pass,
this makes `for u in get_users(): u.save()` resolve to `User.save`
even when `get_users` is imported from another module.
Captured as `@type-binding.alias` (rawName = function identifier,
without parens) so the existing chain-follow walks the alias to the
function's return-type binding without any new code path.
Verification:
- Flag-off: 191/191 (identical baseline).
- Flag-on: 12 fail / 179 pass (was 16/175; +4 for-loop call-iterable
tests across get_users / get_repos fixtures).
- tsc --noEmit clean.
* feat(python-scope): collapse free-call edges per (caller, target)
Free calls (no explicit receiver) now emit a single CALLS edge per
(caller, target) pair regardless of how many call sites the caller
contains. Mirrors the legacy DAG's per-pair dedup contract — what
the `default-params`, `variadic`, and `overload` fixtures expect.
Member calls keep position-based dedup so distinct resolved targets
(e.g. UserService.find_user vs AdminService.find_user from the same
caller) still produce distinct edges.
Implementation: bypass `tryEmitEdge` (which dedupes positionally) and
hand-roll the relationship with a position-independent rel.id
(`rel:CALLS:<caller>-><target>`). Site handling is now unconditional —
even when the dedup-collapse skips the actual emit, we mark the site
handled so the shared `emit-references` doesn't fight us with its
fallback.
Verification:
- Flag-off: 191/191 (identical baseline).
- Flag-on: 10 fail / 181 pass (was 12/179; +2 — both `default
parameter arity` tests now pass).
- tsc --noEmit clean.
* fix(python-scope): match legacy CALLS reason for import-resolved free calls
The arity-narrowing test asserts \`rel.reason === 'import-resolved'\`
for cross-file free-call edges. Switch the free-call fallback's
reason to mirror legacy DAG semantics:
- target-file !== source-file → 'import-resolved'
- same file → 'local-call'
Verification:
- Flag-off: 191/191 (identical baseline).
- Flag-on: 9 fail / 182 pass (was 10/181; +1 arity-narrowing test).
- tsc --noEmit clean.
* fix(python-scope): drop dead pre-seeding from receiver-bound pass
The pre-seeding loop at the top of \`emitReceiverBoundCalls\` populated
\`seen\` with every reference the shared resolver had already resolved.
That was useful when emit-references ran FIRST. After Unit 9 reversed
the order (emit-references runs after the Python passes and uses
\`handledSites\` to skip what we processed), the pre-seed only causes
harm: when an MRO walk in Case 0 (compound receiver) and Case 4
(simple typeBinding) both touch the same site at the same position
but resolve to different targets, the pre-seed suppresses the second
emission because the shared resolver had already entered the wrong
target into \`seen\`.
Concrete case: \`c.greet().save()\` — Case 0 emits the outer save edge
to Greeting.save; Case 4 then resolves the inner \`c.greet()\` to
A.greet via MRO walk. With pre-seed both edges should emit (different
targets, different rel.ids); without removing the pre-seed the inner
emission was being deduped against an already-seeded entry and the
A.greet edge was lost.
Verification:
- Flag-off: 191/191 (identical baseline).
- Flag-on: 8 fail / 183 pass (was 9/182; +1 — \`c.greet() to A#greet
via MRO walk\` now passes).
- tsc --noEmit clean.
* feat(python-scope): enumerate(X) for-loop tuple destructuring
Adds two new typeBinding capture patterns for the canonical enumerate
pattern:
for (i, u) in enumerate(users): ... ; tuple_pattern
for i, u in enumerate(users): ... ; pattern_list
Both bind the second tuple element (u) to the iterable identifier
(users). The chain-follow then unwraps users → its element type via
the existing generic-strip in interpret.ts (List[User] → User).
The #eq? predicate scopes the pattern to enumerate specifically;
generic tuple destructuring of arbitrary callables is left to a
future iteration once we have a richer signal for "what does this
call yield".
Verification:
- Flag-off: 191/191 (identical baseline).
- Flag-on: 7 fail / 184 pass (was 8/183; +1 — `parenthesized tuple:
for (i, u) in enumerate(users)` now passes).
- tsc --noEmit clean.
* feat(python-scope): dict.items() value-type unwrapping
Two changes that together resolve `for k, v in data.items(): v.save()`:
- `interpret.ts stripGeneric`: extends to `dict[K, V]` /
`Dict[K, V]` / `Mapping[K, V]` etc., stripping to the value type V.
Previously only single-arg generics (list[User] → User) were
stripped; multi-arg ones returned the raw text.
- `query.ts` + `scopes.scm`: new typeBinding patterns for
`for k, v in X.items()` (both pattern_list and tuple_pattern). The
second tuple element binds to X; the chain-follow then unwraps X's
dict annotation to V via the new stripGeneric branch.
Verification:
- Flag-off: 191/191 (identical baseline).
- Flag-on: 6 fail / 185 pass (was 7/184; +1 — `dict.items() loop`
test now passes).
- tsc --noEmit clean.
* feat(python-scope): nested tuple destructuring for enumerate(d.items())
Two more for-loop typeBinding patterns:
- `for i, (k, v) in enumerate(d.items())` — nested tuple destructuring
where v is the value of the dict's items() yield.
- `for v in d.values()` — explicit values() form (companion to items).
Both bind the loop var to the dict identifier; the chain-follow
unwraps via the dict-aware stripGeneric to the value type.
Verification:
- Flag-off: 191/191 (identical baseline).
- Flag-on: 5 fail / 186 pass (was 6/185; +1 nested tuple test).
- tsc --noEmit clean.
* feat(python-scope): 3-var flat destructuring for enumerate(d.items())
Adds the \`for i, k, v in enumerate(d.items())\` shape — flat
3-variable destructuring of the (i, (k, v)) tuple yielded by
\`enumerate\` over \`items()\`. Binds v (the last identifier in the
pattern_list) to the dict identifier; the existing dict-aware
stripGeneric unwraps to the value type.
Verification:
- Flag-off: 191/191 (identical baseline).
- Flag-on: 4 fail / 187 pass (was 5/186; +1).
- tsc --noEmit clean.
* feat(python-scope): write ACCESSES edges for attribute assignments
Three changes that together produce ACCESSES (write) edges for
\`obj.field = value\` assignments:
- New \`@reference.write.member\` capture in query.ts and scopes.scm
matching \`(assignment left: (attribute object: ... attribute: ...))\`.
Reuses the existing receiver/name capture shape so the
receiver-bound emit pass can resolve obj's class and look up the
field.
- \`populateMethodOwnerIds\` now sets ownerId on class-body fields too,
not only on methods. Previously it only walked Function scopes
whose parent was Class; class-body annotations like \`name: str\`
live directly in the Class scope's ownedDefs and were missed, so
\`findOwnedMember(User, "name")\` returned undefined.
- \`emit-core isLinkableLabel\` extends to Variable and Property so
field nodes appear in the graph-node lookup (the legacy parser
emits both kinds for class-body annotations).
- Case 4 in receiver-bound pass now uses the kind word as the edge
reason for read/write sites — matches the legacy DAG convention
the test asserts on.
Verification:
- Flag-off: 191/191 (identical baseline).
- Flag-on: 3 fail / 188 pass (was 4/187; +1 — write-ACCESSES test).
- tsc --noEmit clean.
* feat(python-scope): chain-typebinding + field-fallback method lookup
Reaches the architectural-plan target of >= 189/191 flag-on passing.
Two intertwined changes:
- Field-fallback in resolveCompoundReceiverClass: when method lookup
on the receiver's class (and its MRO) fails, walk the class's
fields and try the same lookup on each field's type. Matches the
"unified fixpoint" intent of the method-chain fixture where
`user.get_city()` reaches `Address.get_city` through User's
`address: Address` field.
- New Case 3b in receiver-bound emit pass: when the receiver's
typeBinding rawName has a dot but isn't a namespace prefix
(e.g. `city -> user.get_city` from the constructor-inferred capture
for `city = user.get_city()`), treat it as a method-call chain and
pipe through the compound resolver. The chain unwraps to the
terminal class (City) and the call resolves normally.
Verification:
- Flag-off: 191/191 (identical baseline).
- Flag-on: 2 fail / 189 pass (was 3/188; +1 city.save method chain).
- tsc --noEmit clean.
Remaining 2 failures are fixture-driven (self.users / self.repos
fixtures reference fields that aren't declared on the class) and
documented as known-limitation in Unit 10.
* feat(python-scope): flip Python to registry-primary (191/191 parity)
Adds the \`for u in self.X\` heuristic typeBinding capture (binds u to
the attribute name X so the chain-follow can resolve via the enclosing
method's parameter typeBinding) — closes the last two failing
fixtures whose classes reference \`self.X\` for fields that are
actually method parameters.
With 191/191 passing on BOTH legacy and registry-primary paths,
flips \`MIGRATED_LANGUAGES\` to include \`SupportedLanguages.Python\`.
Effects:
- Production default for Python files: registry-primary path.
- CI parity gate auto-discovers Python via the script + workflow
(\`scripts/ci-list-migrated-languages.ts\` /
\`.github/workflows/ci-scope-parity.yml\`) and runs the resolver
integration test BOTH ways on every PR.
- Operators retain the \`REGISTRY_PRIMARY_PYTHON=0\` escape hatch.
Verification:
- REGISTRY_PRIMARY_PYTHON=0 (legacy): 191/191.
- REGISTRY_PRIMARY_PYTHON=1 (registry): 191/191.
- Default (unset, post-flip): 191/191 (uses registry).
- tsc --noEmit clean.
This concludes RFC #909 Ring 3 — Python migration.
* refactor(emit-core): EmitProvider interface + promote 5 generic helpers
G-Units 1-2 of the emit-pipeline generalization plan.
Adds:
- emit-core/emit-provider.ts — typed EmitProvider contract (6 required +
2 optional fields). Will be consumed by the generic orchestrator in
G-Unit 6. Documents the LanguageProvider vs EmitProvider boundary.
- emit-core/emit-free-call.ts — emitFreeCallFallback promoted as-is
(drops the unused referenceIndex pre-seed parameter; underscore-prefixed
to keep the signature compatible).
- emit-core/propagate-return-types.ts — propagateImportedReturnTypes +
followChainPostFinalize. Documents the mutation contract (Invariant
I3 + I6 from the plan): runs after finalize, before resolve, mutates
the non-frozen Scope.typeBindings map.
- emit-core/scope-walkers.ts: + findEnclosingClassDef +
findExportedDefByName. Both were already generic in the Python
source.
python-scope-emit.ts shrinks 1055 → 799 lines (–256). Imports the
promoted helpers from emit-core. No behavior change.
Verification:
- REGISTRY_PRIMARY_PYTHON=0 (legacy): 191/191.
- REGISTRY_PRIMARY_PYTHON=1 (registry): 191/191.
- tsc --noEmit clean.
* refactor(emit-core): promote receiver-bound dispatcher + compound resolver
G-Unit 3 of the emit-pipeline generalization plan.
- emit-core/emit-compound-receiver.ts — resolveCompoundReceiverClass
+ matchingOpenParen + COMPOUND_RECEIVER_MAX_DEPTH. Field-fallback
is now an option (default true) so strictly-typed languages can
opt out via EmitProvider.fieldFallbackOnMethodLookup.
- emit-core/emit-receiver-bound.ts — the 7-case dispatcher (super,
Cases 0/1/2/3/3b/4). Accepts a ReceiverBoundProviderSubset
(isSuperReceiver + fieldFallbackOnMethodLookup) so partial wiring
works during the rest of the migration. Documents Contract
Invariants I4 (case order) and I5 (no pre-seeding).
python-scope-emit.ts shrinks 799 → 384 lines. The orchestrator now
calls the generic emitReceiverBoundCalls with an inline minimal
provider (pythonEmitProviderInline) — full provider lands in G-Unit 6
when the orchestrator itself moves to languages/python/emit/.
Verification:
- REGISTRY_PRIMARY_PYTHON=0 (legacy): 191/191.
- REGISTRY_PRIMARY_PYTHON=1 (registry): 191/191.
- tsc --noEmit clean.
* refactor(emit-core): promote MRO walk + populateClassOwnedMembers
G-Units 4-5 of the emit-pipeline generalization plan.
- emit-core/build-mro.ts — generic buildMro takes a LinearizeStrategy
hook receiving (classDefId, directParents, parentsByDefId). Three
shared steps (collect EXTENDS, build defId-by-graphId, walk per
class) + parametric linearization. Default strategy is BFS-with-
visited (Python's depth-first first-seen, also correct for
single-inheritance languages).
- emit-core/scope-walkers.ts: + populateClassOwnedMembers — generic
OO ownership rule (methods + class-body fields). Both rules ship
together because every OO language migrated so far (Python; planned
TS/JS/Java/Kotlin) wants both. Languages that need different rules
can compose with this as a base step.
python-scope-emit.ts shrinks 384 → 255 lines.
Verification:
- REGISTRY_PRIMARY_PYTHON=0 (legacy): 191/191.
- REGISTRY_PRIMARY_PYTHON=1 (registry): 191/191.
- tsc --noEmit clean.
* refactor(scope-resolution): generic orchestrator + language-agnostic phase
G-Units 6-7 of the emit-pipeline generalization plan, plus the
pipeline-phase generalization (the user's observation that the phase
itself is generic once the orchestrator is).
Changes:
- emit-core/orchestrator.ts — runScopeResolution(input, provider).
The 180 lines of pipeline glue moved here, parametrized by
EmitProvider. Provider supplies LanguageProvider, importEdgeReason,
and the 6 emit-side hooks.
- emit-core/emit-provider.ts — EmitProvider gains languageProvider
and importEdgeReason fields so the orchestrator needs nothing else.
resolveImportTarget now takes (targetRaw, fromFile, allFilePaths).
- languages/python/emit/index.ts — pythonEmitProvider + thin
runPythonScopeResolution wrapper. The first reference impl every
next-language migration copies.
- emit-providers-registry.ts (NEW) — registry of per-language
EmitProviders keyed by SupportedLanguages. Adding a language is
one line here + the provider file.
- pipeline-phases/scope-resolution.ts (NEW) — language-agnostic phase
iterating EMIT_PROVIDERS ∩ MIGRATED_LANGUAGES. Replaces
pipeline-phases/python-scope.ts (deleted).
- python-scope-emit.ts deleted.
- pipeline.ts swaps pythonScopePhase → scopeResolutionPhase.
The next language migration is now: implement EmitProvider, register
it, add to MIGRATED_LANGUAGES. No new pipeline phase, no orchestrator
copy-paste. The Python migration's 700+ lines of glue collapse to
~80 lines per future language.
Verification:
- REGISTRY_PRIMARY_PYTHON=0 (legacy): 191/191.
- REGISTRY_PRIMARY_PYTHON=1 (registry): 191/191.
- Default (post MIGRATED_LANGUAGES flip): 191/191.
- tsc --noEmit clean.
* docs(emit-provider): migration cookbook for next-language porters
* refactor(scope-resolution): rename emit-core/ → scope-resolution/, EmitProvider → ScopeResolver
Reorganizes the registry-primary resolution layer for clarity and
contributor onboarding. Driven by feedback that "emit" was triple-
overloaded (graph-edge emission + tree-sitter capture extraction +
the provider name itself), and the flat 16-file emit-core/ folder
mixed five concerns.
External research (rust-analyzer hir-def/nameres, Pyright analyzer/,
TypeScript binder/checker, Roslyn Binder, IntelliJ Resolver, swc
semantic/, biome semantic/, semgrep naming/, JDT Binding, clangd
Sema) consistently uses **the phase name** for this layer, never an
output verb. "Scope resolution" matches our pipeline-phase name, the
plan, and the RFC.
## Folder rename
emit-core/ → scope-resolution/
├── (16 flat files) → ├── contract/scope-resolver.ts
├── pipeline/{run,registry,phase}.ts
├── passes/{receiver-bound-calls,
│ free-call-fallback,
│ compound-receiver,
│ imported-return-types,
│ mro}.ts
├── graph-bridge/{node-lookup,ids,
│ edges,references-to-edges,
│ imports-to-edges,
│ method-dispatch}.ts
└── scope/{walkers,namespace-targets}.ts
Each subfolder maps to one concern a new contributor needs to find:
*the contract I implement / the runner that calls me / the helpers I
reuse / the graph layer I shouldn't touch / the scope walkers*.
## Symbol renames
EmitProvider → ScopeResolver
pythonEmitProvider → pythonScopeResolver
runPythonScopeResolution → resolvePythonScope
EMIT_PROVIDERS → SCOPE_RESOLVERS
getEmitProvider → getScopeResolver
RunPythonScopeResolution{Input,Stats} → ResolvePythonScope{Input,Stats}
## File renames (per-language)
languages/python/emit/index.ts → languages/python/scope-resolver.ts
languages/python/emit-captures.ts → languages/python/captures.ts
(kills the parse-side "emit" collision)
## Mechanics
- Used `git mv` for all files so blame history is preserved.
- Updated ~30 import lines across 18 files plus the pipeline-phases
barrel and pipeline.ts.
- Updated JSDoc cross-references throughout to match the new vocabulary.
Verification:
- REGISTRY_PRIMARY_PYTHON=0 (legacy): 191/191.
- REGISTRY_PRIMARY_PYTHON=1 (registry): 191/191.
- Default (post MIGRATED_LANGUAGES flip): 191/191.
- tsc --noEmit clean.
Migration cookbook in `scope-resolution/contract/scope-resolver.ts`
JSDoc points the next-language porter at all the new names and
folder locations.
* docs(scope-resolution): finalize phase JSDoc + drop python emoji from generic log line
* perf(scope-resolution): O(1) workspace lookup index
Introduces `WorkspaceResolutionIndex` — a precomputed bundle of
lookup tables built ONCE per resolution run, after `populateOwners`
and after finalize, before any pass that needs to find members,
exported defs, or class scopes by id.
What it replaces (all are pre-existing O(N×D) linear scans of
parsedFiles, called inside the receiver-bound MRO chain):
- `findOwnedMember(ownerId, name, parsedFiles)` → `Map.get` via
`index.memberByOwner.get(ownerId)?.get(name)`. Was the worst
offender — receiver-bound dispatcher calls this O(sites × MRO
depth) times.
- `findExportedDef(filePath, name, parsedFiles)` → `Map.get` via
`index.defsByFileAndName`. Hot for namespace-receiver case.
- `findExportedDefByName` workspace-wide fallback scan → `Map.get`
via `index.callablesBySimpleName`.
- `classScopeByDefId` (rebuilt inside `emitReceiverBoundCalls` on
every invocation) — moved to one-shot build during finalize, read
from `index.classScopeByDefId` everywhere.
- `moduleScopeByFile` (rebuilt inside `propagateImportedReturnTypes`
on every invocation) — read from `index.moduleScopeByFile`.
Findings from a synthetic 100-file Python workload (60 model files
each defining 5 classes × 3 methods + 40 user files calling them
heavily):
scope-resolution wall time: 764ms → 710ms (median, 5 iters)
That's a ~7% in-layer win. The smaller-than-expected gain was
informative: profiling the synthetic workload shows scope-resolution
breakdown is `extract=62% resolve=30% emit=4%`; the index touched
the 4% slice (emit + walker calls inside it). Larger O(D) per owner
classes will benefit more.
Profiling the FULL pipeline (49 fixtures × 3 iters) shows
scope-resolution accounts for ~1% of pipeline wall time — the
remaining 99% is parse (tree-sitter), heritage, ORM, MRO, processes,
and DB writes. So further optimization of this specific layer has
marginal pipeline impact; the next-biggest wins live in those
phases. Documented as the "double-parse" finding in the audit
(captures.ts re-parses each Python file even though the parse phase
already produced a tree-sitter Tree) — that's a separate plumbing
project across phase boundaries.
Bonus: opt-in PROF_SCOPE_RESOLUTION=1 env var prints a per-phase
ms breakdown to stderr, so future perf work can measure without
extra code changes.
Verification:
- REGISTRY_PRIMARY_PYTHON=0 (legacy): 191/191.
- REGISTRY_PRIMARY_PYTHON=1 (registry): 191/191.
- tsc --noEmit clean.
* perf(parse/heritage/mro): typed graph iterator + cross-phase tree cache
Two structural perf wins targeting the parse / heritage / MRO
layers, identified by the post-WorkspaceResolutionIndex profiling
(scope-resolution = ~1% of pipeline; the bulk lives upstream).
## 1. KnowledgeGraph.iterRelationshipsByType (PHM-Units 1-2)
- Adds a per-type `Map<RelationshipType, Map<id, Relationship>>`
index inside `createKnowledgeGraph`, maintained on add / remove /
removeNode / removeNodesByFile.
- New `iterRelationshipsByType(type)` returns a typed iterator that
yields only the requested type. Backwards-compatible: existing
`iterRelationships()` / `forEachRelationship()` callers untouched.
- Migrated two MRO call sites:
- `mro-processor.ts buildAdjacency`: split the single
`forEachRelationship` (which scanned every edge in the graph and
type-filtered per-iteration) into three typed iterations
(EXTENDS, IMPLEMENTS, HAS_METHOD).
- `scope-resolution/passes/mro.ts buildMro`: replaced
`for (const rel of graph.iterRelationships()) if (rel.type !== 'EXTENDS') continue`
with `for (const rel of graph.iterRelationshipsByType('EXTENDS'))`.
- Heritage-processor (PHM-Unit 3) was a no-op: it only WRITES
EXTENDS/IMPLEMENTS edges, never re-reads. Index is still useful
for the seven other graph-iter consumers (community-processor,
csv-generator, wildcard-synthesis, process-processor, etc.) — those
follow-ups can switch to the typed iterator without touching the
graph layer.
- Adds 5 unit tests for the new method (add/remove/dedupe semantics,
empty-type fresh iterator, removeNode index sync).
## 2. Cross-phase tree cache (PHM-Units 4-5)
The audit's #2 finding: Python files are parsed by tree-sitter once
in the parse phase, then re-parsed inside scope-resolution's
`captures.ts`. Eliminate the second parse by sharing the Tree across
phases.
- `parse-impl.ts` now maintains TWO ASTCaches with distinct lifetimes:
- `astCache` (chunk-local, cleared between chunks) — unchanged;
used by call/heritage/import processors during parse.
- `scopeTreeCache` (total-parseable-sized, never cleared) — new,
exposed via `ParseOutput.astCache` for cross-phase consumption.
- `parsing-processor.ts` writes every sequentially-parsed Tree to
BOTH caches. Worker-mode parses skip the persistent cache too
(Trees can't cross MessageChannels).
- `LanguageProvider.emitScopeCaptures` gains an optional `cachedTree`
parameter (typed `unknown` to keep the tree-sitter dep out of the
contract).
- `captures.ts` short-circuits its own `parser.parse(sourceText)`
when a cached Tree is supplied. Cache miss falls back to a fresh
parse — same correctness path as before.
- `runScopeResolution` accepts an optional `treeCache` and forwards
per-file `cachedTree` to `extractParsedFile`.
- `scope-resolution/pipeline/phase.ts` reads
`getPhaseOutput<{astCache}>(deps, 'parse')` and passes through.
Verified end-to-end: a small fixture run with PROF_SCOPE_RESOLUTION=1
shows 6/6 cache hits (100% hit rate) on the python-grandparent fixture
that exercises the full pipeline below the worker-pool threshold.
## Verification
- REGISTRY_PRIMARY_PYTHON=0 (legacy): 191/191.
- REGISTRY_PRIMARY_PYTHON=1 (registry): 191/191.
- New graph.test.ts: 25/25 (was 20).
- tsc --noEmit clean.
## Where the win lands
Wall-clock on the 49-fixture integration suite: 14050ms → 14080ms
(within noise). Fixtures are 1-3 files each, dominated by per-fixture
pipeline overhead (worker-pool init, DB writes, fixture startup).
The cache + typed-iterator wins are constant-factor improvements
that scale linearly with workload size and visible only on larger
repos. The dev-mode `PROF_SCOPE_RESOLUTION` instrumentation +
`getPythonCaptureCacheStats()` are kept for future perf work.
## Plan
docs/plans/2026-04-20-002-perf-parse-heritage-mro-plan.md.
PHM-Unit 3 (heritage-processor migration) intentionally collapsed
to a no-op — heritage only writes, never re-reads.
* perf(scope-resolution): bound tree-cache lifetime + gate population
Address P1 residuals from ce:review of
|
||
|
|
daca8360bf
|
fix(python): avoid local matches for external dotted imports (#899) | ||
|
|
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> |
||
|
|
c19e76a4a3
|
feat(SM-9): Add lookupMethodByOwnerWithMRO using HeritageMap (#740)
* Initial plan
* feat(SM-9): add lookupMethodByOwnerWithMRO with HeritageMap parent chain walking
- Export c3Linearize from mro-processor.ts for reuse
- Add lookupMethodByOwnerWithMRO in call-processor.ts with MRO strategy support
- Update resolveMethodByOwner to fall back to MRO walk when HeritageMap available
- Thread heritageMap through walkMixedChain for chain resolution
- Add 10 unit tests covering all acceptance criteria
Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/cc58249b-42f1-45a9-89fb-e3917e4d0171
Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com>
* feat(SM-9): add Java integration test with class Child extends Parent fixture
Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/cc58249b-42f1-45a9-89fb-e3917e4d0171
Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com>
* docs: address code review comments on MRO strategy documentation
Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/cc58249b-42f1-45a9-89fb-e3917e4d0171
Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com>
* perf(SM-9): address PR #740 review comments
- Eliminate double direct lookup in resolveMethodByOwner: delegate
straight to lookupMethodByOwnerWithMRO when a HeritageMap is
available (the MRO helper already does the direct lookup before
walking ancestors). Fallback path handles the no-HeritageMap case.
- Memoize C3 linearization per HeritageMap via a WeakMap keyed cache.
HeritageMap is immutable after build, so C3 results are stable for
its lifetime; WeakMap lets the cache auto-drain when the HeritageMap
is GC'd. Null sentinel caches linearization failures so cyclic
hierarchies are not reprocessed. Eliminates per-call buildParentMap +
c3Linearize on Python codebases.
- ancestors variable typed as readonly to accept the cached result
without copying.
- Add four missing MRO unit tests: Kotlin implements-split, C#
implements-split, JavaScript first-wins (separate provider from TS),
and C++ leftmost-base diamond (first diamond test for C++).
* fix(SM-9): CI prettier + address PR #740 follow-up review
- Fix CI prettier failure in test/integration/resolvers/java.test.ts
(auto-formatted — was introduced in
|
||
|
|
0561d24efd
|
feat: METHOD_IMPLEMENTS edges, overload disambiguation, MethodExtractor unification (#574) (#642) | ||
|
|
63fc4c795f
|
feat: MethodExtractor configs for Python, PHP, Swift, Dart, Rust, Ruby (#624)
* feat: MethodExtractor configs for Python, PHP, Swift, Dart, Rust, Ruby with exhaustive integration tests Add per-language MethodExtractionConfig for all remaining tree-sitter languages (RFC #568 PR 2). Each config follows the established createMethodExtractor() factory pattern — no new types, no parse-worker changes. Configs: - Python: @abstractmethod, @staticmethod/@classmethod, *args/**kwargs, type hints, _/__ visibility - PHP: abstract/final/static keywords, PHP 8 #[] attributes, __construct/__destruct - Swift: 5-level visibility, protocol-as-abstract, static/class methods, @ attributes - Dart: _ convention visibility, abstract (no body), method_signature unwrapping - Rust: pub visibility, &self receiver, trait_item + impl_item, #[] attributes - Ruby: positional visibility via sibling-walk, singleton_method as static Integration fixtures (18 directories) covering 3 resolution patterns: - Method enrichment: parameterTypes, isAbstract, isFinal, annotations on graph nodes - Overload dispatch: arity-based CALLS resolution via parameterTypes - Abstract dispatch: abstract/concrete method distinction (Python, PHP, Rust, Swift) Go deferred — requires factory changes for receiver-based method extraction. Closes #571 * fix: address code review findings across 6 MethodExtractor configs Fix all actionable items from the PR #624 deep-dive review: Dart (critical — fixes 6 CI failures): - isDartStatic: check children first, siblings as fallback - isDartAbstract: handle declaration nodes for abstract methods - extractSingleParam: detect required keyword as sibling token - Add declaration to methodNodeTypes, mixin_declaration to typeDeclarationNodes - Add member call query for variable assignments in tree-sitter-queries Python: - hasDecorator now matches dotted paths (e.g. @abc.abstractmethod) - Fix version comment from ^0.23.6 to 0.23.4 PHP: - Add enum_declaration to typeDeclarationNodes (PHP 8.1+) - Add version comment for 0.23.12 Swift: - Add isOverride using hasKeyword/hasModifier pattern Rust: - Fix version comment from ^0.23.2 to 0.23.1 Also: identifier fallback in generic.ts for mixin owner names, Dart integration test label fix (Method vs Function), version comment for tree-sitter-dart 1.0.0. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: Dart extension_declaration and Ruby module_function support Dart: - Add extension_declaration to typeDeclarationNodes and extension_body to bodyNodeTypes — extension methods are now extracted into the graph - Add extension_declaration and mixin_declaration to CLASS_CONTAINER_TYPES for HAS_METHOD edge resolution Ruby: - module_function now maps to visibility 'private' in extractRubyVisibility - module_function methods marked isStatic via backward-walk in isStatic - Override semantics: private/public after module_function resets isStatic Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(go): Go MethodExtractor config with receiver-based extraction Add Go as the 13th language with a per-language MethodExtractor config. Go methods are top-level (not nested in struct bodies), so this adds extractFromNode() to the MethodExtractor interface for direct method node extraction without an enclosing class. Config extracts: - Name from field_identifier (methods) / identifier (functions) - Return type including multi-return (first type from parameter_list) - Parameters with variadic support - Visibility via uppercase/lowercase convention - Receiver type with pointer unwrapping (*User → User) - isStatic for functions (no receiver) Infrastructure: - extractOwnerName optional hook on MethodExtractionConfig - extractFromNode on MethodExtractor (factory auto-implements) - Parse-worker uses extractFromNode when no enclosing class found - method_declaration added to CLASS_CONTAINER_TYPES Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test: method enrichment integration tests for 7 languages + TS abstract class fix Add method-enrichment integration test fixtures and test blocks for Go, C++, Java, Kotlin, TypeScript, JavaScript, and C#. Each fixture tests: class detection, HAS_METHOD edges, EXTENDS edges, isAbstract, isStatic, annotations, parameterTypes, and CALLS edge resolution. Fixes found during testing: - Remove method_declaration from CLASS_CONTAINER_TYPES (added for Go but broke Java/C# HAS_METHOD edge resolution — method_declaration is also Java's method node type) - Add abstract_class_declaration query to TypeScript tree-sitter queries (was missing, so abstract classes were invisible to pipeline) 1699 integration tests pass across 20 test files, 0 regressions. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * style: format typeDeclarationNodes array for better readability in PHP config * fix: Go interface methods + Rust impl-for-Struct owner resolution Go: - Add method_elem to methodNodeTypes so interface method signatures are extractable as abstract methods - Integration test: Animal interface detected, Speak isAbstract, CALLS edges from app.go Rust: - Add extractOwnerName to resolve impl Trait for Struct to the concrete Struct (not the Trait) — fixes method misattribution - Fix findEnclosingClassId to generate Struct: label (not Impl:) for impl blocks so HAS_METHOD edges resolve to struct nodes - Tighten abstract-dispatch test: assert SqlRepo owns find/save generic.ts: - Fix extractOwnerName fallback: when hook returns a value, skip both name-field and type_identifier scan (was overwriting result) 1703 integration tests pass, 0 regressions. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: code review response — Rust impl label, Swift params, Dart async, sequential methodExtractor Address code review findings from PR #624: - ast-helpers: Rust `impl Trait for Struct` uses Struct label (matches existing graph node), plain `impl Struct` uses Impl label (matches definition.impl) - swift: fix parameter type extraction (user_type not type_annotation), detect default values as function_declaration siblings, add version comment - dart: isDartAsync now detects async*/sync* generators, add clarifying comment for declaration nodes in extension bodies - python: correct isFinal comment (PEP 591 @typing.final exists, just not modeled) - parsing-processor: port methodExtractor enrichment to sequential path so isAbstract/isStatic/visibility/annotations/isFinal populate on <15-file repos - tests: remove silent `if (prop !== undefined)` guards, assert properties directly, fix label queries (Dart Method vs Function, Swift Method for protocol methods), add Rust HAS_METHOD sourceLabel tests, Swift parameterTypes tests, and Dart async/sync* integration tests with fixture Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: Rust grammar gap + qualified method IDs to resolve same-file collisions Phase 1 — Rust grammar: - Add function_signature_item query to RUST_QUERIES so abstract trait methods (fn speak(&self) -> String;) become graph nodes with isAbstract=true Phase 2 — Qualified method IDs: - findEnclosingClassInfo returns {classId, className} for AST-based class lookup - Both parsing paths (sequential + worker) qualify method/property IDs with enclosing class: Method:file:ClassName.method instead of Method:file:method - extractFuncNameFromSourceId handles ClassName.method format - Fixes silent data loss when same-name methods in different classes shared a file (e.g., Animal.speak and Dog.speak both now exist as distinct graph nodes) Test updates: - Rust: abstract+concrete trait methods both verified, function count adjusted - Python: static method disambiguation now emits 2 CALLS edges (correct — no more ID collision masking the second call) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: owner-aware resolution for qualified method IDs Address Codex adversarial review findings after qualified ID change: - findEnclosingFunction: disambiguate candidates by ownerId when multiple same-name methods exist in file; qualify fallback-generated IDs - findEnclosingFunctionId (worker): qualify sourceIds with enclosing class name so CALLS source attribution matches definition-phase node IDs - buildExportedTypeMapFromGraph: use lookupExactAll + nodeId match instead of lookupExactFull which returns first definition for bare name Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: methodExtractor variadic arity, return type preservation, PHP abstract dispatch Three bugs in the methodExtractor enrichment path broke 17 integration tests: 1. Variadic parameterCount: buildMethodProps and parse-worker set parameterCount = info.parameters.length even for variadic functions, causing arity filtering to reject valid calls. Now checks isVariadic and sets parameterCount = undefined (matching extractMethodSignature). 2. C++ bare `...` token: extractCppParameters only iterated named children, missing the unnamed `...` token in C-style variadics like log_entry(const char* fmt, ...). Added fallback scan of all children. 3. Return type stripping: All 11 language extractReturnType functions used extractSimpleTypeName() which strips generic parameters (List<User> → "List", Task<User> → "Task"). Changed to .text?.trim() to preserve full generic types needed for for-loop iterable resolution, async-await binding, and return-type inference. Also fixes PHP abstract dispatch test that matched SqlRepository instead of the interface due to ambiguous filePath.includes('Repository') filter, and adds parent-walk fallback in PHP isAbstract for extractFromNode path. * chore: remove plan and review artifacts from PR * fix: address Round 4 review findings + infrastructure improvements - Ruby: add singleton_class support for class << self methods (4 new tests) - PHP: add enum_declaration to CLASS_CONTAINER_TYPES - Dart: add mixin/extension labels to CONTAINER_TYPE_TO_LABEL - Swift: add TODO for unverifiable struct/enum node types on Node 22 - C#: add grammar version comment (0.23.1) - Ruby: fix version comment range to pin (0.23.1) - Rust/ast-helpers: add cross-reference comments for impl_item duplication - ast-helpers: document CLASS_CONTAINER_TYPES ↔ typeDeclarationNodes invariant - generic.ts: replace Array.includes with Set for O(1) dedup in addNestedBodies - Go/Python/Ruby: align isAbstract signature with 2-param interface contract - CLAUDE.md: fix malformed backtick around gitnexus:start HTML comment - parsing-processor: add per-class method extraction cache (eliminates O(N*M)) - ast-helpers: add scoped_type_identifier to impl_item resolution - call-processor: add dev-mode warnings at silent candidates[0] fallbacks - MCP context(): surface methodMetadata for Method/Function/Constructor nodes - resources.ts: update schema to list all stored Method properties * fix: singleton_class HAS_METHOD edge regression in findEnclosingClassInfo singleton_class (class << self) was added to CLASS_CONTAINER_TYPES but has no name field — its receiver `self` has node type 'self', not 'identifier'. findEnclosingClassInfo now walks up to the enclosing class/module to inherit its name, matching ruby.ts:extractOwnerName. Also fixes findEnclosingClassNode in parse-worker.ts to skip singleton_class and return the actual class/module node. Adds integration test assertions for from_habitat (class << self method): HAS_METHOD edge from Animal, isStatic=true, parameterCount=1. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
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. |
||
|
|
cde3858e03
|
refactor: Phase 8 & 9 — Field Types and Return-Type Binding (#494)
* feat(phase8): add field type data structures and extractor interface
* feat(phase8): implement TypeScript field extractor
* feat-phase9-add-call-result-binding
* test-phase8-add-field-extraction-unit-tests
* docs: update documentation for Phase 8 and Phase 9
* feat(swift): Phase 8/9 integration tests for field-type and call-result binding
Add Swift field-type resolution and call-result binding integration tests
with fixtures, plus merge-conflict fixes for the FieldExtractor code.
**Swift integration tests:**
- `swift-field-types/` fixture (Models.swift + App.swift) — tests
HAS_PROPERTY edges, field-chain CALLS resolution (user.address.save()
→ Address#save), and ACCESSES edges for field reads.
- `swift-call-result-binding/` fixture — tests call-result binding
(let user = getUser(); user.save() → User#save).
- 2 new describe blocks in swift.test.ts with skipIf(!swiftAvailable).
**Swift arity fix:**
- extractMethodSignature fallback counts direct `parameter` children
when no wrapper list node exists (Swift's tree-sitter grammar places
parameters as direct children of function_declaration). Without this,
all Swift functions had parameterCount: 0 and the arity filter rejected
valid call targets.
**FieldExtractor merge-conflict fixes:**
- field-extractor.ts: update import from removed ./utils.js to
./utils/ast-helpers.js; use typeEnv.fileScope() instead of .get('').
- field-extractors/typescript.ts: same import fix.
- field-types.ts: alias TypeEnvironment as TypeEnv (renamed on main).
- field-extraction.test.ts: mock TypeEnvironment interface properly.
* feat(field-extractors): generic table-driven field extractors for all 14 languages, wired into pipeline
Implements field extractors for all supported languages and integrates
them into the ingestion pipeline as the single source of truth for
Property node metadata.
**Generic field extractor factory** — `field-extractors/generic.ts`
defines a `createFieldExtractor(config)` factory that generates
FieldExtractor instances from a per-language `FieldExtractionConfig`.
Each config specifies AST node types, name/type/visibility extraction
functions, and static/readonly detection — typically 20-40 lines per
language vs 300+ for a hand-written extractor.
**Per-language configs** — `field-extractors/configs/` has 11 config
files covering 13 languages (TS/JS share, Java/Kotlin share).
TypeScript keeps its hand-written extractor for richer handling.
**LanguageProvider integration** — New optional `fieldExtractor` property
on LanguageProviderConfig, set via defineLanguage() in each language
file. Follows the same strategy pattern as typeConfig, exportChecker,
and labelOverride. Removed the separate FieldExtractorRegistry class
and field-extractors/index.ts — extractors are accessed via
getProvider(lang).fieldExtractor.
**Pipeline wiring** — Both parse-worker.ts (worker pool) and
parsing-processor.ts (sequential fallback) now call the FieldExtractor
during Property node creation. Results are cached per class node.
Property nodes are enriched with: declaredType, visibility, isStatic,
isReadonly.
**extractPropertyDeclaredType removed** — The 100-line multi-strategy
function in type-extractors/shared.ts is replaced by the FieldExtractor.
All 14 languages register an extractor, eliminating the need for a
generic fallback. The Python config's extractType was fixed to handle
annotation-without-value patterns (address: Address).
**Integration tests** — Each language's resolver test file gains
pipeline-based assertions verifying visibility/isStatic/isReadonly on
Property nodes via getNodesByLabelFull. Tests run through
runPipelineFromRepo with real fixtures — no direct extractor calls.
* fix(type-env): thread enclosingFunctionFinder through scope resolution, unskip Dart ACCESSES test
The type-env's findEnclosingScopeKey had the same Dart sibling problem
as findEnclosingFunction — it walked parents but never found
function_signature because the call lives inside function_body (a
sibling). Instead of hardcoding a function_body check, thread the
provider's enclosingFunctionFinder hook through BuildTypeEnvOptions →
lookupInEnv → findEnclosingScopeKey. All three buildTypeEnv call sites
(call-processor, parsing-processor, parse-worker) now pass the hook.
This enables the type-env to resolve scoped parameter bindings for Dart
(e.g., `user: User` in processUser), which lets the chain-resolution
tier (Step 1c) walk `user.address` and emit ACCESSES edges.
Dart integration test unskipped — 10/10 passing including ACCESSES.
Reverted CHANGELOG.md to origin/main.
* fix: resolve all PR #494 review findings (10 items)
CRITICAL:
- parse-worker.ts: classNode: any → SyntaxNode on getFieldInfo
and findEnclosingClassNode; removed redundant as number casts
- parsing-processor.ts: classNode: any → SyntaxNode on seqGetFieldInfo
HIGH:
- ruby.ts: attr_accessor now extracts ALL symbol arguments via
extractNames hook in generic factory (was firstNamedChild only)
- typescript.ts: added JSDoc explaining why hand-written extractor
coexists with config-based typescript-javascript.ts
MEDIUM:
- field-types.ts: FieldVisibility union type replaces string
('public'|'private'|'protected'|'internal'|'package'|'fileprivate'|'open')
Propagated through field-extractor.ts, generic.ts, all 7 config files
- typescript.ts: extractFullType collapsed from 12 branches to 3 lines
- generic.ts: added extractNames? optional hook + buildField refactor
LOW:
- ruby.ts: extractVisibility(node) → extractVisibility(_node)
- python.ts: fixed misleading isStatic comment
TypeScript compiles cleanly.
* test: add 24 field extraction tests for generic factory + 5 languages
Generic factory (4 tests):
- createFieldExtractor with TypeScript config validates factory itself
- Body discovery for interfaces, static/readonly modifiers
- Non-type node rejection
Python (4 tests):
- Annotated class field extraction
- Underscore-based visibility: _name=protected, __name=private
Go (5 tests):
- isTypeDeclaration on type_declaration nodes
- Config functions: uppercase=public, lowercase=package visibility
- extractType, isStatic, isReadonly
C++ (5 tests):
- public/private/protected access specifier backward-sibling walk
- Default visibility: class=private, struct=public
- static/const modifier detection
Ruby (6 tests):
- attr_accessor multi-symbol: :name, :email, :age → 3 fields
- attr_reader=readonly, attr_writer=non-readonly
- Multiple attr_* calls in one class
Total: 46 tests passing
* chore: remove plan doc from PR
---------
Co-authored-by: Gergo Magyar <gergomagyar@icloud.com>
|
||
|
|
b959b9933b
|
fix(python): resolve two remaining alias gaps (#417) (#505) | ||
|
|
cb1293b718 |
fix(python): route module aliases directly to moduleAliasMap in import processor
`import models as m` aliases were stored in namedImportMap (a symbol-binding map) then cross-referenced in pipeline.ts — semantic misuse and inefficient. Refactored: NamedBinding gains `isModuleAlias` flag. applyImportResult routes tagged bindings directly to moduleAliasMap at import time. Removes the pipeline.ts post-processing loop entirely. Added test fixture and 5 integration tests for `import X as Y` with multi-module disambiguation (both models.py and auth.py export User). |
||
|
|
bbd95457df |
test(python): strengthen module-import tests, un-skip match/case, add perf guard
- Rewrite Issue #337 test suite: 5 tests → 19 tests with exact node/edge counts, sourceFilePath guards, negative tests, method call disambiguation (u.save(), a.login(), v.verify()), HAS_METHOD verification, and cross-module collision assertions - Un-skip 2 match/case as-pattern tests (they pass now) and remove leftover DEBUG test - Add per-chunk language guard for synthesizeWildcardImportBindings — skips full graph traversal for TS/JS-only chunks, avoiding O(chunks × graph_size) - Rename fixture method check → verify to avoid BUILT_IN_NAMES noise filter - Expand fixture with method calls on constructor-inferred receivers - Fix stale comment referencing only "Go package imports" |
||
|
|
f90aabf9a8 |
fix(python): resolve module-qualified calls via moduleAliasMap
Previously, Python was added to WILDCARD_IMPORT_LANGUAGES which expanded all exported symbols into namedImportMap using first-seen wins. This caused `auth.User()` to incorrectly resolve to `models.py:User` when both modules exported a class named User. Root cause: Python `import models` is a namespace import, not wildcard symbol expansion. Expanding all symbols produces ambiguous bindings that cannot be disambiguated later. Fix: - Remove Python from WILDCARD_IMPORT_LANGUAGES - Add ModuleAliasMap (callerFile → alias → sourceFile) to ResolutionContext - In synthesizeWildcardImportBindings, build moduleAliasMap for Python using the filename stem as the module alias - In resolveCallTarget, add module-alias disambiguation step: when multiple candidates survive filtering and the receiver name matches a module alias, narrow candidates to the aliased file Result: `models.User()` → models.py:User, `auth.User()` → auth.py:User even when both modules export a class named User. Adds regression test for the ambiguity case. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
cd1c0ff7dc |
fix(python): resolve module-qualified constructor calls (Issue #337)
Python repos were producing 0 CALLS edges for module-qualified constructor calls like `models.User()` where `import models` is a bare module import. Root causes: 1. `SupportedLanguages.Python` was absent from `WILDCARD_IMPORT_LANGUAGES`, so `synthesizeWildcardImportBindings` never ran for Python files — bare module imports never received per-symbol namedImportMap bindings. 2. Synthesis only ran in the Phase 14 pre-pass, after all chunks had already been call-resolved. When `models.User()` was processed in Phase 3+4, `namedImportMap` was empty for Python → Tier 2a-named fell through to Tier 2a which found both `models.py:User` and `auth.py:User` (ambiguous). 3. `filterCallableCandidates` with `callForm='member'` excluded `Class` nodes (only `CALLABLE_SYMBOL_TYPES` = Function/Method/Constructor/…). With 2 ambiguous Class candidates both were dropped, producing 0 CALLS edges. Fixes: - Add `SupportedLanguages.Python` to `WILDCARD_IMPORT_LANGUAGES` so that `import models` expands to per-symbol namedImportMap entries (first-seen semantics: `User→models.py:User`, `Admin→auth.py:Admin`). - Call `synthesizeWildcardImportBindings` inline in the chunk loop, after `processImportsFromExtracted` but BEFORE `processCallsFromExtracted`. This ensures Tier 2a-named can disambiguate `module.ClassName()` at initial call-resolution time. The Phase 14 pre-pass remains as a final safety net. - Add a fallback in `resolveCallTarget`: if `callForm='member'` yields 0 filtered candidates, retry with `callForm='constructor'`. This handles the case where a module-qualified class instantiation (e.g. `models.User()`) is syntactically an attribute-access call but semantically a constructor call. The fallback only triggers for 0-candidate member calls, so it cannot over-eagerly promote normal member calls. Tests: add `python-module-import` fixture (models.py/auth.py/app.py) with 4 regression tests covering IMPORTS edges, name-collision disambiguation for `models.User()`, and `auth.Admin()`. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
6c972079e0 |
feat(type-resolution): per-language cross-file binding tests + resolver fixes
Add cross-file binding propagation integration tests for 6 languages (Python, JavaScript, Java, PHP, C#, Kotlin) with fixture repos and HAS_METHOD edge assertions. Fix 3 language resolver issues uncovered by tests: - Kotlin: class methods now labeled Method (not Function) via shared isKotlinClassMethod() utility; non-aliased imports create NamedImportMap entries; 2-segment package-directory fallback for top-level function imports - PHP: namespace-directory fallback for use-function imports; SuffixIndex preferred over linear scan; path traversal rejection; PSR-4 sort cached - JVM: root-level package path matching; indexOf→lastIndexOf for correctness Address code review findings: - Extract runCrossFileBindingPropagation() from 810-line pipeline function - Replace (importCtx as any) casts with typed dispose() method - Remove Tarjan's SCC (dev-only YAGNI, ~85 lines) - Remove dead PARALLEL_RE_RESOLUTION_THRESHOLD constant - Fix constructor handling divergence in getLabelFromCaptures - Optimize gap pre-scan with early exit once threshold exceeded - Fix findEnclosingFunction any→SyntaxNode type |
||
|
|
c3a2815186 |
feat(type-resolution): optional parameter arity resolution
Add requiredParameterCount to SymbolDefinition and MethodSignature, enabling range-based arity filtering in filterCallableCandidates. Calls with omitted optional/default arguments now resolve correctly. Supported: TS, Python, Kotlin, C#, C++, PHP, Ruby (7 languages). Detection via OPTIONAL_PARAM_TYPES set + hasDefaultValue helper. 9 integration tests added across all 7 languages. |
||
|
|
e9ccec1a52 |
test(type-resolution): add integration tests for Milestone D across all 11 languages + fix Kotlin null-check narrowing
Adds 17 new fixture directories and 23 new describe blocks covering every
feature in Milestone D (Phases A, B, C) with full cross-language integration
test coverage:
Phase A — Fixpoint Completeness:
- TS/JS object destructuring (const { field } = obj → fieldAccess resolution)
- TS/JS post-fixpoint for-loop replay (iterable var resolved by fixpoint)
- Rust struct_pattern destructuring (let Point { x, y } = p)
Phase B — Inheritance & Receivers:
- Grandparent MRO (depth-2 C→B→A) for all 9 OOP languages:
TS, Kotlin, C#, C++, Java, PHP, Python, Ruby, JS
- Go inc/dec write access (obj.Field++/-- emit ACCESSES write edges)
Phase C — Branch-Sensitive Narrowing:
- Null-check narrowing for TS (!==null, !=null, !==undefined),
C# (!=null, is not null), and Kotlin (!=null)
Bug fix — Kotlin null-check narrowing (3 issues in jvm.ts):
1. patternBindingNodeTypes registered 'comparison_expression' but
tree-sitter-kotlin produces 'equality_expression' for !=
2. Handler checked for 'null_literal' named child but 'null' is an
anonymous node in the Kotlin grammar
3. extractKotlinParameter only searched for 'user_type' direct child,
missing 'nullable_type' wrapper (so x: User? never got a base binding)
17 fixtures, 23 describe blocks, 705 new lines of test code, 0 failures.
|
||
|
|
e6b8edc1ac |
feat: Phase 9C unified fixpoint with field access and method-call-result binding
Replace the sequential Tier 2b/2a propagation with a unified fixpoint loop that handles four binding kinds: callResult, copy, fieldAccess, and methodCallResult. The loop iterates until no new bindings are produced (max 10 iterations), enabling arbitrary-depth mixed chains: const user = getUser(); // callResult → User const addr = user.address; // fieldAccess → Address const city = addr.getCity(); // methodCallResult → City city.save(); // resolves to City#save Infrastructure: - PendingAssignment union extended with fieldAccess and methodCallResult - resolveFieldType helper: typeName → class nodeId → lookupFieldByOwner - resolveMethodReturnType helper: typeName → class nodeId → lookupFuzzyCallable filtered by ownerId - Fixpoint also resolves reverse-order copy chains that single-pass missed Languages: TS, JS, Java, Kotlin, C#, Go, Rust, Python, PHP, Ruby, C++. Each gets field access and/or method-call-with-receiver detection in extractPendingAssignment, plus method-chain-binding test fixtures. |
||
|
|
5769872b70 |
feat: Phase 9 call-result variable binding across 11 languages
Activate the dormant Tier 2b pendingCallResults infrastructure in
type-env.ts by extending each language's extractPendingAssignment to
emit { kind: 'callResult', lhs, callee } when the RHS of an untyped
variable declaration is a simple function call.
This enables `var user = getUser(); user.save()` to resolve at TypeEnv
build time. Tier 2b now runs before Tier 2a copy-propagation, enabling
mixed chains like `const user = getUser(); const alias = user;
alias.save()`.
Languages: TS, JS, Java, Kotlin, C#, Go, Rust, Python, PHP, Ruby, C++.
Swift excluded. Each language gets a call-result-binding test fixture
and integration tests.
Conservative: only simple calls (no method calls with receivers), only
when exactly one callable matches, first-writer-wins.
|
||
|
|
1e19986ef3 | fix(tests): update property edge and write access expectations across multiple language tests | ||
|
|
973c7bfbf0
|
feat: ACCESSES edge type with read/write field access tracking (#372)
* feat: Phase 1 ACCESSES edge type — read tracking from chain resolution Add ACCESSES relationship type to track field read access during call chain resolution. When walkMixedChain resolves a field access (e.g., user.address.save()), an ACCESSES edge with reason 'read' is emitted from the calling function to the Property node. Schema: ACCESSES added to RelationshipType, REL_TYPES, VALID_RELATION_TYPES, context queries, tools/resources descriptions. Excluded from default impact BFS to prevent traversal explosion. Implementation: resolveFieldAccessType now returns FieldResolution with fieldNodeId. walkMixedChain accepts optional onFieldResolved callback. makeAccessEmitter factory provides Set-based dedup per source node. Bug fix: Added Java 'field_access' to FIELD_ACCESS_NODE_TYPES — was missing, causing extractMixedChain to fail for Java member access. * feat: Phase 2 ACCESSES write edges — assignment detection across 12 languages Add tree-sitter query patterns for field write detection (obj.field = value) across all supported languages: TS/JS, Python, Java, Go, C++, C#, Rust, PHP, Ruby (setter syntax), Kotlin, Swift. Processing: Sequential path handles assignment captures inline. Worker path extracts ExtractedAssignment data for deferred resolution via new processAssignmentsFromExtracted function. Bug fix: Kotlin/Swift assignment queries used invalid navigation_expression wrapper — fixed to match actual directly_assignable_expression AST structure. Tests: Write access integration tests for TS, Java, Python, Go with dedicated fixtures. All use strict toBe() assertions. * test: add unit tests for call-routing, shared type extractors, and symbol-table branches Add 215 new unit tests across 3 files to increase branch coverage toward the 23% global threshold (was 21.49%): - call-routing.test.ts (49 tests): Ruby call routing — require/require_relative, include/extend/prepend heritage, attr_accessor properties with YARD types - shared-type-extractors.test.ts (108 tests): pure string functions — extractElementTypeFromString, stripNullable, extractReturnTypeName, methodToTypeArgPosition, getContainerDescriptor - symbol-table.test.ts (+29 tests): Property/fieldByOwner index, metadata spread branches, lazy callable index, lookupExactFull shape * fix: defer write-access resolution to fix Ruby cross-file property timing Ruby attr_accessor properties are registered during processCalls (not the parsing phase), so lookupFieldByOwner fails when service.rb is processed before models.rb. Fix by collecting pending write-access edges during the file loop and resolving them after all files are done. Also adds write-access integration tests and fixtures for 7 languages (C++, C#, JS, Kotlin, PHP, Ruby, Rust), Ruby compound assignment query, PHP static property write query, and Kotlin property type extraction. * fix: address PR #372 review — write-access constructor bindings parity and docs - Add verified constructor bindings fallback to write-access resolution in both sequential path (receiverIndex lookup) and worker path (constructorBindings param for processAssignmentsFromExtracted), closing the read/write ACCESSES edge asymmetry for factory-returned receivers - Clarify inner guard control flow comment in processCalls match loop - Document Go inc_statement/dec_statement gap in roadmap - Clarify PHP nullsafe write footnote (invalid syntax, not just untracked) - Update symbol-table tests for intentional fieldByOwner behavior change (Properties without declaredType now indexed for dynamic language write-access tracking) |
||
|
|
11a3d0515c
|
feat: Phase 8 field/property type resolution (#354)
* feat: Phase 8 field/property type resolution — resolve chained member access
Add field/property type extraction to the type resolution system so that
chained member access like `user.address.save()` resolves the intermediate
receiver type (`address → Address`) through Property symbols in SymbolTable.
Key changes:
- SymbolTable: add `declaredType` field, `fieldByOwner` O(1) index,
`lookupFieldByOwner()` method, P0 conditional callableIndex invalidation,
P2 exclude Properties from globalIndex to prevent namespace pollution
- tree-sitter queries: add `definition.property` for TypeScript, Java, Go
- parse-worker: extract declared types for Property nodes via
`extractPropertyDeclaredType()`, capture field-access receiver info
- call-processor: add `resolveFieldAccessType()` helper and field-access
branch in both sequential and worker receiver resolution paths
- Integration tests: new field-types test suite verifying end-to-end
`user.address.save() → Address#save` resolution
* fix: Go tree-sitter query captures field_declaration not field_declaration_list
Post-review fix: the Go struct field query incorrectly put @definition.property
on field_declaration_list (the list container) instead of field_declaration
(the individual field). Also removed unused `language` parameter from
extractPropertyDeclaredType.
* feat: expand field-type tests to 6 languages, fix Go ownerId and Kotlin navigation_expression
- Add integration test fixtures for Java, C#, Go, Kotlin, PHP (alongside existing TS)
- Fix Go: add type_declaration handling in findEnclosingClassId for struct fields
(field_declaration → field_declaration_list → struct_type → type_spec → type_declaration)
- Fix Kotlin: add navigation_expression handling in field-access resolution
(Kotlin uses navigation_expression + navigation_suffix, not member_expression)
- Add extractMemberAccessParts helper in call-processor for cross-language member access
- All 24 field-type tests pass across 6 languages, 181 Go+Kotlin tests pass with no regressions
* refactor: split HAS_METHOD into HAS_METHOD + HAS_PROPERTY edge types
Property nodes now use HAS_PROPERTY edges instead of HAS_METHOD, giving
the graph schema proper semantic separation between methods and fields.
- HAS_METHOD: Method, Constructor, Function (when inside a class)
- HAS_PROPERTY: Property nodes (class fields, struct fields, attributes)
MRO processor only reads HAS_METHOD — properties correctly excluded from
method resolution order. Impact analysis accepts both edge types.
Updated 12 files: graph types, schema, tools docs, parse-worker,
parsing-processor, call-processor, and 6 test files.
* fix(test): update security test to expect 7 VALID_RELATION_TYPES (added HAS_PROPERTY)
* test: add unit tests for Phase 8 SymbolTable features (39 tests, up from 19)
Cover all new branches: declaredType metadata, Property exclusion from
globalIndex, conditional callableIndex invalidation, lookupFieldByOwner
(happy path + edge cases), lookupFuzzyCallable filtering, and clear()
with fieldByOwner. Fixes branch coverage threshold (21.8% → 23%+).
* feat: Phase 8B mixed field+method chain resolution, C++/Rust chain fixes
Unify field and method chain resolution into a single `extractMixedChain`
walker that handles interleaved patterns like `svc.getUser().address.save()`.
Fix C++ chain calls (tree-sitter-cpp `field_expression` uses `argument` not
`object`), Rust unit struct instantiation (`let svc = TypeName;`), and add
stdlib passthrough for `unwrap()`/`clone()`/`expect()` in chain loops.
Key changes:
- Replace `receiverCallChain` + `receiverFieldAccess` with unified
`receiverMixedChain: MixedChainStep[]` on ExtractedCall
- Add `extractMixedChain` in utils.ts (handles both call_expression and
field_expression nodes, including C++ `argument` field)
- Add `TYPE_PRESERVING_METHODS` set for stdlib identity operations
- Add C++ inline method double-indexing guard in parsing-processor.ts
and parse-worker.ts
- Add Rust unit struct recognition in type-extractors/rust.ts
- Split field-types.test.ts into per-language test files
- Add ts-mixed-chain fixture and integration tests
- Resolve rust.test.ts todo: Option<T>.unwrap().save() now works
- Update roadmap: Phases 7+8 complete, Phase 9 is next
* fix: Python declaredType extraction and sequential-path property registration
- Move @definition.property capture from expression_statement to assignment
node in Python queries so Strategy 1 childForFieldName('type') succeeds
- Pass item.declaredType through ctx.symbols.add in sequential call-processor
path, matching worker path behavior (fixes Ruby YARD declaredType drop)
- Add Python chain resolution integration test (user.address.save → Address#save)
- Update Rust/Python status in roadmap and system docs to reflect actual coverage
* fix: Python/Ruby field type disambiguation and Rust chain test
Three fixes from PR #354 third review:
1. Python typed_parameter name extraction: tree-sitter-python's
typed_parameter uses positional children for the name, not a named
field. TypeEnv and extractParameter now fall back to firstNamedChild.
2. Ruby/Python call-step field resolution: Ruby's AST uses `call` nodes
for both property access and method calls. The chain walker now tries
resolveFieldAccessType before resolveCallTarget for call steps, so
attr_accessor properties resolve via declaredType.
3. Rust chain resolution test: added missing integration test asserting
user.address.save() resolves to Address#save.
Also splits C/C++ and TS/JS columns in type-resolution-system.md
language matrix with footnotes for accuracy.
1062 resolver integration tests passing, 0 failures.
* refactor: Phase 8 code review cleanup — extract walkMixedChain, fix MCP agent gaps
- Extract duplicated chain resolution loop into shared walkMixedChain() helper,
eliminating ~60 lines of copy-pasted code between sequential and worker paths
- Add returnType to ResolveResult, removing redundant lookupFuzzy+find per chain step
- Fix context() tool to include HAS_METHOD, HAS_PROPERTY, OVERRIDES in queries
so agents can discover class members
- Fix p.declaredType Cypher example (column doesn't exist) → p.description
- Add HAS_METHOD, HAS_PROPERTY, OVERRIDES to schema resource
- Document HAS_METHOD/HAS_PROPERTY in impact tool description
- Delete dead code extractMemberAccessParts (superseded by extractMixedChain)
- Replace any with SyntaxNode on extractPropertyDeclaredType
- Add Rust deep-field-chain test (5 tests), Java mixed-chain (4), Go mixed-chain (4)
- All 1075 tests pass (13 new, 0 regressions)
* refactor: type SymbolDefinition.type as NodeLabel, add O(1) receiver index
- Change SymbolDefinition.type from string to NodeLabel union (35 members)
across symbol-table.ts, parse-worker.ts, parsing-processor.ts — compiler
now enforces correctness at all comparison/assignment sites
- Replace O(N*M) linear scan in lookupReceiverType with pre-built
ReceiverTypeIndex (Map<funcName, Map<varName, Entry>>) for O(1) lookups
with proper ambiguity handling and file-level fallback
- All 1075 tests pass, 0 regressions
* fix: capture C++ pointer/ref fields, Kotlin data class props, PHP constructor promotion
Add tree-sitter query patterns for three previously missed property declaration
forms: C++ pointer/reference member fields (Address* addr; Address& ref;),
Kotlin primary constructor val/var parameters (data class User(val name: String)),
and PHP 8.0+ constructor property promotion (public Address $address).
Fix "10 languages" off-by-one in docs (Ruby is single-level only, not deep chain).
Update Python feature matrix cell from No* to Yes* after
|
||
|
|
aa1bab597b
|
feat: add Python enumerate() for-loop support with nested tuple patterns (#356)
- Handle `for i, k, v in enumerate(d.items())` — flat pattern - Handle `for i, (k, v) in enumerate(d.items())` — nested tuple_pattern - Handle `for (k, v) in enumerate(users)` — parenthesized tuple as top-level Extract helper functions for cleaner code: - `extractMethodCall()` — deduplicate method call parsing - `collectPatternIdentifiers()` — recursively collect identifiers from patterns Add unit tests for TypeEnv and integration tests verifying CALLS edges. Made-with: Cursor Co-authored-by: chirag-nighut <chiragnighut@gmail.com> |
||
|
|
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
|
||
|
|
f6b92d4f13
|
fix(resolver): fix for same-directory python imports (#328)
* fix(resolver): prefer same-directory file for Python bare imports
Python's sys.path searches the importing script's own directory first,
so `import user` from services/auth.py should resolve to services/user.py
even if models/user.py was indexed first in the suffix index.
Add a proximity check in resolveImportPath that consults the existing
dirMap index (O(1)) before falling back to global suffix matching, for
single-segment bare Python imports only.
Made-with: Cursor
* refactor(resolver): replace dirMap scan with O(1) allFiles.has() for proximity check
The previous implementation used index.getFilesInDir() + siblings.find()
which had two issues:
- dirMap stores all suffix levels, so getFilesInDir('services') matched
files from every directory named 'services/' across the repo — false
positives in monorepos
- siblings.find() was an O(n) linear scan despite the O(1) claim
Replace with a direct allFiles.has(importerDir + '/' + name + '.py') lookup.
allFiles is a Set<string> of full repo-relative paths, so the lookup is
truly O(1) and exact — no suffix ambiguity possible.
Also fixes: dead code (the '.rb' branch was unreachable since the outer if
gates on Python), and Windows backslash handling via normalize before split.
Made-with: Cursor
* test: remove flag-based demo from unit tests
Made-with: Cursor
* fix(resolver): cover package __init__.py in proximity check and add end-to-end CALLS test
- Also try importerDir/name/__init__.py as a second O(1) candidate so that
`import user` resolves to services/user/__init__.py when the target is a
package rather than a bare module file
- Add unit tests for package proximity, __init__.py fallback, and Windows
backslash path handling
- Add end-to-end CALLS assertion to the bare-import integration test:
svc.execute() must resolve to UserService#execute in services/user.py,
proving the fix propagates correctly through the type inference pipeline
Made-with: Cursor
* refactor: extract Python import resolution into resolvers/python.ts
- Move PEP 328 relative import and proximity-based bare import logic
from standard.ts into a dedicated resolvers/python.ts (resolvePythonImport)
- Dispatch Python imports from resolveLanguageImport in import-processor.ts,
consistent with how Ruby, PHP, and other languages are handled
- standard.ts is now language-agnostic (TS/JS aliases, Rust paths, suffix fallback)
- Add inline comment on __init__.py vs .py resolution order edge case
- Update unit tests to call resolvePythonImport directly
Made-with: Cursor
* docs: add PEP 302/328/451 references to python.ts comments
Made-with: Cursor
* fix(python): address reviewer comments on PEP compliance
- Guard dirParts.pop() against over-traversal: return null when dot
count exceeds directory depth, matching CPython's ImportError for
'attempted relative import beyond top-level package' (PEP 328)
- Swap __init__.py / .py check order to match CPython's finder
precedence (PEP 451 §4); coexistence is physically impossible so
order only matters for spec compliance
- Fix overstated PEP 302 comment: proximity check is a static
heuristic, not a sys.path[0] implementation
- Acknowledge namespace package gap (PEP 420) in docstring
- Add unit test for over-traversal guard
Made-with: Cursor
* test(python): document namespace package resolution behaviour
Add two unit tests for PEP 420 namespace packages (directory with no
__init__.py): bare import returns null (expected — no file exists to
resolve to, CPython sets __file__ = None), while the submodule form
(import user.model) resolves correctly via suffixResolve fallback.
Made-with: Cursor
---------
Co-authored-by: chirag-nighut <chiragnighut@gmail.com>
|
||
|
|
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() |
||
|
|
5fa73bafdf
|
feat: Phase 4 type resolution — nullable unwrapping, for-loop typing, assignment chains, code review fixes (#310)
* feat: Phase 4 type resolution — nullable unwrapping, for-loop typing, assignment chains, Kotlin return types
Phase 4.1: Nullable/optional chain unwrapping
- Add stripNullable utility in shared.ts for stripping nullable wrappers
- Apply in lookupInEnv to unwrap User | null → User, User? → User before receiver lookup
- Handles TS union, Kotlin/C#/Swift nullable suffix, Python Union[T, None], Rust Option<T>
- Enables receiver-type disambiguation through ?. optional chaining
Phase 4.2: For-loop element typing (Tier 0 — Java/C#/Kotlin)
- Add ForLoopExtractor type and forLoopNodeTypes to LanguageTypeConfig
- Java enhanced_for_statement, C# foreach_statement, Kotlin for_statement extractors
- Only explicit element types in AST (Tier 0); inference-based languages deferred
Phase 4.3: Assignment chain propagation (single-pass, depth-1)
- Add PendingAssignmentExtractor to LanguageTypeConfig with per-language implementations
- Handles TS/JS variable_declarator, Rust let_declaration, Python assignment,
Go short_var_declaration, C# equals_value_clause, Java/Kotlin variable_declarator
- Single post-walk propagation pass (no fixpoint iteration per Sorbet/Pyright design)
- Resolves const b = a; b.save() when a has known type from Tier 0/1/1b
Phase 4.5: Kotlin return type extraction (bug fix)
- Fix extractMethodSignature to handle Kotlin user_type after function_value_parameters
- Remove lenient test assertions, add strict disambiguation proof
Integration tests across 10+ languages with competing same-name methods
and negative assertions proving disambiguation.
* fix: per-language assignment chain gaps from code review
- Kotlin: new extractKotlinPendingAssignment for property_declaration →
variable_declaration AST (Java's variable_declarator doesn't exist in Kotlin)
- Go: handle var_spec (var b = u) alongside short_var_declaration (:=)
- PHP: add extractPendingAssignment for $alias = $user with $ prefix preserved
Integration tests added for all three languages with competing
same-name methods and negative disambiguation assertions.
* fix: code review fixes — DRY nullable keywords, avoid array allocations, clarify depth comment
Addresses findings from 6-agent code review on PR #310:
- Move stripNullable JSDoc to correct position (was orphaned above NULLABLE_KEYWORDS)
- DRY: reuse NULLABLE_KEYWORDS set in pipe-split filter instead of inline strings
- Replace node.children.find() with findChildByType/manual loops in jvm.ts,
go.ts, csharp.ts to avoid unnecessary array allocations per tree-sitter call
- Clarify "depth-1" comment in type-env.ts: single-pass resolves multi-hop
chains when forward-declared; reverse-order is depth-1 only
- Annotate extractGenericTypeArgs as Phase 5 infrastructure (zero production callers)
- Re-export PendingAssignmentExtractor from index.ts for API consistency
- Add explicit return undefined in Go extractPendingAssignment
- Remove redundant child.text === '=' check in Kotlin extractor
Test coverage:
- 20 new unit tests: stripNullable edge cases, per-language assignment chains,
reverse-order depth limitation, nullable lookup resolution
- 15 new integration tests: multi-hop chains (a→b→c), nullable+chain combined
(User|null + alias), Python User|None through stripNullable path
- 3 new fixtures: ts-multi-hop-chain, ts-nullable-chain, python-nullable-chain
* fix: third-pass review — walrus chain, scanner allocations, Kotlin variable_declaration, C# type guard
Addresses 4 new findings from third-pass CI review:
1. Python walrus operator (:=) now handled by extractPendingAssignment —
named_expression nodes propagate alias chains alongside regular assignment
2. Scanner .namedChildren.find()/.some() in jvm.ts replaced with
findChildByType() — consistent with
|
||
|
|
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.
|