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.
- package/dist/_shared/scope-resolution/symbol-definition.d.ts +4 -0
- package/dist/_shared/scope-resolution/symbol-definition.d.ts.map +1 -1
- package/dist/core/ingestion/languages/cpp/arity-metadata.js +6 -2
- package/dist/core/ingestion/languages/cpp/captures.js +24 -1
- package/dist/core/ingestion/languages/cpp/query.js +23 -0
- package/dist/core/ingestion/method-extractors/configs/c-cpp.js +6 -13
- package/dist/core/ingestion/method-extractors/generic.js +1 -0
- package/dist/core/ingestion/method-types.d.ts +2 -0
- package/dist/core/ingestion/model/symbol-table.d.ts +1 -0
- package/dist/core/ingestion/model/symbol-table.js +1 -0
- package/dist/core/ingestion/parsing-processor.js +1 -0
- package/dist/core/ingestion/scope-extractor.js +3 -0
- package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.js +12 -0
- package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +145 -24
- package/dist/core/ingestion/scope-resolution/pipeline/reconcile-ownership.js +43 -3
- package/dist/core/ingestion/scope-resolution/resolution-outcome.d.ts +1 -1
- package/dist/core/ingestion/utils/method-props.js +1 -0
- package/dist/core/ingestion/workers/parse-worker.d.ts +1 -0
- package/dist/core/ingestion/workers/parse-worker.js +3 -0
- package/package.json +1 -1
|
@@ -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
|
|
197
|
-
|
|
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' ||
|
|
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
|
-
|
|
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 ===
|
|
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. */
|
|
@@ -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)) {
|
|
@@ -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 =
|
|
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
|
-
|
|
184
|
-
|
|
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)
|
|
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
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
if (
|
|
240
|
-
|
|
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 =
|
|
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
|
-
|
|
430
|
-
|
|
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
|
|
470
|
-
|
|
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
|
-
|
|
508
|
-
//
|
|
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
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
if (
|
|
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 =
|
|
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
|
-
|
|
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
|
}
|
|
@@ -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