ucn 5.5.1 → 5.5.3

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.
Files changed (97) hide show
  1. package/.claude/skills/ucn/SKILL.md +35 -9
  2. package/.claude/skills/ucn/references/commands.md +40 -18
  3. package/.claude/skills/ucn/references/resolution-rules.md +126 -20
  4. package/.claude/skills/ucn/references/trust-contract.md +5 -3
  5. package/README.md +32 -21
  6. package/cli/index.js +2 -1
  7. package/core/accessors.js +341 -54
  8. package/core/account.js +74 -20
  9. package/core/analysis.js +257 -125
  10. package/core/ast-analysis.js +12 -3
  11. package/core/bridge.js +15 -65
  12. package/core/brief.js +8 -4
  13. package/core/build-worker.js +2 -1
  14. package/core/cache.js +137 -6
  15. package/core/call-name-cache.js +134 -0
  16. package/core/callable-scope.js +21 -0
  17. package/core/callers.js +4726 -940
  18. package/core/check.js +22 -5
  19. package/core/class-identity.js +399 -12
  20. package/core/client-receivers.js +189 -0
  21. package/core/constructor-calls.js +831 -0
  22. package/core/constructor-hierarchy.js +147 -0
  23. package/core/constructor-protocol.js +196 -0
  24. package/core/contract-membership.js +346 -140
  25. package/core/cpp-conversion-constraints.js +123 -0
  26. package/core/cpp-conversion-syntax.js +216 -0
  27. package/core/cpp-conversions.js +414 -0
  28. package/core/cpp-scope.js +245 -44
  29. package/core/deadcode.js +61 -16
  30. package/core/decorator-binding.js +137 -3
  31. package/core/enum-reference-syntax.js +134 -0
  32. package/core/execute.js +17 -1
  33. package/core/external-macros.js +42 -10
  34. package/core/go-packages.js +171 -0
  35. package/core/graph-build.js +2 -0
  36. package/core/graph.js +75 -59
  37. package/core/ignore-rules.js +2 -1
  38. package/core/imports.js +145 -19
  39. package/core/index-ir.js +41 -4
  40. package/core/inprocess-clients.js +41 -15
  41. package/core/ir.js +27 -6
  42. package/core/macro-expansion.js +38 -2
  43. package/core/out-of-class-members.js +182 -0
  44. package/core/output/analysis-ext.js +4 -2
  45. package/core/output/analysis.js +89 -20
  46. package/core/output/graph.js +12 -3
  47. package/core/output/lines.js +4 -3
  48. package/core/output/public.js +2 -0
  49. package/core/output/reporting.js +12 -1
  50. package/core/output/shared.js +2 -0
  51. package/core/output/tracing.js +28 -22
  52. package/core/output-budget.js +2 -2
  53. package/core/parallel-build.js +52 -1
  54. package/core/project.js +217 -112
  55. package/core/provenance-constructor-base.js +161 -0
  56. package/core/provenance-facts.js +257 -27
  57. package/core/provenance-overload.js +458 -54
  58. package/core/provenance-type.js +487 -0
  59. package/core/provenance.js +234 -15
  60. package/core/public-command.js +17 -2
  61. package/core/query-bodies.js +159 -0
  62. package/core/reflection.js +270 -2
  63. package/core/rename-token-syntax.js +111 -0
  64. package/core/reporting.js +13 -7
  65. package/core/request-helpers.js +63 -7
  66. package/core/rust-macro-expansion.js +151 -19
  67. package/core/rust-modules.js +1 -1
  68. package/core/rust-names.js +607 -0
  69. package/core/search.js +70 -17
  70. package/core/shared.js +98 -1
  71. package/core/source-memo.js +201 -0
  72. package/core/text-usage-syntax.js +38 -0
  73. package/core/tracing.js +7 -1
  74. package/core/type-denotation.js +124 -37
  75. package/core/type-references.js +615 -246
  76. package/core/value-references.js +2777 -0
  77. package/core/verify.js +1097 -281
  78. package/languages/adapter.js +28 -4
  79. package/languages/c-family.js +1118 -164
  80. package/languages/constructor-bases.js +105 -0
  81. package/languages/cpp-platform.js +26 -0
  82. package/languages/cpp-site-syntax.js +83 -0
  83. package/languages/csharp-scope.js +439 -0
  84. package/languages/csharp.js +428 -68
  85. package/languages/go.js +385 -207
  86. package/languages/html.js +4 -2
  87. package/languages/index.js +397 -2
  88. package/languages/java-platform.js +226 -0
  89. package/languages/java-unicode.js +269 -0
  90. package/languages/java.js +461 -117
  91. package/languages/javascript.js +418 -66
  92. package/languages/lexical-scope.js +190 -23
  93. package/languages/python.js +404 -54
  94. package/languages/rust.js +510 -82
  95. package/languages/utils.js +67 -8
  96. package/mcp/server.js +8 -2
  97. package/package.json +3 -3
package/languages/go.js CHANGED
@@ -108,7 +108,7 @@ function _processFunction(node, functions, processedRanges, lines) {
108
108
  const child = node.namedChild(i);
109
109
  if (child.type === 'identifier') names.push(child);
110
110
  }
111
- const { startLine, endLine, indent } = nodeToLocation(node, lines);
111
+ const { startLine, endLine, indent, sourceSpan } = nodeToLocation(node, lines);
112
112
  for (const nameNode of names) {
113
113
  const isExported = /^[A-Z]/.test(nameNode.text);
114
114
  functions.push({
@@ -117,6 +117,7 @@ function _processFunction(node, functions, processedRanges, lines) {
117
117
  paramsStructured: parseStructuredParams(paramsNode, 'go'),
118
118
  startLine,
119
119
  endLine,
120
+ sourceSpan,
120
121
  indent,
121
122
  modifiers: isExported ? ['export'] : [],
122
123
  isFunctionVariable: true,
@@ -135,7 +136,7 @@ function _processFunction(node, functions, processedRanges, lines) {
135
136
  const paramsNode = node.childForFieldName('parameters');
136
137
 
137
138
  if (nameNode) {
138
- const { startLine, endLine, indent } = nodeToLocation(node, lines);
139
+ const { startLine, endLine, indent, sourceSpan } = nodeToLocation(node, lines);
139
140
  const returnType = extractReturnType(node);
140
141
  const returnedFunctionResult = extractReturnedFunctionResult(node);
141
142
  const docstring = extractGoDocstring(lines, startLine);
@@ -148,6 +149,7 @@ function _processFunction(node, functions, processedRanges, lines) {
148
149
  paramsStructured: parseStructuredParams(paramsNode, 'go'),
149
150
  startLine,
150
151
  endLine,
152
+ sourceSpan,
151
153
  indent,
152
154
  modifiers: isExported ? ['export'] : [],
153
155
  ...(returnType && { returnType }),
@@ -169,7 +171,7 @@ function _processFunction(node, functions, processedRanges, lines) {
169
171
  const receiverNode = node.childForFieldName('receiver');
170
172
 
171
173
  if (nameNode) {
172
- const { startLine, endLine, indent } = nodeToLocation(node, lines);
174
+ const { startLine, endLine, indent, sourceSpan } = nodeToLocation(node, lines);
173
175
  const receiver = extractReceiver(receiverNode);
174
176
  const returnType = extractReturnType(node);
175
177
  const returnedFunctionResult = extractReturnedFunctionResult(node);
@@ -182,6 +184,7 @@ function _processFunction(node, functions, processedRanges, lines) {
182
184
  paramsStructured: parseStructuredParams(paramsNode, 'go'),
183
185
  startLine,
184
186
  endLine,
187
+ sourceSpan,
185
188
  indent,
186
189
  isMethod: true,
187
190
  receiver,
@@ -342,7 +345,7 @@ function _processClass(node, types, processedRanges, lines) {
342
345
  const typeNode = spec.childForFieldName('type');
343
346
 
344
347
  if (nameNode && typeNode) {
345
- const { startLine, endLine } = nodeToLocation(node, lines);
348
+ const { startLine, endLine, sourceSpan } = nodeToLocation(node, lines);
346
349
  const name = nameNode.text;
347
350
  const docstring = extractGoDocstring(lines, startLine);
348
351
  const typeParams = extractTypeParams(spec);
@@ -376,6 +379,7 @@ function _processClass(node, types, processedRanges, lines) {
376
379
  name,
377
380
  startLine,
378
381
  endLine,
382
+ sourceSpan,
379
383
  ...(nameLine !== startLine && { nameLine }),
380
384
  type: typeKind,
381
385
  members,
@@ -406,12 +410,13 @@ function _processClass(node, types, processedRanges, lines) {
406
410
  // type of another package (fix #378).
407
411
  const aliasQualifier = aliased?.type === 'qualified_type'
408
412
  ? aliased.childForFieldName('package')?.text : null;
409
- const { startLine, endLine } = nodeToLocation(node, lines);
413
+ const { startLine, endLine, sourceSpan } = nodeToLocation(node, lines);
410
414
  const nameLine = nameNode.startPosition.row + 1;
411
415
  types.push({
412
416
  name: nameNode.text,
413
417
  startLine,
414
418
  endLine,
419
+ sourceSpan,
415
420
  ...(nameLine !== startLine && { nameLine }),
416
421
  type: 'type',
417
422
  members: [],
@@ -467,6 +472,25 @@ function _blockHasIota(constDecl) {
467
472
  * Returns true if node was matched, false otherwise
468
473
  */
469
474
  function _processState(node, objects, lines) {
475
+ // Package variables and constants are bindings even when unexported.
476
+ // A grouped specification can declare several names with one value list.
477
+ if (['const_declaration', 'var_declaration'].includes(node.type) &&
478
+ node.parent?.type === 'source_file') {
479
+ const specs = node.namedChildren.flatMap(child => child.type === 'var_spec_list'
480
+ ? child.namedChildren : [child]);
481
+ for (const spec of specs) {
482
+ if (!['const_spec', 'var_spec'].includes(spec.type)) continue;
483
+ for (let i = 0; i < spec.childCount; i++) {
484
+ if (spec.fieldNameForChild(i) !== 'name') continue;
485
+ const name = spec.child(i);
486
+ if (name.type !== 'identifier' || name.text === '_') continue;
487
+ objects.push({ name: name.text, startLine: name.startPosition.row + 1,
488
+ endLine: spec.endPosition.row + 1,
489
+ ...(node.type === 'const_declaration' && { isConst: true }) });
490
+ }
491
+ }
492
+ return true;
493
+ }
470
494
  if (node.type === 'const_declaration') {
471
495
  const isIotaBlock = _blockHasIota(node);
472
496
  for (let i = 0; i < node.namedChildCount; i++) {
@@ -478,14 +502,14 @@ function _processState(node, objects, lines) {
478
502
  const name = nameNode.text;
479
503
 
480
504
  if (valueNode && _isCompositeLiteral(valueNode) && GO_STATE_PATTERN.test(name)) {
481
- const { startLine, endLine } = nodeToLocation(spec, lines);
482
- objects.push({ name, startLine, endLine });
505
+ const { startLine, endLine, sourceSpan } = nodeToLocation(spec, lines);
506
+ objects.push({ name, startLine, endLine, sourceSpan });
483
507
  } else if (isIotaBlock && /^[A-Z]/.test(name)) {
484
- const { startLine, endLine } = nodeToLocation(spec, lines);
485
- objects.push({ name, startLine, endLine, isConst: true });
508
+ const { startLine, endLine, sourceSpan } = nodeToLocation(spec, lines);
509
+ objects.push({ name, startLine, endLine, sourceSpan, isConst: true });
486
510
  } else if (_isGoExportedName(name)) {
487
- const { startLine, endLine } = nodeToLocation(spec, lines);
488
- objects.push({ name, startLine, endLine, isConst: true });
511
+ const { startLine, endLine, sourceSpan } = nodeToLocation(spec, lines);
512
+ objects.push({ name, startLine, endLine, sourceSpan, isConst: true });
489
513
  }
490
514
  }
491
515
  }
@@ -502,11 +526,11 @@ function _processState(node, objects, lines) {
502
526
  if (nameNode) {
503
527
  const name = nameNode.text;
504
528
  if (valueNode && _isCompositeLiteral(valueNode) && GO_STATE_PATTERN.test(name)) {
505
- const { startLine, endLine } = nodeToLocation(spec, lines);
506
- objects.push({ name, startLine, endLine });
529
+ const { startLine, endLine, sourceSpan } = nodeToLocation(spec, lines);
530
+ objects.push({ name, startLine, endLine, sourceSpan });
507
531
  } else if (_isGoExportedName(name)) {
508
- const { startLine, endLine } = nodeToLocation(spec, lines);
509
- objects.push({ name, startLine, endLine });
532
+ const { startLine, endLine, sourceSpan } = nodeToLocation(spec, lines);
533
+ objects.push({ name, startLine, endLine, sourceSpan });
510
534
  }
511
535
  }
512
536
  }
@@ -582,18 +606,22 @@ function extractStructFields(structNode, codeOrLines) {
582
606
  for (let i = 0; i < fieldListNode.namedChildCount; i++) {
583
607
  const field = fieldListNode.namedChild(i);
584
608
  if (field.type === 'field_declaration') {
585
- const { startLine, endLine } = nodeToLocation(field, codeOrLines);
586
- const nameNode = field.childForFieldName('name');
609
+ const { startLine, endLine, sourceSpan } = nodeToLocation(field, codeOrLines);
610
+ const nameNodes = field.childrenForFieldName('name');
587
611
  const typeNode = field.childForFieldName('type');
588
612
 
589
- if (nameNode) {
590
- fields.push({
591
- name: nameNode.text,
592
- startLine,
593
- endLine,
594
- memberType: 'field',
595
- ...(typeNode && { fieldType: typeNode.text })
596
- });
613
+ if (nameNodes.length > 0) {
614
+ // `Keys, Values []string` declares every name (fix #430).
615
+ for (const nameNode of nameNodes) {
616
+ fields.push({
617
+ name: nameNode.text,
618
+ startLine,
619
+ endLine,
620
+ sourceSpan,
621
+ memberType: 'field',
622
+ ...(typeNode && { fieldType: typeNode.text })
623
+ });
624
+ }
597
625
  } else if (typeNode) {
598
626
  // Embedded field: has type but no name (e.g., `Base` in `type Child struct { Base; Name string }`)
599
627
  // Use the type name as the field name
@@ -607,6 +635,7 @@ function extractStructFields(structNode, codeOrLines) {
607
635
  name: embeddedName,
608
636
  startLine,
609
637
  endLine,
638
+ sourceSpan,
610
639
  memberType: 'field',
611
640
  embedded: true,
612
641
  fieldType: typeNode.text
@@ -627,7 +656,7 @@ function extractInterfaceMembers(interfaceNode, codeOrLines) {
627
656
  const child = interfaceNode.namedChild(i);
628
657
  // tree-sitter Go uses method_elem (or method_spec in older versions)
629
658
  if (child.type === 'method_elem' || child.type === 'method_spec') {
630
- const { startLine, endLine } = nodeToLocation(child, codeOrLines);
659
+ const { startLine, endLine, sourceSpan } = nodeToLocation(child, codeOrLines);
631
660
  // Name is in a field_identifier child
632
661
  let nameText = null;
633
662
  let paramsText = null;
@@ -678,6 +707,7 @@ function extractInterfaceMembers(interfaceNode, codeOrLines) {
678
707
  name: nameText,
679
708
  startLine,
680
709
  endLine,
710
+ sourceSpan,
681
711
  memberType: 'field',
682
712
  embedded: true,
683
713
  fieldType: nameText
@@ -687,6 +717,7 @@ function extractInterfaceMembers(interfaceNode, codeOrLines) {
687
717
  name: nameText,
688
718
  startLine,
689
719
  endLine,
720
+ sourceSpan,
690
721
  memberType: 'method',
691
722
  ...(paramsText !== null && { params: paramsText }),
692
723
  ...(paramsNode && { paramsStructured: parseStructuredParams(paramsNode, 'go') }),
@@ -696,7 +727,7 @@ function extractInterfaceMembers(interfaceNode, codeOrLines) {
696
727
  }
697
728
  } else if (child.type === 'type_identifier' || child.type === 'qualified_type') {
698
729
  // Standalone type identifier inside interface body — embedded interface
699
- const { startLine, endLine } = nodeToLocation(child, codeOrLines);
730
+ const { startLine, endLine, sourceSpan } = nodeToLocation(child, codeOrLines);
700
731
  let embName = child.text;
701
732
  const dotIdx = embName.indexOf('.');
702
733
  if (dotIdx >= 0) embName = embName.slice(dotIdx + 1);
@@ -704,6 +735,7 @@ function extractInterfaceMembers(interfaceNode, codeOrLines) {
704
735
  name: embName,
705
736
  startLine,
706
737
  endLine,
738
+ sourceSpan,
707
739
  memberType: 'field',
708
740
  embedded: true,
709
741
  fieldType: child.text
@@ -714,7 +746,7 @@ function extractInterfaceMembers(interfaceNode, codeOrLines) {
714
746
  for (let j = 0; j < child.namedChildCount; j++) {
715
747
  const sub = child.namedChild(j);
716
748
  if (sub.type === 'type_identifier' || sub.type === 'qualified_type') {
717
- const { startLine, endLine } = nodeToLocation(sub, codeOrLines);
749
+ const { startLine, endLine, sourceSpan } = nodeToLocation(sub, codeOrLines);
718
750
  let embName = sub.text;
719
751
  const dotIdx = embName.indexOf('.');
720
752
  if (dotIdx >= 0) embName = embName.slice(dotIdx + 1);
@@ -722,6 +754,7 @@ function extractInterfaceMembers(interfaceNode, codeOrLines) {
722
754
  name: embName,
723
755
  startLine,
724
756
  endLine,
757
+ sourceSpan,
725
758
  memberType: 'field',
726
759
  embedded: true,
727
760
  fieldType: sub.text
@@ -1514,6 +1547,61 @@ const GO_BUILTINS = new Set([
1514
1547
  // Value positions of an instantiated generic function value (fix #378).
1515
1548
  const GENERIC_VALUE_PARENTS = new Set(['argument_list', 'literal_element', 'expression_list']);
1516
1549
 
1550
+ // A value in argument position (fix #425): a call argument, or the operand
1551
+ // of a generic instantiation read as a conversion (`conv[[]uint16](Uint16s)`
1552
+ // with `type conv[T any] func(..)` converts the function value Uint16s, and
1553
+ // tree-sitter reads every `X[T](v)` that way).
1554
+ function isArgumentValue(node) {
1555
+ const parent = node?.parent;
1556
+ if (!parent) return false;
1557
+ if (parent.type === 'argument_list') return true;
1558
+ return parent.type === 'type_conversion_expression' &&
1559
+ parent.childForFieldName('type')?.type === 'generic_type' &&
1560
+ sameNode(parent.childForFieldName('operand'), node);
1561
+ }
1562
+
1563
+ // A value an instantiated generic function denotes (fix #378, #425): an
1564
+ // argument, a literal element, or a value list (right-hand side, var spec
1565
+ // value, return).
1566
+ function isGenericValuePosition(node) {
1567
+ const parent = node?.parent;
1568
+ if (!parent) return false;
1569
+ if (isArgumentValue(node)) return true;
1570
+ if (!GENERIC_VALUE_PARENTS.has(parent.type)) return false;
1571
+ return parent.type !== 'expression_list' || isValueExpressionList(parent);
1572
+ }
1573
+
1574
+ // Predeclared types no function value converts to.
1575
+ const GO_NON_FUNCTION_TYPES = new Set(['bool', 'string', 'error', 'byte', 'rune', 'uintptr',
1576
+ 'int', 'int8', 'int16', 'int32', 'int64', 'uint', 'uint8', 'uint16', 'uint32', 'uint64',
1577
+ 'float32', 'float64', 'complex64', 'complex128']);
1578
+
1579
+ /**
1580
+ * Is a positional element of a slice, array or map literal of a predeclared
1581
+ * non-function element type (`[]string{r.Name}`, fix #425)? Such an element
1582
+ * is never a method value. Nested element literals (`[][]string{{..}}`)
1583
+ * descend one element type per level; struct literals and named element
1584
+ * types are undecided (false).
1585
+ */
1586
+ function holdsNoFunctionValue(literalElement) {
1587
+ let depth = 0;
1588
+ let n = literalElement.parent;
1589
+ while (n?.type === 'literal_value' && n.parent?.type === 'literal_element' &&
1590
+ n.parent.parent?.type === 'literal_value') {
1591
+ depth++;
1592
+ n = n.parent.parent;
1593
+ }
1594
+ if (n?.type !== 'literal_value' || n.parent?.type !== 'composite_literal') return false;
1595
+ let type = n.parent.childForFieldName('type');
1596
+ for (let i = 0; i <= depth; i++) {
1597
+ if (type?.type === 'slice_type' || type?.type === 'array_type' ||
1598
+ type?.type === 'implicit_length_array_type') type = type.childForFieldName('element');
1599
+ else if (type?.type === 'map_type') type = type.childForFieldName('value');
1600
+ else return false;
1601
+ }
1602
+ return type?.type === 'type_identifier' && GO_NON_FUNCTION_TYPES.has(type.text);
1603
+ }
1604
+
1517
1605
  // An expression_list holding VALUES: the right side of `=` / `:=`, a var
1518
1606
  // spec's values, or a return list (never an assignment's left side).
1519
1607
  function isValueExpressionList(list) {
@@ -1741,6 +1829,8 @@ function findCallsInCode(code, parser, options = {}) {
1741
1829
  const packageTypeQualifiers = new Map();
1742
1830
  // Names whose scope type is a New*-prefix GUESS (fix #266) — per scope
1743
1831
  const scopeGuesses = new Map();
1832
+ // Open type switches whose binding is typed per clause (fix #425).
1833
+ const typeSwitches = [];
1744
1834
  // Container-typed variables (slice/map/array declared TYPE TEXT) for
1745
1835
  // range-element typing (fix #300, mux-measured): `for _, route := range
1746
1836
  // r.routes` types route from the field's declared []*Route. Element
@@ -1963,6 +2053,109 @@ function findCallsInCode(code, parser, options = {}) {
1963
2053
  }
1964
2054
  return null;
1965
2055
  };
2056
+ // A parenthesized selector operand (fix #425): `(*T).M`, `(T).M` and
2057
+ // `(*pkg.T).M` are method expressions that name type T's method M as
2058
+ // `T.M` does (Go spec, Method expressions); `(*p).M` and `(&v).M` select
2059
+ // the method of a value. The operand reduces to the name it holds, an
2060
+ // identifier or a package-qualified name, resolved like the unwrapped
2061
+ // form; a written instantiated type (`(*Pair[int, string]).M`) names its
2062
+ // generic type. null for any other parenthesized expression.
2063
+ const methodOperandOf = (operandNode) => {
2064
+ if (operandNode?.type !== 'parenthesized_expression') return null;
2065
+ let n = operandNode;
2066
+ const unparen = () => {
2067
+ while (n?.type === 'parenthesized_expression') n = n.namedChildCount === 1 ? n.namedChild(0) : null;
2068
+ };
2069
+ unparen();
2070
+ if (n?.type === 'unary_expression' && ['*', '&'].includes(n.child(0)?.type)) {
2071
+ n = n.childForFieldName('operand');
2072
+ unparen();
2073
+ }
2074
+ if (n?.type === 'type_instantiation_expression') {
2075
+ let t = n.childForFieldName('type');
2076
+ if (t?.type === 'pointer_type') t = t.namedChildCount === 1 ? t.namedChild(0) : null;
2077
+ if (t?.type === 'type_identifier') return { node: t, name: t.text, writtenType: true };
2078
+ if (t?.type === 'qualified_type') {
2079
+ const pkg = t.childForFieldName('package');
2080
+ const name = t.childForFieldName('name');
2081
+ return pkg && name && isPackageQualifier(pkg, pkg.text)
2082
+ ? { node: t, name: name.text, qualifier: pkg.text, writtenType: true } : null;
2083
+ }
2084
+ return null;
2085
+ }
2086
+ if (n?.type === 'identifier') return { node: n, name: n.text };
2087
+ if (n?.type === 'selector_expression') {
2088
+ const pkg = n.childForFieldName('operand');
2089
+ const name = n.childForFieldName('field');
2090
+ if (pkg?.type === 'identifier' && name && isPackageQualifier(pkg, pkg.text)) {
2091
+ return { node: n, name: name.text, qualifier: pkg.text, packageNode: pkg };
2092
+ }
2093
+ }
2094
+ return null;
2095
+ };
2096
+ // A selector VALUE (fix #425: one record shape for every value position,
2097
+ // which used to repeat it per position): `obj.method` (a method value),
2098
+ // `pkg.Func` (a package-qualified function reference), `T.M` / `(*T).M`
2099
+ // (a method expression) and `(&T{..}).M`. A receiver no local binds
2100
+ // names a package-level declaration (`receiverPackageLevel`): the
2101
+ // package's type of that name makes the value a method expression.
2102
+ const selectorValueRecord = (valueNode, extra = {}, bindingEnclosing = false) => {
2103
+ const fieldNode = valueNode.childForFieldName('field');
2104
+ let operandNode = valueNode.childForFieldName('operand');
2105
+ if (!fieldNode || !operandNode) return null;
2106
+ const methodOperand = methodOperandOf(operandNode);
2107
+ let qualifiedOperand = null;
2108
+ if (methodOperand?.qualifier) qualifiedOperand = methodOperand;
2109
+ else if (methodOperand && !methodOperand.writtenType) operandNode = methodOperand.node;
2110
+ const receiver = qualifiedOperand ? undefined : operandNode.type === 'identifier'
2111
+ ? operandNode.text : methodOperand?.writtenType ? methodOperand.name : undefined;
2112
+ // `pkg.Func` as a value is a package-qualified function
2113
+ // reference, never a method value (fix #378).
2114
+ const pkgValue = !methodOperand && isPackageQualifier(operandNode, receiver);
2115
+ let receiverTypeEvidence;
2116
+ let receiverType = receiver && !methodOperand?.writtenType
2117
+ ? getReceiverType(receiver, operandNode) : undefined;
2118
+ let receiverTypeQualifier = receiverType
2119
+ ? getReceiverTypeQualifier(receiver, operandNode) : undefined;
2120
+ // Composite-literal receiver (fix #298): the literal's type is
2121
+ // compiler-true, never guessed.
2122
+ if (!receiver && !receiverType && !qualifiedOperand) {
2123
+ const lit = literalReceiverInfo(operandNode);
2124
+ if (lit) {
2125
+ receiverType = lit.receiverType;
2126
+ receiverTypeEvidence = typeOrigin('constructor', operandNode);
2127
+ receiverTypeQualifier = lit.receiverTypeQualifier;
2128
+ }
2129
+ }
2130
+ const packageLevel = !!(receiver && !pkgValue && !receiverType &&
2131
+ (methodOperand?.writtenType || !isShadowedByLocal(operandNode, receiver)) &&
2132
+ !importAliases.has(receiver));
2133
+ const enclosingFunction = bindingEnclosing && receiver && !methodOperand?.writtenType
2134
+ ? getCurrentEnclosingFunction(operandNode, receiver)
2135
+ : getCurrentEnclosingFunction();
2136
+ return {
2137
+ name: fieldNode.text,
2138
+ // #223 name-node convention: the field's own line.
2139
+ line: fieldNode.startPosition.row + 1,
2140
+ column: fieldNode.startPosition.column,
2141
+ isMethod: !pkgValue,
2142
+ ...(pkgValue && { isFunctionReference: true }),
2143
+ receiver,
2144
+ ...(receiverType && { receiverType, ...(receiverTypeEvidence ? { receiverTypeSource: 'constructor', receiverTypeEvidence } : getReceiverType(receiver, valueNode, true) || { receiverTypeSource: 'unknown' }) }),
2145
+ ...(receiverTypeQualifier && { receiverTypeQualifier }),
2146
+ ...(receiverType && receiver && isGuessedType(receiver) && { receiverTypeGuessed: true }),
2147
+ ...(packageLevel && { receiverPackageLevel: true }),
2148
+ ...(qualifiedOperand && {
2149
+ receiverRoot: qualifiedOperand.qualifier,
2150
+ receiverField: qualifiedOperand.name,
2151
+ receiverRootIsModule: true,
2152
+ }),
2153
+ enclosingFunction,
2154
+ isPotentialCallback: true,
2155
+ uncertain: false,
2156
+ ...extra,
2157
+ };
2158
+ };
1966
2159
  // Is the FIRST scope-chain hit for this variable a New*-prefix GUESS
1967
2160
  // (fix #266, viper-measured)? `registry := NewCodecRegistry()` types
1968
2161
  // registry as 'CodecRegistry' by NAME CONVENTION — the actual return
@@ -2351,6 +2544,64 @@ function findCallsInCode(code, parser, options = {}) {
2351
2544
  }
2352
2545
  }
2353
2546
 
2547
+ // Type switch bindings (fix #425): in `switch v := x.(type)`, a
2548
+ // clause listing exactly one type gives v that type; a clause of
2549
+ // several types and the default clause give v the type of x (Go
2550
+ // spec, Type switches). The binding is the switch's own: the outer
2551
+ // meaning of the name returns after the switch (onLeave).
2552
+ if (node.type === 'type_switch_statement' && functionStack.length > 0) {
2553
+ const alias = node.childForFieldName('alias');
2554
+ const id = alias?.type === 'expression_list' && alias.namedChildCount === 1
2555
+ ? alias.namedChild(0) : alias;
2556
+ if (id?.type === 'identifier' && id.text !== '_') {
2557
+ const scopeKey = functionStack[functionStack.length - 1].startLine;
2558
+ const typeMap = scopeTypes.get(scopeKey);
2559
+ const qualifiers = scopeTypeQualifiers.get(scopeKey);
2560
+ const value = node.childForFieldName('value');
2561
+ const valueType = value?.type === 'identifier' && !isGuessedType(value.text)
2562
+ ? getReceiverType(value.text, value) : undefined;
2563
+ typeSwitches.push({
2564
+ node, name: id.text, scopeKey, typeMap, qualifiers,
2565
+ valueType,
2566
+ valueQualifier: valueType ? getReceiverTypeQualifier(value.text, value) : undefined,
2567
+ valueEvidence: valueType ? getReceiverType(value.text, value, true) : undefined,
2568
+ saved: typeMap?.has(id.text) ? {
2569
+ type: typeMap.get(id.text), origin: typeMap.origins.get(id.text),
2570
+ qualifier: qualifiers?.get(id.text),
2571
+ guessed: !!scopeGuesses.get(scopeKey)?.has(id.text),
2572
+ } : null,
2573
+ });
2574
+ }
2575
+ }
2576
+ if ((node.type === 'type_case' || node.type === 'default_case') &&
2577
+ typeSwitches.length > 0 && sameNode(typeSwitches.at(-1).node, node.parent)) {
2578
+ const sw = typeSwitches.at(-1);
2579
+ const types = node.type === 'type_case' ? node.childrenForFieldName('type') : [];
2580
+ let typeName = null;
2581
+ let qualifier = null;
2582
+ let origin = null;
2583
+ if (types.length === 1) {
2584
+ typeName = extractTypeName(types[0]);
2585
+ qualifier = typeName ? extractTypeQualifier(types[0]) : null;
2586
+ origin = { source: 'type-assertion', node: types[0] };
2587
+ } else if (sw.valueType) {
2588
+ typeName = sw.valueType;
2589
+ qualifier = sw.valueQualifier || null;
2590
+ }
2591
+ if (sw.typeMap) {
2592
+ if (typeName) {
2593
+ if (origin) sw.typeMap.set(sw.name, typeName, origin.source, origin.node);
2594
+ else sw.typeMap.set(sw.name, typeName, sw.valueEvidence?.receiverTypeEvidence || 'flow');
2595
+ if (qualifier) sw.qualifiers?.set(sw.name, qualifier);
2596
+ else sw.qualifiers?.delete(sw.name);
2597
+ } else {
2598
+ sw.typeMap.delete(sw.name);
2599
+ sw.qualifiers?.delete(sw.name);
2600
+ }
2601
+ scopeGuesses.get(sw.scopeKey)?.delete(sw.name);
2602
+ }
2603
+ }
2604
+
2354
2605
  // Track local variable types from composite literals and typed assignments
2355
2606
  // e.g., s := &Status{...} → s has type Status
2356
2607
  // registry := Registry{...} → registry has type Registry
@@ -2734,6 +2985,10 @@ function findCallsInCode(code, parser, options = {}) {
2734
2985
  // Method or package call: obj.Method() or pkg.Func()
2735
2986
  const fieldNode = funcNode.childForFieldName('field');
2736
2987
  let operandNode = funcNode.childForFieldName('operand');
2988
+ // `(*T).M(t, ..)`, `(*p).M(..)` and `(*pkg.T).M(..)` receive
2989
+ // like `T.M(t, ..)`, `p.M(..)` and `pkg.T.M(..)` (fix #425).
2990
+ const methodOperand = methodOperandOf(operandNode);
2991
+ if (methodOperand && !methodOperand.writtenType) operandNode = methodOperand.node;
2737
2992
  // fix #381: `c.Load()` after `c := s.config` receives like
2738
2993
  // `s.config.Load()`.
2739
2994
  if (operandNode?.type === 'identifier' && !importAliases.has(operandNode.text) &&
@@ -2743,17 +2998,19 @@ function findCallsInCode(code, parser, options = {}) {
2743
2998
  }
2744
2999
 
2745
3000
  if (fieldNode) {
2746
- const receiver = operandNode?.type === 'identifier' ? operandNode.text : undefined;
3001
+ const writtenReceiver = methodOperand?.writtenType && !methodOperand.qualifier
3002
+ ? methodOperand.name : undefined;
3003
+ const receiver = operandNode?.type === 'identifier' ? operandNode.text : writtenReceiver;
2747
3004
  // Distinguish pkg.Func() (package-qualified) from obj.Method()
2748
3005
  // If receiver is a known import alias, this is a package call, not a method call
2749
3006
  // A local binding shadows a same-named import inside its
2750
3007
  // scope (`fs := NewMemMapFs(); fs.Chown(...)` in a file
2751
3008
  // importing "io/fs"): that call is a METHOD call on the
2752
3009
  // local, never the package function (fix #360).
2753
- const isPkgCall = receiver && importAliases.has(receiver) &&
2754
- !isShadowedByLocal(operandNode, receiver);
3010
+ const isPkgCall = !writtenReceiver && !methodOperand && receiver &&
3011
+ importAliases.has(receiver) && !isShadowedByLocal(operandNode, receiver);
2755
3012
  let receiverTypeEvidence;
2756
- let receiverType = (!isPkgCall && receiver)
3013
+ let receiverType = (!isPkgCall && receiver && !writtenReceiver)
2757
3014
  ? getReceiverType(receiver, operandNode) : undefined;
2758
3015
  let receiverTypeQualifier = receiverType
2759
3016
  ? getReceiverTypeQualifier(receiver, operandNode) : undefined;
@@ -2764,7 +3021,7 @@ function findCallsInCode(code, parser, options = {}) {
2764
3021
  // package (fix #399): typed at query time. So is an
2765
3022
  // untyped package-level variable of this file, whose
2766
3023
  // value may come from another package (fix #400).
2767
- const receiverPackageScope = !!(receiver && !isPkgCall && !receiverType &&
3024
+ const receiverPackageScope = !!(receiver && !writtenReceiver && !isPkgCall && !receiverType &&
2768
3025
  !receiverIndexedSource &&
2769
3026
  (!fileLevelNames().has(receiver) || fileVarNames().has(receiver)) &&
2770
3027
  !isShadowedByLocal(operandNode, receiver));
@@ -2989,52 +3246,12 @@ function findCallsInCode(code, parser, options = {}) {
2989
3246
  }
2990
3247
 
2991
3248
  // Detect function references passed as arguments: dc.worker passed to UntilWithContext(ctx, dc.worker, ...)
2992
- // selector_expression inside argument_list (not inside call_expression as the function)
2993
- if (node.type === 'selector_expression' && node.parent?.type === 'argument_list') {
2994
- // Only if this selector_expression is NOT the function being called
2995
- const grandparent = node.parent?.parent;
2996
- if (!grandparent || grandparent.type !== 'call_expression' || grandparent.childForFieldName('function') !== node) {
2997
- const fieldNode = node.childForFieldName('field');
2998
- const operandNode = node.childForFieldName('operand');
2999
- if (fieldNode && operandNode) {
3000
- const receiver = operandNode.type === 'identifier' ? operandNode.text : undefined;
3001
- // `pkg.Func` as a value is a package-qualified function
3002
- // reference, never a method value (fix #378).
3003
- const pkgValue = isPackageQualifier(operandNode, receiver);
3004
- let receiverTypeEvidence;
3005
- let receiverType = receiver
3006
- ? getReceiverType(receiver, operandNode) : undefined;
3007
- let receiverTypeQualifier = receiverType
3008
- ? getReceiverTypeQualifier(receiver, operandNode) : undefined;
3009
- // Composite-literal receiver (fix #298): the literal's
3010
- // type is compiler-true, never guessed.
3011
- if (!receiver && !receiverType) {
3012
- const lit = literalReceiverInfo(operandNode);
3013
- if (lit) {
3014
- receiverType = lit.receiverType;
3015
- receiverTypeEvidence = typeOrigin('constructor', operandNode);
3016
- receiverTypeQualifier = lit.receiverTypeQualifier;
3017
- }
3018
- }
3019
- const enclosingFunction = receiver
3020
- ? getCurrentEnclosingFunction(operandNode, receiver)
3021
- : getCurrentEnclosingFunction();
3022
- calls.push({
3023
- name: fieldNode.text,
3024
- line: fieldNode.startPosition.row + 1,
3025
- column: fieldNode.startPosition.column,
3026
- isMethod: !pkgValue,
3027
- ...(pkgValue && { isFunctionReference: true }),
3028
- receiver,
3029
- ...(receiverType && { receiverType, ...(receiverTypeEvidence ? { receiverTypeSource: 'constructor', receiverTypeEvidence } : getReceiverType(receiver, node, true) || { receiverTypeSource: 'unknown' }) }),
3030
- ...(receiverTypeQualifier && { receiverTypeQualifier }),
3031
- ...(receiverType && receiver && isGuessedType(receiver) && { receiverTypeGuessed: true }),
3032
- enclosingFunction,
3033
- isPotentialCallback: true,
3034
- uncertain: false
3035
- });
3036
- }
3037
- }
3249
+ // selector_expression inside argument_list (not inside call_expression as the function),
3250
+ // or the operand of a conversion to a generic function type (fix #425:
3251
+ // `anyFieldC[[]uint16](Uint16s)` where anyFieldC is a type).
3252
+ if (node.type === 'selector_expression' && isArgumentValue(node)) {
3253
+ const record = selectorValueRecord(node, {}, true);
3254
+ if (record) calls.push(record);
3038
3255
  }
3039
3256
 
3040
3257
  // Instantiated generic function VALUES (fix #378): `id[int]` /
@@ -3043,18 +3260,37 @@ function findCallsInCode(code, parser, options = {}) {
3043
3260
  // The same syntax indexes a slice or map: a locally bound operand is
3044
3261
  // an index, and a package-level non-function never matches a
3045
3262
  // function pin (Go forbids a same-package var and func of one name).
3046
- if (node.type === 'index_expression' && GENERIC_VALUE_PARENTS.has(node.parent?.type) &&
3047
- (node.parent.type !== 'expression_list' || isValueExpressionList(node.parent))) {
3048
- const operandNode = node.childForFieldName('operand');
3049
- if (operandNode?.type === 'identifier') {
3050
- const name = operandNode.text;
3263
+ // Several type arguments (`Map[int, string]`, `lo.Map[int, string]`)
3264
+ // parse as a type_instantiation_expression (fix #425), which only
3265
+ // instantiates.
3266
+ if ((node.type === 'index_expression' || node.type === 'type_instantiation_expression') &&
3267
+ isGenericValuePosition(node)) {
3268
+ let nameNode = null;
3269
+ let qualifierNode = null;
3270
+ if (node.type === 'index_expression') {
3271
+ const operandNode = node.childForFieldName('operand');
3272
+ if (operandNode?.type === 'identifier') nameNode = operandNode;
3273
+ else if (operandNode?.type === 'selector_expression') {
3274
+ qualifierNode = operandNode.childForFieldName('operand');
3275
+ nameNode = operandNode.childForFieldName('field');
3276
+ }
3277
+ } else {
3278
+ const typeNode = node.childForFieldName('type');
3279
+ if (typeNode?.type === 'type_identifier') nameNode = typeNode;
3280
+ else if (typeNode?.type === 'qualified_type') {
3281
+ qualifierNode = typeNode.childForFieldName('package');
3282
+ nameNode = typeNode.childForFieldName('name');
3283
+ }
3284
+ }
3285
+ if (nameNode && !qualifierNode) {
3286
+ const name = nameNode.text;
3051
3287
  if (!GO_SKIP_IDENTS.has(name) && !GO_BUILTINS.has(name) &&
3052
- !importAliases.has(name) && !isShadowedByLocal(operandNode, name) &&
3288
+ !importAliases.has(name) && !isShadowedByLocal(nameNode, name) &&
3053
3289
  !isFuncTypedParam(name) && !getLocalClosure(name)) {
3054
3290
  calls.push({
3055
3291
  name,
3056
- line: operandNode.startPosition.row + 1,
3057
- column: operandNode.startPosition.column,
3292
+ line: nameNode.startPosition.row + 1,
3293
+ column: nameNode.startPosition.column,
3058
3294
  isMethod: false,
3059
3295
  isFunctionReference: true,
3060
3296
  isPotentialCallback: true,
@@ -3062,15 +3298,14 @@ function findCallsInCode(code, parser, options = {}) {
3062
3298
  uncertain: false,
3063
3299
  });
3064
3300
  }
3065
- } else if (operandNode?.type === 'selector_expression') {
3066
- const qualifier = operandNode.childForFieldName('operand');
3067
- const fieldNode = operandNode.childForFieldName('field');
3068
- const receiver = qualifier?.type === 'identifier' ? qualifier.text : undefined;
3069
- if (fieldNode && isPackageQualifier(qualifier, receiver)) {
3301
+ } else if (nameNode && qualifierNode) {
3302
+ const receiver = ['identifier', 'package_identifier'].includes(qualifierNode.type)
3303
+ ? qualifierNode.text : undefined;
3304
+ if (isPackageQualifier(qualifierNode, receiver)) {
3070
3305
  calls.push({
3071
- name: fieldNode.text,
3072
- line: fieldNode.startPosition.row + 1,
3073
- column: fieldNode.startPosition.column,
3306
+ name: nameNode.text,
3307
+ line: nameNode.startPosition.row + 1,
3308
+ column: nameNode.startPosition.column,
3074
3309
  isMethod: false,
3075
3310
  receiver,
3076
3311
  isFunctionReference: true,
@@ -3084,7 +3319,8 @@ function findCallsInCode(code, parser, options = {}) {
3084
3319
 
3085
3320
  // Detect plain identifier function references passed as arguments:
3086
3321
  // e.g., r.GET("/users", listUsers) — listUsers is a plain identifier in argument_list
3087
- if (node.type === 'identifier' && node.parent?.type === 'argument_list') {
3322
+ // (or converted: `conv[[]uint16](Uint16s)`, fix #425)
3323
+ if (node.type === 'identifier' && isArgumentValue(node)) {
3088
3324
  const name = node.text;
3089
3325
  if (!GO_SKIP_IDENTS.has(name) && !importAliases.has(name)) {
3090
3326
  const enclosingFunction = getCurrentEnclosingFunction();
@@ -3121,43 +3357,8 @@ function findCallsInCode(code, parser, options = {}) {
3121
3357
  for (const rhs of rhsNodes) {
3122
3358
  // selector_expression: sched.schedulePod
3123
3359
  if (rhs.type === 'selector_expression') {
3124
- const fieldNode = rhs.childForFieldName('field');
3125
- const operandNode = rhs.childForFieldName('operand');
3126
- if (fieldNode && operandNode) {
3127
- const receiver = operandNode.type === 'identifier' ? operandNode.text : undefined;
3128
- // `pkg.Func` as a value is a package-qualified function
3129
- // reference, never a method value (fix #378).
3130
- const pkgValue = isPackageQualifier(operandNode, receiver);
3131
- let receiverTypeEvidence;
3132
- let receiverType = receiver
3133
- ? getReceiverType(receiver, operandNode) : undefined;
3134
- let receiverTypeQualifier = receiverType
3135
- ? getReceiverTypeQualifier(receiver, operandNode) : undefined;
3136
- // Composite-literal receiver (fix #298).
3137
- if (!receiver && !receiverType) {
3138
- const lit = literalReceiverInfo(operandNode);
3139
- if (lit) {
3140
- receiverType = lit.receiverType;
3141
- receiverTypeEvidence = typeOrigin('constructor', operandNode);
3142
- receiverTypeQualifier = lit.receiverTypeQualifier;
3143
- }
3144
- }
3145
- const enclosingFunction = getCurrentEnclosingFunction();
3146
- calls.push({
3147
- name: fieldNode.text,
3148
- line: fieldNode.startPosition.row + 1,
3149
- column: fieldNode.startPosition.column,
3150
- isMethod: !pkgValue,
3151
- ...(pkgValue && { isFunctionReference: true }),
3152
- receiver,
3153
- ...(receiverType && { receiverType, ...(receiverTypeEvidence ? { receiverTypeSource: 'constructor', receiverTypeEvidence } : getReceiverType(receiver, node, true) || { receiverTypeSource: 'unknown' }) }),
3154
- ...(receiverTypeQualifier && { receiverTypeQualifier }),
3155
- ...(receiverType && receiver && isGuessedType(receiver) && { receiverTypeGuessed: true }),
3156
- enclosingFunction,
3157
- isPotentialCallback: true,
3158
- uncertain: false
3159
- });
3160
- }
3360
+ const record = selectorValueRecord(rhs);
3361
+ if (record) calls.push(record);
3161
3362
  }
3162
3363
  // Plain identifier: schedulePod (standalone function reference)
3163
3364
  if (rhs.type === 'identifier') {
@@ -3194,44 +3395,20 @@ function findCallsInCode(code, parser, options = {}) {
3194
3395
  : (exprList ? [exprList] : []);
3195
3396
  for (const val of values) {
3196
3397
  if (val.type !== 'selector_expression') continue;
3197
- const fieldNode = val.childForFieldName('field');
3198
- const operandNode = val.childForFieldName('operand');
3199
- if (!fieldNode || !operandNode) continue;
3200
- const receiver = operandNode.type === 'identifier' ? operandNode.text : undefined;
3201
- // `pkg.Func` as a value is a package-qualified function
3202
- // reference, never a method value (fix #378).
3203
- const pkgValue = isPackageQualifier(operandNode, receiver);
3204
- let receiverTypeEvidence;
3205
- let receiverType = receiver
3206
- ? getReceiverType(receiver, operandNode) : undefined;
3207
- let receiverTypeQualifier = receiverType
3208
- ? getReceiverTypeQualifier(receiver, operandNode) : undefined;
3209
- if (!receiver && !receiverType) {
3210
- const lit = literalReceiverInfo(operandNode);
3211
- if (lit) {
3212
- receiverType = lit.receiverType;
3213
- receiverTypeEvidence = typeOrigin('constructor', operandNode);
3214
- receiverTypeQualifier = lit.receiverTypeQualifier;
3215
- }
3216
- }
3217
- calls.push({
3218
- name: fieldNode.text,
3219
- // #223 name-node convention: the field's own line.
3220
- line: fieldNode.startPosition.row + 1,
3221
- column: fieldNode.startPosition.column,
3222
- isMethod: !pkgValue,
3223
- ...(pkgValue && { isFunctionReference: true }),
3224
- receiver,
3225
- ...(receiverType && { receiverType, ...(receiverTypeEvidence ? { receiverTypeSource: 'constructor', receiverTypeEvidence } : getReceiverType(receiver, node, true) || { receiverTypeSource: 'unknown' }) }),
3226
- ...(receiverTypeQualifier && { receiverTypeQualifier }),
3227
- ...(receiverType && receiver && isGuessedType(receiver) && { receiverTypeGuessed: true }),
3228
- enclosingFunction: getCurrentEnclosingFunction(),
3229
- isPotentialCallback: true,
3230
- uncertain: false
3231
- });
3398
+ const record = selectorValueRecord(val);
3399
+ if (record) calls.push(record);
3232
3400
  }
3233
3401
  }
3234
3402
 
3403
+ // Selector values as positional composite-literal elements (fix #425,
3404
+ // zap: `[]withAny{(*SugaredLogger).With}`, a struct literal's
3405
+ // `(*Logger).With` field value): the same values as keyed ones.
3406
+ if (node.type === 'selector_expression' && node.parent?.type === 'literal_element' &&
3407
+ node.parent.parent?.type !== 'keyed_element' && !holdsNoFunctionValue(node.parent)) {
3408
+ const record = selectorValueRecord(node);
3409
+ if (record) calls.push(record);
3410
+ }
3411
+
3235
3412
  // Detect function references in composite literal fields:
3236
3413
  // ResourceEventHandlerFuncs{AddFunc: addNodeToCache, UpdateFunc: updateNode}
3237
3414
  // keyed_element → literal_element(key) ":" literal_element(value)
@@ -3288,45 +3465,11 @@ function findCallsInCode(code, parser, options = {}) {
3288
3465
  }
3289
3466
  }
3290
3467
  if (valueNode.type === 'selector_expression') {
3291
- const fieldNode = valueNode.childForFieldName('field');
3292
- const operandNode = valueNode.childForFieldName('operand');
3293
- if (fieldNode && operandNode) {
3294
- const receiver = operandNode.type === 'identifier' ? operandNode.text : undefined;
3295
- // `pkg.Func` as a value is a package-qualified function
3296
- // reference, never a method value (fix #378).
3297
- const pkgValue = isPackageQualifier(operandNode, receiver);
3298
- let receiverTypeEvidence;
3299
- let receiverType = receiver
3300
- ? getReceiverType(receiver, operandNode) : undefined;
3301
- let receiverTypeQualifier = receiverType
3302
- ? getReceiverTypeQualifier(receiver, operandNode) : undefined;
3303
- // Composite-literal receiver (fix #298).
3304
- if (!receiver && !receiverType) {
3305
- const lit = literalReceiverInfo(operandNode);
3306
- if (lit) {
3307
- receiverType = lit.receiverType;
3308
- receiverTypeEvidence = typeOrigin('constructor', operandNode);
3309
- receiverTypeQualifier = lit.receiverTypeQualifier;
3310
- }
3311
- }
3312
- const enclosingFunction = getCurrentEnclosingFunction();
3313
- calls.push({
3314
- name: fieldNode.text,
3315
- line: fieldNode.startPosition.row + 1,
3316
- column: fieldNode.startPosition.column,
3317
- isMethod: !pkgValue,
3318
- ...(pkgValue && { isFunctionReference: true }),
3319
- receiver,
3320
- ...(receiverType && { receiverType, ...(receiverTypeEvidence ? { receiverTypeSource: 'constructor', receiverTypeEvidence } : getReceiverType(receiver, node, true) || { receiverTypeSource: 'unknown' }) }),
3321
- ...(receiverTypeQualifier && { receiverTypeQualifier }),
3322
- ...(receiverType && receiver && isGuessedType(receiver) && { receiverTypeGuessed: true }),
3323
- enclosingFunction,
3324
- isPotentialCallback: true,
3325
- uncertain: false,
3326
- ...(compositeType && { compositeType }),
3327
- ...(fieldName && { fieldName }),
3328
- });
3329
- }
3468
+ const record = selectorValueRecord(valueNode, {
3469
+ ...(compositeType && { compositeType }),
3470
+ ...(fieldName && { fieldName }),
3471
+ });
3472
+ if (record) calls.push(record);
3330
3473
  }
3331
3474
  // Inline closure: RunE: func(cmd *cobra.Command, args []string) { ... }
3332
3475
  // Mark the enclosing function as the entry point (the closure itself has no name)
@@ -3353,6 +3496,25 @@ function findCallsInCode(code, parser, options = {}) {
3353
3496
  return true;
3354
3497
  }, {
3355
3498
  onLeave: (node) => {
3499
+ if (node.type === 'type_switch_statement' && typeSwitches.length > 0 &&
3500
+ sameNode(typeSwitches.at(-1).node, node)) {
3501
+ const sw = typeSwitches.pop();
3502
+ if (sw.typeMap) {
3503
+ if (sw.saved) {
3504
+ sw.typeMap.set(sw.name, sw.saved.type, sw.saved.origin || 'unknown');
3505
+ if (sw.saved.qualifier) sw.qualifiers?.set(sw.name, sw.saved.qualifier);
3506
+ else sw.qualifiers?.delete(sw.name);
3507
+ if (sw.saved.guessed) {
3508
+ let gset = scopeGuesses.get(sw.scopeKey);
3509
+ if (!gset) scopeGuesses.set(sw.scopeKey, gset = new Set());
3510
+ gset.add(sw.name);
3511
+ }
3512
+ } else {
3513
+ sw.typeMap.delete(sw.name);
3514
+ sw.qualifiers?.delete(sw.name);
3515
+ }
3516
+ }
3517
+ }
3356
3518
  if (isFunctionNode(node)) {
3357
3519
  const leaving = functionStack.pop();
3358
3520
  if (leaving) {
@@ -3664,6 +3826,21 @@ function findUsagesInCode(code, name, parser, tree, options = {}) {
3664
3826
  return usages;
3665
3827
  }
3666
3828
 
3829
+ /**
3830
+ * Does a local binding hide the receiver of the selector whose field token
3831
+ * starts at (line, column) (fix #425: `pkg.F` names an import only when no
3832
+ * local binds `pkg`)? true / false; null when no such selector is there.
3833
+ */
3834
+ function selectorReceiverIsLocal(tree, line, column) {
3835
+ const point = { row: line - 1, column };
3836
+ const field = tree?.rootNode?.descendantForPosition(point);
3837
+ if (field?.type !== 'field_identifier' || field.parent?.type !== 'selector_expression') return null;
3838
+ const operand = field.parent.childForFieldName('operand');
3839
+ if (operand?.type !== 'identifier') return null;
3840
+ const scope = referenceScope(operand, 'go');
3841
+ return !!scope && typeof scope !== 'string';
3842
+ }
3843
+
3667
3844
  /**
3668
3845
  * Classify a Go symbol as a runtime entry point of a specific kind.
3669
3846
  * Returns 'test' | 'main' | null.
@@ -3720,6 +3897,7 @@ module.exports = {
3720
3897
  findImportsInCode,
3721
3898
  findExportsInCode,
3722
3899
  findUsagesInCode,
3900
+ selectorReceiverIsLocal,
3723
3901
  isPlatformConcreteCall,
3724
3902
  isPlatformConcreteType,
3725
3903
  isEntryPoint,