gitnexus 1.6.12-rc.2 → 1.6.12-rc.21
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/dist/_shared/graph/types.d.ts +1 -1
- package/dist/_shared/graph/types.d.ts.map +1 -1
- package/dist/_shared/language-detection.d.ts.map +1 -1
- package/dist/_shared/language-detection.js +2 -0
- package/dist/_shared/language-detection.js.map +1 -1
- package/dist/_shared/languages.d.ts +1 -0
- package/dist/_shared/languages.d.ts.map +1 -1
- package/dist/_shared/languages.js +1 -0
- package/dist/_shared/languages.js.map +1 -1
- package/dist/_shared/lbug/schema-constants.d.ts +1 -1
- package/dist/_shared/lbug/schema-constants.d.ts.map +1 -1
- package/dist/_shared/lbug/schema-constants.js +2 -0
- package/dist/_shared/lbug/schema-constants.js.map +1 -1
- package/dist/_shared/scope-resolution/language-classification.d.ts +2 -1
- package/dist/_shared/scope-resolution/language-classification.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/language-classification.js +3 -1
- package/dist/_shared/scope-resolution/language-classification.js.map +1 -1
- package/dist/_shared/scope-resolution/registries/class-registry.d.ts +3 -3
- package/dist/_shared/scope-resolution/registries/class-registry.js +3 -3
- package/dist/_shared/scope-resolution/registries/context.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/registries/context.js +2 -0
- package/dist/_shared/scope-resolution/registries/context.js.map +1 -1
- package/dist/_shared/scope-resolution/resolve-type-ref.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/resolve-type-ref.js +2 -0
- package/dist/_shared/scope-resolution/resolve-type-ref.js.map +1 -1
- package/dist/cli/analyze-config.d.ts +2 -2
- package/dist/cli/analyze-config.js +10 -33
- package/dist/cli/doctor.js +6 -4
- package/dist/cli/eval-server.js +13 -1
- package/dist/cli/i18n/en.d.ts +4 -2
- package/dist/cli/i18n/en.js +4 -2
- package/dist/cli/i18n/resources.d.ts +6 -2
- package/dist/cli/i18n/zh-CN.d.ts +2 -0
- package/dist/cli/i18n/zh-CN.js +4 -2
- package/dist/core/analysis-feature-registry.d.ts +1 -1
- package/dist/core/analysis-feature-registry.js +2 -0
- package/dist/core/auto-sync/starter.js +2 -2
- package/dist/core/embeddings/ast-utils.js +8 -3
- package/dist/core/embeddings/chunker.js +67 -10
- package/dist/core/embeddings/embedding-pipeline.d.ts +1 -1
- package/dist/core/embeddings/embedding-pipeline.js +1 -1
- package/dist/core/embeddings/structural-extractor.js +2 -2
- package/dist/core/embeddings/types.d.ts +4 -2
- package/dist/core/embeddings/types.js +18 -0
- package/dist/core/git-ref.d.ts +22 -0
- package/dist/core/git-ref.js +114 -0
- package/dist/core/git-staleness.js +14 -0
- package/dist/core/group/extractors/graphql-extractor.d.ts +1 -1
- package/dist/core/group/extractors/graphql-extractor.js +149 -13
- package/dist/core/group/extractors/manifest-extractor.d.ts +1 -1
- package/dist/core/group/extractors/manifest-extractor.js +2 -2
- package/dist/core/ingestion/call-extractors/zig-static-gating.d.ts +5 -8
- package/dist/core/ingestion/content-language-classification.d.ts +10 -0
- package/dist/core/ingestion/content-language-classification.js +30 -0
- package/dist/core/ingestion/filesystem-walker.d.ts +1 -0
- package/dist/core/ingestion/filesystem-walker.js +1 -1
- package/dist/core/ingestion/import-resolvers/node-workspace-packages.js +3 -2
- package/dist/core/ingestion/language-config.d.ts +82 -13
- package/dist/core/ingestion/language-config.js +217 -13
- package/dist/core/ingestion/language-provider.d.ts +88 -1
- package/dist/core/ingestion/languages/index.d.ts +7 -0
- package/dist/core/ingestion/languages/index.js +30 -1
- package/dist/core/ingestion/languages/objective-c/analysis-features.d.ts +10 -0
- package/dist/core/ingestion/languages/objective-c/analysis-features.js +19 -0
- package/dist/core/ingestion/languages/objective-c/compilation-unit-siblings.d.ts +13 -0
- package/dist/core/ingestion/languages/objective-c/compilation-unit-siblings.js +124 -0
- package/dist/core/ingestion/languages/objective-c/facts.d.ts +148 -0
- package/dist/core/ingestion/languages/objective-c/facts.js +1150 -0
- package/dist/core/ingestion/languages/objective-c/import-target.d.ts +11 -0
- package/dist/core/ingestion/languages/objective-c/import-target.js +122 -0
- package/dist/core/ingestion/languages/objective-c/macro-marker-preprocess.d.ts +24 -0
- package/dist/core/ingestion/languages/objective-c/macro-marker-preprocess.js +261 -0
- package/dist/core/ingestion/languages/objective-c/resolution-config.d.ts +41 -0
- package/dist/core/ingestion/languages/objective-c/resolution-config.js +282 -0
- package/dist/core/ingestion/languages/objective-c/scope-resolver.d.ts +2 -0
- package/dist/core/ingestion/languages/objective-c/scope-resolver.js +503 -0
- package/dist/core/ingestion/languages/objective-c.d.ts +2 -0
- package/dist/core/ingestion/languages/objective-c.js +300 -0
- package/dist/core/ingestion/languages/typescript/query.d.ts +31 -0
- package/dist/core/ingestion/languages/typescript/query.js +7 -1
- package/dist/core/ingestion/languages/typescript/tsconfig.js +3 -2
- package/dist/core/ingestion/languages/typescript.js +1 -1
- package/dist/core/ingestion/languages/zig/query.d.ts +26 -0
- package/dist/core/ingestion/languages/zig/query.js +61 -1
- package/dist/core/ingestion/languages/zig/range-binding.d.ts +8 -0
- package/dist/core/ingestion/languages/zig/range-binding.js +24 -0
- package/dist/core/ingestion/languages/zig/scope-resolver.d.ts +3 -2
- package/dist/core/ingestion/languages/zig/scope-resolver.js +18 -5
- package/dist/core/ingestion/languages/zig/this-alias-bindings.d.ts +58 -0
- package/dist/core/ingestion/languages/zig/this-alias-bindings.js +176 -0
- package/dist/core/ingestion/languages/zig/workspace-static-gating.d.ts +8 -0
- package/dist/core/ingestion/languages/zig/workspace-static-gating.js +82 -0
- package/dist/core/ingestion/model/registration-table.d.ts +3 -3
- package/dist/core/ingestion/model/registration-table.js +9 -5
- package/dist/core/ingestion/model/symbol-table.d.ts +1 -1
- package/dist/core/ingestion/model/symbol-table.js +2 -0
- package/dist/core/ingestion/model/type-registry.d.ts +1 -1
- package/dist/core/ingestion/parsing-processor.d.ts +22 -6
- package/dist/core/ingestion/parsing-processor.js +39 -20
- package/dist/core/ingestion/pipeline-phases/parse-impl.d.ts +9 -1
- package/dist/core/ingestion/pipeline-phases/parse-impl.js +307 -96
- package/dist/core/ingestion/pipeline-phases/parse-round-budget.d.ts +42 -0
- package/dist/core/ingestion/pipeline-phases/parse-round-budget.js +50 -0
- package/dist/core/ingestion/pipeline-phases/parse.d.ts +7 -1
- package/dist/core/ingestion/scope-extractor.js +4 -0
- package/dist/core/ingestion/scope-resolution/contract/scope-resolver.d.ts +13 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/ids.js +2 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/node-lookup.js +2 -0
- package/dist/core/ingestion/scope-resolution/passes/property-dispatch.d.ts +78 -5
- package/dist/core/ingestion/scope-resolution/passes/property-dispatch.js +262 -7
- package/dist/core/ingestion/scope-resolution/pipeline/phase.js +5 -2
- package/dist/core/ingestion/scope-resolution/pipeline/registry.js +2 -0
- package/dist/core/ingestion/scope-resolution/pipeline/run.js +10 -1
- package/dist/core/ingestion/scope-resolution/scope/walkers.d.ts +39 -3
- package/dist/core/ingestion/scope-resolution/scope/walkers.js +82 -3
- package/dist/core/ingestion/scope-resolution/value-ref-edges.d.ts +14 -0
- package/dist/core/ingestion/scope-resolution/value-ref-edges.js +14 -0
- package/dist/core/ingestion/tree-sitter-queries.js +2 -0
- package/dist/core/ingestion/utils/symbol-labels.js +2 -0
- package/dist/core/ingestion/workers/parse-worker.d.ts +1 -1
- package/dist/core/ingestion/workers/parse-worker.js +63 -3
- package/dist/core/ingestion/workers/worker-pool.d.ts +56 -2
- package/dist/core/ingestion/workers/worker-pool.js +117 -26
- package/dist/core/lbug/csv-generator.js +2 -0
- package/dist/core/lbug/schema.d.ts +3 -1
- package/dist/core/lbug/schema.js +27 -0
- package/dist/core/search/fts-indexes.js +13 -4
- package/dist/core/search/fts-schema.js +2 -0
- package/dist/core/tree-sitter/parser-loader.js +10 -0
- package/dist/core/tree-sitter/vendored-grammars.d.ts +1 -1
- package/dist/core/tree-sitter/vendored-grammars.js +2 -1
- package/dist/mcp/local/local-backend.d.ts +33 -0
- package/dist/mcp/local/local-backend.js +166 -5
- package/dist/mcp/tools.js +6 -4
- package/dist/server/analyze-job.d.ts +19 -1
- package/dist/server/analyze-job.js +14 -3
- package/dist/server/analyze-launch.d.ts +14 -0
- package/dist/server/analyze-launch.js +59 -14
- package/dist/server/analyze-worker-ipc.d.ts +6 -5
- package/dist/server/analyze-worker-ipc.js +4 -0
- package/dist/server/api.js +68 -21
- package/dist/server/git-clone.d.ts +38 -5
- package/dist/server/git-clone.js +216 -38
- package/dist/server/repo-projection.d.ts +88 -0
- package/dist/server/repo-projection.js +53 -0
- package/dist/storage/file-lock.js +5 -3
- package/dist/storage/parse-cache.js +32 -1
- package/dist/storage/parsedfile-store.js +53 -1
- package/dist/storage/v8-sidecar.d.ts +11 -0
- package/dist/storage/v8-sidecar.js +15 -7
- package/dist/utils/process-identity.d.ts +13 -0
- package/dist/utils/process-identity.js +18 -0
- package/package.json +1 -1
- package/scripts/build-tree-sitter-grammars.cjs +4 -3
- package/vendor/tree-sitter-objc/LICENSE +21 -0
- package/vendor/tree-sitter-objc/README.md +18 -0
- package/vendor/tree-sitter-objc/binding.gyp +35 -0
- package/vendor/tree-sitter-objc/bindings/node/binding.cc +19 -0
- package/vendor/tree-sitter-objc/bindings/node/binding_test.js +9 -0
- package/vendor/tree-sitter-objc/bindings/node/index.d.ts +27 -0
- package/vendor/tree-sitter-objc/bindings/node/index.js +11 -0
- package/vendor/tree-sitter-objc/grammar.js +1286 -0
- package/vendor/tree-sitter-objc/package.json +56 -0
- package/vendor/tree-sitter-objc/prebuilds/SHA256SUMS +6 -0
- package/vendor/tree-sitter-objc/prebuilds/darwin-arm64/tree-sitter-objc.node +0 -0
- package/vendor/tree-sitter-objc/prebuilds/darwin-x64/tree-sitter-objc.node +0 -0
- package/vendor/tree-sitter-objc/prebuilds/linux-arm64/tree-sitter-objc.node +0 -0
- package/vendor/tree-sitter-objc/prebuilds/linux-x64/tree-sitter-objc.node +0 -0
- package/vendor/tree-sitter-objc/prebuilds/win32-arm64/tree-sitter-objc.node +0 -0
- package/vendor/tree-sitter-objc/prebuilds/win32-x64/tree-sitter-objc.node +0 -0
- package/vendor/tree-sitter-objc/queries/folds.scm +20 -0
- package/vendor/tree-sitter-objc/queries/highlights.scm +216 -0
- package/vendor/tree-sitter-objc/queries/indents.scm +1 -0
- package/vendor/tree-sitter-objc/queries/injections.scm +10 -0
- package/vendor/tree-sitter-objc/queries/locals.scm +1 -0
- package/vendor/tree-sitter-objc/src/grammar.json +16292 -0
- package/vendor/tree-sitter-objc/src/node-types.json +7524 -0
- package/vendor/tree-sitter-objc/src/parser.c +684023 -0
- package/vendor/tree-sitter-objc/src/tree_sitter/alloc.h +54 -0
- package/vendor/tree-sitter-objc/src/tree_sitter/array.h +291 -0
- package/vendor/tree-sitter-objc/src/tree_sitter/parser.h +266 -0
- package/vendor/tree-sitter-objc/tree-sitter-objc.wasm +0 -0
- package/vendor/tree-sitter-objc/tree-sitter.json +38 -0
- package/web/assets/{agent-BMRqXorA.js → agent-C2n33ANd.js} +1 -1
- package/web/assets/{index-Dj3vQOHK.js → index-CHJoW_P1.js} +18 -18
- package/web/assets/src-jc8Ffy3T.js +1 -0
- package/web/index.html +2 -2
- package/web/assets/src-Df5C1Nz4.js +0 -1
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
*/
|
|
18
18
|
import type { PipelinePhase } from './types.js';
|
|
19
19
|
import type { BindingAccumulator } from '../binding-accumulator.js';
|
|
20
|
-
import type { ParsedFile } from '../../../_shared/index.js';
|
|
20
|
+
import type { ParsedFile, SupportedLanguages } from '../../../_shared/index.js';
|
|
21
21
|
import type { ExtractedFetchCall, ExtractedRoute, ExtractedDecoratorRoute, ExtractedToolDef, ExtractedORMQuery, FetchWrapperDef } from '../workers/parse-worker.js';
|
|
22
22
|
import type { MutableSemanticModel } from '../model/index.js';
|
|
23
23
|
import type { ModuleConstants } from '../route-extractors/constant-resolver.js';
|
|
@@ -71,6 +71,12 @@ export interface ParseOutput {
|
|
|
71
71
|
* costing ~58s on a 1000-file repo).
|
|
72
72
|
*/
|
|
73
73
|
readonly parsedFiles: readonly ParsedFile[];
|
|
74
|
+
/**
|
|
75
|
+
* Content-derived language decisions for files such as `.h` whose extension
|
|
76
|
+
* is ambiguous. Scope resolution reuses this result rather than rereading
|
|
77
|
+
* source solely to classify it.
|
|
78
|
+
*/
|
|
79
|
+
readonly contentLanguageByPath: ReadonlyMap<string, SupportedLanguages | null>;
|
|
74
80
|
/**
|
|
75
81
|
* Repo-wide string constants harvested by the providers that declare
|
|
76
82
|
* `extractModuleConstants`, keyed by file path and already through each
|
|
@@ -625,6 +625,10 @@ function normalizeNodeLabel(kindStr) {
|
|
|
625
625
|
switch (kindStr.toLowerCase()) {
|
|
626
626
|
case 'class':
|
|
627
627
|
return 'Class';
|
|
628
|
+
case 'protocol':
|
|
629
|
+
return 'Protocol';
|
|
630
|
+
case 'category':
|
|
631
|
+
return 'Category';
|
|
628
632
|
case 'interface':
|
|
629
633
|
return 'Interface';
|
|
630
634
|
case 'enum':
|
|
@@ -615,6 +615,19 @@ export interface ScopeResolver {
|
|
|
615
615
|
readonly populateWorkspaceOwners?: (parsedFiles: readonly ParsedFile[], ctx: {
|
|
616
616
|
readonly fileContents: ReadonlyMap<string, string>;
|
|
617
617
|
}) => void;
|
|
618
|
+
/**
|
|
619
|
+
* Optional workspace-wide enrichment of extracted reference sites. Runs
|
|
620
|
+
* after all files have been extracted and before reference finalization.
|
|
621
|
+
* Use this when a per-file capture needs conservative facts from an
|
|
622
|
+
* imported sibling (for example a compile-time branch constant).
|
|
623
|
+
*/
|
|
624
|
+
readonly populateWorkspaceReferences?: (parsedFiles: ParsedFile[], ctx: {
|
|
625
|
+
readonly fileContents: ReadonlyMap<string, string>;
|
|
626
|
+
readonly treeCache?: {
|
|
627
|
+
get(filePath: string): unknown;
|
|
628
|
+
};
|
|
629
|
+
readonly resolutionConfig?: unknown;
|
|
630
|
+
}) => void;
|
|
618
631
|
/**
|
|
619
632
|
* Recognize a `super(...)`-style receiver text. Python returns
|
|
620
633
|
* `/^super\s*\(/.test(t)`. Java returns `t === 'super'`. C++ may
|
|
@@ -17,9 +17,11 @@
|
|
|
17
17
|
* registries only consult pre-finalize local bindings — imported names live
|
|
18
18
|
* in finalized bindings (the same reason free calls need
|
|
19
19
|
* `emitFreeCallFallback`) — so `resolveReferenceSites` skips `value-ref`
|
|
20
|
-
* sites and this pass resolves them post-finalize
|
|
21
|
-
* `
|
|
22
|
-
*
|
|
20
|
+
* sites and this pass resolves them post-finalize (see
|
|
21
|
+
* `resolveValueRefTarget` — Function/Method/Constructor only, the callable
|
|
22
|
+
* gate that keeps `{ port: DEFAULT_PORT }` from emitting anything, and
|
|
23
|
+
* receiver-aware so a qualified reference binds the owner it was written
|
|
24
|
+
* with).
|
|
23
25
|
*
|
|
24
26
|
* Precision posture (mirrors `emitInterfaceDispatchFor`):
|
|
25
27
|
* - reason `'property-dispatch'` keeps synthesized CALLS auditable;
|
|
@@ -35,11 +37,12 @@
|
|
|
35
37
|
* Language-neutral: consumes only `value-ref` sites (any language that
|
|
36
38
|
* emits the capture participates) and generic member-call sites.
|
|
37
39
|
*/
|
|
38
|
-
import type { ParsedFile } from '../../../../_shared/index.js';
|
|
40
|
+
import type { ParsedFile, ReferenceSite, SymbolDefinition } from '../../../../_shared/index.js';
|
|
39
41
|
import type { KnowledgeGraph } from '../../../graph/types.js';
|
|
40
42
|
import type { ScopeResolutionIndexes } from '../../model/scope-resolution-indexes.js';
|
|
41
43
|
import type { GraphNodeLookup } from '../graph-bridge/node-lookup.js';
|
|
42
44
|
import type { CalleeIdSink } from '../graph-bridge/callee-id-sink.js';
|
|
45
|
+
import type { SemanticModel } from '../../model/semantic-model.js';
|
|
43
46
|
/**
|
|
44
47
|
* Keys registered by more than this many distinct functions are skipped —
|
|
45
48
|
* dispatch through such a name says nothing about which function runs.
|
|
@@ -56,7 +59,77 @@ import type { CalleeIdSink } from '../graph-bridge/callee-id-sink.js';
|
|
|
56
59
|
export declare const MAX_PROPERTY_DISPATCH_FANOUT: number;
|
|
57
60
|
/** Below the 0.85 resolved baseline; same discount idea as interface-dispatch. */
|
|
58
61
|
export declare const PROPERTY_DISPATCH_CONFIDENCE = 0.7;
|
|
59
|
-
|
|
62
|
+
/**
|
|
63
|
+
* Resolve a `value-ref` site to the callable it names.
|
|
64
|
+
*
|
|
65
|
+
* Two shapes, and the difference matters:
|
|
66
|
+
*
|
|
67
|
+
* - BARE (`{ handler: onClick }`, `register(onTick)`) — the name is resolved
|
|
68
|
+
* up the lexical chain, which is what an unqualified name means. Be exact
|
|
69
|
+
* about what that walk does, because it is not a plain lexical lookup:
|
|
70
|
+
* `findCallableBindingInScope` applies the callable predicate WHILE walking,
|
|
71
|
+
* so a scope binding the name to a parameter or a local contributes nothing
|
|
72
|
+
* and the walk continues outward. A nearer non-callable binding is stepped
|
|
73
|
+
* over — the same shape the qualified path guards against below, unguarded
|
|
74
|
+
* here. Pre-existing (#2437) and out of scope for #3399; reachable in JS/TS
|
|
75
|
+
* (`function outer(handler) { return { h: handler }; }` beside a top-level
|
|
76
|
+
* `function handler`), and narrow in Zig, which rejects a local shadowing a
|
|
77
|
+
* container declaration.
|
|
78
|
+
*
|
|
79
|
+
* - QUALIFIED (`bridge.accessor(Element.getNamespaceUri, …)`) — the source
|
|
80
|
+
* WROTE the owner, so the lexical chain is the wrong instrument. It gives
|
|
81
|
+
* local bindings precedence (`walkScopeChain`), so a nested container
|
|
82
|
+
* holding its own `getNamespaceUri` answers first and the registration is
|
|
83
|
+
* attached to a DIFFERENT function than the one written. That is a wrong
|
|
84
|
+
* edge, not a missing one — strictly worse for a tool whose value is that
|
|
85
|
+
* its edges can be trusted — and it is reachable in any language that
|
|
86
|
+
* allows a same-named callable in a nested container, Zig included.
|
|
87
|
+
*
|
|
88
|
+
* So a qualified site resolves through its receiver: name the owner, then take
|
|
89
|
+
* the member off that owner. An owner is either a MODULE or a CLASS-like
|
|
90
|
+
* container — `utils.compare` and `Element.getNamespaceUri` are the same shape
|
|
91
|
+
* written against the two kinds of namespace a language has, and the member-call
|
|
92
|
+
* path already resolves both (receiver-bound-calls Case 1 / Case 2). Both are
|
|
93
|
+
* tried here, MODULE FIRST: see `findNamespaceValueRefTarget` for why a module
|
|
94
|
+
* receiver cannot simply be declined, and the comment on the call below for why
|
|
95
|
+
* the container channel must not go first.
|
|
96
|
+
*
|
|
97
|
+
* If neither channel names the owner, or the owner is named but owns no such
|
|
98
|
+
* callable, this DECLINES rather than falling back to the lexical walk.
|
|
99
|
+
*
|
|
100
|
+
* Be precise about what declining costs, because it is more than one reference:
|
|
101
|
+
* a site that emits NO edge leaves no evidence for `impact`'s value-reference
|
|
102
|
+
* probe to read, so the target keeps `epistemic: "exact"` — silence, not a
|
|
103
|
+
* hedge. That is why the module channel above exists rather than being waved
|
|
104
|
+
* through as "just a decline". What remains declined is the case where the
|
|
105
|
+
* written receiver names nothing this index knows at all (a Zig `@This()` alias
|
|
106
|
+
* whose name differs from its container's, an owner from outside the workspace):
|
|
107
|
+
* there the alternative is not a hedge either, it is a confident edge to a
|
|
108
|
+
* lexically-nearer function that the source did not name, and a wrong edge is
|
|
109
|
+
* strictly worse than a missing one for a tool whose value is that its edges can
|
|
110
|
+
* be trusted.
|
|
111
|
+
*
|
|
112
|
+
* `CALL_TARGET_TYPES`, not a hand-rolled label set: `findOwnedMember` also
|
|
113
|
+
* answers with FIELDS, and a field named like the member would otherwise
|
|
114
|
+
* register as if it were the callable.
|
|
115
|
+
*
|
|
116
|
+
* Exported for `bench/value-ref-resolution/measure.mjs`, which gates both the
|
|
117
|
+
* resolved-target SET and the per-site cost of the four channels above across a
|
|
118
|
+
* 4x file-count step. It is the per-site half of the pass, so timing it in
|
|
119
|
+
* isolation is what makes a workspace-size dependency visible; timing
|
|
120
|
+
* `emitPropertyDispatchCalls` would fold that signal into edge emission, and
|
|
121
|
+
* re-implementing the channel order in the bench would pin the bench's idea of
|
|
122
|
+
* this function rather than this function.
|
|
123
|
+
*/
|
|
124
|
+
export declare function resolveValueRefTarget(site: ReferenceSite, filePath: string, scopes: ScopeResolutionIndexes, model: SemanticModel, publishesImportedNames: boolean): SymbolDefinition | undefined;
|
|
125
|
+
export declare function emitPropertyDispatchCalls(graph: KnowledgeGraph, scopes: ScopeResolutionIndexes, parsedFiles: readonly ParsedFile[], nodeLookup: GraphNodeLookup, model: SemanticModel, calleeIdSink?: CalleeIdSink,
|
|
126
|
+
/**
|
|
127
|
+
* `ScopeResolver.namespaceExportsIncludeImportedNames`, forwarded rather than
|
|
128
|
+
* re-derived. The pass names no language; it asks the provider the same
|
|
129
|
+
* question `receiver-bound-calls` asks before resolving a namespace member,
|
|
130
|
+
* so the CALL and the REGISTRATION forms of `hub.fn` cannot disagree.
|
|
131
|
+
*/
|
|
132
|
+
publishesImportedNames?: boolean): {
|
|
60
133
|
usesEmitted: number;
|
|
61
134
|
callsEmitted: number;
|
|
62
135
|
skippedKeys: number;
|
|
@@ -17,9 +17,11 @@
|
|
|
17
17
|
* registries only consult pre-finalize local bindings — imported names live
|
|
18
18
|
* in finalized bindings (the same reason free calls need
|
|
19
19
|
* `emitFreeCallFallback`) — so `resolveReferenceSites` skips `value-ref`
|
|
20
|
-
* sites and this pass resolves them post-finalize
|
|
21
|
-
* `
|
|
22
|
-
*
|
|
20
|
+
* sites and this pass resolves them post-finalize (see
|
|
21
|
+
* `resolveValueRefTarget` — Function/Method/Constructor only, the callable
|
|
22
|
+
* gate that keeps `{ port: DEFAULT_PORT }` from emitting anything, and
|
|
23
|
+
* receiver-aware so a qualified reference binds the owner it was written
|
|
24
|
+
* with).
|
|
23
25
|
*
|
|
24
26
|
* Precision posture (mirrors `emitInterfaceDispatchFor`):
|
|
25
27
|
* - reason `'property-dispatch'` keeps synthesized CALLS auditable;
|
|
@@ -36,7 +38,9 @@
|
|
|
36
38
|
* emits the capture participates) and generic member-call sites.
|
|
37
39
|
*/
|
|
38
40
|
import { tryEmitEdge } from '../graph-bridge/edges.js';
|
|
39
|
-
import { findCallableBindingInScope } from '../scope/walkers.js';
|
|
41
|
+
import { findCallableBindingInScope, findClassBindingInScope, findOwnedMember, isNamespaceNameShadowed, isOwnerNameShadowedBySomethingElse, lookupBindingsAt, } from '../scope/walkers.js';
|
|
42
|
+
import { VALUE_REF_EDGE_REASON } from '../value-ref-edges.js';
|
|
43
|
+
import { CALL_TARGET_TYPES } from '../../model/symbol-table.js';
|
|
40
44
|
/**
|
|
41
45
|
* Keys registered by more than this many distinct functions are skipped —
|
|
42
46
|
* dispatch through such a name says nothing about which function runs.
|
|
@@ -56,7 +60,258 @@ export const MAX_PROPERTY_DISPATCH_FANOUT = (() => {
|
|
|
56
60
|
})();
|
|
57
61
|
/** Below the 0.85 resolved baseline; same discount idea as interface-dispatch. */
|
|
58
62
|
export const PROPERTY_DISPATCH_CONFIDENCE = 0.7;
|
|
59
|
-
|
|
63
|
+
/**
|
|
64
|
+
* Resolve a `value-ref` site to the callable it names.
|
|
65
|
+
*
|
|
66
|
+
* Two shapes, and the difference matters:
|
|
67
|
+
*
|
|
68
|
+
* - BARE (`{ handler: onClick }`, `register(onTick)`) — the name is resolved
|
|
69
|
+
* up the lexical chain, which is what an unqualified name means. Be exact
|
|
70
|
+
* about what that walk does, because it is not a plain lexical lookup:
|
|
71
|
+
* `findCallableBindingInScope` applies the callable predicate WHILE walking,
|
|
72
|
+
* so a scope binding the name to a parameter or a local contributes nothing
|
|
73
|
+
* and the walk continues outward. A nearer non-callable binding is stepped
|
|
74
|
+
* over — the same shape the qualified path guards against below, unguarded
|
|
75
|
+
* here. Pre-existing (#2437) and out of scope for #3399; reachable in JS/TS
|
|
76
|
+
* (`function outer(handler) { return { h: handler }; }` beside a top-level
|
|
77
|
+
* `function handler`), and narrow in Zig, which rejects a local shadowing a
|
|
78
|
+
* container declaration.
|
|
79
|
+
*
|
|
80
|
+
* - QUALIFIED (`bridge.accessor(Element.getNamespaceUri, …)`) — the source
|
|
81
|
+
* WROTE the owner, so the lexical chain is the wrong instrument. It gives
|
|
82
|
+
* local bindings precedence (`walkScopeChain`), so a nested container
|
|
83
|
+
* holding its own `getNamespaceUri` answers first and the registration is
|
|
84
|
+
* attached to a DIFFERENT function than the one written. That is a wrong
|
|
85
|
+
* edge, not a missing one — strictly worse for a tool whose value is that
|
|
86
|
+
* its edges can be trusted — and it is reachable in any language that
|
|
87
|
+
* allows a same-named callable in a nested container, Zig included.
|
|
88
|
+
*
|
|
89
|
+
* So a qualified site resolves through its receiver: name the owner, then take
|
|
90
|
+
* the member off that owner. An owner is either a MODULE or a CLASS-like
|
|
91
|
+
* container — `utils.compare` and `Element.getNamespaceUri` are the same shape
|
|
92
|
+
* written against the two kinds of namespace a language has, and the member-call
|
|
93
|
+
* path already resolves both (receiver-bound-calls Case 1 / Case 2). Both are
|
|
94
|
+
* tried here, MODULE FIRST: see `findNamespaceValueRefTarget` for why a module
|
|
95
|
+
* receiver cannot simply be declined, and the comment on the call below for why
|
|
96
|
+
* the container channel must not go first.
|
|
97
|
+
*
|
|
98
|
+
* If neither channel names the owner, or the owner is named but owns no such
|
|
99
|
+
* callable, this DECLINES rather than falling back to the lexical walk.
|
|
100
|
+
*
|
|
101
|
+
* Be precise about what declining costs, because it is more than one reference:
|
|
102
|
+
* a site that emits NO edge leaves no evidence for `impact`'s value-reference
|
|
103
|
+
* probe to read, so the target keeps `epistemic: "exact"` — silence, not a
|
|
104
|
+
* hedge. That is why the module channel above exists rather than being waved
|
|
105
|
+
* through as "just a decline". What remains declined is the case where the
|
|
106
|
+
* written receiver names nothing this index knows at all (a Zig `@This()` alias
|
|
107
|
+
* whose name differs from its container's, an owner from outside the workspace):
|
|
108
|
+
* there the alternative is not a hedge either, it is a confident edge to a
|
|
109
|
+
* lexically-nearer function that the source did not name, and a wrong edge is
|
|
110
|
+
* strictly worse than a missing one for a tool whose value is that its edges can
|
|
111
|
+
* be trusted.
|
|
112
|
+
*
|
|
113
|
+
* `CALL_TARGET_TYPES`, not a hand-rolled label set: `findOwnedMember` also
|
|
114
|
+
* answers with FIELDS, and a field named like the member would otherwise
|
|
115
|
+
* register as if it were the callable.
|
|
116
|
+
*
|
|
117
|
+
* Exported for `bench/value-ref-resolution/measure.mjs`, which gates both the
|
|
118
|
+
* resolved-target SET and the per-site cost of the four channels above across a
|
|
119
|
+
* 4x file-count step. It is the per-site half of the pass, so timing it in
|
|
120
|
+
* isolation is what makes a workspace-size dependency visible; timing
|
|
121
|
+
* `emitPropertyDispatchCalls` would fold that signal into edge emission, and
|
|
122
|
+
* re-implementing the channel order in the bench would pin the bench's idea of
|
|
123
|
+
* this function rather than this function.
|
|
124
|
+
*/
|
|
125
|
+
export function resolveValueRefTarget(site, filePath, scopes, model, publishesImportedNames) {
|
|
126
|
+
const receiverName = site.explicitReceiver?.name;
|
|
127
|
+
if (receiverName === undefined) {
|
|
128
|
+
return findCallableBindingInScope(site.inScope, site.name, scopes);
|
|
129
|
+
}
|
|
130
|
+
// NAMESPACE FIRST, and the order is load-bearing. `findClassBindingInScope`
|
|
131
|
+
// does not stop at the scope chain: when its `isClassLike` walk misses — and a
|
|
132
|
+
// namespace handle binds a Module, so it always misses — it falls back to
|
|
133
|
+
// `scopes.qualifiedNames`, a WORKSPACE-wide index, and answers with the unique
|
|
134
|
+
// def of that name anywhere in the repo. Trying it first therefore lets a
|
|
135
|
+
// same-named container in a file this one never imported preempt the `@import`
|
|
136
|
+
// this file actually wrote:
|
|
137
|
+
//
|
|
138
|
+
// const dom_utils = @import("dom_utils.zig"); // namespace-only module
|
|
139
|
+
// … bridge.accessor(dom_utils.compare, …) // → decoy.zig's compare
|
|
140
|
+
//
|
|
141
|
+
// The shadow guard below cannot catch it: the import binds at MODULE scope,
|
|
142
|
+
// which the guard treats as the floor. An import written in this file is the
|
|
143
|
+
// strongest statement about what the name means here, so it outranks a global
|
|
144
|
+
// guess — and when the handle is not an import of this file, this answers
|
|
145
|
+
// nothing and the container channel runs exactly as before.
|
|
146
|
+
const viaNamespace = findNamespaceValueRefTarget(site, filePath, receiverName, scopes, publishesImportedNames);
|
|
147
|
+
if (viaNamespace !== undefined) {
|
|
148
|
+
// `'owned'` is NOT "no answer" — it is "this receiver is a namespace handle
|
|
149
|
+
// this file wrote, and it names no callable member". The two must not be
|
|
150
|
+
// conflated, because falling through from the second one reaches
|
|
151
|
+
// `findClassBindingInScope`, whose miss path answers from the WORKSPACE-wide
|
|
152
|
+
// qualified-name index: a same-named container in a file this one never
|
|
153
|
+
// imported then supplies the member the written module does not have. That
|
|
154
|
+
// is a confident edge into an unrelated file, and the owner-shadow guard
|
|
155
|
+
// below does not stop it — a plain `const utils = @import("utils.zig");`
|
|
156
|
+
// records a namespace IMPORT EDGE, not a module-scope binding, so the guard
|
|
157
|
+
// sees nothing bound under the name and reads the container as unshadowed.
|
|
158
|
+
// Verified with a fixture rather than argued: `dom_utils.onlyOnDecoy`, where
|
|
159
|
+
// `dom_utils.zig` has no such member and `decoy.zig` declares a same-named
|
|
160
|
+
// struct that does, minted `JsApi → onlyOnDecoy` before this line existed.
|
|
161
|
+
//
|
|
162
|
+
// The file said which module it meant. If that module does not expose the
|
|
163
|
+
// name as a callable, the honest answer is no edge.
|
|
164
|
+
return viaNamespace === 'owned' ? undefined : viaNamespace;
|
|
165
|
+
}
|
|
166
|
+
const owner = findClassBindingInScope(site.inScope, receiverName, scopes);
|
|
167
|
+
if (owner !== undefined) {
|
|
168
|
+
// The container lookup is a CLASS-ONLY walk: `walkScopeChain` filters by
|
|
169
|
+
// `isClassLike`, so it steps over a nearer binding that is a value and keeps
|
|
170
|
+
// climbing — and past the scope chain entirely, into a qualified-name
|
|
171
|
+
// fallback that answers with the unique workspace definition of the name.
|
|
172
|
+
// `fn f(Ticker: u8) { register(Ticker.fire) }` in a file that neither
|
|
173
|
+
// declares nor imports `Ticker` therefore resolves to some other file's
|
|
174
|
+
// `Ticker` container. That is the wrong-edge failure R1-2 exists to prevent,
|
|
175
|
+
// arriving through the class channel instead of the lexical one, and a
|
|
176
|
+
// registration pointing at a function the source never named is worse than
|
|
177
|
+
// no registration at all.
|
|
178
|
+
//
|
|
179
|
+
// So the name has to still MEAN that container at this site. The namespace
|
|
180
|
+
// channel below asks the same question through `isNamespaceNameShadowed`;
|
|
181
|
+
// a container needs the variant that exempts the container ITSELF, because
|
|
182
|
+
// `fn make() { const Local = struct {…}; register(Local.go); }` binds the
|
|
183
|
+
// name locally to the very def we resolved, and reading that as its own
|
|
184
|
+
// shadow would suppress the resolutions this path exists to make.
|
|
185
|
+
if (isOwnerNameShadowedBySomethingElse(receiverName, owner, site.inScope, scopes))
|
|
186
|
+
return undefined;
|
|
187
|
+
const member = findOwnedMember(owner.nodeId, site.name, model);
|
|
188
|
+
if (member === undefined || !CALL_TARGET_TYPES.has(member.type))
|
|
189
|
+
return undefined;
|
|
190
|
+
return member;
|
|
191
|
+
}
|
|
192
|
+
return undefined;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* The second kind of owner: a namespace handle.
|
|
196
|
+
*
|
|
197
|
+
* `const utils = @import("utils.zig"); register(utils.compare);` — `utils` is a
|
|
198
|
+
* MODULE, not a class, so `findClassBindingInScope` answers nothing and the
|
|
199
|
+
* class path above declines. Declining here would be a silent hole rather than
|
|
200
|
+
* a conservative one: no USES edge is emitted, so
|
|
201
|
+
* `callableValueReferenceBoundaries` measures a real zero and `impact` on
|
|
202
|
+
* `compare` republishes `exact` — the very claim this feature exists to stop
|
|
203
|
+
* making. Nothing downstream can hedge on evidence that was never recorded.
|
|
204
|
+
*
|
|
205
|
+
* So resolve it, through the SAME channel the member-CALL path already trusts
|
|
206
|
+
* for `utils.compare()` (receiver-bound-calls Case 1): the file's namespace
|
|
207
|
+
* import edges name the target module, and the target module's own local
|
|
208
|
+
* module-scope bindings name its members. `utils.compare` and `utils.compare()`
|
|
209
|
+
* disagreeing about what `utils` is would be the anomaly.
|
|
210
|
+
*
|
|
211
|
+
* The same three guards Case 1 applies, for the same reasons:
|
|
212
|
+
* - a LOCAL declaration shadowing the handle suppresses the resolution
|
|
213
|
+
* (`isNamespaceNameShadowed`) — `fn f(utils: Decoy) { register(utils.compare) }`
|
|
214
|
+
* names the parameter's member, and resolving through the import would be a
|
|
215
|
+
* wrong edge rather than a missing one;
|
|
216
|
+
* - a locally declared member wins, and a name the target file merely IMPORTED
|
|
217
|
+
* counts only when the provider says its imports ARE its exports
|
|
218
|
+
* (`ScopeResolver.namespaceExportsIncludeImportedNames`). That opt-in is not
|
|
219
|
+
* a detail to skip: a Zig HUB — a file made only of re-exports, ghostty's
|
|
220
|
+
* `src/terminal/`, tigerbeetle's `stdx` — declares nothing, so requiring a
|
|
221
|
+
* local declaration declines every member reached through one. `hub.fn()`
|
|
222
|
+
* resolves and `register(hub.fn)` would not, and one name would mean two
|
|
223
|
+
* things depending on whether a `(` followed it. In languages that do not
|
|
224
|
+
* opt in, a module's imports are not its exports and this stays closed;
|
|
225
|
+
* - two distinct defs under one name resolve NOTHING. Never guess a namespace
|
|
226
|
+
* member — the whole point of reading the written receiver is precision.
|
|
227
|
+
*
|
|
228
|
+
* `CALL_TARGET_TYPES` gates the answer for the same reason the class path needs
|
|
229
|
+
* it: `utils.DEFAULT_PORT` is a module-scope binding too, and a registration
|
|
230
|
+
* table full of constants must keep emitting nothing.
|
|
231
|
+
*/
|
|
232
|
+
function findNamespaceValueRefTarget(site, filePath, receiverName, scopes, publishesImportedNames) {
|
|
233
|
+
const moduleScopeId = scopes.moduleScopes.get(filePath);
|
|
234
|
+
if (moduleScopeId === undefined)
|
|
235
|
+
return undefined;
|
|
236
|
+
const targetFiles = [];
|
|
237
|
+
for (const edge of scopes.imports.get(moduleScopeId) ?? []) {
|
|
238
|
+
if (edge.kind !== 'namespace' || edge.localName !== receiverName)
|
|
239
|
+
continue;
|
|
240
|
+
if (edge.targetFile === null)
|
|
241
|
+
continue;
|
|
242
|
+
if (!targetFiles.includes(edge.targetFile))
|
|
243
|
+
targetFiles.push(edge.targetFile);
|
|
244
|
+
}
|
|
245
|
+
if (targetFiles.length === 0)
|
|
246
|
+
return undefined;
|
|
247
|
+
if (isNamespaceNameShadowed(receiverName, site.inScope, scopes))
|
|
248
|
+
return undefined;
|
|
249
|
+
/** The unique callable `select` finds across every target file, or nothing. */
|
|
250
|
+
const uniqueMember = (select) => {
|
|
251
|
+
let picked;
|
|
252
|
+
for (const targetFile of targetFiles) {
|
|
253
|
+
const targetScopeId = scopes.moduleScopes.get(targetFile);
|
|
254
|
+
if (targetScopeId === undefined)
|
|
255
|
+
continue;
|
|
256
|
+
for (const ref of select(targetScopeId)) {
|
|
257
|
+
if (!CALL_TARGET_TYPES.has(ref.def.type))
|
|
258
|
+
continue;
|
|
259
|
+
if (picked !== undefined && picked.nodeId !== ref.def.nodeId)
|
|
260
|
+
return 'ambiguous';
|
|
261
|
+
picked = ref.def;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return picked;
|
|
265
|
+
};
|
|
266
|
+
// A locally declared member first — same precedence `findExportedDef` states
|
|
267
|
+
// and `walkScopeChain` applies: what the target file DECLARED beats what it
|
|
268
|
+
// merely re-published.
|
|
269
|
+
const localRefs = (scope) => (scopes.bindings.get(scope)?.get(site.name) ?? []).filter((ref) => ref.origin === 'local');
|
|
270
|
+
const local = uniqueMember(localRefs);
|
|
271
|
+
if (local === 'ambiguous')
|
|
272
|
+
return 'owned';
|
|
273
|
+
if (local !== undefined)
|
|
274
|
+
return local;
|
|
275
|
+
if (!publishesImportedNames)
|
|
276
|
+
return 'owned';
|
|
277
|
+
// PRECEDENCE IS DECIDED BEFORE THE TYPE GATE, not by it. `uniqueMember`
|
|
278
|
+
// applies `CALL_TARGET_TYPES` while it selects, so a target file declaring a
|
|
279
|
+
// NON-callable under this name answers `undefined` above and would otherwise
|
|
280
|
+
// fall through to the published channel — publishing a re-exported callable
|
|
281
|
+
// under a name the module's own declaration owns. `findExportedDef` does not
|
|
282
|
+
// do that: it returns any local def it finds and lets its caller's type gate
|
|
283
|
+
// reject it, so `findExportedDefIncludingImportedNames` never reaches the
|
|
284
|
+
// imported names for a name the file declares. Same rule here, so `x.f` and
|
|
285
|
+
// `x.f()` cannot disagree about which module owns the name.
|
|
286
|
+
//
|
|
287
|
+
// Not reachable through valid Zig today — a container cannot declare a name
|
|
288
|
+
// twice, so one target file cannot hold both spellings, and Zig is the only
|
|
289
|
+
// provider that sets `namespaceExportsIncludeImportedNames`. It becomes
|
|
290
|
+
// reachable the moment a second provider opts in, or a receiver binds more
|
|
291
|
+
// than one target file; the guard is one `some` and the alternative failure
|
|
292
|
+
// is a confident edge into the wrong module.
|
|
293
|
+
const declaredLocally = targetFiles.some((targetFile) => {
|
|
294
|
+
const targetScopeId = scopes.moduleScopes.get(targetFile);
|
|
295
|
+
return targetScopeId !== undefined && localRefs(targetScopeId).length > 0;
|
|
296
|
+
});
|
|
297
|
+
if (declaredLocally)
|
|
298
|
+
return 'owned';
|
|
299
|
+
// Then a name the target file publishes but did not declare — the hub case.
|
|
300
|
+
// `lookupBindingsAt`, not `scopes.bindings`, because a hub's module scope owns
|
|
301
|
+
// no local binding for these names and the finalized/augmented channel is the
|
|
302
|
+
// only place they exist; the same read `findExportedDefIncludingImportedNames`
|
|
303
|
+
// does for the CALL form.
|
|
304
|
+
const published = uniqueMember((scope) => lookupBindingsAt(scope, site.name, scopes).filter((ref) => ref.origin === 'import' || ref.origin === 'namespace' || ref.origin === 'reexport'));
|
|
305
|
+
return published === 'ambiguous' || published === undefined ? 'owned' : published;
|
|
306
|
+
}
|
|
307
|
+
export function emitPropertyDispatchCalls(graph, scopes, parsedFiles, nodeLookup, model, calleeIdSink,
|
|
308
|
+
/**
|
|
309
|
+
* `ScopeResolver.namespaceExportsIncludeImportedNames`, forwarded rather than
|
|
310
|
+
* re-derived. The pass names no language; it asks the provider the same
|
|
311
|
+
* question `receiver-bound-calls` asks before resolving a namespace member,
|
|
312
|
+
* so the CALL and the REGISTRATION forms of `hub.fn` cannot disagree.
|
|
313
|
+
*/
|
|
314
|
+
publishesImportedNames = false) {
|
|
60
315
|
const seen = new Set();
|
|
61
316
|
let usesEmitted = 0;
|
|
62
317
|
// Sweep 1 — resolve every value-ref site: emit the USES registration edge
|
|
@@ -66,10 +321,10 @@ export function emitPropertyDispatchCalls(graph, scopes, parsedFiles, nodeLookup
|
|
|
66
321
|
for (const site of parsed.referenceSites) {
|
|
67
322
|
if (site.kind !== 'value-ref')
|
|
68
323
|
continue;
|
|
69
|
-
const def =
|
|
324
|
+
const def = resolveValueRefTarget(site, parsed.filePath, scopes, model, publishesImportedNames);
|
|
70
325
|
if (def === undefined)
|
|
71
326
|
continue;
|
|
72
|
-
const ok = tryEmitEdge(graph, scopes, nodeLookup, site, def,
|
|
327
|
+
const ok = tryEmitEdge(graph, scopes, nodeLookup, site, def, VALUE_REF_EDGE_REASON, seen);
|
|
73
328
|
if (ok)
|
|
74
329
|
usesEmitted++;
|
|
75
330
|
if (site.propertyKey === undefined)
|
|
@@ -55,6 +55,7 @@ const NOOP_OUTPUT = Object.freeze({
|
|
|
55
55
|
/** Select source files that must be materialized for one resolver pass. */
|
|
56
56
|
export function selectScopeSourcePathsToRead(provider, primaryFilePaths, preExtractedByPath) {
|
|
57
57
|
const hasPostExtractHooks = provider.populateWorkspaceOwners !== undefined ||
|
|
58
|
+
provider.populateWorkspaceReferences !== undefined ||
|
|
58
59
|
provider.populateNamespaceSiblings !== undefined ||
|
|
59
60
|
provider.populateRangeBindings !== undefined ||
|
|
60
61
|
provider.emitPostResolutionEdges !== undefined;
|
|
@@ -80,7 +81,7 @@ export const scopeResolutionPhase = {
|
|
|
80
81
|
logHeapProbe('scopeResolution-enter');
|
|
81
82
|
const { scannedFiles } = getPhaseOutput(deps, 'structure');
|
|
82
83
|
const parseOutput = getPhaseOutput(deps, 'parse');
|
|
83
|
-
const { model, parsedFiles: workerParsedFiles } = parseOutput;
|
|
84
|
+
const { model, parsedFiles: workerParsedFiles, contentLanguageByPath } = parseOutput;
|
|
84
85
|
const scopeExtractionFailures = new Set(parseOutput.scopeExtractionFailures);
|
|
85
86
|
// SemanticModel populated during `parse`: scope-resolution consumes
|
|
86
87
|
// TypeRegistry / MethodRegistry / SymbolTable lookups instead of
|
|
@@ -146,7 +147,9 @@ export const scopeResolutionPhase = {
|
|
|
146
147
|
// Language-agnostic: keyed by the provider-supplied SupportedLanguages value.
|
|
147
148
|
const filesByLang = new Map();
|
|
148
149
|
for (const f of scannedFiles) {
|
|
149
|
-
const fileLang =
|
|
150
|
+
const fileLang = contentLanguageByPath.has(f.path)
|
|
151
|
+
? (contentLanguageByPath.get(f.path) ?? null)
|
|
152
|
+
: getLanguageFromFilename(f.path);
|
|
150
153
|
if (fileLang === null)
|
|
151
154
|
continue;
|
|
152
155
|
// Tree-sitter providers require an available grammar. Standalone regex
|
|
@@ -26,6 +26,7 @@ import { swiftScopeResolver } from '../../languages/swift/scope-resolver.js';
|
|
|
26
26
|
import { dartScopeResolver } from '../../languages/dart/scope-resolver.js';
|
|
27
27
|
import { vueScopeResolver } from '../../languages/vue/scope-resolver.js';
|
|
28
28
|
import { zigScopeResolver } from '../../languages/zig/scope-resolver.js';
|
|
29
|
+
import { objectiveCScopeResolver } from '../../languages/objective-c/scope-resolver.js';
|
|
29
30
|
/** Map of `SupportedLanguages` → `ScopeResolver`. The scope-resolution phase
|
|
30
31
|
* iterates this map directly — every registered resolver runs. This is the
|
|
31
32
|
* single source of truth for which languages resolve via scope-resolution. */
|
|
@@ -47,4 +48,5 @@ export const SCOPE_RESOLVERS = new Map([
|
|
|
47
48
|
[SupportedLanguages.Dart, dartScopeResolver],
|
|
48
49
|
[SupportedLanguages.Vue, vueScopeResolver],
|
|
49
50
|
[SupportedLanguages.Zig, zigScopeResolver],
|
|
51
|
+
[SupportedLanguages.ObjectiveC, objectiveCScopeResolver],
|
|
50
52
|
]);
|
|
@@ -312,6 +312,11 @@ export function runScopeResolution(input, provider) {
|
|
|
312
312
|
}
|
|
313
313
|
logHeapProbe('sr-extract-end', `lang=${provider.language} parsedFiles=${parsedFiles.length} preExtractedHits=${preExtractedHits} skipped=${filesSkipped}`);
|
|
314
314
|
provider.populateWorkspaceOwners?.(parsedFiles, { fileContents: getFileContents() });
|
|
315
|
+
provider.populateWorkspaceReferences?.(parsedFiles, {
|
|
316
|
+
fileContents: getFileContents(),
|
|
317
|
+
treeCache,
|
|
318
|
+
resolutionConfig: input.resolutionConfig,
|
|
319
|
+
});
|
|
315
320
|
// A callable-flow-only provider has no reason to build the whole-graph
|
|
316
321
|
// lookup or finalize ordinary references when none of its files emitted a
|
|
317
322
|
// callable fact. This keeps the opt-in path proportional to source scanning
|
|
@@ -760,7 +765,11 @@ export function runScopeResolution(input, provider) {
|
|
|
760
765
|
// edges already emitted for a site retain ownership.
|
|
761
766
|
const propertyDispatch = callableFlowOnly
|
|
762
767
|
? { usesEmitted: 0, callsEmitted: 0, skippedKeys: 0, skippedKeyNames: [] }
|
|
763
|
-
: emitPropertyDispatchCalls(graph, indexes, emitParsedFiles, postHeritageNodeLookup, calleeIdAccumulator
|
|
768
|
+
: emitPropertyDispatchCalls(graph, indexes, emitParsedFiles, postHeritageNodeLookup, readonlyModel, calleeIdAccumulator,
|
|
769
|
+
// Same provider hook the receiver-bound pass consults for a namespace
|
|
770
|
+
// member (Case 1). Without it a hub module's re-exported callable
|
|
771
|
+
// resolves when CALLED and declines when REGISTERED.
|
|
772
|
+
provider.namespaceExportsIncludeImportedNames === true);
|
|
764
773
|
if (propertyDispatch.skippedKeys > 0) {
|
|
765
774
|
// Never drop dispatch coverage silently: a hook table larger than the
|
|
766
775
|
// fan-out cap means member calls through those keys get no synthesized
|
|
@@ -176,6 +176,41 @@ export declare function isReceiverOwnedButUnbound(startScope: ScopeId, receiverN
|
|
|
176
176
|
* this walk wants.
|
|
177
177
|
*/
|
|
178
178
|
export declare function isNamespaceNameShadowed(namespaceName: string, inScope: ScopeId, scopes: ScopeResolutionIndexes): boolean;
|
|
179
|
+
/**
|
|
180
|
+
* Does something between `inScope` and its module scope bind `name` to
|
|
181
|
+
* ANYTHING other than `def`?
|
|
182
|
+
*
|
|
183
|
+
* `isNamespaceNameShadowed` asks the same question for a namespace handle,
|
|
184
|
+
* where any local binding of the name is by definition not the import. A
|
|
185
|
+
* CONTAINER receiver needs the extra clause: the container may itself be the
|
|
186
|
+
* local declaration (`fn make() { const Local = struct {…}; … Local.go … }`),
|
|
187
|
+
* and reading that as its own shadow would suppress exactly the resolutions it
|
|
188
|
+
* is meant to permit — the #2723 mistake, one channel over.
|
|
189
|
+
*
|
|
190
|
+
* So a scope that binds the name answers immediately, and the answer is "not
|
|
191
|
+
* shadowed" only when one of that scope's bindings IS `def`. A name bound in a
|
|
192
|
+
* nearer scope to something else — a parameter, a local, a type binding — wins
|
|
193
|
+
* the lexical race, which is the whole point: `findClassBindingInScope` filters
|
|
194
|
+
* the chain by `isClassLike` and therefore cannot see that it lost it.
|
|
195
|
+
*
|
|
196
|
+
* The MODULE scope is inspected too, unlike `isNamespaceNameShadowed`, and the
|
|
197
|
+
* exemption is what makes that safe. That guard stops one rung short because a
|
|
198
|
+
* namespace import writes its own name into the module scope and would read as
|
|
199
|
+
* its own shadow (#2723); here the owner is compared by identity, so the binding
|
|
200
|
+
* that IS the owner exempts itself and only a binding to something ELSE answers
|
|
201
|
+
* `true`. Stopping short would leave the exact hole this walk exists to close:
|
|
202
|
+
* `findClassBindingInScope` steps over a module-scope binding that is not
|
|
203
|
+
* class-like and then answers from a WORKSPACE-wide qualified-name index, so
|
|
204
|
+
* `const Gauge = @import("other.zig").SOME_CONST;` in a file that never imports
|
|
205
|
+
* `Gauge.zig` would still resolve `Gauge.read` to that file's container.
|
|
206
|
+
* `lookupBindingsAt` is used at that scope and only there, because an imported
|
|
207
|
+
* alias lives in the finalized channel rather than in `scope.bindings`.
|
|
208
|
+
*
|
|
209
|
+
* Fail-closed like its sibling: a missing scope or a parent cycle answers
|
|
210
|
+
* `true`, because suppressing a resolution costs a missing edge while trusting a
|
|
211
|
+
* corrupt chain costs a wrong one.
|
|
212
|
+
*/
|
|
213
|
+
export declare function isOwnerNameShadowedBySomethingElse(name: string, def: SymbolDefinition, inScope: ScopeId, scopes: ScopeResolutionIndexes): boolean;
|
|
179
214
|
export declare function findReceiverTypeBinding(startScope: ScopeId, receiverName: string, scopes: ScopeResolutionIndexes): TypeRef | undefined;
|
|
180
215
|
/**
|
|
181
216
|
* Resolve a typeBinding for `name` from the per-namespace channel
|
|
@@ -195,9 +230,10 @@ export declare function moduleScopeIdOf(startScope: ScopeId, scopes: ScopeResolu
|
|
|
195
230
|
/**
|
|
196
231
|
* Look up a class-like binding by name in the given scope's chain.
|
|
197
232
|
*
|
|
198
|
-
* "Class-like" covers `Class | Interface | Struct | Record | Enum |
|
|
199
|
-
*
|
|
200
|
-
*
|
|
233
|
+
* "Class-like" covers `Class | Interface | Struct | Record | Enum | Trait |
|
|
234
|
+
* Protocol | Category` via the shared `isClassLike` predicate. Objective-C
|
|
235
|
+
* protocol and category definitions are graph-side containers rather than
|
|
236
|
+
* `@scope.class` captures.
|
|
201
237
|
*
|
|
202
238
|
* Walks the scope chain upward and consults TWO sources at each step:
|
|
203
239
|
* 1. `scope.bindings` — populated during scope-extraction Pass 2 with
|