gitnexus 1.6.5-rc.41 → 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.
@@ -21,8 +21,9 @@
21
21
  * - `audit::Event& r`, `audit::Event&& rr`
22
22
  * - `std::vector<audit::Event>` (template namespace + template-arg namespaces)
23
23
  *
24
- * Function-pointer arguments, base-class associated namespaces, and the rest
25
- * of the full closure are still deliberately excluded.
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
@@ -21,8 +21,9 @@
21
21
  * - `audit::Event& r`, `audit::Event&& rr`
22
22
  * - `std::vector<audit::Event>` (template namespace + template-arg namespaces)
23
23
  *
24
- * Function-pointer arguments, base-class associated namespaces, and the rest
25
- * of the full closure are still deliberately excluded.
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
@@ -210,12 +211,23 @@ function collectAssociatedNamespacesForAdlArg(arg, scopes, associatedNamespaces)
210
211
  function addAssociatedNamespaceForClassName(simpleClassName, scopes, associatedNamespaces) {
211
212
  if (simpleClassName.length === 0)
212
213
  return;
213
- const classDef = findCppClassDefBySimpleName(simpleClassName, scopes);
214
- if (classDef === undefined)
214
+ const classLookup = findCppClassDefBySimpleName(simpleClassName, scopes);
215
+ if (classLookup === undefined)
215
216
  return;
217
+ const { classDef, ambiguous } = classLookup;
216
218
  const nsQName = classToNamespaceQualifiedName.get(classDef.nodeId);
217
219
  if (nsQName !== undefined)
218
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
+ }
219
231
  }
220
232
  /** Walk upward from a Class scope, finding the innermost enclosing
221
233
  * Namespace scope, and return that namespace's qualified name (dot-
@@ -285,16 +297,25 @@ function findNamespaceDefInScope(scope) {
285
297
  return undefined;
286
298
  }
287
299
  /** Find a class-like def by simple name across the workspace. V1
288
- * arbitrary-pick on collisions (multiple classes share the simple name);
289
- * C++ ADL strictness would require full type-driven lookup, but V1
290
- * 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. */
291
304
  function findCppClassDefBySimpleName(simpleName, scopes) {
305
+ let firstMatch;
292
306
  for (const def of scopes.defs.byId.values()) {
293
307
  if (def.type !== 'Class' && def.type !== 'Struct' && def.type !== 'Interface')
294
308
  continue;
295
309
  const simple = def.qualifiedName?.split('.').pop() ?? def.qualifiedName ?? '';
296
- if (simple === simpleName)
297
- return def;
310
+ if (simple !== simpleName)
311
+ continue;
312
+ if (firstMatch === undefined) {
313
+ firstMatch = def;
314
+ continue;
315
+ }
316
+ return { classDef: firstMatch, ambiguous: true };
298
317
  }
299
- return undefined;
318
+ if (firstMatch === undefined)
319
+ return undefined;
320
+ return { classDef: firstMatch, ambiguous: false };
300
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 ──────────────────────
@@ -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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitnexus",
3
- "version": "1.6.5-rc.41",
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",