gitnexus 1.6.5-rc.37 → 1.6.5-rc.39

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.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * C++ argument-dependent lookup (ADL / Koenig lookup) — V1.
2
+ * C++ argument-dependent lookup (ADL / Koenig lookup).
3
3
  *
4
4
  * When ordinary unqualified lookup fails for a free-call site, ADL also
5
5
  * considers candidates declared in the **associated namespaces** of the
@@ -13,17 +13,18 @@
13
13
  * `using` anything. With V1 ADL: `audit::record` is discovered via
14
14
  * `audit::Event`'s associated namespace.
15
15
  *
16
- * ## V1 boundary
16
+ * ## Current boundary
17
17
  *
18
- * V1 covers ONE associated-entity rule: an argument that's a directly-named
18
+ * The current implementation covers ONE associated-entity rule: an argument that's a directly-named
19
19
  * class type (`audit::Event e`) contributes its **direct enclosing
20
- * namespace** to the candidate set. Anything else — pointer/reference
21
- * arguments, function-pointer arguments, template specializations,
22
- * base-class associated namespaces — is V2 closure work and is
23
- * deliberately excluded. The `cpp-adl-pointer-arg-boundary` fixture
24
- * locks the exclusion in CI.
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.
25
26
  *
26
- * V1 also short-circuits to ADL only when ordinary lookup is empty
27
+ * The current implementation also short-circuits to ADL only when ordinary lookup is empty
27
28
  * (`findCallableBindingInScope` returned undefined). ISO C++ would
28
29
  * normally merge ADL candidates with ordinary-lookup candidates and
29
30
  * run overload resolution over the union; V1 defers that merge to V2.
@@ -54,19 +55,14 @@
54
55
  import type { ParsedFile, SymbolDefinition } from '../../../../_shared/index.js';
55
56
  import type { ScopeResolutionIndexes } from '../../model/scope-resolution-indexes.js';
56
57
  /**
57
- * Per-argument shape information collected at capture time. ADL only
58
- * fires for arguments where `simpleClassName !== ''` AND `!isPointer`
59
- * AND `!isReference` — i.e., directly-named class-type values.
58
+ * Per-argument shape information collected at capture time. ADL fires for
59
+ * arguments where `simpleClassName !== ''`, including class pointers and
60
+ * references whose declarator chain resolves to a named class type.
60
61
  */
61
62
  export interface CppAdlArgInfo {
62
63
  /** Simple class-like type name (last segment of qualified name); empty
63
64
  * for primitives, literals, function pointers, template specs, etc. */
64
65
  readonly simpleClassName: string;
65
- /** True when the variable's declarator was a `pointer_declarator`. V1
66
- * excludes pointer-typed args (closure rules deferred to V2). */
67
- readonly isPointer: boolean;
68
- /** True when the variable's declarator was a `reference_declarator`. */
69
- readonly isReference: boolean;
70
66
  }
71
67
  /** Sentinel returned by `pickCppAdlCandidates` when ADL surfaces multiple
72
68
  * candidates that share normalized parameter types — the caller MUST
@@ -105,8 +101,8 @@ export declare function populateCppAssociatedNamespaces(parsed: ParsedFile): voi
105
101
  *
106
102
  * Fires only when:
107
103
  * - the call site is not in `noAdlSites` (parenthesized form), AND
108
- * - at least one argument is a directly-named class type (not pointer,
109
- * not reference, not literal/primitive).
104
+ * - at least one argument resolves to a named class type (value,
105
+ * pointer, or reference; but not function pointer, literal, or primitive).
110
106
  */
111
107
  export declare function pickCppAdlCandidates(site: {
112
108
  readonly name: string;
@@ -1,5 +1,5 @@
1
1
  /**
2
- * C++ argument-dependent lookup (ADL / Koenig lookup) — V1.
2
+ * C++ argument-dependent lookup (ADL / Koenig lookup).
3
3
  *
4
4
  * When ordinary unqualified lookup fails for a free-call site, ADL also
5
5
  * considers candidates declared in the **associated namespaces** of the
@@ -13,17 +13,18 @@
13
13
  * `using` anything. With V1 ADL: `audit::record` is discovered via
14
14
  * `audit::Event`'s associated namespace.
15
15
  *
16
- * ## V1 boundary
16
+ * ## Current boundary
17
17
  *
18
- * V1 covers ONE associated-entity rule: an argument that's a directly-named
18
+ * The current implementation covers ONE associated-entity rule: an argument that's a directly-named
19
19
  * class type (`audit::Event e`) contributes its **direct enclosing
20
- * namespace** to the candidate set. Anything else — pointer/reference
21
- * arguments, function-pointer arguments, template specializations,
22
- * base-class associated namespaces — is V2 closure work and is
23
- * deliberately excluded. The `cpp-adl-pointer-arg-boundary` fixture
24
- * locks the exclusion in CI.
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.
25
26
  *
26
- * V1 also short-circuits to ADL only when ordinary lookup is empty
27
+ * The current implementation also short-circuits to ADL only when ordinary lookup is empty
27
28
  * (`findCallableBindingInScope` returned undefined). ISO C++ would
28
29
  * normally merge ADL candidates with ordinary-lookup candidates and
29
30
  * run overload resolution over the union; V1 defers that merge to V2.
@@ -118,8 +119,8 @@ export function populateCppAssociatedNamespaces(parsed) {
118
119
  *
119
120
  * Fires only when:
120
121
  * - the call site is not in `noAdlSites` (parenthesized form), AND
121
- * - at least one argument is a directly-named class type (not pointer,
122
- * not reference, not literal/primitive).
122
+ * - at least one argument resolves to a named class type (value,
123
+ * pointer, or reference; but not function pointer, literal, or primitive).
123
124
  */
124
125
  export function pickCppAdlCandidates(site, callerParsed, scopes, parsedFiles) {
125
126
  const key = siteKey(callerParsed.filePath, site.atRange.startLine, site.atRange.startCol);
@@ -128,13 +129,11 @@ export function pickCppAdlCandidates(site, callerParsed, scopes, parsedFiles) {
128
129
  const args = argInfoBySite.get(key);
129
130
  if (args === undefined || args.length === 0)
130
131
  return undefined;
131
- // Collect associated namespace QNames from every value-class-typed arg.
132
+ // Collect associated namespace QNames from every participating class-typed arg.
132
133
  const associatedNamespaces = new Set();
133
134
  for (const arg of args) {
134
135
  if (arg.simpleClassName === '')
135
136
  continue;
136
- if (arg.isPointer || arg.isReference)
137
- continue;
138
137
  const classDef = findCppClassDefBySimpleName(arg.simpleClassName, scopes);
139
138
  if (classDef === undefined)
140
139
  continue;
@@ -657,14 +657,15 @@ function isParenthesizedFunctionCall(callNode) {
657
657
  }
658
658
  /**
659
659
  * Per-argument ADL classification: walk each argument of a free call and
660
- * decide whether it's a directly-named class type (V1 ADL fires) or
661
- * something V1 excludes (pointer, reference, primitive, literal, function
662
- * pointer, template specialization).
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.
663
663
  *
664
- * V1 only fires for value class-typed args: `void f(N::S); N::S s; f(s);`.
665
- * Pointer args (`N::S* p; f(p);`) intentionally return `simpleClassName=''`
666
- * to lock the V1 boundary — the `cpp-adl-pointer-arg-boundary` fixture
667
- * regression-tests this.
664
+ * Class-typed values and class pointers (`N::S`, `N::S*`, `N::S**`) all
665
+ * preserve the pointee class name for associated-namespace lookup.
666
+ * Function pointers remain excluded even when their return type names a
667
+ * class, because the associated entity is the pointed-to function type,
668
+ * not the return type.
668
669
  */
669
670
  function inferCppCallAdlArgs(callNode) {
670
671
  const argList = callNode.childForFieldName('arguments');
@@ -681,7 +682,7 @@ function inferCppCallAdlArgs(callNode) {
681
682
  }
682
683
  return out;
683
684
  }
684
- const EMPTY_ADL_ARG = { simpleClassName: '', isPointer: false, isReference: false };
685
+ const EMPTY_ADL_ARG = { simpleClassName: '' };
685
686
  function classifyAdlArg(argNode) {
686
687
  // Literals and primitive-shaped expressions never have associated namespaces.
687
688
  if (argNode.type === 'number_literal' ||
@@ -727,24 +728,29 @@ function lookupAdlIdentifierType(identNode) {
727
728
  continue;
728
729
  // Unwrap declarator chain to find pointer/reference markers and the
729
730
  // variable name. `init_declarator > pointer_declarator > identifier`
730
- // means pointer-typed; `init_declarator > reference_declarator > ...`
731
- // means reference-typed; bare `init_declarator > identifier` is value.
732
- let isPointer = false;
733
- let isReference = false;
731
+ // means pointer-typed; repeated pointer wrappers still count as pointer
732
+ // typed; `init_declarator > reference_declarator > ...` (or
733
+ // `rvalue_reference_declarator`) means reference-typed; bare
734
+ // `init_declarator > identifier` is value.
735
+ // Function-pointer wrappers (`pointer_declarator > function_declarator`)
736
+ // must not contribute ADL associated namespaces.
737
+ let isFunctionPointer = false;
734
738
  let inner = declarator;
735
739
  let nameText = null;
736
740
  let safety = 16; // bound walk depth defensively
737
741
  while (safety-- > 0) {
738
742
  if (inner.type === 'pointer_declarator') {
739
- isPointer = true;
743
+ if (findFirstDescendantOfType(inner, 'function_declarator') !== null) {
744
+ isFunctionPointer = true;
745
+ break;
746
+ }
740
747
  const next = inner.childForFieldName('declarator');
741
748
  if (next === null)
742
749
  break;
743
750
  inner = next;
744
751
  continue;
745
752
  }
746
- if (inner.type === 'reference_declarator') {
747
- isReference = true;
753
+ if (inner.type === 'reference_declarator' || inner.type === 'rvalue_reference_declarator') {
748
754
  // reference_declarator has a single child (the inner declarator).
749
755
  let next = null;
750
756
  for (let j = 0; j < inner.namedChildCount; j++) {
@@ -766,21 +772,25 @@ function lookupAdlIdentifierType(identNode) {
766
772
  inner = next;
767
773
  continue;
768
774
  }
775
+ if (inner.type === 'function_declarator') {
776
+ isFunctionPointer = true;
777
+ break;
778
+ }
769
779
  // Reached the leaf — usually `identifier`. Take its text.
770
780
  nameText = inner.text;
771
781
  break;
772
782
  }
773
- if (nameText !== varName)
783
+ if (isFunctionPointer || nameText !== varName)
774
784
  continue;
775
785
  const simpleClassName = extractAdlSimpleTypeName(typeNode);
776
- return { simpleClassName, isPointer, isReference };
786
+ return { simpleClassName };
777
787
  }
778
788
  return EMPTY_ADL_ARG;
779
789
  }
780
790
  /** Extract the simple class-like type name from a `type:` field node.
781
- * Returns '' for primitives, template specializations, function pointers,
782
- * and any other shape V1 ADL doesn't support — those args are excluded
783
- * from associated-namespace closure. */
791
+ * Returns '' for primitives, template specializations, and any other
792
+ * unsupported type-only shape. Function pointers are filtered at the
793
+ * declarator level in `lookupAdlIdentifierType`. */
784
794
  function extractAdlSimpleTypeName(typeNode) {
785
795
  if (typeNode.type === 'primitive_type')
786
796
  return '';
@@ -12,6 +12,7 @@ import { populateCppDependentBases, clearCppDependentBases, isCppDependentBaseMe
12
12
  import { populateCppAssociatedNamespaces, clearCppAdlState, pickCppAdlCandidates, ADL_AMBIGUOUS, } from './adl.js';
13
13
  import { clearCppInlineNamespaces, populateCppInlineNamespaceScopes, resolveCppQualifiedNamespaceMember, } from './inline-namespaces.js';
14
14
  import { populateCppRangeBindings } from './range-bindings.js';
15
+ import { isOverloadAmbiguousAfterNormalization, narrowOverloadCandidates, } from '../../scope-resolution/passes/overload-narrowing.js';
15
16
  /**
16
17
  * C++ `ScopeResolver` registered in `SCOPE_RESOLVERS` and consumed by
17
18
  * the generic `runScopeResolution` orchestrator (RFC #909 Ring 3).
@@ -145,6 +146,8 @@ export const cppScopeResolver = {
145
146
  // for cross-file propagation and compound-receiver chain resolution.
146
147
  // cppBindingScopeFor hoists @type-binding.return to Module scope.
147
148
  hoistTypeBindingsToModule: true,
149
+ // Enable receiver-bound explicit-`this` fallback only for C++.
150
+ resolveThisViaEnclosingClass: true,
148
151
  // The `isFileLocalDef` hook on the global free-call fallback names
149
152
  // file-local linkage historically, but semantically gates "logically
150
153
  // invisible cross-file" defs. C++ extends this to also reject class-
@@ -188,6 +191,34 @@ export const cppScopeResolver = {
188
191
  // V1 limitation: only direct enclosing-namespace closure for value
189
192
  // class-typed args; pointer/reference/template-spec args excluded.
190
193
  resolveAdlCandidates: (site, callerParsed, scopes, parsedFiles) => {
194
+ // `using ns::name;` introduces `name` into ordinary unqualified lookup.
195
+ // For template-class method bodies, lexical scope walks can miss this
196
+ // named-using visibility; recover by resolving the imported namespace
197
+ // member directly when the local call name matches a named using import.
198
+ const usingNamedHits = [];
199
+ const seenUsing = new Set();
200
+ for (const imp of callerParsed.parsedImports) {
201
+ if (imp.kind !== 'named')
202
+ continue;
203
+ if (imp.localName !== site.name)
204
+ continue;
205
+ const member = resolveCppQualifiedNamespaceMember(imp.targetRaw, imp.importedName, parsedFiles, scopes);
206
+ if (member === undefined)
207
+ continue;
208
+ if (seenUsing.has(member.nodeId))
209
+ continue;
210
+ seenUsing.add(member.nodeId);
211
+ usingNamedHits.push(member);
212
+ }
213
+ if (usingNamedHits.length > 0) {
214
+ const narrowed = narrowOverloadCandidates(usingNamedHits, site.arity, site.argumentTypes);
215
+ if (isOverloadAmbiguousAfterNormalization(narrowed, site.arity))
216
+ return 'ambiguous';
217
+ if (narrowed.length === 1)
218
+ return narrowed[0];
219
+ if (narrowed.length > 1)
220
+ return 'ambiguous';
221
+ }
191
222
  const result = pickCppAdlCandidates(site, callerParsed, scopes, parsedFiles);
192
223
  if (result === ADL_AMBIGUOUS)
193
224
  return 'ambiguous';
@@ -547,6 +547,17 @@ export interface ScopeResolver {
547
547
  * remaining cases.
548
548
  */
549
549
  readonly resolveQualifiedReceiverMember?: (receiverName: string, memberName: string, callerScope: ScopeId, scopes: ScopeResolutionIndexes, parsedFiles: readonly ParsedFile[]) => SymbolDefinition | undefined;
550
+ /**
551
+ * Enable the receiver-bound Case 0.5 fallback for explicit `this`
552
+ * receivers (`this->m()` / `this.m()`) that resolves against the
553
+ * enclosing class + MRO even when no explicit `this` typeBinding is
554
+ * present in scope.
555
+ *
556
+ * Keep disabled for languages where the existing type-binding path
557
+ * (Case 4) already handles `this` correctly and overload ambiguity
558
+ * suppression must remain unchanged.
559
+ */
560
+ readonly resolveThisViaEnclosingClass?: boolean;
550
561
  /**
551
562
  * Optional post-finalize hook to inject cross-file bindings that
552
563
  * aren't modeled via explicit imports. Runs after
@@ -41,7 +41,7 @@ import type { WorkspaceResolutionIndex } from '../workspace-index.js';
41
41
  /** Subset of `ScopeResolver` consumed by this pass. Accepting the
42
42
  * subset rather than the full provider keeps tests and partial
43
43
  * refactors lighter — callers only need to populate what we read. */
44
- type ReceiverBoundProviderSubset = Pick<ScopeResolver, 'isSuperReceiver' | 'isSuperReceiverInContext' | 'fieldFallbackOnMethodLookup' | 'collapseMemberCallsByCallerTarget' | 'unwrapCollectionAccessor' | 'hoistTypeBindingsToModule' | 'resolveQualifiedReceiverMember'>;
44
+ type ReceiverBoundProviderSubset = Pick<ScopeResolver, 'isSuperReceiver' | 'isSuperReceiverInContext' | 'fieldFallbackOnMethodLookup' | 'collapseMemberCallsByCallerTarget' | 'unwrapCollectionAccessor' | 'hoistTypeBindingsToModule' | 'resolveQualifiedReceiverMember' | 'resolveThisViaEnclosingClass'>;
45
45
  export declare function emitReceiverBoundCalls(graph: KnowledgeGraph, scopes: ScopeResolutionIndexes, parsedFiles: readonly ParsedFile[], nodeLookup: GraphNodeLookup, handledSites: Set<string>, provider: ReceiverBoundProviderSubset, index: WorkspaceResolutionIndex, model: SemanticModel): number;
46
46
  /**
47
47
  * Sentinel returned by `pickOverload` when narrowing leaves >1 candidate
@@ -232,6 +232,72 @@ export function emitReceiverBoundCalls(graph, scopes, parsedFiles, nodeLookup, h
232
232
  }
233
233
  }
234
234
  }
235
+ // ── Case 0.5: implicit `this` receiver ───────────────────────
236
+ // C++ `this->member()` (and same-shape receivers in other OO
237
+ // languages) should resolve against the enclosing class + MRO
238
+ // even when there is no explicit `this` typeBinding in scope.
239
+ if (provider.resolveThisViaEnclosingClass === true && receiverName === 'this') {
240
+ const enclosingClass = findEnclosingClassDef(site.inScope, scopes);
241
+ if (enclosingClass !== undefined) {
242
+ const chain = [
243
+ enclosingClass.nodeId,
244
+ ...scopes.methodDispatch.mroFor(enclosingClass.nodeId),
245
+ ];
246
+ let memberDef;
247
+ let ambiguous = false;
248
+ let hiddenByName = false;
249
+ for (const ownerId of chain) {
250
+ const methodOverloads = model.methods.lookupAllByOwner(ownerId, memberName);
251
+ if (methodOverloads.length > 0) {
252
+ const narrowed = narrowOverloadCandidates(methodOverloads, site.arity, site.argumentTypes);
253
+ if (isOverloadAmbiguousAfterNormalization(narrowed, site.arity)) {
254
+ ambiguous = true;
255
+ break;
256
+ }
257
+ if (narrowed.length === 0) {
258
+ // C++ name hiding: if the derived class declares `f`, base-class
259
+ // overloads named `f` are hidden for member lookup
260
+ // ([basic.lookup.classref]). A non-viable derived overload set
261
+ // therefore terminates lookup instead of falling through to base.
262
+ hiddenByName = true;
263
+ break;
264
+ }
265
+ memberDef = narrowed[0] ?? methodOverloads[0];
266
+ break;
267
+ }
268
+ // Field/property lookup intentionally runs only after the method
269
+ // lookup above: in C++ member-name lookup, functions with this
270
+ // name hide same-named base members; we therefore prefer method
271
+ // candidates first and only target a field when no methods with
272
+ // this name exist on the current owner.
273
+ memberDef = model.fields.lookupFieldByOwner(ownerId, memberName);
274
+ if (memberDef !== undefined) {
275
+ break;
276
+ }
277
+ }
278
+ if (ambiguous) {
279
+ handledSites.add(siteKey);
280
+ continue;
281
+ }
282
+ if (hiddenByName) {
283
+ handledSites.add(siteKey);
284
+ continue;
285
+ }
286
+ if (memberDef !== undefined) {
287
+ const reason = site.kind === 'write' || site.kind === 'read'
288
+ ? site.kind
289
+ : memberDef.filePath !== parsed.filePath
290
+ ? 'import-resolved'
291
+ : 'global';
292
+ const confidence = site.kind === 'write' || site.kind === 'read' ? 1.0 : 0.85;
293
+ const ok = tryEmitEdge(graph, scopes, nodeLookup, site, memberDef, reason, seen, confidence, collapse);
294
+ if (ok)
295
+ emitted++;
296
+ handledSites.add(siteKey);
297
+ continue;
298
+ }
299
+ }
300
+ }
235
301
  // ── Case 1: namespace receiver ───────────────────────────────
236
302
  const targetFiles = namespaceTargets.get(receiverName);
237
303
  if (targetFiles !== undefined) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitnexus",
3
- "version": "1.6.5-rc.37",
3
+ "version": "1.6.5-rc.39",
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",