gitnexus 1.6.6-rc.80 → 1.6.6-rc.81

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.
@@ -24,22 +24,18 @@
24
24
  * V2 additionally walks class ancestors (via MRO), so base-class enclosing
25
25
  * namespaces also contribute associated namespaces.
26
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.
27
+ * Function-reference arguments follow ISO C++ `[basic.lookup.argdep]`:
28
+ * associated entities come from the parameter types and return type of each
29
+ * referenced function in the overload set, not from the function's enclosing
30
+ * namespace. For `void worker()`, the associated set is empty. For
31
+ * `void worker(api::Token)` or `api::Token make_token()`, `api` is associated
32
+ * through `Token`.
33
+ *
34
+ * For qualified refs (e.g. `utils::worker`) the workspace lookup is restricted
35
+ * to functions/methods named `worker` in `utils`; for unqualified refs the
36
+ * workspace is searched for matching functions/methods by simple name. Locally
37
+ * declared function-pointer variables and function parameters are excluded
38
+ * from this path.
43
39
  *
44
40
  * ADL candidates are merged with ordinary unqualified-lookup candidates
45
41
  * in the free-call fallback before overload narrowing.
@@ -94,11 +90,8 @@ export interface CppAdlArgInfo {
94
90
  /** When set, the arg is a potential free-function reference (not a locally-
95
91
  * declared function-pointer variable or function parameter). Contains the
96
92
  * identifier text as written in source (e.g. `"utils::worker"` or
97
- * `"worker"`). GitNexus approximation: the function's enclosing namespace
98
- * is contributed to the ADL associated set. For qualified refs a workspace
99
- * lookup confirms a Function/Method with that simple name exists in the
100
- * namespace before contributing; for unqualified refs every namespace
101
- * containing a matching Function/Method def is contributed. */
93
+ * `"worker"`). Resolution contributes associated namespaces from each
94
+ * referenced Function/Method def's parameter and return types. */
102
95
  readonly functionRefText?: string;
103
96
  }
104
97
  /** Record per-call-site argument info. Called once per call site from
@@ -24,22 +24,18 @@
24
24
  * V2 additionally walks class ancestors (via MRO), so base-class enclosing
25
25
  * namespaces also contribute associated namespaces.
26
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.
27
+ * Function-reference arguments follow ISO C++ `[basic.lookup.argdep]`:
28
+ * associated entities come from the parameter types and return type of each
29
+ * referenced function in the overload set, not from the function's enclosing
30
+ * namespace. For `void worker()`, the associated set is empty. For
31
+ * `void worker(api::Token)` or `api::Token make_token()`, `api` is associated
32
+ * through `Token`.
37
33
  *
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.
34
+ * For qualified refs (e.g. `utils::worker`) the workspace lookup is restricted
35
+ * to functions/methods named `worker` in `utils`; for unqualified refs the
36
+ * workspace is searched for matching functions/methods by simple name. Locally
37
+ * declared function-pointer variables and function parameters are excluded
38
+ * from this path.
43
39
  *
44
40
  * ADL candidates are merged with ordinary unqualified-lookup candidates
45
41
  * in the free-call fallback before overload narrowing.
@@ -67,6 +63,7 @@
67
63
  * but logically share the same namespace. ADL must consider candidates
68
64
  * declared in either file.
69
65
  */
66
+ import { normalizeCppParamType } from './arity-metadata.js';
70
67
  import { isCppInlineNamespaceScope } from './inline-namespaces.js';
71
68
  const argInfoBySite = new Map();
72
69
  const noAdlSites = new Set();
@@ -157,7 +154,7 @@ export function pickCppAdlCandidates(site, callerParsed, scopes, parsedFiles) {
157
154
  for (const arg of args) {
158
155
  collectAssociatedNamespacesForAdlArg(arg, scopes, associatedNamespaces);
159
156
  if (arg.functionRefText !== undefined) {
160
- collectFunctionRefNamespaces(arg.functionRefText, parsedFiles, associatedNamespaces);
157
+ collectFunctionTypeAssociatedNamespaces(arg.functionRefText, scopes, parsedFiles, associatedNamespaces);
161
158
  }
162
159
  }
163
160
  if (associatedNamespaces.size === 0)
@@ -405,22 +402,10 @@ function findCppClassDefBySimpleName(simpleName, scopes) {
405
402
  return { classDef: firstMatch, ambiguous: false };
406
403
  }
407
404
  /**
408
- * Contribute associated namespaces for a function-reference argument.
409
- *
410
- * - **Qualified refs** (`utils::worker`, `outer::inner::fn`): the namespace
411
- * is extracted from the qualifier text (converting `::` to `.` for dot-joined
412
- * QName matching). A workspace lookup then **verifies** that a Function or
413
- * Method def named `worker` (the simple name after the last `::`) actually
414
- * exists in the extracted namespace. This prevents false positives from
415
- * namespace-qualified variables, enum values, and static data members, which
416
- * also produce `qualified_identifier` AST nodes in tree-sitter-cpp (the
417
- * AST node type alone does not distinguish functions from non-function names).
418
- * - **Unqualified refs** (`worker`): the workspace is searched for any
419
- * Function/Method def whose simple name matches. Every distinct enclosing
420
- * namespace found is added — overloads across the same namespace produce
421
- * a single entry; GitNexus does not select a specific overload at this stage.
405
+ * Contribute associated namespaces for a function-reference argument by walking
406
+ * the referenced overload set's parameter and return types.
422
407
  */
423
- function collectFunctionRefNamespaces(refText, parsedFiles, out) {
408
+ function collectFunctionTypeAssociatedNamespaces(refText, scopes, parsedFiles, out) {
424
409
  const colonIdx = refText.lastIndexOf('::');
425
410
  if (colonIdx !== -1) {
426
411
  // Qualified ref: extract namespace prefix and normalise :: → dot notation.
@@ -445,21 +430,18 @@ function collectFunctionRefNamespaces(refText, parsedFiles, out) {
445
430
  if (def.type !== 'Function' && def.type !== 'Method')
446
431
  continue;
447
432
  const simple = def.qualifiedName?.split('.').pop() ?? def.qualifiedName ?? '';
448
- if (simple === simpleName) {
449
- out.add(nsText);
450
- return; // Namespace confirmed; no need to scan further files.
451
- }
433
+ if (simple === simpleName)
434
+ collectAssociatedNamespacesForFunctionDef(def, scopes, out);
452
435
  }
453
436
  }
454
437
  }
455
438
  return;
456
439
  }
457
- // Unqualified: search all namespace scopes for a Function def with this
458
- // simple name and contribute its enclosing namespace.
440
+ // Unqualified function references are approximated workspace-wide, matching
441
+ // the previous V1 lookup scope. The stricter part of this PR is what each
442
+ // overload contributes: only namespaces from parameter/return types, never
443
+ // the function's own enclosing namespace.
459
444
  for (const parsed of parsedFiles) {
460
- const scopesById = new Map();
461
- for (const sc of parsed.scopes)
462
- scopesById.set(sc.id, sc);
463
445
  for (const scope of parsed.scopes) {
464
446
  if (scope.kind !== 'Namespace')
465
447
  continue;
@@ -469,10 +451,101 @@ function collectFunctionRefNamespaces(refText, parsedFiles, out) {
469
451
  const simple = def.qualifiedName?.split('.').pop() ?? def.qualifiedName ?? '';
470
452
  if (simple !== refText)
471
453
  continue;
472
- const nsQName = computeNamespaceQName(scope, scopesById);
473
- if (nsQName !== '')
474
- out.add(nsQName);
454
+ collectAssociatedNamespacesForFunctionDef(def, scopes, out);
475
455
  }
476
456
  }
477
457
  }
478
458
  }
459
+ function collectAssociatedNamespacesForFunctionDef(def, scopes, out) {
460
+ const parameterTypes = def.parameterTypeClasses?.map((typeClass) => typeClass.base);
461
+ for (const paramType of parameterTypes ?? def.parameterTypes ?? []) {
462
+ collectAssociatedNamespacesForFunctionTypeText(paramType, scopes, out);
463
+ }
464
+ if (def.returnType !== undefined) {
465
+ collectAssociatedNamespacesForFunctionTypeText(def.returnType, scopes, out);
466
+ }
467
+ }
468
+ function collectAssociatedNamespacesForFunctionTypeText(typeText, scopes, out) {
469
+ for (const token of extractCppTypeNameTokens(typeText)) {
470
+ if (isIgnoredCppAdlNamespace(token.namespaceName))
471
+ continue;
472
+ addAssociatedNamespaceForClassName(token.simpleName, scopes, out);
473
+ if (token.namespaceName !== '')
474
+ out.add(token.namespaceName);
475
+ }
476
+ }
477
+ function extractCppTypeNameTokens(typeText) {
478
+ const cleaned = normalizeCppParamType(typeText);
479
+ if (cleaned === '' || isPrimitiveCppAdlType(cleaned))
480
+ return [];
481
+ const out = [];
482
+ const seen = new Set();
483
+ const tokenSource = typeText.includes('<') ? `${cleaned} ${typeText}` : cleaned;
484
+ for (const rawToken of tokenSource.match(/[A-Za-z_]\w*(?:::[A-Za-z_]\w*)*/g) ?? []) {
485
+ if (isPrimitiveCppAdlType(rawToken))
486
+ continue;
487
+ const segments = rawToken.split('::').filter((part) => part.length > 0);
488
+ const simpleName = segments.at(-1) ?? '';
489
+ if (simpleName === '' || isPrimitiveCppAdlType(simpleName))
490
+ continue;
491
+ const namespaceName = segments.length > 1 ? segments.slice(0, -1).join('.') : '';
492
+ const key = `${namespaceName}\0${simpleName}`;
493
+ if (seen.has(key))
494
+ continue;
495
+ seen.add(key);
496
+ out.push({
497
+ simpleName,
498
+ namespaceName,
499
+ });
500
+ }
501
+ return out;
502
+ }
503
+ const CPP_ADL_PRIMITIVE_OR_KEYWORD_TYPES = new Set([
504
+ 'alignas',
505
+ 'alignof',
506
+ 'auto',
507
+ 'bool',
508
+ 'char',
509
+ 'char8_t',
510
+ 'char16_t',
511
+ 'char32_t',
512
+ 'class',
513
+ 'const',
514
+ 'consteval',
515
+ 'constexpr',
516
+ 'constinit',
517
+ 'decltype',
518
+ 'double',
519
+ 'enum',
520
+ 'explicit',
521
+ 'extern',
522
+ 'float',
523
+ 'inline',
524
+ 'int',
525
+ 'long',
526
+ 'mutable',
527
+ 'noexcept',
528
+ 'null',
529
+ 'register',
530
+ 'short',
531
+ 'signed',
532
+ 'static',
533
+ 'string',
534
+ 'struct',
535
+ 'template',
536
+ 'thread_local',
537
+ 'typename',
538
+ 'union',
539
+ 'unknown',
540
+ 'unsigned',
541
+ 'void',
542
+ 'volatile',
543
+ 'wchar_t',
544
+ '...',
545
+ ]);
546
+ function isPrimitiveCppAdlType(typeText) {
547
+ return CPP_ADL_PRIMITIVE_OR_KEYWORD_TYPES.has(typeText);
548
+ }
549
+ function isIgnoredCppAdlNamespace(namespaceName) {
550
+ return namespaceName === 'std' || namespaceName.startsWith('std.');
551
+ }
@@ -89,6 +89,10 @@ export function emitCppScopeCaptures(sourceText, filePath, cachedTree) {
89
89
  if (arity.parameterTypeClasses !== undefined) {
90
90
  grouped['@declaration.parameter-type-classes'] = syntheticCapture('@declaration.parameter-type-classes', fnNode, JSON.stringify(arity.parameterTypeClasses));
91
91
  }
92
+ const returnType = extractCppDeclarationReturnType(fnNode);
93
+ if (returnType !== undefined) {
94
+ grouped['@declaration.return-type'] = syntheticCapture('@declaration.return-type', fnNode, returnType);
95
+ }
92
96
  if (hasExplicitSpecifier(fnNode)) {
93
97
  grouped['@declaration.is-explicit'] = syntheticCapture('@declaration.is-explicit', fnNode, 'true');
94
98
  }
@@ -311,6 +315,32 @@ export function emitCppScopeCaptures(sourceText, filePath, cachedTree) {
311
315
  detectCppDependentBases(tree.rootNode, filePath);
312
316
  return out;
313
317
  }
318
+ function extractCppDeclarationReturnType(fnNode) {
319
+ const typeNode = fnNode.childForFieldName('type');
320
+ if (typeNode === null)
321
+ return undefined;
322
+ const funcDeclarator = findFunctionDeclarator(fnNode);
323
+ if (funcDeclarator !== null && isCppUnsupportedReturnTypeDeclarator(funcDeclarator)) {
324
+ return undefined;
325
+ }
326
+ const typeText = typeNode.text.trim();
327
+ if (typeText !== 'auto')
328
+ return typeText.length > 0 ? typeText : undefined;
329
+ if (funcDeclarator === null)
330
+ return typeText;
331
+ for (let i = 0; i < funcDeclarator.namedChildCount; i++) {
332
+ const child = funcDeclarator.namedChild(i);
333
+ if (child?.type !== 'trailing_return_type')
334
+ continue;
335
+ const typeDesc = child.firstNamedChild;
336
+ return typeDesc?.text.trim() || typeText;
337
+ }
338
+ return typeText;
339
+ }
340
+ function isCppUnsupportedReturnTypeDeclarator(funcDeclarator) {
341
+ const text = funcDeclarator.text;
342
+ return /\boperator\b/.test(text) || /(^|[(:\s])~\s*[A-Za-z_]\w*/.test(text);
343
+ }
314
344
  /**
315
345
  * Walk every C++ class/struct base clause and emit `@reference.inherits`
316
346
  * captures for each base so scope resolution can resolve them into EXTENDS
@@ -853,6 +853,7 @@ const KNOWN_SUB_TAGS = new Set([
853
853
  '@declaration.required-parameter-count',
854
854
  '@declaration.parameter-types',
855
855
  '@declaration.parameter-type-classes',
856
+ '@declaration.return-type',
856
857
  '@declaration.template-constraints',
857
858
  '@declaration.is-explicit',
858
859
  ]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitnexus",
3
- "version": "1.6.6-rc.80",
3
+ "version": "1.6.6-rc.81",
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",