gitnexus 1.6.6-rc.115 → 1.6.6-rc.116
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/core/ingestion/languages/c/query.js +9 -0
- package/dist/core/ingestion/languages/cpp/query.js +9 -0
- package/dist/core/ingestion/languages/go/arity-metadata.d.ts +1 -0
- package/dist/core/ingestion/languages/go/arity-metadata.js +31 -1
- package/dist/core/ingestion/languages/go/captures.js +5 -1
- package/dist/core/ingestion/languages/go/interface-impls.js +445 -50
- package/dist/core/ingestion/languages/go/interpret.js +4 -1
- package/dist/core/ingestion/languages/go/method-owners.js +20 -8
- package/dist/core/ingestion/languages/go/query.js +4 -0
- package/dist/core/ingestion/languages/go/range-binding.js +4 -4
- package/dist/core/ingestion/languages/go/receiver-binding.js +1 -1
- package/dist/core/ingestion/languages/go/scope-resolver.js +2 -2
- package/dist/core/ingestion/languages/go/simple-hooks.js +10 -5
- package/dist/core/ingestion/languages/go/type-binding.js +29 -0
- package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +1 -1
- package/dist/core/ingestion/scope-resolution/pipeline/run.js +60 -8
- package/dist/core/ingestion/scope-resolution/scope/walkers.d.ts +6 -0
- package/dist/core/ingestion/scope-resolution/scope/walkers.js +9 -0
- package/dist/core/ingestion/tree-sitter-queries.js +1 -0
- package/dist/core/ingestion/utils/ast-helpers.js +5 -0
- package/package.json +1 -1
|
@@ -98,6 +98,15 @@ const C_SCOPE_QUERY = `
|
|
|
98
98
|
declarator: (init_declarator
|
|
99
99
|
declarator: (identifier) @declaration.name)) @declaration.variable
|
|
100
100
|
|
|
101
|
+
;; Declarations — variables (without initializer), including non-leading
|
|
102
|
+
;; declarators in mixed declaration lists.
|
|
103
|
+
(declaration
|
|
104
|
+
declarator: (identifier) @declaration.name) @declaration.variable
|
|
105
|
+
|
|
106
|
+
(declaration
|
|
107
|
+
declarator: (pointer_declarator
|
|
108
|
+
declarator: (identifier) @declaration.name)) @declaration.variable
|
|
109
|
+
|
|
101
110
|
;; Declarations — macro definitions
|
|
102
111
|
(preproc_def
|
|
103
112
|
name: (identifier) @declaration.name) @declaration.macro
|
|
@@ -272,6 +272,15 @@ const CPP_SCOPE_QUERY = `
|
|
|
272
272
|
declarator: (init_declarator
|
|
273
273
|
declarator: (identifier) @declaration.name)) @declaration.variable
|
|
274
274
|
|
|
275
|
+
;; ─── Declarations — variables (without initializer) ─────────────────
|
|
276
|
+
;; Covers non-leading declarators in mixed declaration lists.
|
|
277
|
+
(declaration
|
|
278
|
+
declarator: (identifier) @declaration.name) @declaration.variable
|
|
279
|
+
|
|
280
|
+
(declaration
|
|
281
|
+
declarator: (pointer_declarator
|
|
282
|
+
declarator: (identifier) @declaration.name)) @declaration.variable
|
|
283
|
+
|
|
275
284
|
;; ─── Declarations — macro definitions ───────────────────────────────
|
|
276
285
|
(preproc_def
|
|
277
286
|
name: (identifier) @declaration.name) @declaration.macro
|
|
@@ -3,6 +3,7 @@ export interface GoArityMetadata {
|
|
|
3
3
|
readonly parameterCount?: number;
|
|
4
4
|
readonly requiredParameterCount?: number;
|
|
5
5
|
readonly parameterTypes?: readonly string[];
|
|
6
|
+
readonly returnType?: string;
|
|
6
7
|
}
|
|
7
8
|
export declare function computeGoDeclarationArity(node: SyntaxNode): GoArityMetadata;
|
|
8
9
|
export declare function computeGoCallArity(callNode: SyntaxNode): number;
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
export function computeGoDeclarationArity(node) {
|
|
2
2
|
const params = node.childForFieldName('parameters');
|
|
3
|
+
const returnType = extractGoReturnType(node);
|
|
3
4
|
if (params === null)
|
|
4
|
-
return {};
|
|
5
|
+
return returnType === undefined ? {} : { returnType };
|
|
6
|
+
return {
|
|
7
|
+
...computeGoParameterMetadata(params),
|
|
8
|
+
...(returnType === undefined ? {} : { returnType }),
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
function computeGoParameterMetadata(params) {
|
|
5
12
|
let count = 0;
|
|
6
13
|
let required = 0;
|
|
7
14
|
const types = [];
|
|
@@ -35,3 +42,26 @@ export function computeGoCallArity(callNode) {
|
|
|
35
42
|
return 0;
|
|
36
43
|
return args.namedChildCount;
|
|
37
44
|
}
|
|
45
|
+
function extractGoReturnType(node) {
|
|
46
|
+
const result = node.childForFieldName('result');
|
|
47
|
+
if (result === null)
|
|
48
|
+
return undefined;
|
|
49
|
+
if (result.type !== 'parameter_list')
|
|
50
|
+
return result.text;
|
|
51
|
+
const returnTypes = [];
|
|
52
|
+
for (const child of result.namedChildren) {
|
|
53
|
+
if (child.type !== 'parameter_declaration')
|
|
54
|
+
continue;
|
|
55
|
+
const typeNode = child.childForFieldName('type');
|
|
56
|
+
if (typeNode === null)
|
|
57
|
+
continue;
|
|
58
|
+
const names = child.namedChildren.filter((c) => c.type === 'identifier');
|
|
59
|
+
const n = Math.max(1, names.length);
|
|
60
|
+
for (let i = 0; i < n; i++) {
|
|
61
|
+
returnTypes.push(typeNode.text);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (returnTypes.length === 0)
|
|
65
|
+
return undefined;
|
|
66
|
+
return returnTypes.length === 1 ? returnTypes[0] : `(${returnTypes.join(', ')})`;
|
|
67
|
+
}
|
|
@@ -74,7 +74,8 @@ export function emitGoScopeCaptures(sourceText, _filePath, cachedTree) {
|
|
|
74
74
|
// @declaration.function / @declaration.method are captured directly on
|
|
75
75
|
// the function_declaration / method_declaration node.
|
|
76
76
|
const fnNode = declAnchorNode.type === 'function_declaration' ||
|
|
77
|
-
declAnchorNode.type === 'method_declaration'
|
|
77
|
+
declAnchorNode.type === 'method_declaration' ||
|
|
78
|
+
declAnchorNode.type === 'method_elem'
|
|
78
79
|
? declAnchorNode
|
|
79
80
|
: null;
|
|
80
81
|
if (fnNode !== null) {
|
|
@@ -88,6 +89,9 @@ export function emitGoScopeCaptures(sourceText, _filePath, cachedTree) {
|
|
|
88
89
|
if (arity.parameterTypes !== undefined) {
|
|
89
90
|
grouped['@declaration.parameter-types'] = syntheticCapture('@declaration.parameter-types', fnNode, JSON.stringify(arity.parameterTypes));
|
|
90
91
|
}
|
|
92
|
+
if (arity.returnType !== undefined) {
|
|
93
|
+
grouped['@declaration.return-type'] = syntheticCapture('@declaration.return-type', fnNode, arity.returnType);
|
|
94
|
+
}
|
|
91
95
|
}
|
|
92
96
|
out.push(grouped);
|
|
93
97
|
continue;
|
|
@@ -1,72 +1,467 @@
|
|
|
1
|
+
import { simpleQualifiedName } from '../../scope-resolution/graph-bridge/ids.js';
|
|
2
|
+
import { resolveInheritanceBaseInScope } from '../../scope-resolution/scope/walkers.js';
|
|
1
3
|
export function detectGoInterfaceImplementations(parsedFiles, _indexes, _model) {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const
|
|
4
|
+
return detectGoInterfaceImplementationsFromIndexes(buildDetectionIndexes(parsedFiles, _indexes));
|
|
5
|
+
}
|
|
6
|
+
function buildDetectionIndexes(parsedFiles, indexes) {
|
|
7
|
+
const interfaces = [];
|
|
8
|
+
const structsById = new Map();
|
|
9
|
+
const methodsByOwner = new Map();
|
|
10
|
+
const effectiveMethodsByStructId = new Map();
|
|
11
|
+
const interfaceById = new Map();
|
|
12
|
+
const interfaceOwnMethodsById = new Map();
|
|
13
|
+
const embeddedSitesByInterfaceId = new Map();
|
|
14
|
+
const parentStructIdsByStructId = new Map();
|
|
15
|
+
const structIdsByMethodName = new Map();
|
|
16
|
+
const signatureContextByDefId = new Map();
|
|
17
|
+
const interfaceIdByScopeId = new Map();
|
|
18
|
+
const structIdByScopeId = new Map();
|
|
19
|
+
for (const parsed of parsedFiles) {
|
|
20
|
+
const signatureContext = signatureContextForFile(parsed, indexes);
|
|
21
|
+
for (const def of parsed.localDefs) {
|
|
22
|
+
signatureContextByDefId.set(def.nodeId, signatureContext);
|
|
23
|
+
if (def.type === 'Interface') {
|
|
24
|
+
interfaces.push(def);
|
|
25
|
+
interfaceById.set(def.nodeId, def);
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
if (def.type === 'Struct') {
|
|
29
|
+
structsById.set(def.nodeId, def);
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
if (def.type !== 'Method' && def.type !== 'Function')
|
|
33
|
+
continue;
|
|
34
|
+
if (def.ownerId === undefined)
|
|
35
|
+
continue;
|
|
36
|
+
if (isPointerReceiverMethod(def))
|
|
37
|
+
continue;
|
|
38
|
+
const methodName = simpleQualifiedName(def);
|
|
39
|
+
if (methodName === undefined || methodName.length === 0)
|
|
40
|
+
continue;
|
|
41
|
+
addMethod(methodsByOwner, def.ownerId, methodName, def);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
for (const parsed of parsedFiles) {
|
|
45
|
+
for (const scope of parsed.scopes) {
|
|
46
|
+
const iface = scope.ownedDefs.find((def) => def.type === 'Interface');
|
|
47
|
+
if (iface !== undefined)
|
|
48
|
+
interfaceIdByScopeId.set(scope.id, iface.nodeId);
|
|
49
|
+
const struct = scope.ownedDefs.find((def) => def.type === 'Struct');
|
|
50
|
+
if (struct !== undefined)
|
|
51
|
+
structIdByScopeId.set(scope.id, struct.nodeId);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
7
54
|
for (const parsed of parsedFiles) {
|
|
8
|
-
|
|
55
|
+
const childScopesByParent = new Map();
|
|
9
56
|
for (const scope of parsed.scopes) {
|
|
10
|
-
if (scope.
|
|
57
|
+
if (scope.parent === null)
|
|
11
58
|
continue;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
59
|
+
const children = childScopesByParent.get(scope.parent) ?? [];
|
|
60
|
+
children.push(scope);
|
|
61
|
+
childScopesByParent.set(scope.parent, children);
|
|
62
|
+
}
|
|
63
|
+
for (const scope of parsed.scopes) {
|
|
64
|
+
const ifaceId = interfaceIdByScopeId.get(scope.id);
|
|
65
|
+
if (ifaceId === undefined)
|
|
15
66
|
continue;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
// Also check if methods have ownerId pointing to this interface
|
|
30
|
-
for (const def of parsed.localDefs) {
|
|
31
|
-
if (def.ownerId === typeDef.nodeId &&
|
|
32
|
-
(def.type === 'Method' || def.type === 'Function')) {
|
|
33
|
-
methodNames.add(def.qualifiedName?.split('.').pop() ?? '');
|
|
34
|
-
}
|
|
67
|
+
const methods = new Map();
|
|
68
|
+
for (const childScope of childScopesByParent.get(scope.id) ?? []) {
|
|
69
|
+
for (const def of childScope.ownedDefs) {
|
|
70
|
+
if (def.type !== 'Method' && def.type !== 'Function')
|
|
71
|
+
continue;
|
|
72
|
+
const methodName = simpleQualifiedName(def);
|
|
73
|
+
if (methodName === undefined || methodName.length === 0)
|
|
74
|
+
continue;
|
|
75
|
+
addMethodOverload(methods, methodName, def);
|
|
35
76
|
}
|
|
36
|
-
interfaceMethods.set(typeDef.nodeId, methodNames);
|
|
37
77
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
78
|
+
interfaceOwnMethodsById.set(ifaceId, methods);
|
|
79
|
+
}
|
|
80
|
+
for (const site of parsed.referenceSites) {
|
|
81
|
+
if (site.kind !== 'inherits')
|
|
82
|
+
continue;
|
|
83
|
+
const ifaceId = interfaceIdByScopeId.get(site.inScope);
|
|
84
|
+
if (ifaceId !== undefined) {
|
|
85
|
+
const sites = embeddedSitesByInterfaceId.get(ifaceId) ?? [];
|
|
86
|
+
sites.push(site);
|
|
87
|
+
embeddedSitesByInterfaceId.set(ifaceId, sites);
|
|
88
|
+
continue;
|
|
47
89
|
}
|
|
90
|
+
const structId = structIdByScopeId.get(site.inScope);
|
|
91
|
+
if (structId === undefined)
|
|
92
|
+
continue;
|
|
93
|
+
const parent = resolveInheritanceBaseInScope(site.inScope, site.name, indexes);
|
|
94
|
+
if (parent === undefined || parent.type !== 'Struct')
|
|
95
|
+
continue;
|
|
96
|
+
addParentStruct(parentStructIdsByStructId, structId, parent.nodeId);
|
|
48
97
|
}
|
|
49
98
|
}
|
|
50
|
-
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
99
|
+
const structMethodSetCache = new Map();
|
|
100
|
+
for (const structId of structsById.keys()) {
|
|
101
|
+
const effective = collectStructMethodSet(structId, {
|
|
102
|
+
parentStructIdsByStructId,
|
|
103
|
+
methodsByOwner,
|
|
104
|
+
}, new Set(), structMethodSetCache);
|
|
105
|
+
if (effective === undefined)
|
|
106
|
+
continue;
|
|
107
|
+
effectiveMethodsByStructId.set(structId, effective);
|
|
108
|
+
for (const methodName of effective.keys()) {
|
|
109
|
+
addStructMethodCandidate(structIdsByMethodName, methodName, structId);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
interfaces,
|
|
114
|
+
structsById,
|
|
115
|
+
methodsByOwner,
|
|
116
|
+
effectiveMethodsByStructId,
|
|
117
|
+
interfaceById,
|
|
118
|
+
interfaceOwnMethodsById,
|
|
119
|
+
embeddedSitesByInterfaceId,
|
|
120
|
+
parentStructIdsByStructId,
|
|
121
|
+
structIdsByMethodName,
|
|
122
|
+
signatureContextByDefId,
|
|
123
|
+
scopeIndexes: indexes,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
function detectGoInterfaceImplementationsFromIndexes(indexes) {
|
|
127
|
+
const implementations = new Map();
|
|
128
|
+
const methodSetCache = new Map();
|
|
129
|
+
for (const iface of indexes.interfaces) {
|
|
130
|
+
const required = collectInterfaceMethodSet(iface, indexes, new Set(), methodSetCache);
|
|
131
|
+
if (required === undefined || required.size === 0)
|
|
132
|
+
continue;
|
|
133
|
+
if (!methodSetHasVerifiableSignatures(required))
|
|
54
134
|
continue;
|
|
55
135
|
const implementors = [];
|
|
56
|
-
for (const
|
|
57
|
-
|
|
136
|
+
for (const structId of candidateStructIdsFor(required, indexes)) {
|
|
137
|
+
const actual = indexes.effectiveMethodsByStructId.get(structId);
|
|
138
|
+
if (actual === undefined)
|
|
139
|
+
continue;
|
|
140
|
+
if (methodSetSatisfies(actual, required, indexes.signatureContextByDefId)) {
|
|
58
141
|
implementors.push(structId);
|
|
59
142
|
}
|
|
60
143
|
}
|
|
61
144
|
if (implementors.length > 0)
|
|
62
|
-
|
|
145
|
+
implementations.set(iface.nodeId, implementors);
|
|
146
|
+
}
|
|
147
|
+
return implementations;
|
|
148
|
+
}
|
|
149
|
+
function addMethod(methodsByOwner, ownerId, methodName, def) {
|
|
150
|
+
let methods = methodsByOwner.get(ownerId);
|
|
151
|
+
if (methods === undefined) {
|
|
152
|
+
methods = new Map();
|
|
153
|
+
methodsByOwner.set(ownerId, methods);
|
|
154
|
+
}
|
|
155
|
+
addMethodOverload(methods, methodName, def);
|
|
156
|
+
}
|
|
157
|
+
function addMethodOverload(methods, methodName, def) {
|
|
158
|
+
const overloads = methods.get(methodName) ?? [];
|
|
159
|
+
overloads.push(def);
|
|
160
|
+
methods.set(methodName, overloads);
|
|
161
|
+
}
|
|
162
|
+
function addStructMethodCandidate(structIdsByMethodName, methodName, structId) {
|
|
163
|
+
const structIds = structIdsByMethodName.get(methodName) ?? new Set();
|
|
164
|
+
structIds.add(structId);
|
|
165
|
+
structIdsByMethodName.set(methodName, structIds);
|
|
166
|
+
}
|
|
167
|
+
function addParentStruct(parentStructIdsByStructId, structId, parentStructId) {
|
|
168
|
+
const parents = parentStructIdsByStructId.get(structId) ?? [];
|
|
169
|
+
parents.push(parentStructId);
|
|
170
|
+
parentStructIdsByStructId.set(structId, parents);
|
|
171
|
+
}
|
|
172
|
+
function collectStructMethodSet(structId, indexes, visiting, cache) {
|
|
173
|
+
const entries = collectStructMethodEntries(structId, indexes, visiting, cache);
|
|
174
|
+
return entries === undefined ? undefined : methodEntriesToMethodSet(entries);
|
|
175
|
+
}
|
|
176
|
+
function collectStructMethodEntries(structId, indexes, visiting, cache) {
|
|
177
|
+
const cached = cache.get(structId);
|
|
178
|
+
if (cached !== undefined)
|
|
179
|
+
return cloneMethodEntries(cached);
|
|
180
|
+
if (visiting.has(structId))
|
|
181
|
+
return undefined;
|
|
182
|
+
visiting.add(structId);
|
|
183
|
+
const merged = directMethodEntries(indexes.methodsByOwner.get(structId));
|
|
184
|
+
for (const parentStructId of indexes.parentStructIdsByStructId.get(structId) ?? []) {
|
|
185
|
+
const parentEntries = collectStructMethodEntries(parentStructId, indexes, visiting, cache);
|
|
186
|
+
if (parentEntries === undefined) {
|
|
187
|
+
visiting.delete(structId);
|
|
188
|
+
return undefined;
|
|
189
|
+
}
|
|
190
|
+
for (const [methodName, entry] of parentEntries) {
|
|
191
|
+
if (entry.ambiguous)
|
|
192
|
+
continue;
|
|
193
|
+
mergePromotedMethodEntry(merged, methodName, {
|
|
194
|
+
overloads: entry.overloads,
|
|
195
|
+
depth: entry.depth + 1,
|
|
196
|
+
ambiguous: false,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
visiting.delete(structId);
|
|
201
|
+
cache.set(structId, cloneMethodEntries(merged));
|
|
202
|
+
return merged;
|
|
203
|
+
}
|
|
204
|
+
function collectInterfaceMethodSet(iface, indexes, visiting, cache) {
|
|
205
|
+
const cached = cache.get(iface.nodeId);
|
|
206
|
+
if (cached !== undefined)
|
|
207
|
+
return cloneMethodSet(cached);
|
|
208
|
+
if (visiting.has(iface.nodeId))
|
|
209
|
+
return undefined;
|
|
210
|
+
visiting.add(iface.nodeId);
|
|
211
|
+
const ownMethods = indexes.methodsByOwner.get(iface.nodeId) ?? indexes.interfaceOwnMethodsById.get(iface.nodeId);
|
|
212
|
+
const merged = cloneMethodSet(ownMethods);
|
|
213
|
+
const embeddedInterfaces = embeddedInterfacesFor(iface, indexes);
|
|
214
|
+
if (embeddedInterfaces === undefined) {
|
|
215
|
+
visiting.delete(iface.nodeId);
|
|
216
|
+
return undefined;
|
|
217
|
+
}
|
|
218
|
+
for (const embeddedIface of embeddedInterfaces) {
|
|
219
|
+
const embeddedMethods = collectInterfaceMethodSet(embeddedIface, indexes, visiting, cache);
|
|
220
|
+
if (embeddedMethods === undefined) {
|
|
221
|
+
visiting.delete(iface.nodeId);
|
|
222
|
+
return undefined;
|
|
223
|
+
}
|
|
224
|
+
mergeMethodSet(merged, embeddedMethods);
|
|
225
|
+
}
|
|
226
|
+
visiting.delete(iface.nodeId);
|
|
227
|
+
cache.set(iface.nodeId, cloneMethodSet(merged));
|
|
228
|
+
return merged;
|
|
229
|
+
}
|
|
230
|
+
function embeddedInterfacesFor(iface, indexes) {
|
|
231
|
+
const embedded = [];
|
|
232
|
+
for (const site of indexes.embeddedSitesByInterfaceId.get(iface.nodeId) ?? []) {
|
|
233
|
+
const resolved = resolveEmbeddedInterface(site, indexes);
|
|
234
|
+
if (resolved === undefined)
|
|
235
|
+
return undefined;
|
|
236
|
+
embedded.push(resolved);
|
|
237
|
+
}
|
|
238
|
+
return embedded;
|
|
239
|
+
}
|
|
240
|
+
function candidateStructIdsFor(required, indexes) {
|
|
241
|
+
let best;
|
|
242
|
+
for (const name of required.keys()) {
|
|
243
|
+
const candidates = indexes.structIdsByMethodName.get(name);
|
|
244
|
+
if (candidates === undefined)
|
|
245
|
+
return [];
|
|
246
|
+
if (best === undefined || candidates.size < best.size)
|
|
247
|
+
best = candidates;
|
|
248
|
+
}
|
|
249
|
+
return best === undefined ? [...indexes.structsById.keys()] : [...best];
|
|
250
|
+
}
|
|
251
|
+
function resolveEmbeddedInterface(site, indexes) {
|
|
252
|
+
const bound = resolveInheritanceBaseInScope(site.inScope, site.name, indexes.scopeIndexes);
|
|
253
|
+
if (bound !== undefined)
|
|
254
|
+
return bound.type === 'Interface' ? bound : undefined;
|
|
255
|
+
const simpleName = simpleTypeName(site.name);
|
|
256
|
+
const matches = [];
|
|
257
|
+
for (const iface of indexes.interfaceById.values()) {
|
|
258
|
+
if (iface.qualifiedName === site.name || iface.qualifiedName === simpleName) {
|
|
259
|
+
matches.push(iface);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
return matches.length === 1 ? matches[0] : undefined;
|
|
263
|
+
}
|
|
264
|
+
function simpleTypeName(name) {
|
|
265
|
+
const dot = name.lastIndexOf('.');
|
|
266
|
+
return dot === -1 ? name : name.slice(dot + 1);
|
|
267
|
+
}
|
|
268
|
+
function cloneMethodSet(methods) {
|
|
269
|
+
const clone = new Map();
|
|
270
|
+
if (methods === undefined)
|
|
271
|
+
return clone;
|
|
272
|
+
for (const [name, overloads] of methods) {
|
|
273
|
+
clone.set(name, [...overloads]);
|
|
274
|
+
}
|
|
275
|
+
return clone;
|
|
276
|
+
}
|
|
277
|
+
function directMethodEntries(methods) {
|
|
278
|
+
const entries = new Map();
|
|
279
|
+
if (methods === undefined)
|
|
280
|
+
return entries;
|
|
281
|
+
for (const [name, overloads] of methods) {
|
|
282
|
+
entries.set(name, { overloads: [...overloads], depth: 0, ambiguous: false });
|
|
283
|
+
}
|
|
284
|
+
return entries;
|
|
285
|
+
}
|
|
286
|
+
function cloneMethodEntries(entries) {
|
|
287
|
+
const clone = new Map();
|
|
288
|
+
for (const [name, entry] of entries) {
|
|
289
|
+
clone.set(name, { ...entry, overloads: [...entry.overloads] });
|
|
290
|
+
}
|
|
291
|
+
return clone;
|
|
292
|
+
}
|
|
293
|
+
function methodEntriesToMethodSet(entries) {
|
|
294
|
+
const methods = new Map();
|
|
295
|
+
for (const [name, entry] of entries) {
|
|
296
|
+
if (entry.ambiguous)
|
|
297
|
+
continue;
|
|
298
|
+
methods.set(name, [...entry.overloads]);
|
|
299
|
+
}
|
|
300
|
+
return methods;
|
|
301
|
+
}
|
|
302
|
+
function mergePromotedMethodEntry(target, methodName, candidate) {
|
|
303
|
+
const existing = target.get(methodName);
|
|
304
|
+
if (existing === undefined || candidate.depth < existing.depth) {
|
|
305
|
+
target.set(methodName, candidate);
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
if (candidate.depth > existing.depth)
|
|
309
|
+
return;
|
|
310
|
+
target.set(methodName, { overloads: [], depth: candidate.depth, ambiguous: true });
|
|
311
|
+
}
|
|
312
|
+
function mergeMethodSet(target, source) {
|
|
313
|
+
for (const [name, overloads] of source) {
|
|
314
|
+
const existing = target.get(name) ?? [];
|
|
315
|
+
existing.push(...overloads);
|
|
316
|
+
target.set(name, existing);
|
|
63
317
|
}
|
|
64
|
-
return impls;
|
|
65
318
|
}
|
|
66
|
-
function
|
|
67
|
-
for (const
|
|
68
|
-
|
|
319
|
+
function methodSetSatisfies(actual, required, signatureContextByDefId) {
|
|
320
|
+
for (const [name, requiredOverloads] of required) {
|
|
321
|
+
const actualOverloads = actual.get(name);
|
|
322
|
+
if (actualOverloads === undefined)
|
|
69
323
|
return false;
|
|
324
|
+
for (const requiredMethod of requiredOverloads) {
|
|
325
|
+
if (!hasCompatibleMethod(actualOverloads, requiredMethod, signatureContextByDefId)) {
|
|
326
|
+
return false;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
70
329
|
}
|
|
71
330
|
return true;
|
|
72
331
|
}
|
|
332
|
+
function hasCompatibleMethod(actualOverloads, requiredMethod, signatureContextByDefId) {
|
|
333
|
+
if (!hasVerifiableSignature(requiredMethod))
|
|
334
|
+
return false;
|
|
335
|
+
return actualOverloads.some((actualMethod) => signaturesCompatible(actualMethod, requiredMethod, signatureContextByDefId));
|
|
336
|
+
}
|
|
337
|
+
function methodSetHasVerifiableSignatures(methods) {
|
|
338
|
+
for (const overloads of methods.values()) {
|
|
339
|
+
if (!overloads.some(hasVerifiableSignature))
|
|
340
|
+
return false;
|
|
341
|
+
}
|
|
342
|
+
return true;
|
|
343
|
+
}
|
|
344
|
+
function isPointerReceiverMethod(def) {
|
|
345
|
+
return def.goReceiverKind === 'pointer';
|
|
346
|
+
}
|
|
347
|
+
function hasVerifiableSignature(def) {
|
|
348
|
+
return (def.parameterCount !== undefined ||
|
|
349
|
+
def.requiredParameterCount !== undefined ||
|
|
350
|
+
(def.parameterTypes !== undefined && def.parameterTypes.length > 0) ||
|
|
351
|
+
def.returnType !== undefined);
|
|
352
|
+
}
|
|
353
|
+
function signaturesCompatible(actual, required, signatureContextByDefId) {
|
|
354
|
+
const actualContext = signatureContextByDefId.get(actual.nodeId);
|
|
355
|
+
const requiredContext = signatureContextByDefId.get(required.nodeId);
|
|
356
|
+
return (countsCompatible(actual.parameterCount, required.parameterCount) &&
|
|
357
|
+
countsCompatible(actual.requiredParameterCount, required.requiredParameterCount) &&
|
|
358
|
+
parameterTypesCompatible(actual.parameterTypes, required.parameterTypes, actualContext, requiredContext) &&
|
|
359
|
+
returnTypesCompatible(actual.returnType, required.returnType, actualContext, requiredContext));
|
|
360
|
+
}
|
|
361
|
+
function countsCompatible(actual, required) {
|
|
362
|
+
return actual === undefined || required === undefined || actual === required;
|
|
363
|
+
}
|
|
364
|
+
function parameterTypesCompatible(actual, required, actualContext, requiredContext) {
|
|
365
|
+
if (actual === undefined || required === undefined)
|
|
366
|
+
return true;
|
|
367
|
+
if (actual.length !== required.length)
|
|
368
|
+
return false;
|
|
369
|
+
return actual.every((type, index) => {
|
|
370
|
+
const actualType = normalizeSignatureType(type, actualContext);
|
|
371
|
+
const requiredType = normalizeSignatureType(required[index], requiredContext);
|
|
372
|
+
return actualType !== undefined && requiredType !== undefined && actualType === requiredType;
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
function returnTypesCompatible(actual, required, actualContext, requiredContext) {
|
|
376
|
+
if (required === undefined)
|
|
377
|
+
return actual === undefined;
|
|
378
|
+
if (actual === undefined)
|
|
379
|
+
return false;
|
|
380
|
+
const actualType = normalizeSignatureType(actual, actualContext);
|
|
381
|
+
const requiredType = normalizeSignatureType(required, requiredContext);
|
|
382
|
+
return actualType !== undefined && requiredType !== undefined && actualType === requiredType;
|
|
383
|
+
}
|
|
384
|
+
function normalizeSignatureType(typeName, context) {
|
|
385
|
+
// Go type identity includes pointer/slice/map/variadic shape and package
|
|
386
|
+
// qualifiers. Only erase whitespace and qualify bare local type names; stripping
|
|
387
|
+
// `*`, `[]`, `...`, or `pkg.` would make non-identical signatures compare equal.
|
|
388
|
+
const compact = typeName.replace(/\s+/g, '');
|
|
389
|
+
if (context === undefined)
|
|
390
|
+
return compact;
|
|
391
|
+
return qualifyGoSignatureTypes(compact, context);
|
|
392
|
+
}
|
|
393
|
+
function qualifyGoSignatureTypes(typeName, context) {
|
|
394
|
+
let unresolvedQualifier = false;
|
|
395
|
+
const qualified = typeName.replace(/[A-Za-z_][A-Za-z0-9_]*/g, (token, offset, source) => {
|
|
396
|
+
if (GO_BUILTIN_TYPES.has(token))
|
|
397
|
+
return token;
|
|
398
|
+
if (hasPackageQualifierDot(source, offset))
|
|
399
|
+
return token;
|
|
400
|
+
if (source[offset + token.length] === '.') {
|
|
401
|
+
const qualifier = context.importQualifiers.get(token);
|
|
402
|
+
if (qualifier !== undefined)
|
|
403
|
+
return qualifier;
|
|
404
|
+
unresolvedQualifier = true;
|
|
405
|
+
return token;
|
|
406
|
+
}
|
|
407
|
+
if (context.packageQualifier === undefined)
|
|
408
|
+
return token;
|
|
409
|
+
return `${context.packageQualifier}.${token}`;
|
|
410
|
+
});
|
|
411
|
+
return unresolvedQualifier ? undefined : qualified;
|
|
412
|
+
}
|
|
413
|
+
function hasPackageQualifierDot(source, offset) {
|
|
414
|
+
return source[offset - 1] === '.' && source[offset - 2] !== '.';
|
|
415
|
+
}
|
|
416
|
+
function signatureContextForFile(parsed, indexes) {
|
|
417
|
+
const importQualifiers = new Map();
|
|
418
|
+
const importEdges = indexes.imports?.get(parsed.moduleScope) ?? [];
|
|
419
|
+
for (const edge of importEdges) {
|
|
420
|
+
if (edge.kind !== 'namespace' || edge.targetFile === null)
|
|
421
|
+
continue;
|
|
422
|
+
const qualifier = packageQualifierForFile(edge.targetFile);
|
|
423
|
+
if (qualifier !== undefined)
|
|
424
|
+
importQualifiers.set(edge.localName, qualifier);
|
|
425
|
+
}
|
|
426
|
+
return {
|
|
427
|
+
packageQualifier: packageQualifierForFile(parsed.filePath),
|
|
428
|
+
importQualifiers,
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
function packageQualifierForFile(filePath) {
|
|
432
|
+
const normalized = filePath.replace(/\\/g, '/');
|
|
433
|
+
const slash = normalized.lastIndexOf('/');
|
|
434
|
+
if (slash === -1)
|
|
435
|
+
return undefined;
|
|
436
|
+
const packageDir = normalized.slice(0, slash);
|
|
437
|
+
return packageDir.length === 0 ? undefined : packageDir;
|
|
438
|
+
}
|
|
439
|
+
const GO_BUILTIN_TYPES = new Set([
|
|
440
|
+
'any',
|
|
441
|
+
'bool',
|
|
442
|
+
'byte',
|
|
443
|
+
'comparable',
|
|
444
|
+
'complex64',
|
|
445
|
+
'complex128',
|
|
446
|
+
'error',
|
|
447
|
+
'float32',
|
|
448
|
+
'float64',
|
|
449
|
+
'func',
|
|
450
|
+
'int',
|
|
451
|
+
'int8',
|
|
452
|
+
'int16',
|
|
453
|
+
'int32',
|
|
454
|
+
'int64',
|
|
455
|
+
'interface',
|
|
456
|
+
'map',
|
|
457
|
+
'rune',
|
|
458
|
+
'string',
|
|
459
|
+
'struct',
|
|
460
|
+
'uint',
|
|
461
|
+
'uint8',
|
|
462
|
+
'uint16',
|
|
463
|
+
'uint32',
|
|
464
|
+
'uint64',
|
|
465
|
+
'uintptr',
|
|
466
|
+
'chan',
|
|
467
|
+
]);
|
|
@@ -28,7 +28,10 @@ export function interpretGoTypeBinding(captures) {
|
|
|
28
28
|
let normalizedType;
|
|
29
29
|
if (captures['@type-binding.self'] !== undefined) {
|
|
30
30
|
source = 'self';
|
|
31
|
-
|
|
31
|
+
// Preserve pointer shape on receiver self-bindings (`*T` vs `T`).
|
|
32
|
+
// Method-owner enrichment consumes that raw shape to model Go value and
|
|
33
|
+
// pointer receiver method sets conservatively.
|
|
34
|
+
normalizedType = type.trim();
|
|
32
35
|
}
|
|
33
36
|
else if (captures['@type-binding.constructor'] !== undefined) {
|
|
34
37
|
source = 'constructor-inferred';
|
|
@@ -36,12 +36,15 @@ export function populateGoWorkspaceOwners(parsedFiles, ctx) {
|
|
|
36
36
|
function populateGoOwnersInPackage(parsedFiles) {
|
|
37
37
|
// Build struct name → def map from ALL scopes' ownedDefs (struct defs
|
|
38
38
|
// live in Class scopes now, not Module scope).
|
|
39
|
-
const structByQualifiedName = new Map();
|
|
39
|
+
const structByQualifiedName = new Map();
|
|
40
40
|
for (const parsed of parsedFiles) {
|
|
41
41
|
for (const scope of parsed.scopes) {
|
|
42
42
|
for (const def of scope.ownedDefs) {
|
|
43
43
|
if (isClassLike(def.type) && def.qualifiedName) {
|
|
44
|
-
structByQualifiedName.set(def.qualifiedName,
|
|
44
|
+
structByQualifiedName.set(def.qualifiedName, {
|
|
45
|
+
nodeId: def.nodeId,
|
|
46
|
+
qualifiedName: def.qualifiedName,
|
|
47
|
+
});
|
|
45
48
|
}
|
|
46
49
|
}
|
|
47
50
|
}
|
|
@@ -59,26 +62,35 @@ function populateGoOwnersInPackage(parsedFiles) {
|
|
|
59
62
|
continue;
|
|
60
63
|
// Find the self typeBinding in this Function scope.
|
|
61
64
|
let receiverType;
|
|
65
|
+
let receiverKind = 'value';
|
|
62
66
|
for (const [, tb] of scope.typeBindings) {
|
|
63
67
|
if (tb.source === 'self') {
|
|
64
68
|
receiverType = tb.rawName;
|
|
69
|
+
receiverKind = tb.rawName.trim().startsWith('*') ? 'pointer' : 'value';
|
|
65
70
|
break;
|
|
66
71
|
}
|
|
67
72
|
}
|
|
68
73
|
if (receiverType === undefined)
|
|
69
74
|
continue;
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
75
|
+
receiverType = receiverType.replace(/^\*+/, '').trim();
|
|
76
|
+
let owner = structByQualifiedName.get(receiverType);
|
|
77
|
+
if (owner === undefined) {
|
|
78
|
+
for (const [qname, candidate] of structByQualifiedName) {
|
|
73
79
|
if (qname.endsWith('.' + receiverType)) {
|
|
74
|
-
|
|
80
|
+
owner = candidate;
|
|
75
81
|
break;
|
|
76
82
|
}
|
|
77
83
|
}
|
|
78
84
|
}
|
|
79
|
-
if (
|
|
85
|
+
if (owner !== undefined) {
|
|
80
86
|
for (const def of methodDefs) {
|
|
81
|
-
def.ownerId =
|
|
87
|
+
def.ownerId = owner.nodeId;
|
|
88
|
+
def.goReceiverKind = receiverKind;
|
|
89
|
+
const simpleName = def.qualifiedName?.split('.').pop() ?? def.qualifiedName;
|
|
90
|
+
if (simpleName !== undefined && !def.qualifiedName?.includes('.')) {
|
|
91
|
+
def.qualifiedName =
|
|
92
|
+
`${owner.qualifiedName}.${simpleName}`;
|
|
93
|
+
}
|
|
82
94
|
}
|
|
83
95
|
}
|
|
84
96
|
}
|
|
@@ -38,6 +38,10 @@ const GO_SCOPE_QUERY = `
|
|
|
38
38
|
(method_declaration
|
|
39
39
|
name: (field_identifier) @declaration.name) @declaration.method
|
|
40
40
|
|
|
41
|
+
;; Declarations — interface methods
|
|
42
|
+
(method_elem
|
|
43
|
+
name: (field_identifier) @declaration.name) @declaration.method
|
|
44
|
+
|
|
41
45
|
;; Declarations — struct fields
|
|
42
46
|
(struct_type
|
|
43
47
|
(field_declaration_list
|
|
@@ -54,9 +54,10 @@ export function populateGoRangeBindings(parsedFiles, _indexes, ctx) {
|
|
|
54
54
|
continue;
|
|
55
55
|
// Resolve range expression type
|
|
56
56
|
let elementType = null;
|
|
57
|
+
const functionScope = findEnclosingFunctionScope(rangeNode, scopeMap);
|
|
57
58
|
if (rangeExpr.type === 'identifier') {
|
|
58
|
-
|
|
59
|
-
|
|
59
|
+
const binding = functionScope?.typeBindings.get(rangeExpr.text) ??
|
|
60
|
+
moduleScope.typeBindings.get(rangeExpr.text);
|
|
60
61
|
if (binding !== null && binding !== undefined) {
|
|
61
62
|
elementType = extractElementType(binding);
|
|
62
63
|
}
|
|
@@ -77,7 +78,6 @@ export function populateGoRangeBindings(parsedFiles, _indexes, ctx) {
|
|
|
77
78
|
}
|
|
78
79
|
if (elementType !== null && valueVar !== null) {
|
|
79
80
|
// Inject type binding for the range variable onto the enclosing function scope
|
|
80
|
-
const functionScope = findEnclosingFunctionScope(rangeNode, scopeMap);
|
|
81
81
|
const targetScope = functionScope ?? moduleScope;
|
|
82
82
|
const mutable = targetScope.typeBindings;
|
|
83
83
|
mutable.set(valueVar, {
|
|
@@ -111,7 +111,7 @@ function findEnclosingFunctionScope(node, scopeMap) {
|
|
|
111
111
|
// same line/column as the tree-sitter node.
|
|
112
112
|
for (const scope of scopeMap.values()) {
|
|
113
113
|
if (scope.kind === 'Function' &&
|
|
114
|
-
scope.range.startLine === current.startPosition.row &&
|
|
114
|
+
scope.range.startLine === current.startPosition.row + 1 &&
|
|
115
115
|
scope.range.startCol === current.startPosition.column) {
|
|
116
116
|
return scope;
|
|
117
117
|
}
|
|
@@ -12,7 +12,7 @@ export function synthesizeGoReceiverBinding(fnNode) {
|
|
|
12
12
|
const typeNode = param.childForFieldName('type');
|
|
13
13
|
if (nameNode === null || typeNode === null)
|
|
14
14
|
return null;
|
|
15
|
-
const typeName = typeNode.text
|
|
15
|
+
const typeName = typeNode.text;
|
|
16
16
|
return {
|
|
17
17
|
'@type-binding.self': syntheticCapture('@type-binding.self', fnNode, nameNode.text),
|
|
18
18
|
'@type-binding.name': syntheticCapture('@type-binding.name', nameNode, nameNode.text),
|
|
@@ -26,8 +26,8 @@ export const goScopeResolver = {
|
|
|
26
26
|
allowGlobalFreeCallFallback: true,
|
|
27
27
|
populateNamespaceSiblings: populateGoPackageSiblings,
|
|
28
28
|
mirrorNamespaceTypeBindings: mirrorGoNamespaceTypeBindings,
|
|
29
|
-
//
|
|
30
|
-
//
|
|
29
|
+
// Go has structural interfaces: implementations are inferred by signature,
|
|
30
|
+
// then fed into generic MRO/interface-dispatch.
|
|
31
31
|
detectInterfaceImplementations: detectGoInterfaceImplementations,
|
|
32
32
|
populateRangeBindings: populateGoRangeBindings,
|
|
33
33
|
};
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
export function goBindingScopeFor(decl, innermost, _tree) {
|
|
2
|
-
// Keep
|
|
3
|
-
// auto-hoist to Module)
|
|
4
|
-
//
|
|
5
|
-
|
|
2
|
+
// Keep receiver and parameter typeBindings in the function scope
|
|
3
|
+
// (prevent auto-hoist to Module). Parameters are local variables:
|
|
4
|
+
// hoisting `repo Repository` from one function to module scope can
|
|
5
|
+
// pollute another function's local receiver resolution.
|
|
6
|
+
if (decl['@type-binding.self'] !== undefined || decl['@type-binding.parameter'] !== undefined) {
|
|
6
7
|
return innermost.id;
|
|
7
8
|
}
|
|
8
9
|
return null; // default auto-hoist for other bindings
|
|
@@ -15,7 +16,11 @@ export function goReceiverBinding(functionScope) {
|
|
|
15
16
|
return null;
|
|
16
17
|
for (const binding of functionScope.typeBindings.values()) {
|
|
17
18
|
if (binding.source === 'self')
|
|
18
|
-
return binding;
|
|
19
|
+
return normalizeGoSelfTypeRef(binding);
|
|
19
20
|
}
|
|
20
21
|
return null;
|
|
21
22
|
}
|
|
23
|
+
function normalizeGoSelfTypeRef(binding) {
|
|
24
|
+
const rawName = binding.rawName.replace(/^\*+/, '').trim();
|
|
25
|
+
return rawName === binding.rawName ? binding : { ...binding, rawName };
|
|
26
|
+
}
|
|
@@ -1,6 +1,23 @@
|
|
|
1
1
|
import { syntheticCapture } from '../../utils/ast-helpers.js';
|
|
2
2
|
export function synthesizeGoTypeBindings(rootNode) {
|
|
3
3
|
const out = [];
|
|
4
|
+
for (const node of rootNode.descendantsOfType('var_spec')) {
|
|
5
|
+
const name = node.childForFieldName('name');
|
|
6
|
+
const value = node.childForFieldName('value');
|
|
7
|
+
if (name?.type !== 'identifier' || value === null)
|
|
8
|
+
continue;
|
|
9
|
+
const rhsExpr = value.namedChildren[0];
|
|
10
|
+
if (rhsExpr === undefined)
|
|
11
|
+
continue;
|
|
12
|
+
const typeNode = extractCompositeLiteralTypeNode(rhsExpr);
|
|
13
|
+
if (typeNode === null)
|
|
14
|
+
continue;
|
|
15
|
+
out.push({
|
|
16
|
+
'@type-binding.constructor': syntheticCapture('@type-binding.constructor', node, node.text),
|
|
17
|
+
'@type-binding.name': syntheticCapture('@type-binding.name', name, name.text),
|
|
18
|
+
'@type-binding.type': syntheticCapture('@type-binding.type', typeNode, extractSimpleTypeNameText(typeNode)),
|
|
19
|
+
});
|
|
20
|
+
}
|
|
4
21
|
for (const node of rootNode.descendantsOfType('short_var_declaration')) {
|
|
5
22
|
const right = node.childForFieldName('right');
|
|
6
23
|
if (right === null)
|
|
@@ -197,6 +214,18 @@ function extractSimpleTypeNameText(node) {
|
|
|
197
214
|
return node.text;
|
|
198
215
|
}
|
|
199
216
|
/** Extract the type/signature node from a RHS expression. */
|
|
217
|
+
function extractCompositeLiteralTypeNode(expr) {
|
|
218
|
+
if (expr.type === 'composite_literal') {
|
|
219
|
+
return (expr.childForFieldName('type') ??
|
|
220
|
+
expr.namedChildren.find((c) => ['type_identifier', 'qualified_type'].includes(c.type)) ??
|
|
221
|
+
null);
|
|
222
|
+
}
|
|
223
|
+
if (expr.type === 'unary_expression') {
|
|
224
|
+
const operand = expr.childForFieldName('operand');
|
|
225
|
+
return operand === null ? null : extractCompositeLiteralTypeNode(operand);
|
|
226
|
+
}
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
200
229
|
function extractTypeNode(expr) {
|
|
201
230
|
if (expr.type === 'composite_literal') {
|
|
202
231
|
return (expr.childForFieldName('type') ??
|
|
@@ -106,7 +106,7 @@ export function emitReceiverBoundCalls(graph, scopes, parsedFiles, nodeLookup, h
|
|
|
106
106
|
const graphIdToClassDef = new Map();
|
|
107
107
|
for (const parsed of parsedFiles) {
|
|
108
108
|
for (const def of parsed.localDefs) {
|
|
109
|
-
if (def.type !== 'Class' && def.type !== 'Interface')
|
|
109
|
+
if (def.type !== 'Class' && def.type !== 'Struct' && def.type !== 'Interface')
|
|
110
110
|
continue;
|
|
111
111
|
const graphId = resolveDefGraphId(parsed.filePath, def, nodeLookup);
|
|
112
112
|
if (graphId !== undefined)
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
*
|
|
23
23
|
* Plan: `docs/plans/2026-04-20-001-refactor-emit-pipeline-generalization-plan.md`.
|
|
24
24
|
*/
|
|
25
|
+
import { generateId } from '../../../../lib/utils.js';
|
|
25
26
|
import { lookupOwnedMembersByOwner } from '../../model/owned-members-lookup.js';
|
|
26
27
|
import { reconcileOwnership, validateOwnershipParity } from './reconcile-ownership.js';
|
|
27
28
|
import { validateBindingsImmutability } from './validate-bindings-immutability.js';
|
|
@@ -36,7 +37,7 @@ import { emitReceiverBoundCalls } from '../passes/receiver-bound-calls.js';
|
|
|
36
37
|
import { emitFreeCallFallback } from '../passes/free-call-fallback.js';
|
|
37
38
|
import { emitReferencesViaLookup } from '../graph-bridge/references-to-edges.js';
|
|
38
39
|
import { emitImportEdges } from '../graph-bridge/imports-to-edges.js';
|
|
39
|
-
import {
|
|
40
|
+
import { findEnclosingClassDef, resolveInheritanceBaseInScope } from '../scope/walkers.js';
|
|
40
41
|
import { buildWorkspaceResolutionIndex } from '../workspace-index.js';
|
|
41
42
|
import { logger } from '../../../logger.js';
|
|
42
43
|
/**
|
|
@@ -108,13 +109,7 @@ function preEmitInheritanceEdges(graph, scopes, nodeLookup) {
|
|
|
108
109
|
// suppression keeps `buildMro` and the final graph class-owned.
|
|
109
110
|
handledSites.add(siteKey);
|
|
110
111
|
}
|
|
111
|
-
const targetDef =
|
|
112
|
-
// Import-aware disambiguation fallback (#1951). Only engages when the
|
|
113
|
-
// scope-chain + single-match lookups above returned undefined because
|
|
114
|
-
// the simple name is ambiguous (multiple same-named class-like defs).
|
|
115
|
-
// Picks the candidate whose defining file is imported/included by the
|
|
116
|
-
// referencing file. Never changes behavior for single-match cases.
|
|
117
|
-
resolveAmbiguousInheritanceBaseViaImports(site.inScope, site.name, scopes);
|
|
112
|
+
const targetDef = resolveInheritanceBaseInScope(site.inScope, site.name, scopes);
|
|
118
113
|
if (targetDef === undefined)
|
|
119
114
|
continue;
|
|
120
115
|
const callerClass = findEnclosingClassDef(site.inScope, scopes);
|
|
@@ -138,6 +133,62 @@ function preEmitInheritanceEdges(graph, scopes, nodeLookup) {
|
|
|
138
133
|
}
|
|
139
134
|
return handledSites;
|
|
140
135
|
}
|
|
136
|
+
/**
|
|
137
|
+
* Emit language-inferred structural interface implementations before MRO and
|
|
138
|
+
* interface dispatch are built. Languages such as Go do not declare
|
|
139
|
+
* `implements` explicitly, so their resolver can infer defId-level interface
|
|
140
|
+
* satisfaction from parsed files and this bridge converts those defIds to
|
|
141
|
+
* graph node ids.
|
|
142
|
+
*
|
|
143
|
+
* Existing explicit IMPLEMENTS edges win: the local `existing` set prevents
|
|
144
|
+
* duplicate structural edges and keeps this hook language-neutral. The reason
|
|
145
|
+
* string carries the provider language (`go-structural-implements`) so callers
|
|
146
|
+
* can distinguish inferred edges from source-declared heritage.
|
|
147
|
+
*/
|
|
148
|
+
function emitDetectedInterfaceImplementations(graph, parsedFiles, nodeLookup, provider, indexes, model) {
|
|
149
|
+
if (provider.detectInterfaceImplementations === undefined)
|
|
150
|
+
return 0;
|
|
151
|
+
const graphIdByDefId = new Map();
|
|
152
|
+
for (const parsed of parsedFiles) {
|
|
153
|
+
for (const def of parsed.localDefs) {
|
|
154
|
+
if (def.type !== 'Class' && def.type !== 'Struct' && def.type !== 'Interface')
|
|
155
|
+
continue;
|
|
156
|
+
const graphId = resolveDefGraphId(parsed.filePath, def, nodeLookup);
|
|
157
|
+
if (graphId !== undefined)
|
|
158
|
+
graphIdByDefId.set(def.nodeId, graphId);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
const existing = new Set();
|
|
162
|
+
for (const rel of graph.iterRelationshipsByType('IMPLEMENTS')) {
|
|
163
|
+
existing.add(`${rel.sourceId}->${rel.targetId}`);
|
|
164
|
+
}
|
|
165
|
+
let emitted = 0;
|
|
166
|
+
const detected = provider.detectInterfaceImplementations(parsedFiles, indexes, model);
|
|
167
|
+
for (const [interfaceDefId, implementorDefIds] of detected) {
|
|
168
|
+
const targetId = graphIdByDefId.get(interfaceDefId);
|
|
169
|
+
if (targetId === undefined)
|
|
170
|
+
continue;
|
|
171
|
+
for (const implementorDefId of implementorDefIds) {
|
|
172
|
+
const sourceId = graphIdByDefId.get(implementorDefId);
|
|
173
|
+
if (sourceId === undefined)
|
|
174
|
+
continue;
|
|
175
|
+
const edgeKey = `${sourceId}->${targetId}`;
|
|
176
|
+
if (existing.has(edgeKey))
|
|
177
|
+
continue;
|
|
178
|
+
existing.add(edgeKey);
|
|
179
|
+
graph.addRelationship({
|
|
180
|
+
id: generateId('IMPLEMENTS', edgeKey),
|
|
181
|
+
sourceId,
|
|
182
|
+
targetId,
|
|
183
|
+
type: 'IMPLEMENTS',
|
|
184
|
+
confidence: 0.85,
|
|
185
|
+
reason: `${provider.language}-structural-implements`,
|
|
186
|
+
});
|
|
187
|
+
emitted++;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return emitted;
|
|
191
|
+
}
|
|
141
192
|
export function runScopeResolution(input, provider) {
|
|
142
193
|
const { graph, files } = input;
|
|
143
194
|
const onWarn = input.onWarn ?? (() => { });
|
|
@@ -251,6 +302,7 @@ export function runScopeResolution(input, provider) {
|
|
|
251
302
|
// `nodeLookup`). Without this rebuild, Property nodes added by the
|
|
252
303
|
// heritage hook are invisible and ACCESSES edges silently fail to emit.
|
|
253
304
|
const postHeritageNodeLookup = provider.emitHeritageEdges !== undefined ? buildGraphNodeLookup(graph) : nodeLookup;
|
|
305
|
+
emitDetectedInterfaceImplementations(graph, parsedFiles, postHeritageNodeLookup, provider, finalized, readonlyModel);
|
|
254
306
|
const mroByClassDefId = provider.buildMro(graph, parsedFiles, postHeritageNodeLookup);
|
|
255
307
|
const extendsOnlyMroByClassDefId = provider.buildExtendsOnlyMro?.(graph, parsedFiles, postHeritageNodeLookup);
|
|
256
308
|
// Replace the empty MethodDispatchIndex that finalizeScopeModel
|
|
@@ -114,6 +114,12 @@ export declare function moduleScopeIdOf(startScope: ScopeId, scopes: ScopeResolu
|
|
|
114
114
|
* Without (2) we'd miss every cross-file class-receiver call.
|
|
115
115
|
*/
|
|
116
116
|
export declare function findClassBindingInScope(startScope: ScopeId, receiverName: string, scopes: ScopeResolutionIndexes): SymbolDefinition | undefined;
|
|
117
|
+
/**
|
|
118
|
+
* Resolve a class-like inheritance target using the shared inheritance
|
|
119
|
+
* resolution chain. Keeps pre-emitted heritage edges and language-specific
|
|
120
|
+
* consumers of `inherits` sites aligned.
|
|
121
|
+
*/
|
|
122
|
+
export declare function resolveInheritanceBaseInScope(startScope: ScopeId, baseName: string, scopes: ScopeResolutionIndexes): SymbolDefinition | undefined;
|
|
117
123
|
/**
|
|
118
124
|
* Import/include-aware disambiguation for an *ambiguous* class-like base
|
|
119
125
|
* name. Engages ONLY as a fallback after `findClassBindingInScope` has
|
|
@@ -286,6 +286,15 @@ export function findClassBindingInScope(startScope, receiverName, scopes) {
|
|
|
286
286
|
}
|
|
287
287
|
return undefined;
|
|
288
288
|
}
|
|
289
|
+
/**
|
|
290
|
+
* Resolve a class-like inheritance target using the shared inheritance
|
|
291
|
+
* resolution chain. Keeps pre-emitted heritage edges and language-specific
|
|
292
|
+
* consumers of `inherits` sites aligned.
|
|
293
|
+
*/
|
|
294
|
+
export function resolveInheritanceBaseInScope(startScope, baseName, scopes) {
|
|
295
|
+
return (findClassBindingInScope(startScope, baseName, scopes) ??
|
|
296
|
+
resolveAmbiguousInheritanceBaseViaImports(startScope, baseName, scopes));
|
|
297
|
+
}
|
|
289
298
|
/**
|
|
290
299
|
* Import/include-aware disambiguation for an *ambiguous* class-like base
|
|
291
300
|
* name. Engages ONLY as a fallback after `findClassBindingInScope` has
|
|
@@ -891,6 +891,7 @@ export const GO_QUERIES = `
|
|
|
891
891
|
; Functions & Methods
|
|
892
892
|
(function_declaration name: (identifier) @name) @definition.function
|
|
893
893
|
(method_declaration name: (field_identifier) @name) @definition.method
|
|
894
|
+
(method_elem name: (field_identifier) @name) @definition.method
|
|
894
895
|
|
|
895
896
|
; Types
|
|
896
897
|
(type_declaration (type_spec name: (type_identifier) @name type: (struct_type))) @definition.struct
|
|
@@ -148,6 +148,9 @@ export const CLASS_CONTAINER_TYPES = new Set([
|
|
|
148
148
|
// Kotlin
|
|
149
149
|
'object_declaration',
|
|
150
150
|
'companion_object',
|
|
151
|
+
// Go
|
|
152
|
+
'struct_type',
|
|
153
|
+
'interface_type',
|
|
151
154
|
]);
|
|
152
155
|
export const CONTAINER_TYPE_TO_LABEL = {
|
|
153
156
|
class_declaration: 'Class',
|
|
@@ -179,6 +182,8 @@ export const CONTAINER_TYPE_TO_LABEL = {
|
|
|
179
182
|
singleton_class: 'Class', // Ruby: class << self inherits enclosing class name
|
|
180
183
|
object_declaration: 'Class',
|
|
181
184
|
companion_object: 'Class',
|
|
185
|
+
struct_type: 'Struct',
|
|
186
|
+
interface_type: 'Interface',
|
|
182
187
|
};
|
|
183
188
|
/**
|
|
184
189
|
* Pre-order walk over a node and all its named descendants, invoking `cb` on
|
package/package.json
CHANGED