gitnexus 1.6.5-rc.44 → 1.6.5-rc.45

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.
@@ -60,8 +60,8 @@ export declare function isCppInlineNamespaceScope(scopeId: ScopeId): boolean;
60
60
  * Returns the most specific (innermost) match — for `outer::foo()`
61
61
  * where `inline namespace v1` declares `foo`, returns `v1::foo`. When
62
62
  * multiple inline-namespace children declare the same name, ISO C++
63
- * leaves the call ambiguous; V1 returns the first match in source
64
- * order (stable across runs).
63
+ * leaves the call ambiguous; returns `'ambiguous'` so the caller
64
+ * suppresses edge emission rather than picking arbitrarily (#1564).
65
65
  */
66
- export declare function resolveCppQualifiedNamespaceMember(receiverName: string, memberName: string, parsedFiles: readonly ParsedFile[], _scopes: ScopeResolutionIndexes): SymbolDefinition | undefined;
66
+ export declare function resolveCppQualifiedNamespaceMember(receiverName: string, memberName: string, parsedFiles: readonly ParsedFile[], _scopes: ScopeResolutionIndexes): SymbolDefinition | 'ambiguous' | undefined;
67
67
  export {};
@@ -26,6 +26,7 @@
26
26
  * `std::vector` qualified calls resolve to the inline-namespace
27
27
  * declaration transparently.
28
28
  */
29
+ import { isOverloadAmbiguousAfterNormalization, narrowOverloadCandidates, } from '../../scope-resolution/passes/overload-narrowing.js';
29
30
  const inlineNamespaceRangesByFile = new Map();
30
31
  const inlineNamespaceScopeIds = new Set();
31
32
  function rangeKey(r) {
@@ -80,10 +81,12 @@ export function isCppInlineNamespaceScope(scopeId) {
80
81
  * Returns the most specific (innermost) match — for `outer::foo()`
81
82
  * where `inline namespace v1` declares `foo`, returns `v1::foo`. When
82
83
  * multiple inline-namespace children declare the same name, ISO C++
83
- * leaves the call ambiguous; V1 returns the first match in source
84
- * order (stable across runs).
84
+ * leaves the call ambiguous; returns `'ambiguous'` so the caller
85
+ * suppresses edge emission rather than picking arbitrarily (#1564).
85
86
  */
86
87
  export function resolveCppQualifiedNamespaceMember(receiverName, memberName, parsedFiles, _scopes) {
88
+ const allHits = [];
89
+ const seenNodeId = new Set();
87
90
  for (const parsed of parsedFiles) {
88
91
  const scopesById = new Map();
89
92
  for (const sc of parsed.scopes)
@@ -97,27 +100,58 @@ export function resolveCppQualifiedNamespaceMember(receiverName, memberName, par
97
100
  const nsName = nsDef.qualifiedName?.split('.').pop() ?? nsDef.qualifiedName ?? '';
98
101
  if (nsName !== receiverName)
99
102
  continue;
100
- // Found a matching namespace scope in this file. Collect the
101
- // member transitively through any inline-namespace children.
102
- const hit = findMemberInNamespaceTransitive(scope, scopesById, memberName);
103
- if (hit !== undefined)
104
- return hit;
103
+ // Found a matching namespace scope in this file. Collect ALL
104
+ // members transitively through any inline-namespace children.
105
+ const hits = findMemberInNamespaceTransitive(scope, scopesById, memberName);
106
+ for (const hit of hits) {
107
+ if (seenNodeId.has(hit.nodeId))
108
+ continue;
109
+ seenNodeId.add(hit.nodeId);
110
+ allHits.push(hit);
111
+ }
105
112
  }
106
113
  }
107
- return undefined;
114
+ if (allHits.length === 0)
115
+ return undefined;
116
+ if (allHits.length === 1)
117
+ return allHits[0];
118
+ // Multi-candidate: the `resolveQualifiedReceiverMember` hook has no
119
+ // access to call-site arity or argument types, so
120
+ // `narrowOverloadCandidates` cannot actually narrow here — the call
121
+ // with `(allHits, undefined, undefined)` is effectively a pass-through.
122
+ // We retain it so that `isOverloadAmbiguousAfterNormalization` can
123
+ // still detect int/long-style normalization collisions on this path,
124
+ // but for any multi-hit case where candidates have genuinely distinct
125
+ // signatures (e.g. `foo(int)` vs `foo(double)` in different inline
126
+ // children), we conservatively suppress rather than pick arbitrarily.
127
+ // A future enhancement could thread call-site argument info through
128
+ // the `resolveQualifiedReceiverMember` contract to enable real
129
+ // narrowing here.
130
+ const narrowed = narrowOverloadCandidates(allHits, undefined, undefined);
131
+ if (narrowed.length === 1)
132
+ return narrowed[0];
133
+ if (narrowed.length === 0)
134
+ return undefined;
135
+ if (isOverloadAmbiguousAfterNormalization(narrowed, undefined))
136
+ return 'ambiguous';
137
+ // Multiple surviving candidates (distinct signatures) — conservative
138
+ // suppress because we lack call-site info to disambiguate.
139
+ return 'ambiguous';
108
140
  }
109
141
  /** Recursively search a namespace scope and any inline-namespace
110
- * descendants for a callable def with the given simple name. Non-inline
142
+ * descendants for callable defs with the given simple name. Non-inline
111
143
  * nested namespaces are NOT traversed — they require explicit
112
- * qualification (`outer::nested::foo`). */
144
+ * qualification (`outer::nested::foo`). Returns ALL matches so the
145
+ * caller can detect same-name ambiguity across inline children (#1564). */
113
146
  function findMemberInNamespaceTransitive(scope, scopesById, memberName) {
147
+ const results = [];
114
148
  // Check this scope's own ownedDefs first.
115
149
  for (const def of scope.ownedDefs) {
116
150
  if (def.type !== 'Function' && def.type !== 'Method' && def.type !== 'Constructor')
117
151
  continue;
118
152
  const simple = def.qualifiedName?.split('.').pop() ?? def.qualifiedName ?? '';
119
153
  if (simple === memberName)
120
- return def;
154
+ results.push(def);
121
155
  }
122
156
  // Descend into inline-namespace children.
123
157
  for (const childScope of scopesById.values()) {
@@ -127,11 +161,11 @@ function findMemberInNamespaceTransitive(scope, scopesById, memberName) {
127
161
  continue;
128
162
  if (!inlineNamespaceScopeIds.has(childScope.id))
129
163
  continue;
130
- const hit = findMemberInNamespaceTransitive(childScope, scopesById, memberName);
131
- if (hit !== undefined)
132
- return hit;
164
+ const childHits = findMemberInNamespaceTransitive(childScope, scopesById, memberName);
165
+ for (const hit of childHits)
166
+ results.push(hit);
133
167
  }
134
- return undefined;
168
+ return results;
135
169
  }
136
170
  function findNamespaceDefInScope(scope) {
137
171
  for (const def of scope.ownedDefs) {
@@ -205,7 +205,7 @@ export const cppScopeResolver = {
205
205
  if (imp.localName !== site.name)
206
206
  continue;
207
207
  const member = resolveCppQualifiedNamespaceMember(imp.targetRaw, imp.importedName, parsedFiles, scopes);
208
- if (member === undefined)
208
+ if (member === undefined || member === 'ambiguous')
209
209
  continue;
210
210
  if (seenUsing.has(member.nodeId))
211
211
  continue;
@@ -542,10 +542,11 @@ export interface ScopeResolver {
542
542
  *
543
543
  * Receiver-bound-calls invokes this hook AFTER Case 1 (namespace
544
544
  * imports) and AFTER Case 2 (class-name receiver) fail to resolve.
545
- * Returns the target def, or `undefined` to fall through to the
546
- * remaining cases.
545
+ * Returns the target def, `'ambiguous'` when multiple inline-namespace
546
+ * children declare the same name (suppresses edge emission), or
547
+ * `undefined` to fall through to the remaining cases.
547
548
  */
548
- readonly resolveQualifiedReceiverMember?: (receiverName: string, memberName: string, callerScope: ScopeId, scopes: ScopeResolutionIndexes, parsedFiles: readonly ParsedFile[]) => SymbolDefinition | undefined;
549
+ readonly resolveQualifiedReceiverMember?: (receiverName: string, memberName: string, callerScope: ScopeId, scopes: ScopeResolutionIndexes, parsedFiles: readonly ParsedFile[]) => SymbolDefinition | 'ambiguous' | undefined;
549
550
  /**
550
551
  * Enable the receiver-bound Case 0.5 fallback for explicit `this`
551
552
  * receivers (`this->m()` / `this.m()`) that resolves against the
@@ -324,6 +324,12 @@ export function emitReceiverBoundCalls(graph, scopes, parsedFiles, nodeLookup, h
324
324
  // class with the same simple name.
325
325
  if (provider.resolveQualifiedReceiverMember !== undefined) {
326
326
  const memberDef = provider.resolveQualifiedReceiverMember(receiverName, memberName, site.inScope, scopes, parsedFiles);
327
+ if (memberDef === 'ambiguous') {
328
+ // Same-name ambiguity across inline-namespace children (#1564):
329
+ // suppress edge emission, mark site handled.
330
+ handledSites.add(siteKey);
331
+ continue;
332
+ }
327
333
  if (memberDef !== undefined) {
328
334
  const ok = tryEmitEdge(graph, scopes, nodeLookup, site, memberDef, memberDef.filePath !== parsed.filePath ? 'import-resolved' : 'global', seen, 0.85, collapse);
329
335
  if (ok)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitnexus",
3
- "version": "1.6.5-rc.44",
3
+ "version": "1.6.5-rc.45",
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",