gitnexus 1.6.5-rc.41 → 1.6.5-rc.43
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/core/ingestion/languages/cpp/adl.d.ts +29 -2
- package/dist/core/ingestion/languages/cpp/adl.js +124 -11
- package/dist/core/ingestion/languages/cpp/captures.js +160 -10
- package/dist/core/ingestion/languages/cpp/file-local-linkage.d.ts +25 -0
- package/dist/core/ingestion/languages/cpp/file-local-linkage.js +72 -1
- package/dist/core/ingestion/languages/cpp/scope-resolver.js +5 -4
- package/package.json +1 -1
|
@@ -21,8 +21,25 @@
|
|
|
21
21
|
* - `audit::Event& r`, `audit::Event&& rr`
|
|
22
22
|
* - `std::vector<audit::Event>` (template namespace + template-arg namespaces)
|
|
23
23
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
24
|
+
* V2 additionally walks class ancestors (via MRO), so base-class enclosing
|
|
25
|
+
* namespaces also contribute associated namespaces.
|
|
26
|
+
*
|
|
27
|
+
* **GitNexus approximation (not strict ISO C++ ADL):** passing a qualified
|
|
28
|
+
* function reference like `utils::worker` contributes `utils` to the associated
|
|
29
|
+
* set, enabling resolution of unqualified calls like `with_callback(utils::worker)`
|
|
30
|
+
* to `utils::with_callback`. Under ISO C++ `[basic.lookup.argdep]`, associated
|
|
31
|
+
* entities for function-type arguments come from the **parameter types and return
|
|
32
|
+
* type** of each function in the overload set — NOT the function's enclosing
|
|
33
|
+
* namespace. For `void worker()`, the standard-compliant associated set is empty.
|
|
34
|
+
* GitNexus instead contributes the enclosing namespace of any Function/Method
|
|
35
|
+
* def whose simple name matches, because it enables the dominant real-world ADL
|
|
36
|
+
* pattern at reasonable precision cost.
|
|
37
|
+
*
|
|
38
|
+
* For qualified refs (e.g. `utils::worker`) the namespace is confirmed via a
|
|
39
|
+
* workspace lookup (only contributed when a Function/Method named `worker` exists
|
|
40
|
+
* in `utils`). For unqualified refs the workspace is searched for any Function
|
|
41
|
+
* def with that simple name. Locally-declared function-pointer variables
|
|
42
|
+
* (e.g. `void (*g)()`) and function parameters are excluded from this path.
|
|
26
43
|
*
|
|
27
44
|
* The current implementation also short-circuits to ADL only when ordinary lookup is empty
|
|
28
45
|
* (`findCallableBindingInScope` returned undefined). ISO C++ would
|
|
@@ -58,6 +75,7 @@ import type { ScopeResolutionIndexes } from '../../model/scope-resolution-indexe
|
|
|
58
75
|
* Per-argument shape information collected at capture time. ADL fires for
|
|
59
76
|
* arguments where `simpleClassName !== ''`, including class pointers and
|
|
60
77
|
* references whose declarator chain resolves to a named class type.
|
|
78
|
+
* Free-function reference arguments use `functionRefText`.
|
|
61
79
|
*/
|
|
62
80
|
export interface CppAdlArgInfo {
|
|
63
81
|
/** Simple class-like type name (last segment of qualified name); empty
|
|
@@ -75,6 +93,15 @@ export interface CppAdlArgInfo {
|
|
|
75
93
|
/** Enclosing namespaces extracted from explicit type template arguments,
|
|
76
94
|
* recursively bounded. */
|
|
77
95
|
readonly templateArgNamespaces: readonly string[];
|
|
96
|
+
/** When set, the arg is a potential free-function reference (not a locally-
|
|
97
|
+
* declared function-pointer variable or function parameter). Contains the
|
|
98
|
+
* identifier text as written in source (e.g. `"utils::worker"` or
|
|
99
|
+
* `"worker"`). GitNexus approximation: the function's enclosing namespace
|
|
100
|
+
* is contributed to the ADL associated set. For qualified refs a workspace
|
|
101
|
+
* lookup confirms a Function/Method with that simple name exists in the
|
|
102
|
+
* namespace before contributing; for unqualified refs every namespace
|
|
103
|
+
* containing a matching Function/Method def is contributed. */
|
|
104
|
+
readonly functionRefText?: string;
|
|
78
105
|
}
|
|
79
106
|
/** Sentinel returned by `pickCppAdlCandidates` when ADL surfaces multiple
|
|
80
107
|
* candidates that share normalized parameter types — the caller MUST
|
|
@@ -21,8 +21,25 @@
|
|
|
21
21
|
* - `audit::Event& r`, `audit::Event&& rr`
|
|
22
22
|
* - `std::vector<audit::Event>` (template namespace + template-arg namespaces)
|
|
23
23
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
24
|
+
* V2 additionally walks class ancestors (via MRO), so base-class enclosing
|
|
25
|
+
* namespaces also contribute associated namespaces.
|
|
26
|
+
*
|
|
27
|
+
* **GitNexus approximation (not strict ISO C++ ADL):** passing a qualified
|
|
28
|
+
* function reference like `utils::worker` contributes `utils` to the associated
|
|
29
|
+
* set, enabling resolution of unqualified calls like `with_callback(utils::worker)`
|
|
30
|
+
* to `utils::with_callback`. Under ISO C++ `[basic.lookup.argdep]`, associated
|
|
31
|
+
* entities for function-type arguments come from the **parameter types and return
|
|
32
|
+
* type** of each function in the overload set — NOT the function's enclosing
|
|
33
|
+
* namespace. For `void worker()`, the standard-compliant associated set is empty.
|
|
34
|
+
* GitNexus instead contributes the enclosing namespace of any Function/Method
|
|
35
|
+
* def whose simple name matches, because it enables the dominant real-world ADL
|
|
36
|
+
* pattern at reasonable precision cost.
|
|
37
|
+
*
|
|
38
|
+
* For qualified refs (e.g. `utils::worker`) the namespace is confirmed via a
|
|
39
|
+
* workspace lookup (only contributed when a Function/Method named `worker` exists
|
|
40
|
+
* in `utils`). For unqualified refs the workspace is searched for any Function
|
|
41
|
+
* def with that simple name. Locally-declared function-pointer variables
|
|
42
|
+
* (e.g. `void (*g)()`) and function parameters are excluded from this path.
|
|
26
43
|
*
|
|
27
44
|
* The current implementation also short-circuits to ADL only when ordinary lookup is empty
|
|
28
45
|
* (`findCallableBindingInScope` returned undefined). ISO C++ would
|
|
@@ -129,10 +146,14 @@ export function pickCppAdlCandidates(site, callerParsed, scopes, parsedFiles) {
|
|
|
129
146
|
const args = argInfoBySite.get(key);
|
|
130
147
|
if (args === undefined || args.length === 0)
|
|
131
148
|
return undefined;
|
|
132
|
-
// Collect associated namespace QNames from every participating class-typed arg
|
|
149
|
+
// Collect associated namespace QNames from every participating class-typed arg
|
|
150
|
+
// and from function-reference args.
|
|
133
151
|
const associatedNamespaces = new Set();
|
|
134
152
|
for (const arg of args) {
|
|
135
153
|
collectAssociatedNamespacesForAdlArg(arg, scopes, associatedNamespaces);
|
|
154
|
+
if (arg.functionRefText !== undefined) {
|
|
155
|
+
collectFunctionRefNamespaces(arg.functionRefText, parsedFiles, associatedNamespaces);
|
|
156
|
+
}
|
|
136
157
|
}
|
|
137
158
|
if (associatedNamespaces.size === 0)
|
|
138
159
|
return undefined;
|
|
@@ -210,12 +231,23 @@ function collectAssociatedNamespacesForAdlArg(arg, scopes, associatedNamespaces)
|
|
|
210
231
|
function addAssociatedNamespaceForClassName(simpleClassName, scopes, associatedNamespaces) {
|
|
211
232
|
if (simpleClassName.length === 0)
|
|
212
233
|
return;
|
|
213
|
-
const
|
|
214
|
-
if (
|
|
234
|
+
const classLookup = findCppClassDefBySimpleName(simpleClassName, scopes);
|
|
235
|
+
if (classLookup === undefined)
|
|
215
236
|
return;
|
|
237
|
+
const { classDef, ambiguous } = classLookup;
|
|
216
238
|
const nsQName = classToNamespaceQualifiedName.get(classDef.nodeId);
|
|
217
239
|
if (nsQName !== undefined)
|
|
218
240
|
associatedNamespaces.add(nsQName);
|
|
241
|
+
// Preserve V1 collision behavior for the direct class namespace, but avoid
|
|
242
|
+
// amplifying a same-simple-name collision by walking an arbitrary class's
|
|
243
|
+
// full MRO chain.
|
|
244
|
+
if (ambiguous)
|
|
245
|
+
return;
|
|
246
|
+
for (const ancestorDefId of scopes.methodDispatch.mroFor(classDef.nodeId)) {
|
|
247
|
+
const ancestorNsQName = classToNamespaceQualifiedName.get(ancestorDefId);
|
|
248
|
+
if (ancestorNsQName !== undefined)
|
|
249
|
+
associatedNamespaces.add(ancestorNsQName);
|
|
250
|
+
}
|
|
219
251
|
}
|
|
220
252
|
/** Walk upward from a Class scope, finding the innermost enclosing
|
|
221
253
|
* Namespace scope, and return that namespace's qualified name (dot-
|
|
@@ -285,16 +317,97 @@ function findNamespaceDefInScope(scope) {
|
|
|
285
317
|
return undefined;
|
|
286
318
|
}
|
|
287
319
|
/** Find a class-like def by simple name across the workspace. V1
|
|
288
|
-
* arbitrary-
|
|
289
|
-
*
|
|
290
|
-
*
|
|
320
|
+
* still arbitrary-picks the first class on collisions (multiple classes
|
|
321
|
+
* share the simple name), but reports the collision so callers can avoid
|
|
322
|
+
* amplifying that uncertainty (for example by skipping MRO expansion).
|
|
323
|
+
* C++ ADL strictness would require full type-driven lookup. */
|
|
291
324
|
function findCppClassDefBySimpleName(simpleName, scopes) {
|
|
325
|
+
let firstMatch;
|
|
292
326
|
for (const def of scopes.defs.byId.values()) {
|
|
293
327
|
if (def.type !== 'Class' && def.type !== 'Struct' && def.type !== 'Interface')
|
|
294
328
|
continue;
|
|
295
329
|
const simple = def.qualifiedName?.split('.').pop() ?? def.qualifiedName ?? '';
|
|
296
|
-
if (simple
|
|
297
|
-
|
|
330
|
+
if (simple !== simpleName)
|
|
331
|
+
continue;
|
|
332
|
+
if (firstMatch === undefined) {
|
|
333
|
+
firstMatch = def;
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
return { classDef: firstMatch, ambiguous: true };
|
|
337
|
+
}
|
|
338
|
+
if (firstMatch === undefined)
|
|
339
|
+
return undefined;
|
|
340
|
+
return { classDef: firstMatch, ambiguous: false };
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Contribute associated namespaces for a function-reference argument.
|
|
344
|
+
*
|
|
345
|
+
* - **Qualified refs** (`utils::worker`, `outer::inner::fn`): the namespace
|
|
346
|
+
* is extracted from the qualifier text (converting `::` to `.` for dot-joined
|
|
347
|
+
* QName matching). A workspace lookup then **verifies** that a Function or
|
|
348
|
+
* Method def named `worker` (the simple name after the last `::`) actually
|
|
349
|
+
* exists in the extracted namespace. This prevents false positives from
|
|
350
|
+
* namespace-qualified variables, enum values, and static data members, which
|
|
351
|
+
* also produce `qualified_identifier` AST nodes in tree-sitter-cpp (the
|
|
352
|
+
* AST node type alone does not distinguish functions from non-function names).
|
|
353
|
+
* - **Unqualified refs** (`worker`): the workspace is searched for any
|
|
354
|
+
* Function/Method def whose simple name matches. Every distinct enclosing
|
|
355
|
+
* namespace found is added — overloads across the same namespace produce
|
|
356
|
+
* a single entry; GitNexus does not select a specific overload at this stage.
|
|
357
|
+
*/
|
|
358
|
+
function collectFunctionRefNamespaces(refText, parsedFiles, out) {
|
|
359
|
+
const colonIdx = refText.lastIndexOf('::');
|
|
360
|
+
if (colonIdx !== -1) {
|
|
361
|
+
// Qualified ref: extract namespace prefix and normalise :: → dot notation.
|
|
362
|
+
const nsText = refText.slice(0, colonIdx).replace(/::/g, '.');
|
|
363
|
+
if (nsText === '')
|
|
364
|
+
return;
|
|
365
|
+
const simpleName = refText.slice(colonIdx + 2);
|
|
366
|
+
// Verify that a Function/Method named `simpleName` exists in `nsText`.
|
|
367
|
+
// Without this guard every `a::b` qualified_identifier arg (variable,
|
|
368
|
+
// enum value, static member, type alias) would blindly contribute `a`
|
|
369
|
+
// to the associated set and risk a false-positive CALLS edge.
|
|
370
|
+
for (const parsed of parsedFiles) {
|
|
371
|
+
const scopesById = new Map();
|
|
372
|
+
for (const sc of parsed.scopes)
|
|
373
|
+
scopesById.set(sc.id, sc);
|
|
374
|
+
for (const scope of parsed.scopes) {
|
|
375
|
+
if (scope.kind !== 'Namespace')
|
|
376
|
+
continue;
|
|
377
|
+
if (computeNamespaceQName(scope, scopesById) !== nsText)
|
|
378
|
+
continue;
|
|
379
|
+
for (const def of scope.ownedDefs) {
|
|
380
|
+
if (def.type !== 'Function' && def.type !== 'Method')
|
|
381
|
+
continue;
|
|
382
|
+
const simple = def.qualifiedName?.split('.').pop() ?? def.qualifiedName ?? '';
|
|
383
|
+
if (simple === simpleName) {
|
|
384
|
+
out.add(nsText);
|
|
385
|
+
return; // Namespace confirmed; no need to scan further files.
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
// Unqualified: search all namespace scopes for a Function def with this
|
|
393
|
+
// simple name and contribute its enclosing namespace.
|
|
394
|
+
for (const parsed of parsedFiles) {
|
|
395
|
+
const scopesById = new Map();
|
|
396
|
+
for (const sc of parsed.scopes)
|
|
397
|
+
scopesById.set(sc.id, sc);
|
|
398
|
+
for (const scope of parsed.scopes) {
|
|
399
|
+
if (scope.kind !== 'Namespace')
|
|
400
|
+
continue;
|
|
401
|
+
for (const def of scope.ownedDefs) {
|
|
402
|
+
if (def.type !== 'Function' && def.type !== 'Method')
|
|
403
|
+
continue;
|
|
404
|
+
const simple = def.qualifiedName?.split('.').pop() ?? def.qualifiedName ?? '';
|
|
405
|
+
if (simple !== refText)
|
|
406
|
+
continue;
|
|
407
|
+
const nsQName = computeNamespaceQName(scope, scopesById);
|
|
408
|
+
if (nsQName !== '')
|
|
409
|
+
out.add(nsQName);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
298
412
|
}
|
|
299
|
-
return undefined;
|
|
300
413
|
}
|
|
@@ -4,7 +4,7 @@ import { getTreeSitterBufferSize } from '../../constants.js';
|
|
|
4
4
|
import { parseSourceSafe } from '../../../tree-sitter/safe-parse.js';
|
|
5
5
|
import { splitCppInclude, splitCppUsingDecl } from './import-decomposer.js';
|
|
6
6
|
import { computeCppDeclarationArity, computeCppCallArity } from './arity-metadata.js';
|
|
7
|
-
import { markFileLocal } from './file-local-linkage.js';
|
|
7
|
+
import { markCppAnonymousNamespaceRange, markFileLocal } from './file-local-linkage.js';
|
|
8
8
|
import { markCppDependentBase } from './two-phase-lookup.js';
|
|
9
9
|
import { markCppAdlSiteArgs, markCppAdlSiteNoAdl } from './adl.js';
|
|
10
10
|
import { markCppInlineNamespaceRange } from './inline-namespaces.js';
|
|
@@ -150,20 +150,33 @@ export function emitCppScopeCaptures(sourceText, filePath, cachedTree) {
|
|
|
150
150
|
// namespace's source range so `populateCppInlineNamespaceScopes`
|
|
151
151
|
// (during populateOwners) can match it back to the corresponding
|
|
152
152
|
// Namespace scope.
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
153
|
+
// `@declaration.namespace` fires only for NAMED namespaces (the query
|
|
154
|
+
// requires a `name: (namespace_identifier)` child). Use the unconditional
|
|
155
|
+
// `@scope.namespace` capture so the anonymous-namespace branch also runs.
|
|
156
|
+
const namespaceScopeAnchor = grouped['@declaration.namespace'] ?? grouped['@scope.namespace'];
|
|
157
|
+
if (namespaceScopeAnchor !== undefined) {
|
|
158
|
+
const nsNode = findNodeAtRange(tree.rootNode, namespaceScopeAnchor.range, 'namespace_definition');
|
|
159
|
+
if (nsNode !== null) {
|
|
157
160
|
// Range coords stored in the shared Range shape use 1-based
|
|
158
161
|
// line numbers (see `ast-helpers.ts` rangeForNode where
|
|
159
162
|
// `startPosition.row + 1` is applied). Match that convention so
|
|
160
|
-
//
|
|
161
|
-
|
|
163
|
+
// the populators can join against `Scope.range`.
|
|
164
|
+
const nsRange = {
|
|
162
165
|
startLine: nsNode.startPosition.row + 1,
|
|
163
166
|
startCol: nsNode.startPosition.column,
|
|
164
167
|
endLine: nsNode.endPosition.row + 1,
|
|
165
168
|
endCol: nsNode.endPosition.column,
|
|
166
|
-
}
|
|
169
|
+
};
|
|
170
|
+
if (isInlineNamespace(nsNode)) {
|
|
171
|
+
markCppInlineNamespaceRange(filePath, nsRange);
|
|
172
|
+
}
|
|
173
|
+
// Anonymous namespace: `namespace_definition` with no `name` field.
|
|
174
|
+
// Recorded so `expandCppWildcardNames` can propagate its members
|
|
175
|
+
// to including TUs even though their names are also `markFileLocal`'d
|
|
176
|
+
// (which blocks the global free-call fallback's cross-file path).
|
|
177
|
+
if ((nsNode.childForFieldName?.('name') ?? null) === null) {
|
|
178
|
+
markCppAnonymousNamespaceRange(filePath, nsRange);
|
|
179
|
+
}
|
|
167
180
|
}
|
|
168
181
|
}
|
|
169
182
|
// ── ADL (Koenig lookup) per-site recording ──────────────────────
|
|
@@ -704,15 +717,93 @@ function classifyAdlArg(argNode) {
|
|
|
704
717
|
argNode.type === 'nullptr') {
|
|
705
718
|
return EMPTY_ADL_ARG;
|
|
706
719
|
}
|
|
720
|
+
// Qualified expression (a::b) — may be a function, variable, enum value,
|
|
721
|
+
// or static member. Record as a potential function reference; resolution
|
|
722
|
+
// time verifies via workspace lookup that a Function/Method with this simple
|
|
723
|
+
// name exists in the extracted namespace before contributing to the set.
|
|
724
|
+
if (argNode.type === 'qualified_identifier') {
|
|
725
|
+
return {
|
|
726
|
+
simpleClassName: '',
|
|
727
|
+
templateSimpleClassName: '',
|
|
728
|
+
templateNamespace: '',
|
|
729
|
+
templateArgClassNames: [],
|
|
730
|
+
templateArgNamespaces: [],
|
|
731
|
+
functionRefText: argNode.text,
|
|
732
|
+
};
|
|
733
|
+
}
|
|
707
734
|
// Variable reference — look up its declared type (preserving pointer /
|
|
708
735
|
// reference / qualified-name shape; the existing arity-narrowing helper
|
|
709
736
|
// strips this info).
|
|
710
737
|
if (argNode.type === 'identifier') {
|
|
711
|
-
|
|
738
|
+
const result = lookupAdlIdentifierType(argNode);
|
|
739
|
+
if (result === null) {
|
|
740
|
+
// Not found in the local compound_statement scope — could be a
|
|
741
|
+
// free-function reference (unqualified name, namespace scope).
|
|
742
|
+
return {
|
|
743
|
+
simpleClassName: '',
|
|
744
|
+
templateSimpleClassName: '',
|
|
745
|
+
templateNamespace: '',
|
|
746
|
+
templateArgClassNames: [],
|
|
747
|
+
templateArgNamespaces: [],
|
|
748
|
+
functionRefText: argNode.text,
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
return result;
|
|
712
752
|
}
|
|
713
753
|
// Other shapes (calls, member access, operators) — V1 unsupported.
|
|
714
754
|
return EMPTY_ADL_ARG;
|
|
715
755
|
}
|
|
756
|
+
/**
|
|
757
|
+
* Returns `true` when `varName` appears as a parameter name in the nearest
|
|
758
|
+
* enclosing `function_definition` or `function_declarator` that contains
|
|
759
|
+
* `identNode`. Parameters live in `parameter_list` (a sibling of the
|
|
760
|
+
* `compound_statement`), so the `compound_statement`-local declaration scan
|
|
761
|
+
* in `lookupAdlIdentifierType` would not find them — causing them to be
|
|
762
|
+
* mistakenly classified as potential free-function references.
|
|
763
|
+
*
|
|
764
|
+
* In tree-sitter-cpp a `function_definition` does NOT expose `parameters`
|
|
765
|
+
* as a direct named field; parameters live inside the nested
|
|
766
|
+
* `function_declarator`. For `function_declarator` nodes the `parameters`
|
|
767
|
+
* field IS direct. Both cases are handled below.
|
|
768
|
+
*/
|
|
769
|
+
function isIdentifierAFunctionParameter(identNode, varName) {
|
|
770
|
+
let node = identNode.parent;
|
|
771
|
+
let safety = 64;
|
|
772
|
+
while (node !== null && safety-- > 0) {
|
|
773
|
+
let params = null;
|
|
774
|
+
if (node.type === 'function_declarator') {
|
|
775
|
+
// parameters is a direct field on function_declarator.
|
|
776
|
+
params = node.childForFieldName('parameters');
|
|
777
|
+
}
|
|
778
|
+
else if (node.type === 'function_definition') {
|
|
779
|
+
// function_definition carries parameters inside its `declarator` field
|
|
780
|
+
// (which is a function_declarator). Walk through it.
|
|
781
|
+
const decl = node.childForFieldName('declarator');
|
|
782
|
+
if (decl !== null && decl.type === 'function_declarator') {
|
|
783
|
+
params = decl.childForFieldName('parameters');
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
if (params !== null) {
|
|
787
|
+
for (let i = 0; i < params.namedChildCount; i++) {
|
|
788
|
+
const param = params.namedChild(i);
|
|
789
|
+
if (param === null)
|
|
790
|
+
continue;
|
|
791
|
+
const declNode = param.childForFieldName('declarator');
|
|
792
|
+
if (declNode === null)
|
|
793
|
+
continue;
|
|
794
|
+
const leafName = extractDeclaratorLeafName(declNode);
|
|
795
|
+
if (leafName === varName)
|
|
796
|
+
return true;
|
|
797
|
+
}
|
|
798
|
+
// Only check the immediately enclosing function — do not climb further.
|
|
799
|
+
break;
|
|
800
|
+
}
|
|
801
|
+
if (node.type === 'translation_unit')
|
|
802
|
+
break;
|
|
803
|
+
node = node.parent;
|
|
804
|
+
}
|
|
805
|
+
return false;
|
|
806
|
+
}
|
|
716
807
|
function lookupAdlIdentifierType(identNode) {
|
|
717
808
|
const varName = identNode.text;
|
|
718
809
|
let scope = identNode.parent;
|
|
@@ -722,7 +813,15 @@ function lookupAdlIdentifierType(identNode) {
|
|
|
722
813
|
scope = scope.parent;
|
|
723
814
|
}
|
|
724
815
|
if (scope === null)
|
|
816
|
+
return null;
|
|
817
|
+
// Function parameters live in the enclosing function's `parameter_list`,
|
|
818
|
+
// NOT inside the `compound_statement`, so the declaration scan below would
|
|
819
|
+
// never find them and would return `null` — incorrectly triggering the
|
|
820
|
+
// free-function-reference path. Check the parameter_list first.
|
|
821
|
+
if (isIdentifierAFunctionParameter(identNode, varName)) {
|
|
725
822
|
return EMPTY_ADL_ARG;
|
|
823
|
+
}
|
|
824
|
+
let foundAsLocalFunctionPointer = false;
|
|
726
825
|
for (let i = 0; i < scope.childCount; i++) {
|
|
727
826
|
const stmt = scope.child(i);
|
|
728
827
|
if (stmt === null || stmt.type !== 'declaration')
|
|
@@ -751,6 +850,9 @@ function lookupAdlIdentifierType(identNode) {
|
|
|
751
850
|
if (inner.type === 'pointer_declarator') {
|
|
752
851
|
if (findFirstDescendantOfType(inner, 'function_declarator') !== null) {
|
|
753
852
|
isFunctionPointer = true;
|
|
853
|
+
// Extract the name from within the function-pointer declarator chain
|
|
854
|
+
// so `foundAsLocalFunctionPointer` can detect a matching declaration.
|
|
855
|
+
nameText = extractDeclaratorLeafName(inner);
|
|
754
856
|
break;
|
|
755
857
|
}
|
|
756
858
|
const next = inner.childForFieldName('declarator');
|
|
@@ -783,12 +885,22 @@ function lookupAdlIdentifierType(identNode) {
|
|
|
783
885
|
}
|
|
784
886
|
if (inner.type === 'function_declarator') {
|
|
785
887
|
isFunctionPointer = true;
|
|
888
|
+
// Extract the name from the inner declarator (e.g. `(*g)` in `void (*g)()`).
|
|
889
|
+
const innerDecl = inner.childForFieldName('declarator');
|
|
890
|
+
if (innerDecl !== null)
|
|
891
|
+
nameText = extractDeclaratorLeafName(innerDecl);
|
|
786
892
|
break;
|
|
787
893
|
}
|
|
788
894
|
// Reached the leaf — usually `identifier`. Take its text.
|
|
789
895
|
nameText = inner.text;
|
|
790
896
|
break;
|
|
791
897
|
}
|
|
898
|
+
if (nameText === varName && isFunctionPointer) {
|
|
899
|
+
// Explicitly declared as a function-pointer variable — must not be
|
|
900
|
+
// treated as a free-function reference by the caller.
|
|
901
|
+
foundAsLocalFunctionPointer = true;
|
|
902
|
+
continue;
|
|
903
|
+
}
|
|
792
904
|
if (isFunctionPointer || nameText !== varName)
|
|
793
905
|
continue;
|
|
794
906
|
const simpleClassName = extractAdlSimpleTypeName(typeNode);
|
|
@@ -801,7 +913,22 @@ function lookupAdlIdentifierType(identNode) {
|
|
|
801
913
|
templateArgNamespaces,
|
|
802
914
|
};
|
|
803
915
|
}
|
|
804
|
-
|
|
916
|
+
// If the identifier was found in local scope as a function-pointer variable,
|
|
917
|
+
// return EMPTY_ADL_ARG so the caller does NOT treat it as a free-function
|
|
918
|
+
// reference. Otherwise return null to indicate "not in local scope".
|
|
919
|
+
//
|
|
920
|
+
// Known limitation (Finding 4): variables whose type is a typedef/using alias
|
|
921
|
+
// for a function-pointer type are NOT detected here. For example:
|
|
922
|
+
// using Callback = void (*)();
|
|
923
|
+
// Callback g;
|
|
924
|
+
// foo(g); // `g`'s declarator is `identifier` with type `Callback`
|
|
925
|
+
// The declarator has no `pointer_declarator` wrapper, so `isFunctionPointer`
|
|
926
|
+
// stays false and `extractAdlSimpleTypeName` returns `"Callback"`. ADL then
|
|
927
|
+
// looks for a class named `Callback`; if none exists, this degrades to
|
|
928
|
+
// EMPTY_ADL_ARG (class not found → no namespace contributed). If a class
|
|
929
|
+
// named `Callback` does exist, a spurious namespace contribution could occur.
|
|
930
|
+
// Risk is low in practice; a future fix should resolve the typedef/alias chain.
|
|
931
|
+
return foundAsLocalFunctionPointer ? EMPTY_ADL_ARG : null;
|
|
805
932
|
}
|
|
806
933
|
/** Extract the simple class-like type name from a `type:` field node.
|
|
807
934
|
* Returns '' for primitives and any other
|
|
@@ -950,6 +1077,29 @@ function extractNamespaceFromQualifiedText(text) {
|
|
|
950
1077
|
return '';
|
|
951
1078
|
return normalizeCppNamespaceQName(cleaned.slice(0, idx));
|
|
952
1079
|
}
|
|
1080
|
+
/**
|
|
1081
|
+
* Walk a declarator node chain, unwrapping pointer/reference/function/
|
|
1082
|
+
* parenthesized wrappers, and return the text of the innermost identifier.
|
|
1083
|
+
* Returns `null` when no identifier is found within `safety` steps.
|
|
1084
|
+
* Used by `lookupAdlIdentifierType` to extract the variable name from
|
|
1085
|
+
* function-pointer declarator trees such as `(*g)()` in `void (*g)()`.
|
|
1086
|
+
*/
|
|
1087
|
+
function extractDeclaratorLeafName(node) {
|
|
1088
|
+
let cur = node;
|
|
1089
|
+
let safety = 16;
|
|
1090
|
+
while (safety-- > 0) {
|
|
1091
|
+
if (cur.type === 'identifier' || cur.type === 'type_identifier')
|
|
1092
|
+
return cur.text;
|
|
1093
|
+
// Common wrapper nodes — follow the 'declarator' field when present.
|
|
1094
|
+
const next = cur.childForFieldName('declarator') ??
|
|
1095
|
+
// parenthesized_declarator: single named child
|
|
1096
|
+
(cur.type === 'parenthesized_declarator' ? cur.namedChild(0) : null);
|
|
1097
|
+
if (next === null)
|
|
1098
|
+
return null;
|
|
1099
|
+
cur = next;
|
|
1100
|
+
}
|
|
1101
|
+
return null;
|
|
1102
|
+
}
|
|
953
1103
|
/**
|
|
954
1104
|
* Check if a C++ function_definition or declaration has `static` storage class.
|
|
955
1105
|
*/
|
|
@@ -1,10 +1,34 @@
|
|
|
1
1
|
import type { ParsedFile, ScopeId } from '../../../../_shared/index.js';
|
|
2
|
+
interface RangeKeyShape {
|
|
3
|
+
readonly startLine: number;
|
|
4
|
+
readonly startCol: number;
|
|
5
|
+
readonly endLine: number;
|
|
6
|
+
readonly endCol: number;
|
|
7
|
+
}
|
|
2
8
|
/** Record a symbol name as file-local (static or anonymous namespace). */
|
|
3
9
|
export declare function markFileLocal(filePath: string, name: string): void;
|
|
4
10
|
/** Check whether a symbol name has file-local linkage in the given file. */
|
|
5
11
|
export declare function isFileLocal(filePath: string, name: string): boolean;
|
|
12
|
+
/** Capture-time: record an anonymous `namespace_definition` source range. */
|
|
13
|
+
export declare function markCppAnonymousNamespaceRange(filePath: string, range: RangeKeyShape): void;
|
|
14
|
+
/** Predicate consumed by `populateCppNonGloballyVisible` and
|
|
15
|
+
* `expandCppWildcardNames` to exempt anonymous-namespace scopes from
|
|
16
|
+
* the cross-file unqualified-lookup exclusion that applies to ordinary
|
|
17
|
+
* named namespaces. */
|
|
18
|
+
export declare function isCppAnonymousNamespaceScope(scopeId: ScopeId): boolean;
|
|
6
19
|
/** Clear tracked file-local names (call at start of each resolution pass). */
|
|
7
20
|
export declare function clearFileLocalNames(): void;
|
|
21
|
+
/** Resolve recorded anonymous-namespace source ranges to `ScopeId`s.
|
|
22
|
+
* Must run inside `populateOwners` BEFORE `populateCppNonGloballyVisible`
|
|
23
|
+
* consults the resolved set. */
|
|
24
|
+
export declare function populateCppAnonymousNamespaceScopes(parsed: {
|
|
25
|
+
readonly filePath: string;
|
|
26
|
+
readonly scopes: readonly {
|
|
27
|
+
readonly id: ScopeId;
|
|
28
|
+
readonly kind: string;
|
|
29
|
+
readonly range: RangeKeyShape;
|
|
30
|
+
}[];
|
|
31
|
+
}): void;
|
|
8
32
|
/**
|
|
9
33
|
* Populate per-file "not globally visible" nodeIds by walking the parsed
|
|
10
34
|
* file's scopes. Run as part of the `populateOwners` hook so every C++
|
|
@@ -91,3 +115,4 @@ export declare function isCppDefGloballyVisible(filePath: string, nodeId: string
|
|
|
91
115
|
* members never enter `localDefs` unqualified.
|
|
92
116
|
*/
|
|
93
117
|
export declare function expandCppWildcardNames(targetModuleScope: ScopeId, parsedFiles: readonly ParsedFile[]): readonly string[];
|
|
118
|
+
export {};
|
|
@@ -31,6 +31,24 @@ const fileLocalNames = new Map();
|
|
|
31
31
|
* this set encodes).
|
|
32
32
|
*/
|
|
33
33
|
const nonGloballyVisibleNodeIds = new Map();
|
|
34
|
+
/**
|
|
35
|
+
* Per-file set of source-range keys identifying `namespace { ... }` blocks.
|
|
36
|
+
* Resolved to `ScopeId`s in `populateCppAnonymousNamespaceScopes` and
|
|
37
|
+
* consumed via `isCppAnonymousNamespaceScope`.
|
|
38
|
+
*
|
|
39
|
+
* Anonymous namespaces have file-local linkage but, unlike `static`, their
|
|
40
|
+
* members propagate to any TU that `#include`s the declaring file — each
|
|
41
|
+
* including TU gets its own internal-linkage copy. So for wildcard import
|
|
42
|
+
* expansion (`expandCppWildcardNames`) we treat anonymous-namespace owned
|
|
43
|
+
* defs as if declared at the enclosing scope. Cross-file unqualified
|
|
44
|
+
* lookup that does NOT go through `#include` is still blocked by the
|
|
45
|
+
* `isFileLocal` mark recorded on the def's name.
|
|
46
|
+
*/
|
|
47
|
+
const anonymousNamespaceRangesByFile = new Map();
|
|
48
|
+
const anonymousNamespaceScopeIds = new Set();
|
|
49
|
+
function rangeKey(r) {
|
|
50
|
+
return `${r.startLine}:${r.startCol}:${r.endLine}:${r.endCol}`;
|
|
51
|
+
}
|
|
34
52
|
/** Record a symbol name as file-local (static or anonymous namespace). */
|
|
35
53
|
export function markFileLocal(filePath, name) {
|
|
36
54
|
let names = fileLocalNames.get(filePath);
|
|
@@ -44,10 +62,43 @@ export function markFileLocal(filePath, name) {
|
|
|
44
62
|
export function isFileLocal(filePath, name) {
|
|
45
63
|
return fileLocalNames.get(filePath)?.has(name) ?? false;
|
|
46
64
|
}
|
|
65
|
+
/** Capture-time: record an anonymous `namespace_definition` source range. */
|
|
66
|
+
export function markCppAnonymousNamespaceRange(filePath, range) {
|
|
67
|
+
let set = anonymousNamespaceRangesByFile.get(filePath);
|
|
68
|
+
if (set === undefined) {
|
|
69
|
+
set = new Set();
|
|
70
|
+
anonymousNamespaceRangesByFile.set(filePath, set);
|
|
71
|
+
}
|
|
72
|
+
set.add(rangeKey(range));
|
|
73
|
+
}
|
|
74
|
+
/** Predicate consumed by `populateCppNonGloballyVisible` and
|
|
75
|
+
* `expandCppWildcardNames` to exempt anonymous-namespace scopes from
|
|
76
|
+
* the cross-file unqualified-lookup exclusion that applies to ordinary
|
|
77
|
+
* named namespaces. */
|
|
78
|
+
export function isCppAnonymousNamespaceScope(scopeId) {
|
|
79
|
+
return anonymousNamespaceScopeIds.has(scopeId);
|
|
80
|
+
}
|
|
47
81
|
/** Clear tracked file-local names (call at start of each resolution pass). */
|
|
48
82
|
export function clearFileLocalNames() {
|
|
49
83
|
fileLocalNames.clear();
|
|
50
84
|
nonGloballyVisibleNodeIds.clear();
|
|
85
|
+
anonymousNamespaceRangesByFile.clear();
|
|
86
|
+
anonymousNamespaceScopeIds.clear();
|
|
87
|
+
}
|
|
88
|
+
/** Resolve recorded anonymous-namespace source ranges to `ScopeId`s.
|
|
89
|
+
* Must run inside `populateOwners` BEFORE `populateCppNonGloballyVisible`
|
|
90
|
+
* consults the resolved set. */
|
|
91
|
+
export function populateCppAnonymousNamespaceScopes(parsed) {
|
|
92
|
+
const ranges = anonymousNamespaceRangesByFile.get(parsed.filePath);
|
|
93
|
+
if (ranges === undefined || ranges.size === 0)
|
|
94
|
+
return;
|
|
95
|
+
for (const scope of parsed.scopes) {
|
|
96
|
+
if (scope.kind !== 'Namespace')
|
|
97
|
+
continue;
|
|
98
|
+
if (ranges.has(rangeKey(scope.range))) {
|
|
99
|
+
anonymousNamespaceScopeIds.add(scope.id);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
51
102
|
}
|
|
52
103
|
/**
|
|
53
104
|
* Populate per-file "not globally visible" nodeIds by walking the parsed
|
|
@@ -75,6 +126,14 @@ export function populateCppNonGloballyVisible(parsed) {
|
|
|
75
126
|
// unqualified lookup can still see their callable defs.
|
|
76
127
|
if (scope.kind === 'Namespace' && isCppInlineNamespaceScope(scope.id))
|
|
77
128
|
continue;
|
|
129
|
+
// Anonymous namespaces give internal linkage but their contents are
|
|
130
|
+
// visible at the enclosing scope within the same TU and propagate to
|
|
131
|
+
// any TU that `#include`s the declaring file. The `isFileLocal` mark
|
|
132
|
+
// (recorded on the def's name in this file) still blocks cross-file
|
|
133
|
+
// unqualified lookup that does not go through #include, so dropping
|
|
134
|
+
// the structural visibility exclusion here is safe.
|
|
135
|
+
if (scope.kind === 'Namespace' && anonymousNamespaceScopeIds.has(scope.id))
|
|
136
|
+
continue;
|
|
78
137
|
for (const def of scope.ownedDefs) {
|
|
79
138
|
set.add(def.nodeId);
|
|
80
139
|
}
|
|
@@ -173,15 +232,27 @@ export function expandCppWildcardNames(targetModuleScope, parsedFiles) {
|
|
|
173
232
|
// including TU. When the owning scope is unknown we default to
|
|
174
233
|
// include (preserves prior behavior for any def whose structural
|
|
175
234
|
// ownership wasn't recorded in `Scope.ownedDefs`).
|
|
235
|
+
//
|
|
236
|
+
// Anonymous namespaces are exempt: their members propagate to the
|
|
237
|
+
// enclosing scope of any TU that #includes the declaring file (each
|
|
238
|
+
// including TU gets its own internal-linkage copy per ISO C++).
|
|
176
239
|
const ownerScope = ownerScopeByNodeId.get(def.nodeId);
|
|
240
|
+
const ownerIsAnonymousNamespace = ownerScope !== undefined &&
|
|
241
|
+
ownerScope.kind === 'Namespace' &&
|
|
242
|
+
anonymousNamespaceScopeIds.has(ownerScope.id);
|
|
177
243
|
if (ownerScope !== undefined &&
|
|
244
|
+
!ownerIsAnonymousNamespace &&
|
|
178
245
|
(ownerScope.kind === 'Namespace' || ownerScope.kind === 'Class')) {
|
|
179
246
|
continue;
|
|
180
247
|
}
|
|
181
248
|
const name = simpleName(def);
|
|
182
249
|
if (name === '')
|
|
183
250
|
continue;
|
|
184
|
-
|
|
251
|
+
// Same exemption for the `isFileLocal` mark — anonymous-namespace
|
|
252
|
+
// names are recorded as file-local to suppress the global free-call
|
|
253
|
+
// fallback's cross-file leak, but they MUST still propagate through
|
|
254
|
+
// wildcard import expansion to including TUs.
|
|
255
|
+
if (!ownerIsAnonymousNamespace && isFileLocal(target.filePath, name))
|
|
185
256
|
continue;
|
|
186
257
|
if (seen.has(name))
|
|
187
258
|
continue;
|
|
@@ -7,7 +7,7 @@ import { cppArityCompatibility } from './arity.js';
|
|
|
7
7
|
import { cppMergeBindings } from './merge-bindings.js';
|
|
8
8
|
import { resolveCppImportTarget } from './import-target.js';
|
|
9
9
|
import { scanCppHeaderFiles } from './header-scan.js';
|
|
10
|
-
import { expandCppWildcardNames, isFileLocal, clearFileLocalNames, populateCppNonGloballyVisible, isCppDefGloballyVisible, } from './file-local-linkage.js';
|
|
10
|
+
import { expandCppWildcardNames, isFileLocal, clearFileLocalNames, populateCppAnonymousNamespaceScopes, populateCppNonGloballyVisible, isCppDefGloballyVisible, } from './file-local-linkage.js';
|
|
11
11
|
import { populateCppDependentBases, clearCppDependentBases, isCppDependentBaseMember, } from './two-phase-lookup.js';
|
|
12
12
|
import { populateCppAssociatedNamespaces, clearCppAdlState, pickCppAdlCandidates, ADL_AMBIGUOUS, } from './adl.js';
|
|
13
13
|
import { clearCppInlineNamespaces, populateCppInlineNamespaceScopes, resolveCppQualifiedNamespaceMember, } from './inline-namespaces.js';
|
|
@@ -61,10 +61,11 @@ export const cppScopeResolver = {
|
|
|
61
61
|
buildMro: (graph, parsedFiles, nodeLookup) => buildMro(graph, parsedFiles, nodeLookup, defaultLinearize),
|
|
62
62
|
populateOwners: (parsed) => {
|
|
63
63
|
populateClassOwnedMembers(parsed);
|
|
64
|
-
// Resolve inline-namespace ranges (recorded at capture
|
|
65
|
-
// ScopeIds BEFORE `populateCppNonGloballyVisible` runs, so
|
|
66
|
-
//
|
|
64
|
+
// Resolve inline- and anonymous-namespace ranges (recorded at capture
|
|
65
|
+
// time) to ScopeIds BEFORE `populateCppNonGloballyVisible` runs, so
|
|
66
|
+
// both exemptions see the populated Sets.
|
|
67
67
|
populateCppInlineNamespaceScopes(parsed);
|
|
68
|
+
populateCppAnonymousNamespaceScopes(parsed);
|
|
68
69
|
// Track namespace-nested and class-nested defs so the global free-call
|
|
69
70
|
// fallback and wildcard expansion can suppress them as unqualified
|
|
70
71
|
// cross-file callables.
|
package/package.json
CHANGED