gitnexus 1.6.5-rc.40 → 1.6.5-rc.42

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.
@@ -15,14 +15,15 @@
15
15
  *
16
16
  * ## Current boundary
17
17
  *
18
- * The current implementation covers ONE associated-entity rule: an argument that's a directly-named
19
- * class type (`audit::Event e`) contributes its **direct enclosing
20
- * namespace** to the candidate set. V2 extends that one step to
21
- * pointer-typed and reference-typed class args (`audit::Event* p`,
22
- * `audit::Event& r`, `audit::Event&& rr`): they contribute the pointee /
23
- * referred class's enclosing namespace too. Function-pointer arguments,
24
- * template specializations, base-class associated namespaces, and the
25
- * rest of the full closure are still deliberately excluded.
18
+ * The current implementation covers class-typed arguments (value, pointer,
19
+ * and reference) and template specializations with explicit type arguments:
20
+ * - `audit::Event e`, `audit::Event* p`, `audit::Event** pp`
21
+ * - `audit::Event& r`, `audit::Event&& rr`
22
+ * - `std::vector<audit::Event>` (template namespace + template-arg namespaces)
23
+ *
24
+ * Function-pointer arguments and the rest of the full closure are still
25
+ * deliberately excluded. V2 additionally walks class ancestors (via MRO),
26
+ * so base-class enclosing namespaces also contribute associated namespaces.
26
27
  *
27
28
  * The current implementation also short-circuits to ADL only when ordinary lookup is empty
28
29
  * (`findCallableBindingInScope` returned undefined). ISO C++ would
@@ -61,8 +62,20 @@ import type { ScopeResolutionIndexes } from '../../model/scope-resolution-indexe
61
62
  */
62
63
  export interface CppAdlArgInfo {
63
64
  /** Simple class-like type name (last segment of qualified name); empty
64
- * for primitives, literals, function pointers, template specs, etc. */
65
+ * for primitives, literals, function pointers, etc. */
65
66
  readonly simpleClassName: string;
67
+ /** Template's own simple class-like name (e.g. `vector` for
68
+ * `std::vector<N::T>`), empty when arg type is not a template spec. */
69
+ readonly templateSimpleClassName: string;
70
+ /** Template's own enclosing namespace (dot-qualified, e.g. `std`), empty
71
+ * when unavailable / unqualified. */
72
+ readonly templateNamespace: string;
73
+ /** Class-like names extracted from explicit type template arguments,
74
+ * recursively bounded. */
75
+ readonly templateArgClassNames: readonly string[];
76
+ /** Enclosing namespaces extracted from explicit type template arguments,
77
+ * recursively bounded. */
78
+ readonly templateArgNamespaces: readonly string[];
66
79
  }
67
80
  /** Sentinel returned by `pickCppAdlCandidates` when ADL surfaces multiple
68
81
  * candidates that share normalized parameter types — the caller MUST
@@ -15,14 +15,15 @@
15
15
  *
16
16
  * ## Current boundary
17
17
  *
18
- * The current implementation covers ONE associated-entity rule: an argument that's a directly-named
19
- * class type (`audit::Event e`) contributes its **direct enclosing
20
- * namespace** to the candidate set. V2 extends that one step to
21
- * pointer-typed and reference-typed class args (`audit::Event* p`,
22
- * `audit::Event& r`, `audit::Event&& rr`): they contribute the pointee /
23
- * referred class's enclosing namespace too. Function-pointer arguments,
24
- * template specializations, base-class associated namespaces, and the
25
- * rest of the full closure are still deliberately excluded.
18
+ * The current implementation covers class-typed arguments (value, pointer,
19
+ * and reference) and template specializations with explicit type arguments:
20
+ * - `audit::Event e`, `audit::Event* p`, `audit::Event** pp`
21
+ * - `audit::Event& r`, `audit::Event&& rr`
22
+ * - `std::vector<audit::Event>` (template namespace + template-arg namespaces)
23
+ *
24
+ * Function-pointer arguments and the rest of the full closure are still
25
+ * deliberately excluded. V2 additionally walks class ancestors (via MRO),
26
+ * so base-class enclosing namespaces also contribute associated namespaces.
26
27
  *
27
28
  * The current implementation also short-circuits to ADL only when ordinary lookup is empty
28
29
  * (`findCallableBindingInScope` returned undefined). ISO C++ would
@@ -132,14 +133,7 @@ export function pickCppAdlCandidates(site, callerParsed, scopes, parsedFiles) {
132
133
  // Collect associated namespace QNames from every participating class-typed arg.
133
134
  const associatedNamespaces = new Set();
134
135
  for (const arg of args) {
135
- if (arg.simpleClassName === '')
136
- continue;
137
- const classDef = findCppClassDefBySimpleName(arg.simpleClassName, scopes);
138
- if (classDef === undefined)
139
- continue;
140
- const nsQName = classToNamespaceQualifiedName.get(classDef.nodeId);
141
- if (nsQName !== undefined)
142
- associatedNamespaces.add(nsQName);
136
+ collectAssociatedNamespacesForAdlArg(arg, scopes, associatedNamespaces);
143
137
  }
144
138
  if (associatedNamespaces.size === 0)
145
139
  return undefined;
@@ -196,6 +190,45 @@ export function pickCppAdlCandidates(site, callerParsed, scopes, parsedFiles) {
196
190
  // unique-survivor requirement (see `pick-implicit-this-overload.test.ts`).
197
191
  return ADL_AMBIGUOUS;
198
192
  }
193
+ function collectAssociatedNamespacesForAdlArg(arg, scopes, associatedNamespaces) {
194
+ // For template args this may be the template name itself (e.g. `vector`);
195
+ // simple-name lookup can match project classes with the same name (known
196
+ // V1/V2 simplification).
197
+ addAssociatedNamespaceForClassName(arg.simpleClassName, scopes, associatedNamespaces);
198
+ // Includes template-owner namespaces (e.g. `std` in std::vector<T>). If
199
+ // that surfaces extra candidates, ADL_AMBIGUOUS suppression below prevents
200
+ // arbitrary edge emission.
201
+ if (arg.templateNamespace.length > 0)
202
+ associatedNamespaces.add(arg.templateNamespace);
203
+ for (const ns of arg.templateArgNamespaces) {
204
+ if (ns.length > 0)
205
+ associatedNamespaces.add(ns);
206
+ }
207
+ for (const className of arg.templateArgClassNames) {
208
+ addAssociatedNamespaceForClassName(className, scopes, associatedNamespaces);
209
+ }
210
+ }
211
+ function addAssociatedNamespaceForClassName(simpleClassName, scopes, associatedNamespaces) {
212
+ if (simpleClassName.length === 0)
213
+ return;
214
+ const classLookup = findCppClassDefBySimpleName(simpleClassName, scopes);
215
+ if (classLookup === undefined)
216
+ return;
217
+ const { classDef, ambiguous } = classLookup;
218
+ const nsQName = classToNamespaceQualifiedName.get(classDef.nodeId);
219
+ if (nsQName !== undefined)
220
+ associatedNamespaces.add(nsQName);
221
+ // Preserve V1 collision behavior for the direct class namespace, but avoid
222
+ // amplifying a same-simple-name collision by walking an arbitrary class's
223
+ // full MRO chain.
224
+ if (ambiguous)
225
+ return;
226
+ for (const ancestorDefId of scopes.methodDispatch.mroFor(classDef.nodeId)) {
227
+ const ancestorNsQName = classToNamespaceQualifiedName.get(ancestorDefId);
228
+ if (ancestorNsQName !== undefined)
229
+ associatedNamespaces.add(ancestorNsQName);
230
+ }
231
+ }
199
232
  /** Walk upward from a Class scope, finding the innermost enclosing
200
233
  * Namespace scope, and return that namespace's qualified name (dot-
201
234
  * joined, outermost-first). Returns '' when the class has no enclosing
@@ -264,16 +297,25 @@ function findNamespaceDefInScope(scope) {
264
297
  return undefined;
265
298
  }
266
299
  /** Find a class-like def by simple name across the workspace. V1
267
- * arbitrary-pick on collisions (multiple classes share the simple name);
268
- * C++ ADL strictness would require full type-driven lookup, but V1
269
- * trades that for simplicity. */
300
+ * still arbitrary-picks the first class on collisions (multiple classes
301
+ * share the simple name), but reports the collision so callers can avoid
302
+ * amplifying that uncertainty (for example by skipping MRO expansion).
303
+ * C++ ADL strictness would require full type-driven lookup. */
270
304
  function findCppClassDefBySimpleName(simpleName, scopes) {
305
+ let firstMatch;
271
306
  for (const def of scopes.defs.byId.values()) {
272
307
  if (def.type !== 'Class' && def.type !== 'Struct' && def.type !== 'Interface')
273
308
  continue;
274
309
  const simple = def.qualifiedName?.split('.').pop() ?? def.qualifiedName ?? '';
275
- if (simple === simpleName)
276
- return def;
310
+ if (simple !== simpleName)
311
+ continue;
312
+ if (firstMatch === undefined) {
313
+ firstMatch = def;
314
+ continue;
315
+ }
316
+ return { classDef: firstMatch, ambiguous: true };
277
317
  }
278
- return undefined;
318
+ if (firstMatch === undefined)
319
+ return undefined;
320
+ return { classDef: firstMatch, ambiguous: false };
279
321
  }
@@ -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
- if (grouped['@declaration.namespace'] !== undefined) {
154
- const anchor = grouped['@declaration.namespace'];
155
- const nsNode = findNodeAtRange(tree.rootNode, anchor.range, 'namespace_definition');
156
- if (nsNode !== null && isInlineNamespace(nsNode)) {
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
- // `populateCppInlineNamespaceScopes` can join against `Scope.range`.
161
- markCppInlineNamespaceRange(filePath, {
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 ──────────────────────
@@ -657,12 +670,14 @@ function isParenthesizedFunctionCall(callNode) {
657
670
  }
658
671
  /**
659
672
  * Per-argument ADL classification: walk each argument of a free call and
660
- * decide whether it resolves to a directly-named class or class-pointer
661
- * type (ADL fires) or to an excluded shape such as a reference, function
662
- * pointer, primitive, literal, or template specialization.
673
+ * classify its declared type for associated-namespace lookup.
674
+ *
675
+ * Value/pointer/reference class-typed args and template specializations
676
+ * with explicit type arguments contribute; function pointers, primitives,
677
+ * literals, and other unsupported shapes produce an empty result.
663
678
  *
664
- * Class-typed values and class pointers (`N::S`, `N::S*`, `N::S**`) all
665
- * preserve the pointee class name for associated-namespace lookup.
679
+ * Class-typed values/pointers/references (`N::S`, `N::S*`, `N::S&`) all
680
+ * preserve the class name for associated-namespace lookup.
666
681
  * Function pointers remain excluded even when their return type names a
667
682
  * class, because the associated entity is the pointed-to function type,
668
683
  * not the return type.
@@ -682,7 +697,14 @@ function inferCppCallAdlArgs(callNode) {
682
697
  }
683
698
  return out;
684
699
  }
685
- const EMPTY_ADL_ARG = { simpleClassName: '' };
700
+ const ADL_TEMPLATE_RECURSION_MAX_DEPTH = 8;
701
+ const EMPTY_ADL_ARG = {
702
+ simpleClassName: '',
703
+ templateSimpleClassName: '',
704
+ templateNamespace: '',
705
+ templateArgClassNames: [],
706
+ templateArgNamespaces: [],
707
+ };
686
708
  function classifyAdlArg(argNode) {
687
709
  // Literals and primitive-shaped expressions never have associated namespaces.
688
710
  if (argNode.type === 'number_literal' ||
@@ -783,21 +805,51 @@ function lookupAdlIdentifierType(identNode) {
783
805
  if (isFunctionPointer || nameText !== varName)
784
806
  continue;
785
807
  const simpleClassName = extractAdlSimpleTypeName(typeNode);
786
- return { simpleClassName };
808
+ const { templateSimpleClassName, templateNamespace, templateArgClassNames, templateArgNamespaces, } = extractAdlTemplateInfo(typeNode);
809
+ return {
810
+ simpleClassName,
811
+ templateSimpleClassName,
812
+ templateNamespace,
813
+ templateArgClassNames,
814
+ templateArgNamespaces,
815
+ };
787
816
  }
788
817
  return EMPTY_ADL_ARG;
789
818
  }
790
819
  /** Extract the simple class-like type name from a `type:` field node.
791
- * Returns '' for primitives, template specializations, and any other
820
+ * Returns '' for primitives and any other
792
821
  * unsupported type-only shape. Function pointers are filtered at the
793
822
  * declarator level in `lookupAdlIdentifierType`. */
794
823
  function extractAdlSimpleTypeName(typeNode) {
824
+ if (typeNode.type === 'type_descriptor') {
825
+ const innerType = typeNode.childForFieldName('type');
826
+ if (innerType !== null)
827
+ return extractAdlSimpleTypeName(innerType);
828
+ for (let i = 0; i < typeNode.childCount; i++) {
829
+ const child = typeNode.child(i);
830
+ if (child === null)
831
+ continue;
832
+ if (child.type === 'type_identifier' ||
833
+ child.type === 'qualified_identifier' ||
834
+ child.type === 'template_type') {
835
+ return extractAdlSimpleTypeName(child);
836
+ }
837
+ }
838
+ return '';
839
+ }
795
840
  if (typeNode.type === 'primitive_type')
796
841
  return '';
797
842
  if (typeNode.type === 'sized_type_specifier')
798
843
  return '';
799
844
  if (typeNode.type === 'type_identifier')
800
845
  return typeNode.text;
846
+ if (typeNode.type === 'template_type') {
847
+ const nameNode = typeNode.childForFieldName('name');
848
+ if (nameNode !== null)
849
+ return extractAdlSimpleTypeName(nameNode);
850
+ const id = findFirstDescendantOfType(typeNode, 'type_identifier');
851
+ return id !== null ? id.text : '';
852
+ }
801
853
  if (typeNode.type === 'qualified_identifier') {
802
854
  const nameNode = typeNode.childForFieldName('name');
803
855
  if (nameNode !== null)
@@ -805,9 +857,112 @@ function extractAdlSimpleTypeName(typeNode) {
805
857
  const id = findFirstDescendantOfType(typeNode, 'type_identifier');
806
858
  return id !== null ? id.text : '';
807
859
  }
808
- // template_type (e.g. `vector<int>`), function pointers, decltype — V1 excludes.
860
+ // Function pointers, decltype, etc — unsupported for ADL participation.
861
+ return '';
862
+ }
863
+ function extractAdlTypeNamespace(typeNode) {
864
+ if (typeNode.type === 'type_descriptor') {
865
+ const innerType = typeNode.childForFieldName('type');
866
+ if (innerType !== null)
867
+ return extractAdlTypeNamespace(innerType);
868
+ for (let i = 0; i < typeNode.childCount; i++) {
869
+ const child = typeNode.child(i);
870
+ if (child === null)
871
+ continue;
872
+ if (child.type === 'qualified_identifier' ||
873
+ child.type === 'template_type' ||
874
+ child.type === 'type_identifier') {
875
+ return extractAdlTypeNamespace(child);
876
+ }
877
+ }
878
+ return '';
879
+ }
880
+ if (typeNode.type === 'template_type') {
881
+ const nameNode = typeNode.childForFieldName('name');
882
+ return nameNode !== null ? extractAdlTypeNamespace(nameNode) : '';
883
+ }
884
+ if (typeNode.type === 'qualified_identifier') {
885
+ const scope = typeNode.childForFieldName('scope');
886
+ if (scope !== null)
887
+ return normalizeCppNamespaceQName(scope.text);
888
+ return extractNamespaceFromQualifiedText(typeNode.text);
889
+ }
809
890
  return '';
810
891
  }
892
+ function extractAdlTemplateInfo(typeNode) {
893
+ const templateTypeNode = findTemplateTypeNode(typeNode);
894
+ if (templateTypeNode === null) {
895
+ return {
896
+ templateSimpleClassName: '',
897
+ templateNamespace: '',
898
+ templateArgClassNames: [],
899
+ templateArgNamespaces: [],
900
+ };
901
+ }
902
+ const templateArgClassNames = [];
903
+ const templateArgNamespaces = [];
904
+ collectAdlTemplateArgs(templateTypeNode, 0, templateArgClassNames, templateArgNamespaces);
905
+ return {
906
+ templateSimpleClassName: extractAdlSimpleTypeName(templateTypeNode),
907
+ templateNamespace: extractAdlTypeNamespace(typeNode),
908
+ templateArgClassNames,
909
+ templateArgNamespaces,
910
+ };
911
+ }
912
+ function collectAdlTemplateArgs(templateTypeNode, depth, outClassNames, outNamespaces) {
913
+ if (depth >= ADL_TEMPLATE_RECURSION_MAX_DEPTH)
914
+ return;
915
+ if (templateTypeNode.type !== 'template_type')
916
+ return;
917
+ const argList = templateTypeNode.childForFieldName('arguments') ??
918
+ findChildOfType(templateTypeNode, ['template_argument_list']);
919
+ if (argList === null)
920
+ return;
921
+ for (let i = 0; i < argList.namedChildCount; i++) {
922
+ const arg = argList.namedChild(i);
923
+ if (arg === null || arg.type !== 'type_descriptor')
924
+ continue;
925
+ const simpleClassName = extractAdlSimpleTypeName(arg);
926
+ if (simpleClassName.length > 0)
927
+ outClassNames.push(simpleClassName);
928
+ const ns = extractAdlTypeNamespace(arg);
929
+ if (ns.length > 0)
930
+ outNamespaces.push(ns);
931
+ const nestedType = arg.childForFieldName('type');
932
+ const nestedTemplate = nestedType !== null ? findTemplateTypeNode(nestedType) : null;
933
+ if (nestedTemplate !== null) {
934
+ collectAdlTemplateArgs(nestedTemplate, depth + 1, outClassNames, outNamespaces);
935
+ }
936
+ }
937
+ }
938
+ function findTemplateTypeNode(typeNode) {
939
+ if (typeNode.type === 'template_type')
940
+ return typeNode;
941
+ if (typeNode.type === 'type_descriptor') {
942
+ const innerType = typeNode.childForFieldName('type');
943
+ if (innerType !== null)
944
+ return findTemplateTypeNode(innerType);
945
+ return null;
946
+ }
947
+ if (typeNode.type === 'qualified_identifier') {
948
+ const nameNode = typeNode.childForFieldName('name');
949
+ if (nameNode !== null)
950
+ return findTemplateTypeNode(nameNode);
951
+ return null;
952
+ }
953
+ return null;
954
+ }
955
+ function normalizeCppNamespaceQName(text) {
956
+ const normalized = text.replace(/^::/, '').replace(/::$/, '').replace(/::/g, '.');
957
+ return normalized;
958
+ }
959
+ function extractNamespaceFromQualifiedText(text) {
960
+ const cleaned = text.replace(/\s+/g, '');
961
+ const idx = cleaned.lastIndexOf('::');
962
+ if (idx <= 0)
963
+ return '';
964
+ return normalizeCppNamespaceQName(cleaned.slice(0, idx));
965
+ }
811
966
  /**
812
967
  * Check if a C++ function_definition or declaration has `static` storage class.
813
968
  */
@@ -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
- if (isFileLocal(target.filePath, name))
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 time) to
65
- // ScopeIds BEFORE `populateCppNonGloballyVisible` runs, so the
66
- // inline-namespace exemption sees the populated Set.
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.
@@ -188,8 +189,10 @@ export const cppScopeResolver = {
188
189
  // C++ argument-dependent / Koenig lookup (U2 of plan 2026-05-13-001).
189
190
  // Fires after `findCallableBindingInScope` returns undefined; surfaces
190
191
  // candidates from the associated namespaces of class-typed arguments.
191
- // V1 limitation: only direct enclosing-namespace closure for value
192
- // class-typed args; pointer/reference/template-spec args excluded.
192
+ // Current boundary: class-typed value/pointer/reference args and template
193
+ // specializations with explicit type arguments contribute associated
194
+ // namespaces. Function-pointer args, base-class associated namespaces,
195
+ // and full ordinary+ADL merge remain excluded.
193
196
  resolveAdlCandidates: (site, callerParsed, scopes, parsedFiles) => {
194
197
  // `using ns::name;` introduces `name` into ordinary unqualified lookup.
195
198
  // For template-class method bodies, lexical scope walks can miss this
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitnexus",
3
- "version": "1.6.5-rc.40",
3
+ "version": "1.6.5-rc.42",
4
4
  "description": "Graph-powered code intelligence for AI agents. Index any codebase, query via MCP or CLI.",
5
5
  "author": "Abhigyan Patwari",
6
6
  "license": "PolyForm-Noncommercial-1.0.0",