gitnexus 1.6.6-rc.120 → 1.6.6-rc.121

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.
@@ -4,7 +4,7 @@ import { recordGoCacheHit, recordGoCacheMiss } from './cache-stats.js';
4
4
  import { computeGoCallArity, computeGoDeclarationArity } from './arity-metadata.js';
5
5
  import { splitGoImportStatement } from './import-decomposer.js';
6
6
  import { synthesizeGoReceiverBinding } from './receiver-binding.js';
7
- import { synthesizeGoTypeBindings } from './type-binding.js';
7
+ import { synthesizeGoTypeBindings, extractSimpleTypeNameText } from './type-binding.js';
8
8
  import { getTreeSitterBufferSize } from '../../constants.js';
9
9
  import { parseSourceSafe } from '../../../tree-sitter/safe-parse.js';
10
10
  export function emitGoScopeCaptures(sourceText, _filePath, cachedTree) {
@@ -69,6 +69,7 @@ export function emitGoScopeCaptures(sourceText, _filePath, cachedTree) {
69
69
  }
70
70
  if (isRawMultiAssignTypeBinding(nodeMap))
71
71
  continue;
72
+ normalizeGenericConstructorCapture(nodeMap, grouped);
72
73
  const declAnchorNode = nodeMap['@declaration.function'] ?? nodeMap['@declaration.method'];
73
74
  if (declAnchorNode !== undefined) {
74
75
  // @declaration.function / @declaration.method are captured directly on
@@ -306,6 +307,22 @@ function nodeRangeEquals(a, b) {
306
307
  a.endPosition.row === b.endPosition.row &&
307
308
  a.endPosition.column === b.endPosition.column);
308
309
  }
310
+ function normalizeGenericConstructorCapture(nodeMap, grouped) {
311
+ const typeNode = grouped['@type-binding.constructor'] !== undefined ? nodeMap['@type-binding.type'] : undefined;
312
+ if (typeNode !== undefined && typeNode.type === 'generic_type') {
313
+ const base = typeNode.childForFieldName('type');
314
+ if (base !== null) {
315
+ grouped['@type-binding.type'] = syntheticCapture('@type-binding.type', base, extractSimpleTypeNameText(base));
316
+ }
317
+ }
318
+ const referenceNode = grouped['@reference.call.constructor'] !== undefined ? nodeMap['@reference.name'] : undefined;
319
+ if (referenceNode !== undefined && referenceNode.type === 'generic_type') {
320
+ const base = referenceNode.childForFieldName('type');
321
+ if (base !== null) {
322
+ grouped['@reference.name'] = syntheticCapture('@reference.name', base, extractSimpleTypeNameText(base));
323
+ }
324
+ }
325
+ }
309
326
  function isRawMultiAssignTypeBinding(nodeMap) {
310
327
  const anchor = nodeMap['@type-binding.constructor'] ??
311
328
  nodeMap['@type-binding.call-return'] ??
@@ -203,8 +203,13 @@ function collectStructMethodEntries(structId, indexes, visiting, cache) {
203
203
  }
204
204
  function collectInterfaceMethodSet(iface, indexes, visiting, cache) {
205
205
  const cached = cache.get(iface.nodeId);
206
+ // NOTE: Returns a direct reference to the cached map. Callers (the detection
207
+ // loop in detectGoInterfaceImplementationsFromIndexes) only READ the result
208
+ // (keys/values passed to candidateStructIdsFor and methodSetSatisfies). If a
209
+ // future caller needs to mutate the returned map, it must clone first — the
210
+ // cache entry is shared and must remain immutable after this function returns.
206
211
  if (cached !== undefined)
207
- return cloneMethodSet(cached);
212
+ return cached;
208
213
  if (visiting.has(iface.nodeId))
209
214
  return undefined;
210
215
  visiting.add(iface.nodeId);
@@ -224,6 +229,8 @@ function collectInterfaceMethodSet(iface, indexes, visiting, cache) {
224
229
  mergeMethodSet(merged, embeddedMethods);
225
230
  }
226
231
  visiting.delete(iface.nodeId);
232
+ // Store a clone in the cache so the returned `merged` reference is independent.
233
+ // This protects the cache from mutation if a caller modifies the return value.
227
234
  cache.set(iface.nodeId, cloneMethodSet(merged));
228
235
  return merged;
229
236
  }
@@ -238,15 +245,31 @@ function embeddedInterfacesFor(iface, indexes) {
238
245
  return embedded;
239
246
  }
240
247
  function candidateStructIdsFor(required, indexes) {
241
- let best;
248
+ let result;
242
249
  for (const name of required.keys()) {
243
250
  const candidates = indexes.structIdsByMethodName.get(name);
244
251
  if (candidates === undefined)
245
252
  return [];
246
- if (best === undefined || candidates.size < best.size)
247
- best = candidates;
253
+ if (result === undefined) {
254
+ // First method name: start with its full candidate set (copy to avoid
255
+ // corrupting the shared index).
256
+ result = new Set(candidates);
257
+ }
258
+ else {
259
+ // Intersect: keep only struct IDs present in this method's candidate set.
260
+ // Iterate a snapshot to avoid mutating while iterating.
261
+ const toDelete = [];
262
+ for (const id of result) {
263
+ if (!candidates.has(id))
264
+ toDelete.push(id);
265
+ }
266
+ for (const id of toDelete)
267
+ result.delete(id);
268
+ }
269
+ if (result.size === 0)
270
+ return []; // Early exit: no struct has all required methods
248
271
  }
249
- return best === undefined ? [...indexes.structsById.keys()] : [...best];
272
+ return result === undefined ? indexes.structsById.keys() : result;
250
273
  }
251
274
  function resolveEmbeddedInterface(site, indexes) {
252
275
  const bound = resolveInheritanceBaseInScope(site.inScope, site.name, indexes.scopeIndexes);
@@ -322,6 +345,14 @@ function methodSetSatisfies(actual, required, signatureContextByDefId) {
322
345
  if (actualOverloads === undefined)
323
346
  return false;
324
347
  for (const requiredMethod of requiredOverloads) {
348
+ // Fast arity pre-filter: if the required method has a known parameter
349
+ // count, reject immediately when no actual overload matches it. This
350
+ // avoids the expensive signature normalization loop for obvious mismatches.
351
+ if (requiredMethod.parameterCount !== undefined) {
352
+ if (!actualOverloads.some((a) => a.parameterCount === requiredMethod.parameterCount)) {
353
+ return false;
354
+ }
355
+ }
325
356
  if (!hasCompatibleMethod(actualOverloads, requiredMethod, signatureContextByDefId)) {
326
357
  return false;
327
358
  }
@@ -84,7 +84,7 @@ const GO_SCOPE_QUERY = `
84
84
  left: (expression_list (identifier) @type-binding.name)
85
85
  right: (expression_list
86
86
  (composite_literal
87
- type: [(type_identifier) (qualified_type)] @type-binding.type))) @type-binding.constructor
87
+ type: [(type_identifier) (qualified_type) (generic_type)] @type-binding.type))) @type-binding.constructor
88
88
 
89
89
  ;; Type bindings — pointer constructor (:= &T{})
90
90
  (short_var_declaration
@@ -93,7 +93,7 @@ const GO_SCOPE_QUERY = `
93
93
  (unary_expression
94
94
  "&"
95
95
  operand: (composite_literal
96
- type: [(type_identifier) (qualified_type)] @type-binding.type)))) @type-binding.constructor
96
+ type: [(type_identifier) (qualified_type) (generic_type)] @type-binding.type)))) @type-binding.constructor
97
97
 
98
98
  ;; Type bindings — type assertion (:= s.(T))
99
99
  (short_var_declaration
@@ -168,7 +168,7 @@ const GO_SCOPE_QUERY = `
168
168
 
169
169
  ;; References — constructor calls (T{})
170
170
  (composite_literal
171
- type: [(type_identifier) (qualified_type)] @reference.name) @reference.call.constructor
171
+ type: [(type_identifier) (qualified_type) (generic_type)] @reference.name) @reference.call.constructor
172
172
 
173
173
  ;; References — field reads
174
174
  (selector_expression
@@ -1,3 +1,4 @@
1
1
  import type { CaptureMatch } from '../../../../_shared/index.js';
2
2
  import { type SyntaxNode } from '../../utils/ast-helpers.js';
3
3
  export declare function synthesizeGoTypeBindings(rootNode: SyntaxNode): CaptureMatch[];
4
+ export declare function extractSimpleTypeNameText(node: SyntaxNode): string;
@@ -1,4 +1,9 @@
1
1
  import { syntheticCapture } from '../../utils/ast-helpers.js';
2
+ const COMPOSITE_LITERAL_TYPE_NODE_TYPES = new Set([
3
+ 'type_identifier',
4
+ 'qualified_type',
5
+ 'generic_type',
6
+ ]);
2
7
  export function synthesizeGoTypeBindings(rootNode) {
3
8
  const out = [];
4
9
  for (const node of rootNode.descendantsOfType('var_spec')) {
@@ -206,18 +211,22 @@ function synthesizeElementAccessBindings(rootNode, out) {
206
211
  }
207
212
  }
208
213
  }
209
- function extractSimpleTypeNameText(node) {
214
+ export function extractSimpleTypeNameText(node) {
210
215
  if (node.type === 'qualified_type') {
211
216
  const parts = node.text.split('.');
212
217
  return parts[parts.length - 1] ?? node.text;
213
218
  }
219
+ if (node.type === 'generic_type') {
220
+ const base = node.childForFieldName('type');
221
+ return base === null ? node.text : extractSimpleTypeNameText(base);
222
+ }
214
223
  return node.text;
215
224
  }
216
225
  /** Extract the type/signature node from a RHS expression. */
217
226
  function extractCompositeLiteralTypeNode(expr) {
218
227
  if (expr.type === 'composite_literal') {
219
228
  return (expr.childForFieldName('type') ??
220
- expr.namedChildren.find((c) => ['type_identifier', 'qualified_type'].includes(c.type)) ??
229
+ expr.namedChildren.find((c) => COMPOSITE_LITERAL_TYPE_NODE_TYPES.has(c.type)) ??
221
230
  null);
222
231
  }
223
232
  if (expr.type === 'unary_expression') {
@@ -229,7 +238,7 @@ function extractCompositeLiteralTypeNode(expr) {
229
238
  function extractTypeNode(expr) {
230
239
  if (expr.type === 'composite_literal') {
231
240
  return (expr.childForFieldName('type') ??
232
- expr.namedChildren.find((c) => ['type_identifier', 'qualified_type'].includes(c.type)) ??
241
+ expr.namedChildren.find((c) => COMPOSITE_LITERAL_TYPE_NODE_TYPES.has(c.type)) ??
233
242
  null);
234
243
  }
235
244
  if (expr.type === 'unary_expression') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitnexus",
3
- "version": "1.6.6-rc.120",
3
+ "version": "1.6.6-rc.121",
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",