gitnexus 1.6.8-rc.11 → 1.6.8-rc.12

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.
@@ -54,6 +54,10 @@ export interface SymbolDefinition {
54
54
  * Currently used by C++ overload ranking to exclude explicit constructors
55
55
  * from implicit user-defined conversion candidates. */
56
56
  isExplicit?: boolean;
57
+ /** True when the callable is declared unavailable (for example C++ `= delete`).
58
+ * Unavailable callables still participate in overload selection, but a
59
+ * selected unavailable target must suppress edge emission. */
60
+ isDeleted?: boolean;
57
61
  /** Links Method/Constructor/Property to owning Class/Struct/Trait nodeId */
58
62
  ownerId?: string;
59
63
  /** #1982/#1993: bridge-held enclosing-namespace path (e.g. `NS1`, `Outer.Inner`)
@@ -1 +1 @@
1
- {"version":3,"file":"symbol-definition.d.ts","sourceRoot":"","sources":["../../src/scope-resolution/symbol-definition.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAEnD,MAAM,WAAW,kBAAkB;IACjC,wFAAwF;IACxF,IAAI,EAAE,MAAM,CAAC;IACb,8EAA8E;IAC9E,EAAE,EAAE,MAAM,GAAG,OAAO,GAAG,UAAU,GAAG,gBAAgB,GAAG,SAAS,CAAC;IACjE,4CAA4C;IAC5C,WAAW,EAAE,OAAO,GAAG,YAAY,GAAG,YAAY,GAAG,SAAS,GAAG,SAAS,CAAC;IAC3E,4EAA4E;IAC5E,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,SAAS,CAAC;IAChB;;+FAE2F;IAC3F,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;iHAC6G;IAC7G,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC;uFACmF;IACnF,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B;4FACwF;IACxF,oBAAoB,CAAC,EAAE,kBAAkB,EAAE,CAAC;IAC5C,6EAA6E;IAC7E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gGAAgG;IAChG,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gGAAgG;IAChG,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B;;;;;yEAKqE;IACrE,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B;;4DAEwD;IACxD,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,4EAA4E;IAC5E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;;;iEAO6D;IAC7D,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B"}
1
+ {"version":3,"file":"symbol-definition.d.ts","sourceRoot":"","sources":["../../src/scope-resolution/symbol-definition.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAEnD,MAAM,WAAW,kBAAkB;IACjC,wFAAwF;IACxF,IAAI,EAAE,MAAM,CAAC;IACb,8EAA8E;IAC9E,EAAE,EAAE,MAAM,GAAG,OAAO,GAAG,UAAU,GAAG,gBAAgB,GAAG,SAAS,CAAC;IACjE,4CAA4C;IAC5C,WAAW,EAAE,OAAO,GAAG,YAAY,GAAG,YAAY,GAAG,SAAS,GAAG,SAAS,CAAC;IAC3E,4EAA4E;IAC5E,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,SAAS,CAAC;IAChB;;+FAE2F;IAC3F,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;iHAC6G;IAC7G,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC;uFACmF;IACnF,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B;4FACwF;IACxF,oBAAoB,CAAC,EAAE,kBAAkB,EAAE,CAAC;IAC5C,6EAA6E;IAC7E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gGAAgG;IAChG,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gGAAgG;IAChG,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B;;;;;yEAKqE;IACrE,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B;;4DAEwD;IACxD,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;mEAE+D;IAC/D,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,4EAA4E;IAC5E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;;;iEAO6D;IAC7D,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B"}
@@ -193,8 +193,12 @@ function findFuncDeclarator(node) {
193
193
  }
194
194
  return null;
195
195
  }
196
- // Unwrap pointer_declarator / reference_declarator
197
- while (decl.type === 'pointer_declarator' || decl.type === 'reference_declarator') {
196
+ // Unwrap declarator wrappers. Deleted free functions are represented as
197
+ // `init_declarator(function_declarator, delete_expression)` by
198
+ // tree-sitter-cpp 0.23.
199
+ while (decl.type === 'pointer_declarator' ||
200
+ decl.type === 'reference_declarator' ||
201
+ decl.type === 'init_declarator') {
198
202
  const next = decl.childForFieldName('declarator');
199
203
  if (next === null) {
200
204
  // reference_declarator may not use field name
@@ -113,6 +113,9 @@ export function emitCppScopeCaptures(sourceText, filePath, cachedTree) {
113
113
  if (hasExplicitSpecifier(fnNode)) {
114
114
  grouped['@declaration.is-explicit'] = syntheticCapture('@declaration.is-explicit', fnNode, 'true');
115
115
  }
116
+ if (hasDeletedMethodClause(fnNode, grouped['@declaration.name']?.text)) {
117
+ grouped['@declaration.is-deleted'] = syntheticCapture('@declaration.is-deleted', fnNode, 'true');
118
+ }
116
119
  // Detect static storage class (file-local linkage)
117
120
  if (hasStaticStorageClass(fnNode)) {
118
121
  const nameText = grouped['@declaration.name']?.text;
@@ -1562,8 +1565,11 @@ function extractDeclaratorLeafName(node) {
1562
1565
  let cur = node;
1563
1566
  let safety = 16;
1564
1567
  while (safety-- > 0) {
1565
- if (cur.type === 'identifier' || cur.type === 'type_identifier')
1568
+ if (cur.type === 'identifier' ||
1569
+ cur.type === 'type_identifier' ||
1570
+ cur.type === 'operator_name') {
1566
1571
  return cur.text;
1572
+ }
1567
1573
  // Common wrapper nodes — follow the 'declarator' field when present.
1568
1574
  const next = cur.childForFieldName('declarator') ??
1569
1575
  // parenthesized_declarator: single named child
@@ -1590,6 +1596,23 @@ function hasExplicitSpecifier(node) {
1590
1596
  }
1591
1597
  return /\bexplicit\b/.test(node.text.slice(0, 128));
1592
1598
  }
1599
+ function hasDeletedMethodClause(node, callableName) {
1600
+ for (let i = 0; i < node.namedChildCount; i++) {
1601
+ const child = node.namedChild(i);
1602
+ if (child?.type === 'delete_method_clause')
1603
+ return true;
1604
+ // tree-sitter-cpp 0.23 parses a deleted free-function declaration as
1605
+ // `declaration > init_declarator > delete_expression`, while class
1606
+ // members use the dedicated `delete_method_clause`.
1607
+ if (child?.type === 'init_declarator' &&
1608
+ child.childForFieldName('value')?.type === 'delete_expression' &&
1609
+ callableName !== undefined &&
1610
+ extractDeclaratorLeafName(child.childForFieldName('declarator') ?? child) === callableName) {
1611
+ return true;
1612
+ }
1613
+ }
1614
+ return false;
1615
+ }
1593
1616
  /**
1594
1617
  * Check if a C++ function_definition or declaration has `static` storage class.
1595
1618
  */
@@ -193,6 +193,29 @@ const CPP_SCOPE_QUERY = `
193
193
  declarator: (function_declarator
194
194
  declarator: (identifier) @declaration.name)) @declaration.function
195
195
 
196
+ ;; tree-sitter-cpp 0.23 represents a deleted free function as an
197
+ ;; init_declarator whose value is a delete_expression.
198
+ (declaration
199
+ declarator: (init_declarator
200
+ declarator: (function_declarator
201
+ declarator: (identifier) @declaration.name)
202
+ value: (delete_expression))) @declaration.function
203
+
204
+ ;; Deleted free operator declaration.
205
+ (declaration
206
+ declarator: (init_declarator
207
+ declarator: (function_declarator
208
+ declarator: (operator_name) @declaration.name)
209
+ value: (delete_expression))) @declaration.function
210
+
211
+ ;; Deleted free function with a pointer return type.
212
+ (declaration
213
+ declarator: (init_declarator
214
+ declarator: (pointer_declarator
215
+ declarator: (function_declarator
216
+ declarator: (identifier) @declaration.name))
217
+ value: (delete_expression))) @declaration.function
218
+
196
219
  ;; Free operator prototype: std::ostream& operator<<(std::ostream&, T)
197
220
  (declaration
198
221
  declarator: (function_declarator
@@ -33,18 +33,12 @@ function findFunctionDeclarator(node) {
33
33
  }
34
34
  return null;
35
35
  }
36
- /**
37
- * Detect `= delete` and `= default` special member function declarations.
38
- * These are not callable methods and should be suppressed from extraction.
39
- * tree-sitter-cpp ^0.23.4 emits `delete_method_clause` / `default_method_clause`
40
- * as named children of the function_definition node.
41
- */
42
- function isDeletedOrDefaulted(node) {
36
+ /** Detect a C++ special member clause by its tree-sitter node type. */
37
+ function hasSpecialMethodClause(node, clauseType) {
43
38
  for (let i = 0; i < node.namedChildCount; i++) {
44
39
  const child = node.namedChild(i);
45
- if (child?.type === 'delete_method_clause' || child?.type === 'default_method_clause') {
40
+ if (child?.type === clauseType)
46
41
  return true;
47
- }
48
42
  }
49
43
  return false;
50
44
  }
@@ -57,10 +51,6 @@ function extractCppMethodName(node) {
57
51
  const funcDecl = findFunctionDeclarator(node);
58
52
  if (!funcDecl)
59
53
  return undefined;
60
- // Suppress `= delete` and `= default` special members — these are not callable
61
- // methods and should not appear in HAS_METHOD edges.
62
- if (isDeletedOrDefaulted(node))
63
- return undefined;
64
54
  const nameNode = funcDecl.childForFieldName('declarator');
65
55
  if (!nameNode)
66
56
  return undefined;
@@ -360,6 +350,9 @@ export const cppMethodConfig = {
360
350
  }
361
351
  return false;
362
352
  },
353
+ isDeleted(node) {
354
+ return hasSpecialMethodClause(node, 'delete_method_clause');
355
+ },
363
356
  };
364
357
  // ---------------------------------------------------------------------------
365
358
  // C config (minimal — C has no classes/methods, only struct function pointers)
@@ -198,6 +198,7 @@ function buildMethod(node, ownerNode, context, config) {
198
198
  ...(config.isAsync?.(node) ? { isAsync: true } : {}),
199
199
  ...(config.isPartial?.(node) ? { isPartial: true } : {}),
200
200
  ...(config.isConst?.(node) ? { isConst: true } : {}),
201
+ ...(config.isDeleted?.(node) ? { isDeleted: true } : {}),
201
202
  annotations: config.extractAnnotations?.(node) ?? [],
202
203
  sourceFile: context.filePath,
203
204
  line: node.startPosition.row + 1,
@@ -27,6 +27,7 @@ export interface MethodInfo {
27
27
  isAsync?: boolean;
28
28
  isPartial?: boolean;
29
29
  isConst?: boolean;
30
+ isDeleted?: boolean;
30
31
  annotations: string[];
31
32
  sourceFile: string;
32
33
  line: number;
@@ -74,6 +75,7 @@ export interface MethodExtractionConfig {
74
75
  isAsync?: (node: SyntaxNode) => boolean;
75
76
  isPartial?: (node: SyntaxNode) => boolean;
76
77
  isConst?: (node: SyntaxNode) => boolean;
78
+ isDeleted?: (node: SyntaxNode) => boolean;
77
79
  /** Owner node types where member functions are effectively static (e.g.
78
80
  * Ruby singleton_class, Kotlin companion_object / object_declaration).
79
81
  * When the ownerNode matches one of these types, isStatic is forced true. */
@@ -102,6 +102,7 @@ export interface AddMetadata {
102
102
  templateArguments?: string[];
103
103
  ownerId?: string;
104
104
  qualifiedName?: string;
105
+ isDeleted?: boolean;
105
106
  }
106
107
  /**
107
108
  * Pure read-only view over the file and callable indexes. Does NOT
@@ -136,6 +136,7 @@ export const createSymbolTable = () => {
136
136
  ? { templateArguments: metadata.templateArguments }
137
137
  : {}),
138
138
  ...(metadata?.ownerId !== undefined ? { ownerId: metadata.ownerId } : {}),
139
+ ...(metadata?.isDeleted === true ? { isDeleted: true } : {}),
139
140
  };
140
141
  // A. File Index — unconditional.
141
142
  if (!fileIndex.has(filePath)) {
@@ -49,6 +49,7 @@ export const mergeChunkResults = (graph, symbolTable, chunkResults, exportedType
49
49
  templateArguments: sym.templateArguments,
50
50
  ownerId: sym.ownerId,
51
51
  qualifiedName: sym.qualifiedName,
52
+ isDeleted: sym.isDeleted,
52
53
  });
53
54
  }
54
55
  if (exportedTypeMap) {
@@ -399,6 +399,7 @@ function buildDefFromDeclarationMatch(match, anchor, filePath) {
399
399
  const returnType = match['@declaration.return-type']?.text;
400
400
  const templateConstraints = parseJsonCapture(match['@declaration.template-constraints']);
401
401
  const isExplicit = parseBooleanCapture(match['@declaration.is-explicit']);
402
+ const isDeleted = parseBooleanCapture(match['@declaration.is-deleted']);
402
403
  return {
403
404
  nodeId: makeDefId(filePath, anchor.range, type, nameCap.text),
404
405
  filePath,
@@ -413,6 +414,7 @@ function buildDefFromDeclarationMatch(match, anchor, filePath) {
413
414
  ...(templateArguments !== undefined ? { templateArguments } : {}),
414
415
  ...(templateConstraints !== undefined ? { templateConstraints } : {}),
415
416
  ...(isExplicit === true ? { isExplicit: true } : {}),
417
+ ...(isDeleted === true ? { isDeleted: true } : {}),
416
418
  };
417
419
  }
418
420
  /** Parse an opaque JSON payload synthesized by per-language captures
@@ -922,6 +924,7 @@ const KNOWN_SUB_TAGS = new Set([
922
924
  '@declaration.return-type',
923
925
  '@declaration.template-constraints',
924
926
  '@declaration.is-explicit',
927
+ '@declaration.is-deleted',
925
928
  ]);
926
929
  /**
927
930
  * Return the anchor capture for a match — the one whose name begins with
@@ -289,6 +289,18 @@ export function emitFreeCallFallback(graph, scopes, parsedFiles, nodeLookup, _re
289
289
  }
290
290
  if (fnDef === undefined)
291
291
  continue;
292
+ if (fnDef.isDeleted === true) {
293
+ recordSuppressedOutcome(options.recordResolutionOutcome, {
294
+ phase: 'free-call-fallback',
295
+ filePath: parsed.filePath,
296
+ name: site.name,
297
+ range: site.atRange,
298
+ reason: 'selected-callable-deleted',
299
+ candidates: [fnDef],
300
+ });
301
+ handledSites.add(siteKey(parsed.filePath, site));
302
+ continue;
303
+ }
292
304
  if ((fnDefFromImplicitThis || fnDef.type === 'Method' || fnDef.type === 'Constructor') &&
293
305
  options.isCallableVisibleFromCaller !== undefined &&
294
306
  !options.isCallableVisibleFromCaller({
@@ -138,9 +138,12 @@ export function emitReceiverBoundCalls(graph, scopes, parsedFiles, nodeLookup, h
138
138
  return 0;
139
139
  let n = 0;
140
140
  for (const implDef of impls) {
141
- const implMember = findOwnedMember(implDef.nodeId, memberName, model);
142
- if (implMember === undefined)
141
+ const implMember = pickOverload(implDef.nodeId, memberName, site, model, provider);
142
+ if (implMember === undefined ||
143
+ implMember === OVERLOAD_AMBIGUOUS ||
144
+ implMember.isDeleted === true) {
143
145
  continue;
146
+ }
144
147
  if (implMember.nodeId === primaryMemberDef.nodeId)
145
148
  continue;
146
149
  const ok = tryEmitEdge(graph, scopes, nodeLookup, site, implMember, 'interface-dispatch', seen, confidence, collapse);
@@ -179,12 +182,30 @@ export function emitReceiverBoundCalls(graph, scopes, parsedFiles, nodeLookup, h
179
182
  ? extendsOnly(enclosingClass.nodeId)
180
183
  : scopes.methodDispatch.mroFor(enclosingClass.nodeId);
181
184
  let memberDef;
185
+ let ambiguousOwnerId;
182
186
  for (const ownerId of ancestors) {
183
- memberDef = findOwnedMember(ownerId, memberName, model);
184
- if (memberDef !== undefined)
187
+ const picked = site.kind === 'call'
188
+ ? pickOverload(ownerId, memberName, site, model, provider)
189
+ : findOwnedMember(ownerId, memberName, model);
190
+ if (picked === OVERLOAD_AMBIGUOUS) {
191
+ ambiguousOwnerId = ownerId;
192
+ break;
193
+ }
194
+ if (picked !== undefined) {
195
+ memberDef = picked;
185
196
  break;
197
+ }
198
+ }
199
+ if (ambiguousOwnerId !== undefined) {
200
+ recordReceiverOverloadSuppression(options.recordResolutionOutcome, parsed.filePath, site, ambiguousOwnerId, memberName, model, provider);
201
+ handledSites.add(siteKey);
202
+ continue;
186
203
  }
187
204
  if (memberDef !== undefined) {
205
+ if (suppressDeletedCallTarget(options.recordResolutionOutcome, parsed.filePath, site, memberDef)) {
206
+ handledSites.add(siteKey);
207
+ continue;
208
+ }
188
209
  // Super/base calls resolve through the MRO chain, not
189
210
  // through imports — the ancestor method is found by
190
211
  // walking `methodDispatch.mroFor(enclosingClass)`, which
@@ -219,9 +240,9 @@ export function emitReceiverBoundCalls(graph, scopes, parsedFiles, nodeLookup, h
219
240
  if (currentClass !== undefined) {
220
241
  const chain = [currentClass.nodeId, ...scopes.methodDispatch.mroFor(currentClass.nodeId)];
221
242
  let memberDef;
243
+ let ambiguousOwnerId;
222
244
  // Static-only filter (#1756 / U3): same shape as Case 4's
223
- // chain walk (skip-and-walk-on) but without overload
224
- // narrowing — Case 0 uses `findOwnedMember` directly. When
245
+ // overload-aware chain walk (skip-and-walk-on). When
225
246
  // an owner's resolved candidate is static-only (Kotlin
226
247
  // companion-promoted), continue to the next ancestor in
227
248
  // the MRO chain so a legitimate instance member can bind.
@@ -233,17 +254,29 @@ export function emitReceiverBoundCalls(graph, scopes, parsedFiles, nodeLookup, h
233
254
  // shapes like `Logger.create("a")`), so there's no wrong
234
255
  // target to suppress.
235
256
  for (const ownerId of chain) {
236
- const candidate = findOwnedMember(ownerId, memberName, model);
237
- if (candidate === undefined)
238
- continue;
239
- if (provider.isStaticOnly?.(candidate) === true) {
240
- // Skip static-only candidate; walk to next ancestor.
257
+ const picked = site.kind === 'call'
258
+ ? pickFirstNonStaticOnly(ownerId, memberName, site, model, provider)
259
+ : findOwnedMember(ownerId, memberName, model);
260
+ if (picked === OVERLOAD_AMBIGUOUS) {
261
+ ambiguousOwnerId = ownerId;
262
+ break;
263
+ }
264
+ if (picked === STATIC_ONLY_FILTERED || picked === undefined) {
241
265
  continue;
242
266
  }
243
- memberDef = candidate;
267
+ memberDef = picked;
244
268
  break;
245
269
  }
270
+ if (ambiguousOwnerId !== undefined) {
271
+ recordReceiverOverloadSuppression(options.recordResolutionOutcome, parsed.filePath, site, ambiguousOwnerId, memberName, model, provider);
272
+ handledSites.add(siteKey);
273
+ continue;
274
+ }
246
275
  if (memberDef !== undefined) {
276
+ if (suppressDeletedCallTarget(options.recordResolutionOutcome, parsed.filePath, site, memberDef)) {
277
+ handledSites.add(siteKey);
278
+ continue;
279
+ }
247
280
  const ok = tryEmitEdge(graph, scopes, nodeLookup, site, memberDef, memberDef.filePath !== parsed.filePath ? 'import-resolved' : 'global', seen, 0.85, collapse);
248
281
  if (ok)
249
282
  emitted++;
@@ -290,6 +323,10 @@ export function emitReceiverBoundCalls(graph, scopes, parsedFiles, nodeLookup, h
290
323
  }
291
324
  if (languageResolution?.kind === 'resolved') {
292
325
  const memberDef = languageResolution.definition;
326
+ if (suppressDeletedCallTarget(options.recordResolutionOutcome, parsed.filePath, site, memberDef)) {
327
+ handledSites.add(siteKey);
328
+ continue;
329
+ }
293
330
  const reason = site.kind === 'write' || site.kind === 'read'
294
331
  ? site.kind
295
332
  : memberDef.filePath !== parsed.filePath
@@ -357,6 +394,10 @@ export function emitReceiverBoundCalls(graph, scopes, parsedFiles, nodeLookup, h
357
394
  continue;
358
395
  }
359
396
  if (memberDef !== undefined) {
397
+ if (suppressDeletedCallTarget(options.recordResolutionOutcome, parsed.filePath, site, memberDef)) {
398
+ handledSites.add(siteKey);
399
+ continue;
400
+ }
360
401
  const reason = site.kind === 'write' || site.kind === 'read'
361
402
  ? site.kind
362
403
  : memberDef.filePath !== parsed.filePath
@@ -373,11 +414,16 @@ export function emitReceiverBoundCalls(graph, scopes, parsedFiles, nodeLookup, h
373
414
  }
374
415
  // ── Case 1: namespace receiver ───────────────────────────────
375
416
  const targetFiles = namespaceTargets.get(receiverName);
376
- if (targetFiles !== undefined) {
417
+ if (targetFiles !== undefined && provider.resolveQualifiedReceiverMember === undefined) {
377
418
  let found = false;
378
419
  for (const targetFile of targetFiles) {
379
420
  const memberDef = findExportedDef(targetFile, memberName, index);
380
421
  if (memberDef !== undefined) {
422
+ if (suppressDeletedCallTarget(options.recordResolutionOutcome, parsed.filePath, site, memberDef)) {
423
+ handledSites.add(siteKey);
424
+ found = true;
425
+ break;
426
+ }
381
427
  const ok = tryEmitEdge(graph, scopes, nodeLookup, site, memberDef, memberDef.filePath !== parsed.filePath ? 'import-resolved' : 'global', seen, 0.85, collapse);
382
428
  if (ok)
383
429
  emitted++;
@@ -413,6 +459,10 @@ export function emitReceiverBoundCalls(graph, scopes, parsedFiles, nodeLookup, h
413
459
  continue;
414
460
  }
415
461
  if (memberDef !== undefined) {
462
+ if (suppressDeletedCallTarget(options.recordResolutionOutcome, parsed.filePath, site, memberDef)) {
463
+ handledSites.add(siteKey);
464
+ continue;
465
+ }
416
466
  const ok = tryEmitEdge(graph, scopes, nodeLookup, site, memberDef, memberDef.filePath !== parsed.filePath ? 'import-resolved' : 'global', seen, 0.85, collapse);
417
467
  if (ok)
418
468
  emitted++;
@@ -425,9 +475,17 @@ export function emitReceiverBoundCalls(graph, scopes, parsedFiles, nodeLookup, h
425
475
  if (classDef !== undefined) {
426
476
  const chain = [classDef.nodeId, ...scopes.methodDispatch.mroFor(classDef.nodeId)];
427
477
  let memberDef;
478
+ let ambiguousOwnerId;
428
479
  for (const ownerId of chain) {
429
- memberDef = findOwnedMember(ownerId, memberName, model);
430
- if (memberDef !== undefined) {
480
+ const picked = site.kind === 'call'
481
+ ? pickOverload(ownerId, memberName, site, model, provider)
482
+ : findOwnedMember(ownerId, memberName, model);
483
+ if (picked === OVERLOAD_AMBIGUOUS) {
484
+ ambiguousOwnerId = ownerId;
485
+ break;
486
+ }
487
+ if (picked !== undefined) {
488
+ memberDef = picked;
431
489
  // The MRO chain is most-derived-first ([classDef, ...ancestors]).
432
490
  // If the most-derived definition is arity-incompatible with the
433
491
  // call site, PHP throws ArgumentCountError at runtime — it does
@@ -441,7 +499,16 @@ export function emitReceiverBoundCalls(graph, scopes, parsedFiles, nodeLookup, h
441
499
  break;
442
500
  }
443
501
  }
502
+ if (ambiguousOwnerId !== undefined) {
503
+ recordReceiverOverloadSuppression(options.recordResolutionOutcome, parsed.filePath, site, ambiguousOwnerId, memberName, model, provider);
504
+ handledSites.add(siteKey);
505
+ continue;
506
+ }
444
507
  if (memberDef !== undefined) {
508
+ if (suppressDeletedCallTarget(options.recordResolutionOutcome, parsed.filePath, site, memberDef)) {
509
+ handledSites.add(siteKey);
510
+ continue;
511
+ }
445
512
  const reason = site.kind === 'write' || site.kind === 'read'
446
513
  ? site.kind
447
514
  : memberDef.filePath !== parsed.filePath
@@ -466,8 +533,22 @@ export function emitReceiverBoundCalls(graph, scopes, parsedFiles, nodeLookup, h
466
533
  for (const targetFile3 of targetFiles3) {
467
534
  const classDef3 = findExportedDef(targetFile3, className, index);
468
535
  if (classDef3 !== undefined) {
469
- const memberDef = findOwnedMember(classDef3.nodeId, memberName, model);
470
- if (memberDef !== undefined) {
536
+ const picked = site.kind === 'call'
537
+ ? pickOverload(classDef3.nodeId, memberName, site, model, provider)
538
+ : findOwnedMember(classDef3.nodeId, memberName, model);
539
+ if (picked === OVERLOAD_AMBIGUOUS) {
540
+ recordReceiverOverloadSuppression(options.recordResolutionOutcome, parsed.filePath, site, classDef3.nodeId, memberName, model, provider);
541
+ handledSites.add(siteKey);
542
+ found3 = true;
543
+ break;
544
+ }
545
+ if (picked !== undefined) {
546
+ const memberDef = picked;
547
+ if (suppressDeletedCallTarget(options.recordResolutionOutcome, parsed.filePath, site, memberDef)) {
548
+ handledSites.add(siteKey);
549
+ found3 = true;
550
+ break;
551
+ }
471
552
  const ok = tryEmitEdge(graph, scopes, nodeLookup, site, memberDef, memberDef.filePath !== parsed.filePath ? 'import-resolved' : 'global', seen);
472
553
  if (ok) {
473
554
  emitted++;
@@ -504,8 +585,9 @@ export function emitReceiverBoundCalls(graph, scopes, parsedFiles, nodeLookup, h
504
585
  if (ownerDef !== undefined) {
505
586
  const chain = [ownerDef.nodeId, ...scopes.methodDispatch.mroFor(ownerDef.nodeId)];
506
587
  let memberDef;
507
- // Static-only filter (#1756 / U3): mirrors Case 0's chain
508
- // walk `findOwnedMember` without overload narrowing. When
588
+ let ambiguousOwnerId;
589
+ // Static-only filter (#1756 / U3): mirrors Case 0's
590
+ // overload-aware chain walk. When
509
591
  // a static-only candidate is found at an ancestor, walk on
510
592
  // so a legitimate instance member can bind. If the entire
511
593
  // chain is static-only, no edge is emitted (Case 3b is fed
@@ -513,16 +595,29 @@ export function emitReceiverBoundCalls(graph, scopes, parsedFiles, nodeLookup, h
513
595
  // `emitReferencesViaLookup` for compound shapes, so no
514
596
  // handled-site marker is needed for chain-only-static).
515
597
  for (const ownerId of chain) {
516
- const candidate = findOwnedMember(ownerId, memberName, model);
517
- if (candidate === undefined)
518
- continue;
519
- if (provider.isStaticOnly?.(candidate) === true) {
598
+ const picked = site.kind === 'call'
599
+ ? pickFirstNonStaticOnly(ownerId, memberName, site, model, provider)
600
+ : findOwnedMember(ownerId, memberName, model);
601
+ if (picked === OVERLOAD_AMBIGUOUS) {
602
+ ambiguousOwnerId = ownerId;
603
+ break;
604
+ }
605
+ if (picked === STATIC_ONLY_FILTERED || picked === undefined) {
520
606
  continue;
521
607
  }
522
- memberDef = candidate;
608
+ memberDef = picked;
523
609
  break;
524
610
  }
611
+ if (ambiguousOwnerId !== undefined) {
612
+ recordReceiverOverloadSuppression(options.recordResolutionOutcome, parsed.filePath, site, ambiguousOwnerId, memberName, model, provider);
613
+ handledSites.add(siteKey);
614
+ continue;
615
+ }
525
616
  if (memberDef !== undefined) {
617
+ if (suppressDeletedCallTarget(options.recordResolutionOutcome, parsed.filePath, site, memberDef)) {
618
+ handledSites.add(siteKey);
619
+ continue;
620
+ }
526
621
  const ok = tryEmitEdge(graph, scopes, nodeLookup, site, memberDef, memberDef.filePath !== parsed.filePath ? 'import-resolved' : 'global', seen, 0.85, collapse);
527
622
  if (ok)
528
623
  emitted++;
@@ -574,6 +669,10 @@ export function emitReceiverBoundCalls(graph, scopes, parsedFiles, nodeLookup, h
574
669
  }
575
670
  if (languageResolution?.kind === 'resolved') {
576
671
  const memberDef = languageResolution.definition;
672
+ if (suppressDeletedCallTarget(options.recordResolutionOutcome, parsed.filePath, site, memberDef)) {
673
+ handledSites.add(siteKey);
674
+ continue;
675
+ }
577
676
  const reason = site.kind === 'write' || site.kind === 'read'
578
677
  ? site.kind
579
678
  : memberDef.filePath !== parsed.filePath
@@ -657,6 +756,10 @@ export function emitReceiverBoundCalls(graph, scopes, parsedFiles, nodeLookup, h
657
756
  continue;
658
757
  }
659
758
  if (memberDef !== undefined) {
759
+ if (suppressDeletedCallTarget(options.recordResolutionOutcome, parsed.filePath, site, memberDef)) {
760
+ handledSites.add(siteKey);
761
+ continue;
762
+ }
660
763
  // For read/write ACCESSES, mirror the legacy DAG's reason
661
764
  // convention so consumers asserting `reason === 'write'`
662
765
  // keep working.
@@ -713,6 +816,10 @@ export function emitReceiverBoundCalls(graph, scopes, parsedFiles, nodeLookup, h
713
816
  continue;
714
817
  }
715
818
  if (picked !== undefined) {
819
+ if (suppressDeletedCallTarget(options.recordResolutionOutcome, parsed.filePath, site, picked)) {
820
+ handledSites.add(siteKey);
821
+ continue;
822
+ }
716
823
  // Static-only filter (#1756 / U3): unlike Case 4 there's no
717
824
  // MRO chain to walk here — Case 5 dispatches on a single
718
825
  // owner via `pickOverload`. When the picked candidate is
@@ -873,6 +980,20 @@ function pickFirstNonStaticOnly(ownerId, memberName, site, model, provider) {
873
980
  return OVERLOAD_AMBIGUOUS;
874
981
  return candidates[0] ?? overloads[0];
875
982
  }
983
+ function suppressDeletedCallTarget(record, filePath, site, target) {
984
+ if (site.kind !== 'call' || target.isDeleted !== true)
985
+ return false;
986
+ record?.({
987
+ kind: 'suppressed',
988
+ phase: 'receiver-bound-calls',
989
+ filePath,
990
+ name: site.name,
991
+ range: site.atRange,
992
+ reason: 'selected-callable-deleted',
993
+ candidateIds: [target.nodeId],
994
+ });
995
+ return true;
996
+ }
876
997
  function recordReceiverOverloadSuppression(record, filePath, site, ownerId, memberName, model, provider) {
877
998
  if (record === undefined)
878
999
  return;
@@ -64,14 +64,39 @@ export function reconcileOwnership(parsedFiles, model) {
64
64
  for (const parsed of parsedFiles) {
65
65
  for (const def of parsed.localDefs) {
66
66
  const ownerId = def.ownerId;
67
- if (ownerId === undefined)
68
- continue;
69
67
  const simple = simpleQualifiedName(def);
70
68
  if (simple === undefined)
71
69
  continue;
72
70
  if (def.type === 'Method' || def.type === 'Function' || def.type === 'Constructor') {
71
+ if (ownerId === undefined) {
72
+ if (def.isDeleted !== true)
73
+ continue;
74
+ const existingDef = model.symbols
75
+ .lookupExactAll(def.filePath, simple)
76
+ .find((candidate) => callableSignatureMatches(candidate, def));
77
+ if (existingDef !== undefined) {
78
+ existingDef.isDeleted = true;
79
+ skippedAlreadyPresent++;
80
+ continue;
81
+ }
82
+ model.symbols.add(def.filePath, simple, def.nodeId, def.type, {
83
+ parameterCount: def.parameterCount,
84
+ requiredParameterCount: def.requiredParameterCount,
85
+ parameterTypes: def.parameterTypes,
86
+ parameterTypeClasses: def.parameterTypeClasses,
87
+ returnType: def.returnType,
88
+ qualifiedName: def.qualifiedName,
89
+ isDeleted: true,
90
+ });
91
+ continue;
92
+ }
73
93
  const existing = model.methods.lookupAllByOwner(ownerId, simple);
74
- if (existing.some((e) => e.nodeId === def.nodeId)) {
94
+ const existingDef = existing.find((candidate) => candidate.nodeId === def.nodeId ||
95
+ (def.isDeleted === true && callableSignatureMatches(candidate, def)));
96
+ if (existingDef !== undefined) {
97
+ if (def.isDeleted === true) {
98
+ existingDef.isDeleted = true;
99
+ }
75
100
  skippedAlreadyPresent++;
76
101
  continue;
77
102
  }
@@ -103,6 +128,21 @@ export function reconcileOwnership(parsedFiles, model) {
103
128
  }
104
129
  return { methodsRegistered, fieldsRegistered, nestedTypesRegistered, skippedAlreadyPresent };
105
130
  }
131
+ function callableSignatureMatches(left, right) {
132
+ if (left.filePath !== right.filePath)
133
+ return false;
134
+ if (left.parameterCount !== right.parameterCount)
135
+ return false;
136
+ if (left.requiredParameterCount !== right.requiredParameterCount)
137
+ return false;
138
+ const leftTypes = left.parameterTypes;
139
+ const rightTypes = right.parameterTypes;
140
+ if (leftTypes === undefined || rightTypes === undefined) {
141
+ return leftTypes === rightTypes;
142
+ }
143
+ return (leftTypes.length === rightTypes.length &&
144
+ leftTypes.every((parameterType, index) => parameterType === rightTypes[index]));
145
+ }
106
146
  /**
107
147
  * Debug-mode parity validator. Runs only when
108
148
  * `VALIDATE_SEMANTIC_MODEL !== '0'` AND `NODE_ENV !== 'production'`.
@@ -1,5 +1,5 @@
1
1
  import type { Range } from '../../../_shared/index.js';
2
- export type ResolutionSuppressionReason = 'adl-ordinary-lookup-blocked' | 'conversion-rank-tied' | 'inline-ns-ambiguous' | 'member-lookup-ambiguous' | 'overload-ambiguous' | 'overload-ambiguous-normalization';
2
+ export type ResolutionSuppressionReason = 'adl-ordinary-lookup-blocked' | 'conversion-rank-tied' | 'inline-ns-ambiguous' | 'member-lookup-ambiguous' | 'selected-callable-deleted' | 'overload-ambiguous' | 'overload-ambiguous-normalization';
3
3
  export type ResolutionOutcome = {
4
4
  readonly kind: 'resolved';
5
5
  readonly targetId: string;
@@ -184,6 +184,7 @@ export function buildMethodProps(info) {
184
184
  ...(info.isAsync ? { isAsync: info.isAsync } : {}),
185
185
  ...(info.isPartial ? { isPartial: info.isPartial } : {}),
186
186
  ...(info.isConst ? { isConst: info.isConst } : {}),
187
+ ...(info.isDeleted ? { isDeleted: info.isDeleted } : {}),
187
188
  ...(info.annotations.length > 0 ? { annotations: info.annotations } : {}),
188
189
  };
189
190
  }
@@ -50,6 +50,7 @@ interface ParsedSymbol {
50
50
  isReadonly?: boolean;
51
51
  isAbstract?: boolean;
52
52
  isFinal?: boolean;
53
+ isDeleted?: boolean;
53
54
  annotations?: string[];
54
55
  }
55
56
  export interface ExtractedCall {
@@ -1673,6 +1673,9 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
1673
1673
  isReadonly: methodProps.isReadonly,
1674
1674
  isAbstract: methodProps.isAbstract,
1675
1675
  isFinal: methodProps.isFinal,
1676
+ ...(methodProps.isDeleted !== undefined
1677
+ ? { isDeleted: methodProps.isDeleted }
1678
+ : {}),
1676
1679
  ...(methodProps.isVirtual !== undefined
1677
1680
  ? { isVirtual: methodProps.isVirtual }
1678
1681
  : {}),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitnexus",
3
- "version": "1.6.8-rc.11",
3
+ "version": "1.6.8-rc.12",
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",