gitnexus 1.6.3-rc.8 → 1.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -5
- package/dist/_shared/graph/types.d.ts +16 -0
- package/dist/_shared/graph/types.d.ts.map +1 -1
- package/dist/_shared/index.d.ts +20 -2
- package/dist/_shared/index.d.ts.map +1 -1
- package/dist/_shared/index.js +11 -0
- package/dist/_shared/index.js.map +1 -1
- package/dist/_shared/scope-resolution/def-index.js +2 -2
- package/dist/_shared/scope-resolution/def-index.js.map +1 -1
- package/dist/_shared/scope-resolution/method-dispatch-index.d.ts +8 -0
- package/dist/_shared/scope-resolution/method-dispatch-index.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/method-dispatch-index.js +2 -2
- package/dist/_shared/scope-resolution/method-dispatch-index.js.map +1 -1
- package/dist/_shared/scope-resolution/module-scope-index.d.ts +8 -0
- package/dist/_shared/scope-resolution/module-scope-index.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/module-scope-index.js +10 -2
- package/dist/_shared/scope-resolution/module-scope-index.js.map +1 -1
- package/dist/_shared/scope-resolution/parsed-file.d.ts +76 -0
- package/dist/_shared/scope-resolution/parsed-file.d.ts.map +1 -0
- package/dist/_shared/scope-resolution/parsed-file.js +54 -0
- package/dist/_shared/scope-resolution/parsed-file.js.map +1 -0
- package/dist/_shared/scope-resolution/position-index.d.ts +12 -0
- package/dist/_shared/scope-resolution/position-index.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/position-index.js +2 -2
- package/dist/_shared/scope-resolution/position-index.js.map +1 -1
- package/dist/_shared/scope-resolution/qualified-name-index.js +2 -2
- package/dist/_shared/scope-resolution/qualified-name-index.js.map +1 -1
- package/dist/_shared/scope-resolution/reference-site.d.ts +75 -0
- package/dist/_shared/scope-resolution/reference-site.d.ts.map +1 -0
- package/dist/_shared/scope-resolution/reference-site.js +24 -0
- package/dist/_shared/scope-resolution/reference-site.js.map +1 -0
- package/dist/_shared/scope-resolution/registries/class-registry.d.ts +27 -0
- package/dist/_shared/scope-resolution/registries/class-registry.d.ts.map +1 -0
- package/dist/_shared/scope-resolution/registries/class-registry.js +30 -0
- package/dist/_shared/scope-resolution/registries/class-registry.js.map +1 -0
- package/dist/_shared/scope-resolution/registries/context.d.ts +69 -0
- package/dist/_shared/scope-resolution/registries/context.d.ts.map +1 -0
- package/dist/_shared/scope-resolution/registries/context.js +44 -0
- package/dist/_shared/scope-resolution/registries/context.js.map +1 -0
- package/dist/_shared/scope-resolution/registries/evidence.d.ts +56 -0
- package/dist/_shared/scope-resolution/registries/evidence.d.ts.map +1 -0
- package/dist/_shared/scope-resolution/registries/evidence.js +150 -0
- package/dist/_shared/scope-resolution/registries/evidence.js.map +1 -0
- package/dist/_shared/scope-resolution/registries/field-registry.d.ts +26 -0
- package/dist/_shared/scope-resolution/registries/field-registry.d.ts.map +1 -0
- package/dist/_shared/scope-resolution/registries/field-registry.js +31 -0
- package/dist/_shared/scope-resolution/registries/field-registry.js.map +1 -0
- package/dist/_shared/scope-resolution/registries/lookup-core.d.ts +81 -0
- package/dist/_shared/scope-resolution/registries/lookup-core.d.ts.map +1 -0
- package/dist/_shared/scope-resolution/registries/lookup-core.js +332 -0
- package/dist/_shared/scope-resolution/registries/lookup-core.js.map +1 -0
- package/dist/_shared/scope-resolution/registries/lookup-qualified.d.ts +33 -0
- package/dist/_shared/scope-resolution/registries/lookup-qualified.d.ts.map +1 -0
- package/dist/_shared/scope-resolution/registries/lookup-qualified.js +56 -0
- package/dist/_shared/scope-resolution/registries/lookup-qualified.js.map +1 -0
- package/dist/_shared/scope-resolution/registries/method-registry.d.ts +36 -0
- package/dist/_shared/scope-resolution/registries/method-registry.d.ts.map +1 -0
- package/dist/_shared/scope-resolution/registries/method-registry.js +32 -0
- package/dist/_shared/scope-resolution/registries/method-registry.js.map +1 -0
- package/dist/_shared/scope-resolution/registries/tie-breaks.d.ts +43 -0
- package/dist/_shared/scope-resolution/registries/tie-breaks.d.ts.map +1 -0
- package/dist/_shared/scope-resolution/registries/tie-breaks.js +60 -0
- package/dist/_shared/scope-resolution/registries/tie-breaks.js.map +1 -0
- package/dist/_shared/scope-resolution/resolve-type-ref.d.ts +1 -10
- package/dist/_shared/scope-resolution/resolve-type-ref.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/resolve-type-ref.js +6 -0
- package/dist/_shared/scope-resolution/resolve-type-ref.js.map +1 -1
- package/dist/_shared/scope-resolution/scope-tree.d.ts +4 -4
- package/dist/_shared/scope-resolution/scope-tree.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/scope-tree.js +3 -2
- package/dist/_shared/scope-resolution/scope-tree.js.map +1 -1
- package/dist/_shared/scope-resolution/shadow/aggregate.d.ts +6 -2
- package/dist/_shared/scope-resolution/shadow/aggregate.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/shadow/aggregate.js +5 -0
- package/dist/_shared/scope-resolution/shadow/aggregate.js.map +1 -1
- package/dist/_shared/scope-resolution/types.d.ts +11 -0
- package/dist/_shared/scope-resolution/types.d.ts.map +1 -1
- package/dist/cli/ai-context.js +35 -4
- package/dist/cli/analyze.d.ts +27 -0
- package/dist/cli/analyze.js +31 -1
- package/dist/cli/clean.js +19 -1
- package/dist/cli/group.js +73 -0
- package/dist/cli/index-repo.js +8 -1
- package/dist/cli/index.js +26 -1
- package/dist/cli/list.js +11 -1
- package/dist/cli/remove.d.ts +30 -0
- package/dist/cli/remove.js +99 -0
- package/dist/cli/setup.js +185 -57
- package/dist/cli/tool.d.ts +5 -0
- package/dist/cli/tool.js +42 -0
- package/dist/config/ignore-service.d.ts +9 -0
- package/dist/config/ignore-service.js +80 -13
- package/dist/core/embedding-mode.d.ts +30 -0
- package/dist/core/embedding-mode.js +30 -0
- package/dist/core/embeddings/ast-utils.js +22 -22
- package/dist/core/embeddings/chunker.js +30 -25
- package/dist/core/embeddings/embedding-pipeline.d.ts +6 -0
- package/dist/core/embeddings/embedding-pipeline.js +15 -6
- package/dist/core/embeddings/text-generator.d.ts +1 -1
- package/dist/core/embeddings/text-generator.js +33 -24
- package/dist/core/embeddings/types.d.ts +43 -1
- package/dist/core/embeddings/types.js +101 -29
- package/dist/core/git-staleness.d.ts +18 -0
- package/dist/core/git-staleness.js +108 -0
- package/dist/core/graph/graph.js +115 -20
- package/dist/core/graph/types.d.ts +12 -1
- package/dist/core/group/config-parser.d.ts +4 -0
- package/dist/core/group/config-parser.js +18 -1
- package/dist/core/group/cross-impact.d.ts +41 -0
- package/dist/core/group/cross-impact.js +441 -0
- package/dist/core/group/extractors/http-patterns/php.js +126 -18
- package/dist/core/group/group-path-utils.d.ts +17 -0
- package/dist/core/group/group-path-utils.js +40 -0
- package/dist/core/group/resolve-at-member.d.ts +10 -0
- package/dist/core/group/resolve-at-member.js +31 -0
- package/dist/core/group/service.d.ts +9 -0
- package/dist/core/group/service.js +259 -25
- package/dist/core/group/types.d.ts +30 -0
- package/dist/core/ingestion/ast-cache.d.ts +16 -1
- package/dist/core/ingestion/ast-cache.js +14 -2
- package/dist/core/ingestion/call-processor.js +9 -0
- package/dist/core/ingestion/emit-references.d.ts +88 -0
- package/dist/core/ingestion/emit-references.js +229 -0
- package/dist/core/ingestion/filesystem-walker.js +6 -4
- package/dist/core/ingestion/finalize-orchestrator.d.ts +63 -0
- package/dist/core/ingestion/finalize-orchestrator.js +139 -0
- package/dist/core/ingestion/framework-detection.js +6 -2
- package/dist/core/ingestion/import-processor.js +4 -0
- package/dist/core/ingestion/import-resolvers/python.js +9 -6
- package/dist/core/ingestion/import-target-adapter.d.ts +73 -0
- package/dist/core/ingestion/import-target-adapter.js +95 -0
- package/dist/core/ingestion/language-provider.d.ts +36 -33
- package/dist/core/ingestion/languages/csharp/accessor-unwrap.d.ts +21 -0
- package/dist/core/ingestion/languages/csharp/accessor-unwrap.js +56 -0
- package/dist/core/ingestion/languages/csharp/arity-metadata.d.ts +26 -0
- package/dist/core/ingestion/languages/csharp/arity-metadata.js +46 -0
- package/dist/core/ingestion/languages/csharp/arity.d.ts +23 -0
- package/dist/core/ingestion/languages/csharp/arity.js +37 -0
- package/dist/core/ingestion/languages/csharp/cache-stats.d.ts +15 -0
- package/dist/core/ingestion/languages/csharp/cache-stats.js +26 -0
- package/dist/core/ingestion/languages/csharp/captures.d.ts +19 -0
- package/dist/core/ingestion/languages/csharp/captures.js +249 -0
- package/dist/core/ingestion/languages/csharp/import-decomposer.d.ts +19 -0
- package/dist/core/ingestion/languages/csharp/import-decomposer.js +93 -0
- package/dist/core/ingestion/languages/csharp/import-target.d.ts +25 -0
- package/dist/core/ingestion/languages/csharp/import-target.js +123 -0
- package/dist/core/ingestion/languages/csharp/index.d.ts +82 -0
- package/dist/core/ingestion/languages/csharp/index.js +82 -0
- package/dist/core/ingestion/languages/csharp/interpret.d.ts +15 -0
- package/dist/core/ingestion/languages/csharp/interpret.js +132 -0
- package/dist/core/ingestion/languages/csharp/merge-bindings.d.ts +27 -0
- package/dist/core/ingestion/languages/csharp/merge-bindings.js +55 -0
- package/dist/core/ingestion/languages/csharp/namespace-siblings.d.ts +50 -0
- package/dist/core/ingestion/languages/csharp/namespace-siblings.js +374 -0
- package/dist/core/ingestion/languages/csharp/query.d.ts +35 -0
- package/dist/core/ingestion/languages/csharp/query.js +515 -0
- package/dist/core/ingestion/languages/csharp/receiver-binding.d.ts +31 -0
- package/dist/core/ingestion/languages/csharp/receiver-binding.js +135 -0
- package/dist/core/ingestion/languages/csharp/scope-resolver.d.ts +10 -0
- package/dist/core/ingestion/languages/csharp/scope-resolver.js +63 -0
- package/dist/core/ingestion/languages/csharp/simple-hooks.d.ts +53 -0
- package/dist/core/ingestion/languages/csharp/simple-hooks.js +76 -0
- package/dist/core/ingestion/languages/csharp.js +14 -0
- package/dist/core/ingestion/languages/python/arity-metadata.d.ts +24 -0
- package/dist/core/ingestion/languages/python/arity-metadata.js +45 -0
- package/dist/core/ingestion/languages/python/arity.d.ts +22 -0
- package/dist/core/ingestion/languages/python/arity.js +38 -0
- package/dist/core/ingestion/languages/python/cache-stats.d.ts +17 -0
- package/dist/core/ingestion/languages/python/cache-stats.js +28 -0
- package/dist/core/ingestion/languages/python/captures.d.ts +19 -0
- package/dist/core/ingestion/languages/python/captures.js +106 -0
- package/dist/core/ingestion/languages/python/import-decomposer.d.ts +15 -0
- package/dist/core/ingestion/languages/python/import-decomposer.js +112 -0
- package/dist/core/ingestion/languages/python/import-target.d.ts +21 -0
- package/dist/core/ingestion/languages/python/import-target.js +99 -0
- package/dist/core/ingestion/languages/python/index.d.ts +80 -0
- package/dist/core/ingestion/languages/python/index.js +80 -0
- package/dist/core/ingestion/languages/python/interpret.d.ts +15 -0
- package/dist/core/ingestion/languages/python/interpret.js +191 -0
- package/dist/core/ingestion/languages/python/merge-bindings.d.ts +16 -0
- package/dist/core/ingestion/languages/python/merge-bindings.js +44 -0
- package/dist/core/ingestion/languages/python/query.d.ts +9 -0
- package/dist/core/ingestion/languages/python/query.js +267 -0
- package/dist/core/ingestion/languages/python/receiver-binding.d.ts +21 -0
- package/dist/core/ingestion/languages/python/receiver-binding.js +116 -0
- package/dist/core/ingestion/languages/python/scope-resolver.d.ts +16 -0
- package/dist/core/ingestion/languages/python/scope-resolver.js +53 -0
- package/dist/core/ingestion/languages/python/simple-hooks.d.ts +23 -0
- package/dist/core/ingestion/languages/python/simple-hooks.js +35 -0
- package/dist/core/ingestion/languages/python.js +14 -0
- package/dist/core/ingestion/model/method-registry.d.ts +9 -0
- package/dist/core/ingestion/model/method-registry.js +4 -0
- package/dist/core/ingestion/model/scope-resolution-indexes.d.ts +59 -0
- package/dist/core/ingestion/model/scope-resolution-indexes.js +42 -0
- package/dist/core/ingestion/model/semantic-model.d.ts +64 -0
- package/dist/core/ingestion/model/semantic-model.js +55 -0
- package/dist/core/ingestion/mro-processor.js +38 -22
- package/dist/core/ingestion/parsing-processor.d.ts +18 -1
- package/dist/core/ingestion/parsing-processor.js +45 -11
- package/dist/core/ingestion/pipeline-phases/index.d.ts +1 -0
- package/dist/core/ingestion/pipeline-phases/index.js +1 -0
- package/dist/core/ingestion/pipeline-phases/parse-impl.d.ts +10 -0
- package/dist/core/ingestion/pipeline-phases/parse-impl.js +17 -2
- package/dist/core/ingestion/pipeline-phases/parse.d.ts +18 -0
- package/dist/core/ingestion/pipeline.js +2 -1
- package/dist/core/ingestion/registry-primary-flag.d.ts +86 -0
- package/dist/core/ingestion/registry-primary-flag.js +111 -0
- package/dist/core/ingestion/resolve-references.d.ts +63 -0
- package/dist/core/ingestion/resolve-references.js +175 -0
- package/dist/core/ingestion/scope-extractor-bridge.d.ts +32 -0
- package/dist/core/ingestion/scope-extractor-bridge.js +44 -0
- package/dist/core/ingestion/scope-extractor.d.ts +86 -0
- package/dist/core/ingestion/scope-extractor.js +758 -0
- package/dist/core/ingestion/scope-resolution/contract/scope-resolver.d.ts +372 -0
- package/dist/core/ingestion/scope-resolution/contract/scope-resolver.js +212 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/edges.d.ts +43 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/edges.js +79 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/ids.d.ts +57 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/ids.js +112 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/imports-to-edges.d.ts +17 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/imports-to-edges.js +46 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/method-dispatch.d.ts +19 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/method-dispatch.js +30 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/node-lookup.d.ts +37 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/node-lookup.js +113 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.d.ts +38 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.js +73 -0
- package/dist/core/ingestion/scope-resolution/passes/compound-receiver.d.ts +42 -0
- package/dist/core/ingestion/scope-resolution/passes/compound-receiver.js +198 -0
- package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.d.ts +27 -0
- package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.js +131 -0
- package/dist/core/ingestion/scope-resolution/passes/imported-return-types.d.ts +48 -0
- package/dist/core/ingestion/scope-resolution/passes/imported-return-types.js +130 -0
- package/dist/core/ingestion/scope-resolution/passes/mro.d.ts +42 -0
- package/dist/core/ingestion/scope-resolution/passes/mro.js +99 -0
- package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.d.ts +26 -0
- package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.js +61 -0
- package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.d.ts +46 -0
- package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +327 -0
- package/dist/core/ingestion/scope-resolution/pipeline/phase.d.ts +47 -0
- package/dist/core/ingestion/scope-resolution/pipeline/phase.js +130 -0
- package/dist/core/ingestion/scope-resolution/pipeline/reconcile-ownership.d.ts +68 -0
- package/dist/core/ingestion/scope-resolution/pipeline/reconcile-ownership.js +125 -0
- package/dist/core/ingestion/scope-resolution/pipeline/registry.d.ts +17 -0
- package/dist/core/ingestion/scope-resolution/pipeline/registry.js +21 -0
- package/dist/core/ingestion/scope-resolution/pipeline/run.d.ts +66 -0
- package/dist/core/ingestion/scope-resolution/pipeline/run.js +157 -0
- package/dist/core/ingestion/scope-resolution/scope/namespace-targets.d.ts +36 -0
- package/dist/core/ingestion/scope-resolution/scope/namespace-targets.js +52 -0
- package/dist/core/ingestion/scope-resolution/scope/walkers.d.ts +127 -0
- package/dist/core/ingestion/scope-resolution/scope/walkers.js +349 -0
- package/dist/core/ingestion/scope-resolution/workspace-index.d.ts +52 -0
- package/dist/core/ingestion/scope-resolution/workspace-index.js +61 -0
- package/dist/core/ingestion/shadow-harness.d.ts +113 -0
- package/dist/core/ingestion/shadow-harness.js +148 -0
- package/dist/core/ingestion/utils/ast-helpers.d.ts +19 -1
- package/dist/core/ingestion/utils/ast-helpers.js +70 -0
- package/dist/core/ingestion/utils/max-file-size.d.ts +20 -0
- package/dist/core/ingestion/utils/max-file-size.js +52 -0
- package/dist/core/ingestion/workers/parse-worker.d.ts +9 -0
- package/dist/core/ingestion/workers/parse-worker.js +57 -21
- package/dist/core/lbug/lbug-adapter.d.ts +22 -2
- package/dist/core/lbug/lbug-adapter.js +58 -14
- package/dist/core/lbug/pool-adapter.d.ts +17 -0
- package/dist/core/lbug/pool-adapter.js +24 -14
- package/dist/core/run-analyze.d.ts +32 -0
- package/dist/core/run-analyze.js +74 -19
- package/dist/core/search/bm25-index.d.ts +18 -0
- package/dist/core/search/bm25-index.js +125 -12
- package/dist/core/tree-sitter/parser-loader.js +6 -1
- package/dist/mcp/local/local-backend.d.ts +67 -3
- package/dist/mcp/local/local-backend.js +296 -34
- package/dist/mcp/resources.d.ts +31 -0
- package/dist/mcp/resources.js +100 -17
- package/dist/mcp/tools.d.ts +4 -1
- package/dist/mcp/tools.js +75 -54
- package/dist/server/api.js +6 -2
- package/dist/storage/git.d.ts +49 -0
- package/dist/storage/git.js +111 -0
- package/dist/storage/repo-manager.d.ts +246 -1
- package/dist/storage/repo-manager.js +391 -9
- package/package.json +7 -6
- package/scripts/bench-scope-resolution.ts +134 -0
- package/scripts/ci-list-migrated-languages.ts +24 -0
- package/skills/gitnexus-cli.md +1 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve a compound-receiver expression's TYPE — `user.address.save()`,
|
|
3
|
+
* `svc.get_user().save()`, `c.greet().save()` — to the class def of
|
|
4
|
+
* the value the receiver expression produces.
|
|
5
|
+
*
|
|
6
|
+
* Three shapes (parsed C-family-style):
|
|
7
|
+
* - bare identifier `name` — look up via typeBinding chain
|
|
8
|
+
* - dotted `obj.field[.field]…` — walk fields via class-scope typeBindings
|
|
9
|
+
* - call `expr.method()` — recurse into expr, find method's return-type
|
|
10
|
+
* typeBinding on its class, resolve to a class
|
|
11
|
+
*
|
|
12
|
+
* **Field-fallback heuristic** (Phase-9C "unified fixpoint"): when the
|
|
13
|
+
* receiver class has no `methodName`, walk its fields and try the
|
|
14
|
+
* lookup on each field's type. Useful for dynamically-typed languages
|
|
15
|
+
* (Python). Strictly-typed languages should pass
|
|
16
|
+
* `fieldFallbackOnMethodLookup: false` via `ScopeResolver`.
|
|
17
|
+
*
|
|
18
|
+
* Generic for any C-family language (`.` member access, `()` call
|
|
19
|
+
* syntax). Languages with non-C-family syntax (Ruby blocks, COBOL)
|
|
20
|
+
* either don't trigger the call branch or skip this pass entirely.
|
|
21
|
+
*/
|
|
22
|
+
import type { ScopeId, SymbolDefinition } from '../../../../_shared/index.js';
|
|
23
|
+
import type { ScopeResolutionIndexes } from '../../model/scope-resolution-indexes.js';
|
|
24
|
+
import type { WorkspaceResolutionIndex } from '../workspace-index.js';
|
|
25
|
+
interface ResolveCompoundReceiverOptions {
|
|
26
|
+
/** When true (default), if method lookup fails on the receiver's
|
|
27
|
+
* class, walk its fields and try the lookup on each field's class.
|
|
28
|
+
* Phase-9C "unified fixpoint" — Python-shaped heuristic. */
|
|
29
|
+
readonly fieldFallback?: boolean;
|
|
30
|
+
/** Language-specific accessor unwrap — `data.Values` on a
|
|
31
|
+
* Dictionary<K,V>-typed receiver yields V (C#), etc. Returns the
|
|
32
|
+
* element type's simple name, or `undefined` to let the regular
|
|
33
|
+
* field-walk handle the access. */
|
|
34
|
+
readonly unwrapCollectionAccessor?: (receiverType: string, accessor: string) => string | undefined;
|
|
35
|
+
/** Walk up from the class scope to ancestor (Module) scopes when
|
|
36
|
+
* looking up a method's return-type typeBinding. Only enable for
|
|
37
|
+
* languages that hoist return-type bindings to Module scope (C#);
|
|
38
|
+
* otherwise we risk picking up unrelated module-level bindings. */
|
|
39
|
+
readonly hoistTypeBindingsToModule?: boolean;
|
|
40
|
+
}
|
|
41
|
+
export declare function resolveCompoundReceiverClass(receiverText: string, inScope: ScopeId, scopes: ScopeResolutionIndexes, index: WorkspaceResolutionIndex, options?: ResolveCompoundReceiverOptions, depth?: number): SymbolDefinition | undefined;
|
|
42
|
+
export {};
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve a compound-receiver expression's TYPE — `user.address.save()`,
|
|
3
|
+
* `svc.get_user().save()`, `c.greet().save()` — to the class def of
|
|
4
|
+
* the value the receiver expression produces.
|
|
5
|
+
*
|
|
6
|
+
* Three shapes (parsed C-family-style):
|
|
7
|
+
* - bare identifier `name` — look up via typeBinding chain
|
|
8
|
+
* - dotted `obj.field[.field]…` — walk fields via class-scope typeBindings
|
|
9
|
+
* - call `expr.method()` — recurse into expr, find method's return-type
|
|
10
|
+
* typeBinding on its class, resolve to a class
|
|
11
|
+
*
|
|
12
|
+
* **Field-fallback heuristic** (Phase-9C "unified fixpoint"): when the
|
|
13
|
+
* receiver class has no `methodName`, walk its fields and try the
|
|
14
|
+
* lookup on each field's type. Useful for dynamically-typed languages
|
|
15
|
+
* (Python). Strictly-typed languages should pass
|
|
16
|
+
* `fieldFallbackOnMethodLookup: false` via `ScopeResolver`.
|
|
17
|
+
*
|
|
18
|
+
* Generic for any C-family language (`.` member access, `()` call
|
|
19
|
+
* syntax). Languages with non-C-family syntax (Ruby blocks, COBOL)
|
|
20
|
+
* either don't trigger the call branch or skip this pass entirely.
|
|
21
|
+
*/
|
|
22
|
+
import { findClassBindingInScope, findExportedDefByName, findReceiverTypeBinding, } from '../scope/walkers.js';
|
|
23
|
+
/** Max depth for compound-receiver chain resolution (`a().b().c().d()`).
|
|
24
|
+
* Practical code rarely exceeds 3-4 hops; the cap prevents
|
|
25
|
+
* pathological recursion if the receiver text is malformed. */
|
|
26
|
+
const COMPOUND_RECEIVER_MAX_DEPTH = 4;
|
|
27
|
+
export function resolveCompoundReceiverClass(receiverText, inScope, scopes, index, options = {}, depth = 0) {
|
|
28
|
+
const classScopeByDefId = index.classScopeByDefId;
|
|
29
|
+
if (depth > COMPOUND_RECEIVER_MAX_DEPTH)
|
|
30
|
+
return undefined;
|
|
31
|
+
const text = receiverText.trim();
|
|
32
|
+
if (text.length === 0)
|
|
33
|
+
return undefined;
|
|
34
|
+
const fieldFallback = options.fieldFallback ?? true;
|
|
35
|
+
// Bare identifier — resolve via typeBinding then class lookup.
|
|
36
|
+
if (!text.includes('.') && !text.includes('(')) {
|
|
37
|
+
const tb = findReceiverTypeBinding(inScope, text, scopes);
|
|
38
|
+
if (tb === undefined)
|
|
39
|
+
return undefined;
|
|
40
|
+
return findClassBindingInScope(tb.declaredAtScope, tb.rawName, scopes);
|
|
41
|
+
}
|
|
42
|
+
// Trailing `()` — call expression. Strip it and resolve the function
|
|
43
|
+
// expression's return type. We only handle the canonical `f()` /
|
|
44
|
+
// `obj.method()` shape; nested-arg expressions like `f(g())` are
|
|
45
|
+
// out of scope for V1 (depth-capped recursion catches infinite loops).
|
|
46
|
+
if (text.endsWith(')')) {
|
|
47
|
+
const openIdx = matchingOpenParen(text);
|
|
48
|
+
if (openIdx === -1)
|
|
49
|
+
return undefined;
|
|
50
|
+
const fnExpr = text.slice(0, openIdx).trim();
|
|
51
|
+
if (fnExpr.length === 0)
|
|
52
|
+
return undefined;
|
|
53
|
+
const lastDot = fnExpr.lastIndexOf('.');
|
|
54
|
+
if (lastDot === -1) {
|
|
55
|
+
// Free call `name()`. Look up function in scope, then its
|
|
56
|
+
// return-type typeBinding (which lives in the function's
|
|
57
|
+
// enclosing scope per the language's return-type hoist rule).
|
|
58
|
+
const fnDef = findExportedDefByName(fnExpr, inScope, scopes, index);
|
|
59
|
+
if (fnDef === undefined)
|
|
60
|
+
return undefined;
|
|
61
|
+
const retType = findReceiverTypeBinding(inScope, fnExpr, scopes);
|
|
62
|
+
if (retType === undefined)
|
|
63
|
+
return undefined;
|
|
64
|
+
return findClassBindingInScope(retType.declaredAtScope, retType.rawName, scopes);
|
|
65
|
+
}
|
|
66
|
+
// `obj.method()` — resolve obj's class, look up method's return
|
|
67
|
+
// type on that class scope (or the MRO).
|
|
68
|
+
const objExpr = fnExpr.slice(0, lastDot);
|
|
69
|
+
const methodName = fnExpr.slice(lastDot + 1);
|
|
70
|
+
const objClass = resolveCompoundReceiverClass(objExpr, inScope, scopes, index, options, depth + 1);
|
|
71
|
+
if (objClass === undefined)
|
|
72
|
+
return undefined;
|
|
73
|
+
let retType;
|
|
74
|
+
const ownerChain = [objClass.nodeId, ...scopes.methodDispatch.mroFor(objClass.nodeId)];
|
|
75
|
+
for (const ownerId of ownerChain) {
|
|
76
|
+
const cs = classScopeByDefId.get(ownerId);
|
|
77
|
+
const candidate = cs?.typeBindings.get(methodName);
|
|
78
|
+
if (candidate !== undefined) {
|
|
79
|
+
retType = candidate;
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
// Fallback: walk up from the class scope looking for a return-
|
|
83
|
+
// type binding on an ancestor (Module) scope. Gated on
|
|
84
|
+
// `hoistTypeBindingsToModule` because only languages that hoist
|
|
85
|
+
// method return-type bindings to Module scope need this path;
|
|
86
|
+
// enabling it unconditionally would let other languages pick up
|
|
87
|
+
// unrelated module-level bindings. See contract doc for the
|
|
88
|
+
// invariant and `propagateImportedReturnTypes` for how the
|
|
89
|
+
// hoisted bindings originate.
|
|
90
|
+
if (cs !== undefined && options.hoistTypeBindingsToModule === true) {
|
|
91
|
+
let curId = cs.parent;
|
|
92
|
+
while (curId !== null) {
|
|
93
|
+
const curScope = scopes.scopeTree.getScope(curId);
|
|
94
|
+
if (curScope === undefined)
|
|
95
|
+
break;
|
|
96
|
+
const cand = curScope.typeBindings.get(methodName);
|
|
97
|
+
if (cand !== undefined) {
|
|
98
|
+
retType = cand;
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
curId = curScope.parent;
|
|
102
|
+
}
|
|
103
|
+
if (retType !== undefined)
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (retType === undefined && fieldFallback) {
|
|
108
|
+
const objCs = classScopeByDefId.get(objClass.nodeId);
|
|
109
|
+
if (objCs !== undefined) {
|
|
110
|
+
for (const [, fieldType] of objCs.typeBindings) {
|
|
111
|
+
const fieldClass = findClassBindingInScope(fieldType.declaredAtScope, fieldType.rawName, scopes);
|
|
112
|
+
if (fieldClass === undefined)
|
|
113
|
+
continue;
|
|
114
|
+
const fcs = classScopeByDefId.get(fieldClass.nodeId);
|
|
115
|
+
const candidate = fcs?.typeBindings.get(methodName);
|
|
116
|
+
if (candidate !== undefined) {
|
|
117
|
+
retType = candidate;
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (retType === undefined)
|
|
124
|
+
return undefined;
|
|
125
|
+
return findClassBindingInScope(retType.declaredAtScope, retType.rawName, scopes);
|
|
126
|
+
}
|
|
127
|
+
// Pure dotted access `obj.field[.field]…` — walk fields.
|
|
128
|
+
const parts = text.split('.');
|
|
129
|
+
// Language-specific collection-accessor suffix (C#'s `data.Values`
|
|
130
|
+
// on Dictionary<K,V>, etc.). When the provider hook recognizes
|
|
131
|
+
// the final segment and unwraps the receiver's generic, return
|
|
132
|
+
// the element class directly. Resolved before the field-walk
|
|
133
|
+
// because Dictionary-family types aren't local class defs.
|
|
134
|
+
if (options.unwrapCollectionAccessor !== undefined && parts.length >= 2) {
|
|
135
|
+
const last = parts[parts.length - 1];
|
|
136
|
+
const prefix = parts.slice(0, -1).join('.');
|
|
137
|
+
let prefixType;
|
|
138
|
+
if (parts.length === 2) {
|
|
139
|
+
prefixType = findReceiverTypeBinding(inScope, prefix, scopes);
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
// Recursive resolution: walk the prefix as a dotted class chain
|
|
143
|
+
// to find its typeRef. We need the TypeRef (not the class def)
|
|
144
|
+
// because the hook inspects the raw generic args (e.g.
|
|
145
|
+
// `Dictionary<string, User>`).
|
|
146
|
+
const headInner = parts[0];
|
|
147
|
+
let cur = findReceiverTypeBinding(inScope, headInner, scopes);
|
|
148
|
+
for (let i = 1; i < parts.length - 1 && cur !== undefined; i++) {
|
|
149
|
+
const cls = findClassBindingInScope(cur.declaredAtScope, cur.rawName, scopes);
|
|
150
|
+
if (cls === undefined) {
|
|
151
|
+
cur = undefined;
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
const cs = classScopeByDefId.get(cls.nodeId);
|
|
155
|
+
cur = cs?.typeBindings.get(parts[i]);
|
|
156
|
+
}
|
|
157
|
+
prefixType = cur;
|
|
158
|
+
}
|
|
159
|
+
if (prefixType !== undefined) {
|
|
160
|
+
const elemName = options.unwrapCollectionAccessor(prefixType.rawName, last);
|
|
161
|
+
if (elemName !== undefined) {
|
|
162
|
+
return findClassBindingInScope(prefixType.declaredAtScope, elemName, scopes);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
const head = parts[0];
|
|
167
|
+
const headType = findReceiverTypeBinding(inScope, head, scopes);
|
|
168
|
+
let currentClass = headType
|
|
169
|
+
? findClassBindingInScope(headType.declaredAtScope, headType.rawName, scopes)
|
|
170
|
+
: undefined;
|
|
171
|
+
for (let i = 1; i < parts.length && currentClass !== undefined; i++) {
|
|
172
|
+
const fieldName = parts[i];
|
|
173
|
+
const cs = classScopeByDefId.get(currentClass.nodeId);
|
|
174
|
+
const fieldType = cs?.typeBindings.get(fieldName);
|
|
175
|
+
if (fieldType === undefined)
|
|
176
|
+
return undefined;
|
|
177
|
+
currentClass = findClassBindingInScope(fieldType.declaredAtScope, fieldType.rawName, scopes);
|
|
178
|
+
}
|
|
179
|
+
return currentClass;
|
|
180
|
+
}
|
|
181
|
+
/** Find the index of the `(` that matches the trailing `)` of a
|
|
182
|
+
* call-expression text. Returns -1 if unbalanced. */
|
|
183
|
+
function matchingOpenParen(text) {
|
|
184
|
+
if (!text.endsWith(')'))
|
|
185
|
+
return -1;
|
|
186
|
+
let depth = 0;
|
|
187
|
+
for (let i = text.length - 1; i >= 0; i--) {
|
|
188
|
+
const ch = text[i];
|
|
189
|
+
if (ch === ')')
|
|
190
|
+
depth++;
|
|
191
|
+
else if (ch === '(') {
|
|
192
|
+
depth--;
|
|
193
|
+
if (depth === 0)
|
|
194
|
+
return i;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return -1;
|
|
198
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Emit CALLS edges for free-call reference sites whose target is
|
|
3
|
+
* imported (or otherwise visible only via post-finalize scope.bindings).
|
|
4
|
+
*
|
|
5
|
+
* The shared `MethodRegistry.lookup` only consults `scope.bindings`
|
|
6
|
+
* (pre-finalize / local-only) for free calls. Cross-file imports land
|
|
7
|
+
* in `indexes.bindings` (post-finalize). Without this fallback, every
|
|
8
|
+
* `from x import f; f()` resolves to "unresolved".
|
|
9
|
+
*
|
|
10
|
+
* **Free-call dedup contract (Contract Invariant I2):** free calls
|
|
11
|
+
* collapse to one CALLS edge per (caller, target) pair regardless of
|
|
12
|
+
* how many call sites the caller contains. Mirrors the legacy DAG's
|
|
13
|
+
* dedup semantics (what the `default-params` / `variadic` / `overload`
|
|
14
|
+
* fixtures expect). Member calls keep position-based dedup elsewhere.
|
|
15
|
+
*
|
|
16
|
+
* Generic; promoted from `languages/python/scope-resolver.ts` per the scope-resolution
|
|
17
|
+
* generalization plan.
|
|
18
|
+
*/
|
|
19
|
+
import type { ParsedFile, Reference, ScopeId } from '../../../../_shared/index.js';
|
|
20
|
+
import type { KnowledgeGraph } from '../../../graph/types.js';
|
|
21
|
+
import type { ScopeResolutionIndexes } from '../../model/scope-resolution-indexes.js';
|
|
22
|
+
import type { SemanticModel } from '../../model/semantic-model.js';
|
|
23
|
+
import type { WorkspaceResolutionIndex } from '../workspace-index.js';
|
|
24
|
+
import type { GraphNodeLookup } from '../graph-bridge/node-lookup.js';
|
|
25
|
+
export declare function emitFreeCallFallback(graph: KnowledgeGraph, scopes: ScopeResolutionIndexes, parsedFiles: readonly ParsedFile[], nodeLookup: GraphNodeLookup, _referenceIndex: {
|
|
26
|
+
readonly bySourceScope: ReadonlyMap<ScopeId, readonly Reference[]>;
|
|
27
|
+
}, handledSites: Set<string>, model: SemanticModel, workspaceIndex: WorkspaceResolutionIndex): number;
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Emit CALLS edges for free-call reference sites whose target is
|
|
3
|
+
* imported (or otherwise visible only via post-finalize scope.bindings).
|
|
4
|
+
*
|
|
5
|
+
* The shared `MethodRegistry.lookup` only consults `scope.bindings`
|
|
6
|
+
* (pre-finalize / local-only) for free calls. Cross-file imports land
|
|
7
|
+
* in `indexes.bindings` (post-finalize). Without this fallback, every
|
|
8
|
+
* `from x import f; f()` resolves to "unresolved".
|
|
9
|
+
*
|
|
10
|
+
* **Free-call dedup contract (Contract Invariant I2):** free calls
|
|
11
|
+
* collapse to one CALLS edge per (caller, target) pair regardless of
|
|
12
|
+
* how many call sites the caller contains. Mirrors the legacy DAG's
|
|
13
|
+
* dedup semantics (what the `default-params` / `variadic` / `overload`
|
|
14
|
+
* fixtures expect). Member calls keep position-based dedup elsewhere.
|
|
15
|
+
*
|
|
16
|
+
* Generic; promoted from `languages/python/scope-resolver.ts` per the scope-resolution
|
|
17
|
+
* generalization plan.
|
|
18
|
+
*/
|
|
19
|
+
import { resolveCallerGraphId, resolveDefGraphId } from '../graph-bridge/ids.js';
|
|
20
|
+
import { findCallableBindingInScope, findClassBindingInScope } from '../scope/walkers.js';
|
|
21
|
+
import { narrowOverloadCandidates } from './overload-narrowing.js';
|
|
22
|
+
export function emitFreeCallFallback(graph, scopes, parsedFiles, nodeLookup, _referenceIndex, handledSites, model, workspaceIndex) {
|
|
23
|
+
let emitted = 0;
|
|
24
|
+
const seen = new Set();
|
|
25
|
+
for (const parsed of parsedFiles) {
|
|
26
|
+
for (const site of parsed.referenceSites) {
|
|
27
|
+
if (site.kind !== 'call')
|
|
28
|
+
continue;
|
|
29
|
+
if (site.explicitReceiver !== undefined)
|
|
30
|
+
continue;
|
|
31
|
+
// Constructor form (`new User(...)`): resolve the class, then
|
|
32
|
+
// emit CALLS to its explicit Constructor def (when present) or
|
|
33
|
+
// to the Class node itself (implicit constructor). Legacy emits
|
|
34
|
+
// the same two targets; see test expectations.
|
|
35
|
+
let fnDef;
|
|
36
|
+
if (site.callForm === 'constructor') {
|
|
37
|
+
const classDef = findClassBindingInScope(site.inScope, site.name, scopes);
|
|
38
|
+
if (classDef !== undefined) {
|
|
39
|
+
fnDef = pickConstructorOrClass(classDef, workspaceIndex);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// Implicit-this overload narrowing: an unqualified call inside
|
|
43
|
+
// a method body might be calling a sibling overload on the
|
|
44
|
+
// enclosing class. When the workspace has multiple methods of
|
|
45
|
+
// the same name in a single class, choose the best match by
|
|
46
|
+
// arity + argument types.
|
|
47
|
+
if (fnDef === undefined) {
|
|
48
|
+
fnDef = pickImplicitThisOverload(site, scopes, workspaceIndex, model);
|
|
49
|
+
}
|
|
50
|
+
if (fnDef === undefined) {
|
|
51
|
+
fnDef = findCallableBindingInScope(site.inScope, site.name, scopes);
|
|
52
|
+
}
|
|
53
|
+
if (fnDef === undefined)
|
|
54
|
+
continue;
|
|
55
|
+
const callerGraphId = resolveCallerGraphId(site.inScope, scopes, nodeLookup);
|
|
56
|
+
if (callerGraphId === undefined)
|
|
57
|
+
continue;
|
|
58
|
+
const tgtGraphId = resolveDefGraphId(fnDef.filePath, fnDef, nodeLookup);
|
|
59
|
+
if (tgtGraphId === undefined)
|
|
60
|
+
continue;
|
|
61
|
+
// Always mark the site as handled — even when the dedup-collapse
|
|
62
|
+
// means we don't add a new edge — so `emit-references` skips its
|
|
63
|
+
// potentially-wrong fallback for the same site.
|
|
64
|
+
handledSites.add(`${parsed.filePath}:${site.atRange.startLine}:${site.atRange.startCol}`);
|
|
65
|
+
const relId = `rel:CALLS:${callerGraphId}->${tgtGraphId}`;
|
|
66
|
+
if (seen.has(relId))
|
|
67
|
+
continue;
|
|
68
|
+
seen.add(relId);
|
|
69
|
+
graph.addRelationship({
|
|
70
|
+
id: relId,
|
|
71
|
+
sourceId: callerGraphId,
|
|
72
|
+
targetId: tgtGraphId,
|
|
73
|
+
type: 'CALLS',
|
|
74
|
+
confidence: 0.85,
|
|
75
|
+
// Match legacy DAG's reason convention so consumers that
|
|
76
|
+
// assert `reason === 'import-resolved'` keep working.
|
|
77
|
+
reason: fnDef.filePath !== parsed.filePath ? 'import-resolved' : 'local-call',
|
|
78
|
+
});
|
|
79
|
+
emitted++;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return emitted;
|
|
83
|
+
}
|
|
84
|
+
/** For a constructor call `new X(...)`, return the X class's explicit
|
|
85
|
+
* Constructor def (by walking the class scope's ownedDefs) or the
|
|
86
|
+
* Class def itself when no explicit Constructor exists. Matches
|
|
87
|
+
* legacy behavior — tests assert targetLabel === 'Class' for implicit
|
|
88
|
+
* ctors and targetLabel === 'Constructor' for explicit ones. */
|
|
89
|
+
function pickConstructorOrClass(classDef, workspaceIndex) {
|
|
90
|
+
const classScope = workspaceIndex.classScopeByDefId.get(classDef.nodeId);
|
|
91
|
+
if (classScope === undefined)
|
|
92
|
+
return classDef;
|
|
93
|
+
for (const def of classScope.ownedDefs) {
|
|
94
|
+
if (def.type === 'Constructor')
|
|
95
|
+
return def;
|
|
96
|
+
}
|
|
97
|
+
return classDef;
|
|
98
|
+
}
|
|
99
|
+
/** Walk up from the call-site scope to the enclosing class scope,
|
|
100
|
+
* pick a method member by name with overload narrowing on arity +
|
|
101
|
+
* argument types. Returns undefined if there's no enclosing class
|
|
102
|
+
* or no matching method. Used for implicit-this calls inside a
|
|
103
|
+
* class body where multiple overloads share the call name. */
|
|
104
|
+
function pickImplicitThisOverload(site, scopes, workspaceIndex, model) {
|
|
105
|
+
// Find the enclosing Class scope by walking parents.
|
|
106
|
+
let curId = site.inScope;
|
|
107
|
+
let classScopeId;
|
|
108
|
+
while (curId !== null) {
|
|
109
|
+
const sc = scopes.scopeTree.getScope(curId);
|
|
110
|
+
if (sc === undefined)
|
|
111
|
+
break;
|
|
112
|
+
if (sc.kind === 'Class') {
|
|
113
|
+
classScopeId = sc.id;
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
curId = sc.parent;
|
|
117
|
+
}
|
|
118
|
+
if (classScopeId === undefined)
|
|
119
|
+
return undefined;
|
|
120
|
+
// O(1) reverse-lookup via inverse map on WorkspaceResolutionIndex.
|
|
121
|
+
const classDefId = workspaceIndex.classScopeIdToDefId.get(classScopeId);
|
|
122
|
+
if (classDefId === undefined)
|
|
123
|
+
return undefined;
|
|
124
|
+
const overloads = model.methods.lookupAllByOwner(classDefId, site.name);
|
|
125
|
+
if (overloads.length === 0)
|
|
126
|
+
return undefined;
|
|
127
|
+
if (overloads.length === 1)
|
|
128
|
+
return overloads[0];
|
|
129
|
+
const candidates = narrowOverloadCandidates(overloads, site.arity, site.argumentTypes);
|
|
130
|
+
return candidates[0];
|
|
131
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-file return-type typeBinding propagation + post-finalize
|
|
3
|
+
* chain re-follow.
|
|
4
|
+
*
|
|
5
|
+
* **Why this lives in scope-resolution:** the algorithm is language-agnostic.
|
|
6
|
+
* Every language with cross-file callable imports needs the same
|
|
7
|
+
* mirror-binding step, otherwise `u = f(); u.save()` only resolves
|
|
8
|
+
* when `f` is in the same file as the call.
|
|
9
|
+
*
|
|
10
|
+
* **Mutation contract (Contract Invariant I3 + I6):**
|
|
11
|
+
* - Mutates `Scope.typeBindings` (a plain `new Map(...)` from
|
|
12
|
+
* `draftToScope`, NOT frozen — intentional, do not freeze).
|
|
13
|
+
* - MUST run AFTER `finalizeScopeModel` (so `indexes.bindings` is
|
|
14
|
+
* populated) but BEFORE `resolveReferenceSites` (so resolution
|
|
15
|
+
* sees the propagated types).
|
|
16
|
+
*
|
|
17
|
+
* Generic; promoted from `languages/python/scope-resolver.ts` per the scope-resolution
|
|
18
|
+
* generalization plan.
|
|
19
|
+
*/
|
|
20
|
+
import type { ParsedFile } from '../../../../_shared/index.js';
|
|
21
|
+
import type { ScopeResolutionIndexes } from '../../model/scope-resolution-indexes.js';
|
|
22
|
+
import type { WorkspaceResolutionIndex } from '../workspace-index.js';
|
|
23
|
+
/**
|
|
24
|
+
* Copy return-type typeBindings across module boundaries via import
|
|
25
|
+
* bindings. For each module-scope import like `from x import f`, look
|
|
26
|
+
* up `f` in the source file's module-scope typeBindings (which carries
|
|
27
|
+
* `f → ReturnType` from the language's return-type annotation
|
|
28
|
+
* capture) and mirror that binding into the importer's module scope.
|
|
29
|
+
*
|
|
30
|
+
* After propagation, re-runs the chain-follow on every scope's
|
|
31
|
+
* typeBindings — the in-extractor pass-4 ran before propagation and
|
|
32
|
+
* missed any chain whose terminal lived in a foreign file.
|
|
33
|
+
*
|
|
34
|
+
* Scope-chain concern (verified 2026-04-21): `pythonImportOwningScope`
|
|
35
|
+
* documents that function-local `from x import y` binds `y` to the
|
|
36
|
+
* inner function scope, which would make a module-only write miss
|
|
37
|
+
* non-module importers. In practice `finalize-algorithm` hoists those
|
|
38
|
+
* bindings into `indexes.bindings[moduleScope]` regardless of where
|
|
39
|
+
* the `import` statement appears — the integration fixture
|
|
40
|
+
* `python-function-local-import-chain` exercises a chained
|
|
41
|
+
* receiver-bound call `u = get_user(); u.save()` inside a function
|
|
42
|
+
* body and emits the expected `do_work → User.save` edge. The
|
|
43
|
+
* module-scope write is sufficient today. If finalize routing ever
|
|
44
|
+
* changes to honor the hook's per-scope contract, this pass must
|
|
45
|
+
* iterate `indexes.bindings` over every scope and mirror into the
|
|
46
|
+
* binding-owning scope's `typeBindings`, not just the module's.
|
|
47
|
+
*/
|
|
48
|
+
export declare function propagateImportedReturnTypes(parsedFiles: readonly ParsedFile[], indexes: ScopeResolutionIndexes, index: WorkspaceResolutionIndex): void;
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-file return-type typeBinding propagation + post-finalize
|
|
3
|
+
* chain re-follow.
|
|
4
|
+
*
|
|
5
|
+
* **Why this lives in scope-resolution:** the algorithm is language-agnostic.
|
|
6
|
+
* Every language with cross-file callable imports needs the same
|
|
7
|
+
* mirror-binding step, otherwise `u = f(); u.save()` only resolves
|
|
8
|
+
* when `f` is in the same file as the call.
|
|
9
|
+
*
|
|
10
|
+
* **Mutation contract (Contract Invariant I3 + I6):**
|
|
11
|
+
* - Mutates `Scope.typeBindings` (a plain `new Map(...)` from
|
|
12
|
+
* `draftToScope`, NOT frozen — intentional, do not freeze).
|
|
13
|
+
* - MUST run AFTER `finalizeScopeModel` (so `indexes.bindings` is
|
|
14
|
+
* populated) but BEFORE `resolveReferenceSites` (so resolution
|
|
15
|
+
* sees the propagated types).
|
|
16
|
+
*
|
|
17
|
+
* Generic; promoted from `languages/python/scope-resolver.ts` per the scope-resolution
|
|
18
|
+
* generalization plan.
|
|
19
|
+
*/
|
|
20
|
+
/** Max chain depth for the post-finalize re-follow. */
|
|
21
|
+
const RECHAIN_MAX_DEPTH = 8;
|
|
22
|
+
/** Walk `ref.rawName` through the scope chain's typeBindings looking
|
|
23
|
+
* for a terminal class-like rawName. Mirrors the in-extractor
|
|
24
|
+
* `followChainedRef` but operates on post-finalize Scope objects so
|
|
25
|
+
* it can see imported return-types propagated by
|
|
26
|
+
* `propagateImportedReturnTypes`. */
|
|
27
|
+
function followChainPostFinalize(start, fromScopeId, scopes) {
|
|
28
|
+
let current = start;
|
|
29
|
+
const visited = new Set();
|
|
30
|
+
for (let depth = 0; depth < RECHAIN_MAX_DEPTH; depth++) {
|
|
31
|
+
if (current.rawName.includes('.'))
|
|
32
|
+
return current;
|
|
33
|
+
let scopeId = fromScopeId;
|
|
34
|
+
let next;
|
|
35
|
+
while (scopeId !== null) {
|
|
36
|
+
const scope = scopes.scopeTree.getScope(scopeId);
|
|
37
|
+
if (scope === undefined)
|
|
38
|
+
break;
|
|
39
|
+
next = scope.typeBindings.get(current.rawName);
|
|
40
|
+
if (next !== undefined && next !== current)
|
|
41
|
+
break;
|
|
42
|
+
next = undefined;
|
|
43
|
+
scopeId = scope.parent;
|
|
44
|
+
}
|
|
45
|
+
if (next === undefined)
|
|
46
|
+
return current;
|
|
47
|
+
if (visited.has(next.rawName))
|
|
48
|
+
return current;
|
|
49
|
+
visited.add(next.rawName);
|
|
50
|
+
current = next;
|
|
51
|
+
}
|
|
52
|
+
return current;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Copy return-type typeBindings across module boundaries via import
|
|
56
|
+
* bindings. For each module-scope import like `from x import f`, look
|
|
57
|
+
* up `f` in the source file's module-scope typeBindings (which carries
|
|
58
|
+
* `f → ReturnType` from the language's return-type annotation
|
|
59
|
+
* capture) and mirror that binding into the importer's module scope.
|
|
60
|
+
*
|
|
61
|
+
* After propagation, re-runs the chain-follow on every scope's
|
|
62
|
+
* typeBindings — the in-extractor pass-4 ran before propagation and
|
|
63
|
+
* missed any chain whose terminal lived in a foreign file.
|
|
64
|
+
*
|
|
65
|
+
* Scope-chain concern (verified 2026-04-21): `pythonImportOwningScope`
|
|
66
|
+
* documents that function-local `from x import y` binds `y` to the
|
|
67
|
+
* inner function scope, which would make a module-only write miss
|
|
68
|
+
* non-module importers. In practice `finalize-algorithm` hoists those
|
|
69
|
+
* bindings into `indexes.bindings[moduleScope]` regardless of where
|
|
70
|
+
* the `import` statement appears — the integration fixture
|
|
71
|
+
* `python-function-local-import-chain` exercises a chained
|
|
72
|
+
* receiver-bound call `u = get_user(); u.save()` inside a function
|
|
73
|
+
* body and emits the expected `do_work → User.save` edge. The
|
|
74
|
+
* module-scope write is sufficient today. If finalize routing ever
|
|
75
|
+
* changes to honor the hook's per-scope contract, this pass must
|
|
76
|
+
* iterate `indexes.bindings` over every scope and mirror into the
|
|
77
|
+
* binding-owning scope's `typeBindings`, not just the module's.
|
|
78
|
+
*/
|
|
79
|
+
export function propagateImportedReturnTypes(parsedFiles, indexes, index) {
|
|
80
|
+
const moduleScopeByFile = index.moduleScopeByFile;
|
|
81
|
+
for (const parsed of parsedFiles) {
|
|
82
|
+
const importerModule = moduleScopeByFile.get(parsed.filePath);
|
|
83
|
+
if (importerModule === undefined)
|
|
84
|
+
continue;
|
|
85
|
+
const finalizedBindings = indexes.bindings.get(importerModule.id);
|
|
86
|
+
if (finalizedBindings === undefined)
|
|
87
|
+
continue;
|
|
88
|
+
for (const [localName, refs] of finalizedBindings) {
|
|
89
|
+
// Skip if importer already has a typeBinding for this name (e.g.
|
|
90
|
+
// an explicit local annotation should win over import-derived).
|
|
91
|
+
if (importerModule.typeBindings.has(localName))
|
|
92
|
+
continue;
|
|
93
|
+
for (const ref of refs) {
|
|
94
|
+
if (ref.origin !== 'import' && ref.origin !== 'reexport')
|
|
95
|
+
continue;
|
|
96
|
+
const sourceModule = moduleScopeByFile.get(ref.def.filePath);
|
|
97
|
+
if (sourceModule === undefined)
|
|
98
|
+
continue;
|
|
99
|
+
// The source file's typeBinding is keyed by the def's simple
|
|
100
|
+
// name (e.g. `get_user`), not the importer's local alias. Use
|
|
101
|
+
// the def's qualifiedName tail.
|
|
102
|
+
const qn = ref.def.qualifiedName;
|
|
103
|
+
if (qn === undefined)
|
|
104
|
+
continue;
|
|
105
|
+
const dot = qn.lastIndexOf('.');
|
|
106
|
+
const sourceName = dot === -1 ? qn : qn.slice(dot + 1);
|
|
107
|
+
const sourceTypeRef = sourceModule.typeBindings.get(sourceName);
|
|
108
|
+
if (sourceTypeRef === undefined)
|
|
109
|
+
continue;
|
|
110
|
+
// Mirror the binding under the importer's local alias —
|
|
111
|
+
// mutating typeBindings is safe because draftToScope produced
|
|
112
|
+
// a non-frozen Map.
|
|
113
|
+
importerModule.typeBindings.set(localName, sourceTypeRef);
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// Re-follow chains across every scope so chains terminating in a
|
|
119
|
+
// freshly-propagated import binding resolve to their terminal type.
|
|
120
|
+
for (const parsed of parsedFiles) {
|
|
121
|
+
for (const scope of parsed.scopes) {
|
|
122
|
+
for (const [name, ref] of scope.typeBindings) {
|
|
123
|
+
const resolved = followChainPostFinalize(ref, scope.id, indexes);
|
|
124
|
+
if (resolved !== ref) {
|
|
125
|
+
scope.typeBindings.set(name, resolved);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic MRO (method-resolution-order) builder.
|
|
3
|
+
*
|
|
4
|
+
* Walks the graph's `EXTENDS` edges to recover an inheritance map,
|
|
5
|
+
* then asks the per-language `LinearizeStrategy` to order each class's
|
|
6
|
+
* ancestors. Returns `Map<classDefId, ancestorDefId[]>` ready to plug
|
|
7
|
+
* into `MethodDispatchIndex` via `buildPopulatedMethodDispatch`.
|
|
8
|
+
*
|
|
9
|
+
* **Why a strategy hook:** linearization differs across languages.
|
|
10
|
+
* - Python (depth-first first-seen, single inheritance): trivially
|
|
11
|
+
* correct; multi-inheritance falls back to BFS dedup. Real C3
|
|
12
|
+
* would handle diamond hierarchies — defer until we hit one.
|
|
13
|
+
* - Java (single-inheritance only): walk one parent.
|
|
14
|
+
* - C++ (multiple inheritance): C3-like or BFS depending on how
|
|
15
|
+
* strict the consumer needs to be.
|
|
16
|
+
* - Languages without inheritance (COBOL): return empty list.
|
|
17
|
+
*
|
|
18
|
+
* The strategy receives the FULL ancestry context (`directParents` +
|
|
19
|
+
* `parentsByDefId`) so C3 implementations have what they need.
|
|
20
|
+
*/
|
|
21
|
+
import type { ParsedFile } from '../../../../_shared/index.js';
|
|
22
|
+
import type { KnowledgeGraph } from '../../../graph/types.js';
|
|
23
|
+
import type { GraphNodeLookup } from '../graph-bridge/node-lookup.js';
|
|
24
|
+
import type { LinearizeStrategy } from '../contract/scope-resolver.js';
|
|
25
|
+
/**
|
|
26
|
+
* Build an MRO map keyed by scope-resolution Class `DefId`.
|
|
27
|
+
*
|
|
28
|
+
* Steps:
|
|
29
|
+
* 1. Collect EXTENDS edges from the graph → `parentsByGraphId`.
|
|
30
|
+
* 2. Collect Class defs from `parsedFiles` and translate to graph
|
|
31
|
+
* ids via `nodeLookup` → `defIdByGraphId` (the bridge between
|
|
32
|
+
* scope-resolution DefId and the legacy graph node id).
|
|
33
|
+
* 3. For each Class def, ask `linearize` for its ancestor order.
|
|
34
|
+
*/
|
|
35
|
+
export declare function buildMro(graph: KnowledgeGraph, parsedFiles: readonly ParsedFile[], nodeLookup: GraphNodeLookup, linearize: LinearizeStrategy): Map<string, string[]>;
|
|
36
|
+
/**
|
|
37
|
+
* Default linearization: depth-first BFS-with-visited, first-seen
|
|
38
|
+
* wins. Correct for single-inheritance languages and for Python's
|
|
39
|
+
* simplified MRO. Multi-inheritance diamond hierarchies need a real
|
|
40
|
+
* C3 implementation; per-language overrides land here.
|
|
41
|
+
*/
|
|
42
|
+
export declare const defaultLinearize: LinearizeStrategy;
|