gitnexus 1.6.6-rc.124 → 1.6.6-rc.125
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.
|
@@ -49,13 +49,18 @@
|
|
|
49
49
|
*
|
|
50
50
|
* ## State lifecycle
|
|
51
51
|
*
|
|
52
|
-
*
|
|
53
|
-
* `clearCppAdlState()` (called from
|
|
52
|
+
* Five pieces of module-level state populated per pipeline invocation, all
|
|
53
|
+
* reset together by `clearCppAdlState()` (called from
|
|
54
|
+
* `cppScopeResolver.loadResolutionConfig`, alongside `clearFileLocalNames` —
|
|
55
|
+
* NOT from `clearFileLocalNames` itself), grouped by when they fill:
|
|
54
56
|
*
|
|
55
57
|
* - `argInfoBySite` — per-call-site argument shape (capture-time)
|
|
56
58
|
* - `noAdlSites` — call sites with parenthesized function (capture-time)
|
|
57
59
|
* - `classToNamespaceQualifiedName` — class def → its enclosing namespace
|
|
58
60
|
* qualified name (`populateCppAssociatedNamespaces` time)
|
|
61
|
+
* - `adlIndex` / `adlIndexSource` — the lazily-built candidate index and the
|
|
62
|
+
* `parsedFiles` reference it was built from (first-`pickCppAdlCandidates`
|
|
63
|
+
* time; see `ensureAdlIndex`)
|
|
59
64
|
*
|
|
60
65
|
* The class→namespace map uses qualified names (not scope IDs) because
|
|
61
66
|
* C++ namespaces are open: `namespace N { ... }` in file A and
|
|
@@ -94,14 +99,59 @@ export interface CppAdlArgInfo {
|
|
|
94
99
|
* referenced Function/Method def's parameter and return types. */
|
|
95
100
|
readonly functionRefText?: string;
|
|
96
101
|
}
|
|
102
|
+
/**
|
|
103
|
+
* ADL candidate index — built **once** per pipeline run from
|
|
104
|
+
* `(scopes, parsedFiles)` and reused by every call site.
|
|
105
|
+
*
|
|
106
|
+
* The legacy `pickCppAdlCandidates` re-scanned all parsed files (rebuilding a
|
|
107
|
+
* per-file `scopesById` map each time), all workspace defs (for the
|
|
108
|
+
* class-by-simple-name lookup), and used an O(scopes²) child-scope walk for
|
|
109
|
+
* hidden friends — once **per unresolved call site**. With hundreds of
|
|
110
|
+
* thousands of unresolved C++ sites that made the scope-resolution emit phase
|
|
111
|
+
* super-linear (observed ~6.7h on a large repo). This index moves all of that
|
|
112
|
+
* work to a single pass; per-site cost drops to O(associated namespaces).
|
|
113
|
+
*/
|
|
114
|
+
export interface AdlCandidateIndex {
|
|
115
|
+
/** simple name → class-like defs (Class/Struct/Interface/Enum), preserving
|
|
116
|
+
* `scopes.defs.byId` iteration order so first-match / ambiguous semantics
|
|
117
|
+
* match the legacy linear scan. */
|
|
118
|
+
readonly classDefsBySimple: Map<string, SymbolDefinition[]>;
|
|
119
|
+
/** namespace QName → simple name → callable defs owned by that namespace,
|
|
120
|
+
* with inline-namespace transparency (inline-ns defs are also registered
|
|
121
|
+
* under the parent namespace's QName). */
|
|
122
|
+
readonly nsCandidates: Map<string, Map<string, SymbolDefinition[]>>;
|
|
123
|
+
/** associated-class enclosing-namespace QName → simple name → hidden-friend
|
|
124
|
+
* and class-member callable defs. */
|
|
125
|
+
readonly friendCandidates: Map<string, Map<string, SymbolDefinition[]>>;
|
|
126
|
+
/** namespace QName (own) → simple name → Function/Method defs, for the
|
|
127
|
+
* qualified function-reference ADL path. */
|
|
128
|
+
readonly nsFunctionsByQName: Map<string, Map<string, SymbolDefinition[]>>;
|
|
129
|
+
/** simple name → Function/Method defs across all namespaces, for the
|
|
130
|
+
* unqualified function-reference ADL path. */
|
|
131
|
+
readonly nsFunctionsBySimple: Map<string, SymbolDefinition[]>;
|
|
132
|
+
/** nodeId → visitation sequence number, used to merge per-namespace buckets
|
|
133
|
+
* back into the exact legacy candidate order (file-major; namespace defs
|
|
134
|
+
* before friend/member defs within a file). */
|
|
135
|
+
readonly seqByNodeId: Map<string, number>;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Return the nodeIds present in the index's candidate buckets
|
|
139
|
+
* (`nsCandidates` + `friendCandidates`) but missing from `seqByNodeId`, each
|
|
140
|
+
* reported once. Empty array means the seq-coverage invariant holds — which it
|
|
141
|
+
* must, since `buildAdlIndex` assigns a seq to every callable def in the same
|
|
142
|
+
* block that buckets it. Exported for the dev-gated guard in `buildAdlIndex`
|
|
143
|
+
* and its unit test.
|
|
144
|
+
*/
|
|
145
|
+
export declare function validateAdlSeqCoverage(idx: AdlCandidateIndex): string[];
|
|
97
146
|
/** Record per-call-site argument info. Called once per call site from
|
|
98
147
|
* `emitCppScopeCaptures`. */
|
|
99
148
|
export declare function markCppAdlSiteArgs(filePath: string, line: number, col: number, args: readonly CppAdlArgInfo[]): void;
|
|
100
149
|
/** Mark a call site as ADL-suppressed (function child wrapped in
|
|
101
150
|
* `parenthesized_expression`, e.g. `(f)(s)`). */
|
|
102
151
|
export declare function markCppAdlSiteNoAdl(filePath: string, line: number, col: number): void;
|
|
103
|
-
/** Clear ADL state. Called from `
|
|
104
|
-
* per-pipeline state is
|
|
152
|
+
/** Clear ADL state. Called from `cppScopeResolver.loadResolutionConfig`
|
|
153
|
+
* (alongside `clearFileLocalNames`) so all C++ resolver per-pipeline state is
|
|
154
|
+
* reset together at the start of each resolution pass. */
|
|
105
155
|
export declare function clearCppAdlState(): void;
|
|
106
156
|
/**
|
|
107
157
|
* Walk `parsed.scopes` to record each Class def's enclosing namespace
|
|
@@ -49,13 +49,18 @@
|
|
|
49
49
|
*
|
|
50
50
|
* ## State lifecycle
|
|
51
51
|
*
|
|
52
|
-
*
|
|
53
|
-
* `clearCppAdlState()` (called from
|
|
52
|
+
* Five pieces of module-level state populated per pipeline invocation, all
|
|
53
|
+
* reset together by `clearCppAdlState()` (called from
|
|
54
|
+
* `cppScopeResolver.loadResolutionConfig`, alongside `clearFileLocalNames` —
|
|
55
|
+
* NOT from `clearFileLocalNames` itself), grouped by when they fill:
|
|
54
56
|
*
|
|
55
57
|
* - `argInfoBySite` — per-call-site argument shape (capture-time)
|
|
56
58
|
* - `noAdlSites` — call sites with parenthesized function (capture-time)
|
|
57
59
|
* - `classToNamespaceQualifiedName` — class def → its enclosing namespace
|
|
58
60
|
* qualified name (`populateCppAssociatedNamespaces` time)
|
|
61
|
+
* - `adlIndex` / `adlIndexSource` — the lazily-built candidate index and the
|
|
62
|
+
* `parsedFiles` reference it was built from (first-`pickCppAdlCandidates`
|
|
63
|
+
* time; see `ensureAdlIndex`)
|
|
59
64
|
*
|
|
60
65
|
* The class→namespace map uses qualified names (not scope IDs) because
|
|
61
66
|
* C++ namespaces are open: `namespace N { ... }` in file A and
|
|
@@ -68,9 +73,216 @@ import { isCppInlineNamespaceScope } from './inline-namespaces.js';
|
|
|
68
73
|
const argInfoBySite = new Map();
|
|
69
74
|
const noAdlSites = new Set();
|
|
70
75
|
const classToNamespaceQualifiedName = new Map();
|
|
76
|
+
let adlIndex;
|
|
77
|
+
let adlIndexSource;
|
|
71
78
|
function siteKey(filePath, line, col) {
|
|
72
79
|
return `${filePath}:${line}:${col}`;
|
|
73
80
|
}
|
|
81
|
+
/** Last segment of a dotted qualified name (matches legacy inline expression). */
|
|
82
|
+
function adlSimpleName(def) {
|
|
83
|
+
return def.qualifiedName?.split('.').pop() ?? def.qualifiedName ?? '';
|
|
84
|
+
}
|
|
85
|
+
function isAdlCallableType(type) {
|
|
86
|
+
return type === 'Function' || type === 'Method' || type === 'Constructor';
|
|
87
|
+
}
|
|
88
|
+
function pushNested(map, outerKey, innerKey, def) {
|
|
89
|
+
let inner = map.get(outerKey);
|
|
90
|
+
if (inner === undefined) {
|
|
91
|
+
inner = new Map();
|
|
92
|
+
map.set(outerKey, inner);
|
|
93
|
+
}
|
|
94
|
+
let arr = inner.get(innerKey);
|
|
95
|
+
if (arr === undefined) {
|
|
96
|
+
arr = [];
|
|
97
|
+
inner.set(innerKey, arr);
|
|
98
|
+
}
|
|
99
|
+
arr.push(def);
|
|
100
|
+
}
|
|
101
|
+
function pushFlat(map, key, def) {
|
|
102
|
+
let arr = map.get(key);
|
|
103
|
+
if (arr === undefined) {
|
|
104
|
+
arr = [];
|
|
105
|
+
map.set(key, arr);
|
|
106
|
+
}
|
|
107
|
+
arr.push(def);
|
|
108
|
+
}
|
|
109
|
+
/** Build the ADL candidate index in a single pass over the workspace. The
|
|
110
|
+
* visitation order (file-major; per file, all namespace scopes before all
|
|
111
|
+
* class scopes; ownedDefs in declaration order) mirrors the legacy push
|
|
112
|
+
* order so `seqByNodeId` reconstructs identical candidate ordering. */
|
|
113
|
+
function buildAdlIndex(scopes, parsedFiles) {
|
|
114
|
+
const idx = {
|
|
115
|
+
classDefsBySimple: new Map(),
|
|
116
|
+
nsCandidates: new Map(),
|
|
117
|
+
friendCandidates: new Map(),
|
|
118
|
+
nsFunctionsByQName: new Map(),
|
|
119
|
+
nsFunctionsBySimple: new Map(),
|
|
120
|
+
seqByNodeId: new Map(),
|
|
121
|
+
};
|
|
122
|
+
// (1) class-like defs by simple name — preserve byId order so arr[0] is the
|
|
123
|
+
// legacy `firstMatch` and `arr.length > 1` is the legacy `ambiguous`.
|
|
124
|
+
for (const def of scopes.defs.byId.values()) {
|
|
125
|
+
if (def.type !== 'Class' &&
|
|
126
|
+
def.type !== 'Struct' &&
|
|
127
|
+
def.type !== 'Interface' &&
|
|
128
|
+
def.type !== 'Enum')
|
|
129
|
+
continue;
|
|
130
|
+
pushFlat(idx.classDefsBySimple, adlSimpleName(def), def);
|
|
131
|
+
}
|
|
132
|
+
let seq = 0;
|
|
133
|
+
for (const parsed of parsedFiles) {
|
|
134
|
+
const scopesById = new Map();
|
|
135
|
+
for (const sc of parsed.scopes)
|
|
136
|
+
scopesById.set(sc.id, sc);
|
|
137
|
+
// parent → children, built once (replaces the legacy O(scopes²) walk).
|
|
138
|
+
const childrenByParent = new Map();
|
|
139
|
+
for (const sc of parsed.scopes) {
|
|
140
|
+
if (sc.parent === null)
|
|
141
|
+
continue;
|
|
142
|
+
let kids = childrenByParent.get(sc.parent);
|
|
143
|
+
if (kids === undefined) {
|
|
144
|
+
kids = [];
|
|
145
|
+
childrenByParent.set(sc.parent, kids);
|
|
146
|
+
}
|
|
147
|
+
kids.push(sc);
|
|
148
|
+
}
|
|
149
|
+
// PASS A — namespace-owned candidates (+ function-reference indexes).
|
|
150
|
+
for (const scope of parsed.scopes) {
|
|
151
|
+
if (scope.kind !== 'Namespace')
|
|
152
|
+
continue;
|
|
153
|
+
const qName = computeNamespaceQName(scope, scopesById);
|
|
154
|
+
// Registration keys reproduce the legacy membership test: own QName
|
|
155
|
+
// always; for an inline namespace child of a Namespace, also the parent
|
|
156
|
+
// QName (ISO C++ inline-namespace transparency for ADL).
|
|
157
|
+
const keys = [];
|
|
158
|
+
if (qName !== '')
|
|
159
|
+
keys.push(qName);
|
|
160
|
+
if (isCppInlineNamespaceScope(scope.id)) {
|
|
161
|
+
const parentScope = scope.parent !== null ? scopesById.get(scope.parent) : undefined;
|
|
162
|
+
if (parentScope !== undefined && parentScope.kind === 'Namespace') {
|
|
163
|
+
const parentQName = computeNamespaceQName(parentScope, scopesById);
|
|
164
|
+
if (parentQName !== '' && parentQName !== qName)
|
|
165
|
+
keys.push(parentQName);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
for (const def of scope.ownedDefs) {
|
|
169
|
+
if (def.type === 'Function' || def.type === 'Method') {
|
|
170
|
+
const sn = adlSimpleName(def);
|
|
171
|
+
pushFlat(idx.nsFunctionsBySimple, sn, def);
|
|
172
|
+
if (qName !== '')
|
|
173
|
+
pushNested(idx.nsFunctionsByQName, qName, sn, def);
|
|
174
|
+
}
|
|
175
|
+
if (!isAdlCallableType(def.type))
|
|
176
|
+
continue;
|
|
177
|
+
const s = seq++;
|
|
178
|
+
idx.seqByNodeId.set(def.nodeId, s);
|
|
179
|
+
const sn = adlSimpleName(def);
|
|
180
|
+
for (const key of keys)
|
|
181
|
+
pushNested(idx.nsCandidates, key, sn, def);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
// PASS B — hidden-friend + class-member candidates for associated classes.
|
|
185
|
+
for (const scope of parsed.scopes) {
|
|
186
|
+
if (scope.kind !== 'Class')
|
|
187
|
+
continue;
|
|
188
|
+
// Enclosing-namespace QName(s) of the class def(s) in this scope.
|
|
189
|
+
const classNsKeys = new Set();
|
|
190
|
+
for (const def of scope.ownedDefs) {
|
|
191
|
+
if (def.type !== 'Class' && def.type !== 'Struct' && def.type !== 'Interface')
|
|
192
|
+
continue;
|
|
193
|
+
const nsQName = classToNamespaceQualifiedName.get(def.nodeId);
|
|
194
|
+
if (nsQName !== undefined)
|
|
195
|
+
classNsKeys.add(nsQName);
|
|
196
|
+
}
|
|
197
|
+
if (classNsKeys.size === 0)
|
|
198
|
+
continue;
|
|
199
|
+
// Friend functions: callable defs in child Function scopes.
|
|
200
|
+
for (const childScope of childrenByParent.get(scope.id) ?? []) {
|
|
201
|
+
if (childScope.kind !== 'Function')
|
|
202
|
+
continue;
|
|
203
|
+
for (const def of childScope.ownedDefs) {
|
|
204
|
+
if (!isAdlCallableType(def.type))
|
|
205
|
+
continue;
|
|
206
|
+
const s = seq++;
|
|
207
|
+
idx.seqByNodeId.set(def.nodeId, s);
|
|
208
|
+
const sn = adlSimpleName(def);
|
|
209
|
+
for (const key of classNsKeys)
|
|
210
|
+
pushNested(idx.friendCandidates, key, sn, def);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
// Class-member callables.
|
|
214
|
+
for (const def of scope.ownedDefs) {
|
|
215
|
+
if (!isAdlCallableType(def.type))
|
|
216
|
+
continue;
|
|
217
|
+
const s = seq++;
|
|
218
|
+
idx.seqByNodeId.set(def.nodeId, s);
|
|
219
|
+
const sn = adlSimpleName(def);
|
|
220
|
+
for (const key of classNsKeys)
|
|
221
|
+
pushNested(idx.friendCandidates, key, sn, def);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
// Dev/test-only invariant guard: every def bucketed into nsCandidates/
|
|
226
|
+
// friendCandidates must have a seqByNodeId entry, otherwise the `?? 0`
|
|
227
|
+
// fallback in pickCppAdlCandidates could collapse two seq-0 candidates and
|
|
228
|
+
// silently drop a CALLS edge. Gated like the rest of the resolver's opt-in
|
|
229
|
+
// validation (see contract/scope-resolver.ts and reconcile-ownership.ts):
|
|
230
|
+
// active in dev/test, off in production and when VALIDATE_SEMANTIC_MODEL=0.
|
|
231
|
+
if (process.env.NODE_ENV !== 'production' && process.env.VALIDATE_SEMANTIC_MODEL !== '0') {
|
|
232
|
+
const missing = validateAdlSeqCoverage(idx);
|
|
233
|
+
if (missing.length > 0) {
|
|
234
|
+
throw new Error(`[cpp-adl] seq-coverage invariant violated: ${missing.length} candidate def(s) ` +
|
|
235
|
+
`bucketed without a seqByNodeId entry (e.g. ${missing.slice(0, 5).join(', ')}). ` +
|
|
236
|
+
`Every def pushed into nsCandidates/friendCandidates must be seq-assigned in the ` +
|
|
237
|
+
`same build block — see pickCppAdlCandidates' \`?? 0\` fallback.`);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return idx;
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Return the nodeIds present in the index's candidate buckets
|
|
244
|
+
* (`nsCandidates` + `friendCandidates`) but missing from `seqByNodeId`, each
|
|
245
|
+
* reported once. Empty array means the seq-coverage invariant holds — which it
|
|
246
|
+
* must, since `buildAdlIndex` assigns a seq to every callable def in the same
|
|
247
|
+
* block that buckets it. Exported for the dev-gated guard in `buildAdlIndex`
|
|
248
|
+
* and its unit test.
|
|
249
|
+
*/
|
|
250
|
+
export function validateAdlSeqCoverage(idx) {
|
|
251
|
+
const missing = new Set();
|
|
252
|
+
for (const buckets of [idx.nsCandidates, idx.friendCandidates]) {
|
|
253
|
+
for (const bySimple of buckets.values()) {
|
|
254
|
+
for (const defs of bySimple.values()) {
|
|
255
|
+
for (const def of defs) {
|
|
256
|
+
if (!idx.seqByNodeId.has(def.nodeId))
|
|
257
|
+
missing.add(def.nodeId);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
return [...missing];
|
|
263
|
+
}
|
|
264
|
+
/** Build the ADL index on first use of a given `parsedFiles` set; reuse it for
|
|
265
|
+
* all subsequent call sites in the same pipeline run. Reset by
|
|
266
|
+
* `clearCppAdlState`.
|
|
267
|
+
*
|
|
268
|
+
* Staleness is keyed on `parsedFiles` reference identity ONLY, but the index
|
|
269
|
+
* is a function of THREE inputs: `parsedFiles` (namespace/friend candidates),
|
|
270
|
+
* `scopes` (`classDefsBySimple`, read from `scopes.defs.byId`), and the
|
|
271
|
+
* module-level `classToNamespaceQualifiedName` (friend-candidate keys). This
|
|
272
|
+
* is sound for the current pipeline because all three are built together once
|
|
273
|
+
* per `runScopeResolution` pass and `clearCppAdlState` runs in
|
|
274
|
+
* `loadResolutionConfig` at the start of every pass. Callers MUST call
|
|
275
|
+
* `clearCppAdlState` between any two passes that change `scopes` or
|
|
276
|
+
* `classToNamespaceQualifiedName` while reusing the same `parsedFiles` array
|
|
277
|
+
* reference — otherwise a stale index would be served. (No such caller exists
|
|
278
|
+
* today; widening the guard to also key on `scopes` is deferred until one
|
|
279
|
+
* does.) */
|
|
280
|
+
function ensureAdlIndex(scopes, parsedFiles) {
|
|
281
|
+
if (adlIndex !== undefined && adlIndexSource === parsedFiles)
|
|
282
|
+
return;
|
|
283
|
+
adlIndex = buildAdlIndex(scopes, parsedFiles);
|
|
284
|
+
adlIndexSource = parsedFiles;
|
|
285
|
+
}
|
|
74
286
|
/** Record per-call-site argument info. Called once per call site from
|
|
75
287
|
* `emitCppScopeCaptures`. */
|
|
76
288
|
export function markCppAdlSiteArgs(filePath, line, col, args) {
|
|
@@ -81,12 +293,15 @@ export function markCppAdlSiteArgs(filePath, line, col, args) {
|
|
|
81
293
|
export function markCppAdlSiteNoAdl(filePath, line, col) {
|
|
82
294
|
noAdlSites.add(siteKey(filePath, line, col));
|
|
83
295
|
}
|
|
84
|
-
/** Clear ADL state. Called from `
|
|
85
|
-
* per-pipeline state is
|
|
296
|
+
/** Clear ADL state. Called from `cppScopeResolver.loadResolutionConfig`
|
|
297
|
+
* (alongside `clearFileLocalNames`) so all C++ resolver per-pipeline state is
|
|
298
|
+
* reset together at the start of each resolution pass. */
|
|
86
299
|
export function clearCppAdlState() {
|
|
87
300
|
argInfoBySite.clear();
|
|
88
301
|
noAdlSites.clear();
|
|
89
302
|
classToNamespaceQualifiedName.clear();
|
|
303
|
+
adlIndex = undefined;
|
|
304
|
+
adlIndexSource = undefined;
|
|
90
305
|
}
|
|
91
306
|
/**
|
|
92
307
|
* Walk `parsed.scopes` to record each Class def's enclosing namespace
|
|
@@ -148,123 +363,54 @@ export function pickCppAdlCandidates(site, callerParsed, scopes, parsedFiles) {
|
|
|
148
363
|
const args = argInfoBySite.get(key);
|
|
149
364
|
if (args === undefined || args.length === 0)
|
|
150
365
|
return undefined;
|
|
366
|
+
// Build the workspace-wide ADL candidate index once; reuse for every site.
|
|
367
|
+
ensureAdlIndex(scopes, parsedFiles);
|
|
151
368
|
// Collect associated namespace QNames from every participating class-typed arg
|
|
152
369
|
// and from function-reference args.
|
|
153
370
|
const associatedNamespaces = new Set();
|
|
154
371
|
for (const arg of args) {
|
|
155
372
|
collectAssociatedNamespacesForAdlArg(arg, scopes, associatedNamespaces);
|
|
156
373
|
if (arg.functionRefText !== undefined) {
|
|
157
|
-
collectFunctionTypeAssociatedNamespaces(arg.functionRefText, scopes,
|
|
374
|
+
collectFunctionTypeAssociatedNamespaces(arg.functionRefText, scopes, associatedNamespaces);
|
|
158
375
|
}
|
|
159
376
|
}
|
|
160
377
|
if (associatedNamespaces.size === 0)
|
|
161
378
|
return undefined;
|
|
162
|
-
//
|
|
163
|
-
//
|
|
164
|
-
//
|
|
165
|
-
//
|
|
166
|
-
//
|
|
167
|
-
|
|
379
|
+
// Gather candidates from the prebuilt index instead of re-scanning every
|
|
380
|
+
// parsed file. For each associated namespace, pull:
|
|
381
|
+
// - namespace-owned callables (`nsCandidates`, includes inline-namespace
|
|
382
|
+
// transparency), AND
|
|
383
|
+
// - hidden-friend / class-member callables of associated classes
|
|
384
|
+
// (`friendCandidates`, ISO C++ `[basic.lookup.argdep]` §2).
|
|
385
|
+
// Dedup by nodeId and sort by visitation sequence so the candidate list is
|
|
386
|
+
// byte-for-byte identical to the legacy file-major scan order.
|
|
387
|
+
const idx = adlIndex;
|
|
388
|
+
if (idx === undefined)
|
|
389
|
+
return undefined;
|
|
390
|
+
const bySeq = new Map();
|
|
168
391
|
const seenKey = new Set();
|
|
169
|
-
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
for (const scope of parsed.scopes) {
|
|
174
|
-
if (scope.kind !== 'Namespace')
|
|
392
|
+
const collectFrom = (buckets) => {
|
|
393
|
+
for (const ns of associatedNamespaces) {
|
|
394
|
+
const matches = buckets.get(ns)?.get(site.name);
|
|
395
|
+
if (matches === undefined)
|
|
175
396
|
continue;
|
|
176
|
-
const
|
|
177
|
-
if (!associatedNamespaces.has(qName)) {
|
|
178
|
-
// Check if this is an inline-namespace child of an associated NS.
|
|
179
|
-
// ISO C++ inline namespaces are transparent for ADL: if the outer
|
|
180
|
-
// namespace is in the associated set, candidates in the inline child
|
|
181
|
-
// are also reachable.
|
|
182
|
-
if (!isCppInlineNamespaceScope(scope.id))
|
|
183
|
-
continue;
|
|
184
|
-
const parentScope = scope.parent !== null ? scopesById.get(scope.parent) : undefined;
|
|
185
|
-
if (parentScope === undefined || parentScope.kind !== 'Namespace')
|
|
186
|
-
continue;
|
|
187
|
-
const parentQName = computeNamespaceQName(parentScope, scopesById);
|
|
188
|
-
if (!associatedNamespaces.has(parentQName))
|
|
189
|
-
continue;
|
|
190
|
-
}
|
|
191
|
-
for (const def of scope.ownedDefs) {
|
|
192
|
-
if (def.type !== 'Function' && def.type !== 'Method' && def.type !== 'Constructor') {
|
|
193
|
-
continue;
|
|
194
|
-
}
|
|
195
|
-
const simple = def.qualifiedName?.split('.').pop() ?? def.qualifiedName ?? '';
|
|
196
|
-
if (simple !== site.name)
|
|
197
|
-
continue;
|
|
198
|
-
// Dedup by nodeId — using normalized parameter-types as the key
|
|
199
|
-
// would collapse `process(int)`/`process(long)`-style overloads
|
|
200
|
-
// (both normalize to `['int']`) before
|
|
201
|
-
// `isOverloadAmbiguousAfterNormalization` can detect them.
|
|
202
|
-
if (seenKey.has(def.nodeId))
|
|
203
|
-
continue;
|
|
204
|
-
seenKey.add(def.nodeId);
|
|
205
|
-
candidates.push(def);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
// ISO C++ `[basic.lookup.argdep]` §2: hidden friend functions declared
|
|
209
|
-
// inside a class body are visible via ADL when the class is an associated
|
|
210
|
-
// class. Scan Class scopes whose enclosing namespace is in the associated
|
|
211
|
-
// set for callable ownedDefs matching the call name. This enables the
|
|
212
|
-
// canonical "hidden friend" idiom:
|
|
213
|
-
// struct Foo { friend void swap(Foo&, Foo&) {} };
|
|
214
|
-
for (const scope of parsed.scopes) {
|
|
215
|
-
if (scope.kind !== 'Class')
|
|
216
|
-
continue;
|
|
217
|
-
// Check if ANY class def in this scope has an associated namespace.
|
|
218
|
-
let isAssociatedClass = false;
|
|
219
|
-
for (const def of scope.ownedDefs) {
|
|
220
|
-
if (def.type !== 'Class' && def.type !== 'Struct' && def.type !== 'Interface')
|
|
221
|
-
continue;
|
|
222
|
-
const nsQName = classToNamespaceQualifiedName.get(def.nodeId);
|
|
223
|
-
if (nsQName !== undefined && associatedNamespaces.has(nsQName)) {
|
|
224
|
-
isAssociatedClass = true;
|
|
225
|
-
break;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
if (!isAssociatedClass)
|
|
229
|
-
continue;
|
|
230
|
-
// Also scan Function scopes that are direct children of this class
|
|
231
|
-
// scope — friend function definitions create their own Function scope
|
|
232
|
-
// underneath the Class scope.
|
|
233
|
-
for (const childScope of parsed.scopes) {
|
|
234
|
-
if (childScope.parent !== scope.id)
|
|
235
|
-
continue;
|
|
236
|
-
if (childScope.kind !== 'Function')
|
|
237
|
-
continue;
|
|
238
|
-
for (const def of childScope.ownedDefs) {
|
|
239
|
-
if (def.type !== 'Function' && def.type !== 'Method' && def.type !== 'Constructor') {
|
|
240
|
-
continue;
|
|
241
|
-
}
|
|
242
|
-
const simple = def.qualifiedName?.split('.').pop() ?? def.qualifiedName ?? '';
|
|
243
|
-
if (simple !== site.name)
|
|
244
|
-
continue;
|
|
245
|
-
if (seenKey.has(def.nodeId))
|
|
246
|
-
continue;
|
|
247
|
-
seenKey.add(def.nodeId);
|
|
248
|
-
candidates.push(def);
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
for (const def of scope.ownedDefs) {
|
|
252
|
-
if (def.type !== 'Function' && def.type !== 'Method' && def.type !== 'Constructor') {
|
|
253
|
-
continue;
|
|
254
|
-
}
|
|
255
|
-
const simple = def.qualifiedName?.split('.').pop() ?? def.qualifiedName ?? '';
|
|
256
|
-
if (simple !== site.name)
|
|
257
|
-
continue;
|
|
397
|
+
for (const def of matches) {
|
|
258
398
|
if (seenKey.has(def.nodeId))
|
|
259
399
|
continue;
|
|
260
400
|
seenKey.add(def.nodeId);
|
|
261
|
-
|
|
401
|
+
// `?? 0` is unreachable: every bucketed def is seq-assigned in the same
|
|
402
|
+
// block that buckets it in buildAdlIndex (PASS A / PASS B). The dev-gated
|
|
403
|
+
// validateAdlSeqCoverage guard in buildAdlIndex fails loudly if that ever
|
|
404
|
+
// breaks, rather than letting two seq-0 defs collide and drop a candidate.
|
|
405
|
+
bySeq.set(idx.seqByNodeId.get(def.nodeId) ?? 0, def);
|
|
262
406
|
}
|
|
263
407
|
}
|
|
264
|
-
}
|
|
265
|
-
|
|
408
|
+
};
|
|
409
|
+
collectFrom(idx.nsCandidates);
|
|
410
|
+
collectFrom(idx.friendCandidates);
|
|
411
|
+
if (bySeq.size === 0)
|
|
266
412
|
return undefined;
|
|
267
|
-
return
|
|
413
|
+
return [...bySeq.entries()].sort((a, b) => a[0] - b[0]).map(([, def]) => def);
|
|
268
414
|
}
|
|
269
415
|
function collectAssociatedNamespacesForAdlArg(arg, scopes, associatedNamespaces) {
|
|
270
416
|
// For template args this may be the template name itself (e.g. `vector`);
|
|
@@ -287,7 +433,7 @@ function collectAssociatedNamespacesForAdlArg(arg, scopes, associatedNamespaces)
|
|
|
287
433
|
function addAssociatedNamespaceForClassName(simpleClassName, scopes, associatedNamespaces) {
|
|
288
434
|
if (simpleClassName.length === 0)
|
|
289
435
|
return;
|
|
290
|
-
const classLookup = findCppClassDefBySimpleName(simpleClassName
|
|
436
|
+
const classLookup = findCppClassDefBySimpleName(simpleClassName);
|
|
291
437
|
if (classLookup === undefined)
|
|
292
438
|
return;
|
|
293
439
|
const { classDef, ambiguous } = classLookup;
|
|
@@ -380,32 +526,25 @@ function findNamespaceDefInScope(scope) {
|
|
|
380
526
|
*
|
|
381
527
|
* ISO C++ `[basic.lookup.argdep]` §2: enumerations contribute their
|
|
382
528
|
* enclosing namespace to the associated set, just like class types. */
|
|
383
|
-
function findCppClassDefBySimpleName(simpleName
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
const simple = def.qualifiedName?.split('.').pop() ?? def.qualifiedName ?? '';
|
|
392
|
-
if (simple !== simpleName)
|
|
393
|
-
continue;
|
|
394
|
-
if (firstMatch === undefined) {
|
|
395
|
-
firstMatch = def;
|
|
396
|
-
continue;
|
|
397
|
-
}
|
|
398
|
-
return { classDef: firstMatch, ambiguous: true };
|
|
399
|
-
}
|
|
400
|
-
if (firstMatch === undefined)
|
|
529
|
+
function findCppClassDefBySimpleName(simpleName) {
|
|
530
|
+
// `classDefsBySimple` preserves `scopes.defs.byId` order, so `[0]` is the
|
|
531
|
+
// legacy first-match and `length > 1` is the legacy `ambiguous` flag.
|
|
532
|
+
const matches = adlIndex?.classDefsBySimple.get(simpleName);
|
|
533
|
+
if (matches === undefined)
|
|
534
|
+
return undefined;
|
|
535
|
+
const first = matches[0];
|
|
536
|
+
if (first === undefined)
|
|
401
537
|
return undefined;
|
|
402
|
-
return { classDef:
|
|
538
|
+
return { classDef: first, ambiguous: matches.length > 1 };
|
|
403
539
|
}
|
|
404
540
|
/**
|
|
405
541
|
* Contribute associated namespaces for a function-reference argument by walking
|
|
406
542
|
* the referenced overload set's parameter and return types.
|
|
407
543
|
*/
|
|
408
|
-
function collectFunctionTypeAssociatedNamespaces(refText, scopes,
|
|
544
|
+
function collectFunctionTypeAssociatedNamespaces(refText, scopes, out) {
|
|
545
|
+
const idx = adlIndex;
|
|
546
|
+
if (idx === undefined)
|
|
547
|
+
return;
|
|
409
548
|
const colonIdx = refText.lastIndexOf('::');
|
|
410
549
|
if (colonIdx !== -1) {
|
|
411
550
|
// Qualified ref: extract namespace prefix and normalise :: → dot notation.
|
|
@@ -413,27 +552,14 @@ function collectFunctionTypeAssociatedNamespaces(refText, scopes, parsedFiles, o
|
|
|
413
552
|
if (nsText === '')
|
|
414
553
|
return;
|
|
415
554
|
const simpleName = refText.slice(colonIdx + 2);
|
|
416
|
-
//
|
|
417
|
-
//
|
|
418
|
-
// enum value
|
|
419
|
-
// to the associated set
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
for (const
|
|
423
|
-
|
|
424
|
-
for (const scope of parsed.scopes) {
|
|
425
|
-
if (scope.kind !== 'Namespace')
|
|
426
|
-
continue;
|
|
427
|
-
if (computeNamespaceQName(scope, scopesById) !== nsText)
|
|
428
|
-
continue;
|
|
429
|
-
for (const def of scope.ownedDefs) {
|
|
430
|
-
if (def.type !== 'Function' && def.type !== 'Method')
|
|
431
|
-
continue;
|
|
432
|
-
const simple = def.qualifiedName?.split('.').pop() ?? def.qualifiedName ?? '';
|
|
433
|
-
if (simple === simpleName)
|
|
434
|
-
collectAssociatedNamespacesForFunctionDef(def, scopes, out);
|
|
435
|
-
}
|
|
436
|
-
}
|
|
555
|
+
// Only Function/Method defs named `simpleName` in `nsText` contribute
|
|
556
|
+
// (the index already restricts to those types); this guards against an
|
|
557
|
+
// `a::b` arg that names a variable / enum value / type alias blindly
|
|
558
|
+
// contributing `a` to the associated set (false-positive CALLS edge).
|
|
559
|
+
const matches = idx.nsFunctionsByQName.get(nsText)?.get(simpleName);
|
|
560
|
+
if (matches !== undefined) {
|
|
561
|
+
for (const def of matches)
|
|
562
|
+
collectAssociatedNamespacesForFunctionDef(def, scopes, out);
|
|
437
563
|
}
|
|
438
564
|
return;
|
|
439
565
|
}
|
|
@@ -441,19 +567,10 @@ function collectFunctionTypeAssociatedNamespaces(refText, scopes, parsedFiles, o
|
|
|
441
567
|
// the previous V1 lookup scope. The stricter part of this PR is what each
|
|
442
568
|
// overload contributes: only namespaces from parameter/return types, never
|
|
443
569
|
// the function's own enclosing namespace.
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
for (const def of scope.ownedDefs) {
|
|
449
|
-
if (def.type !== 'Function' && def.type !== 'Method')
|
|
450
|
-
continue;
|
|
451
|
-
const simple = def.qualifiedName?.split('.').pop() ?? def.qualifiedName ?? '';
|
|
452
|
-
if (simple !== refText)
|
|
453
|
-
continue;
|
|
454
|
-
collectAssociatedNamespacesForFunctionDef(def, scopes, out);
|
|
455
|
-
}
|
|
456
|
-
}
|
|
570
|
+
const matches = idx.nsFunctionsBySimple.get(refText);
|
|
571
|
+
if (matches !== undefined) {
|
|
572
|
+
for (const def of matches)
|
|
573
|
+
collectAssociatedNamespacesForFunctionDef(def, scopes, out);
|
|
457
574
|
}
|
|
458
575
|
}
|
|
459
576
|
function collectAssociatedNamespacesForFunctionDef(def, scopes, out) {
|
package/package.json
CHANGED