ucn 5.5.1 → 5.5.2

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 (93) hide show
  1. package/.claude/skills/ucn/SKILL.md +35 -9
  2. package/.claude/skills/ucn/references/commands.md +38 -16
  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 +30 -21
  6. package/cli/index.js +1 -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/brief.js +8 -4
  12. package/core/build-worker.js +2 -1
  13. package/core/cache.js +137 -6
  14. package/core/call-name-cache.js +134 -0
  15. package/core/callable-scope.js +21 -0
  16. package/core/callers.js +4726 -940
  17. package/core/check.js +22 -5
  18. package/core/class-identity.js +399 -12
  19. package/core/constructor-calls.js +831 -0
  20. package/core/constructor-hierarchy.js +147 -0
  21. package/core/constructor-protocol.js +196 -0
  22. package/core/contract-membership.js +346 -140
  23. package/core/cpp-conversion-constraints.js +123 -0
  24. package/core/cpp-conversion-syntax.js +216 -0
  25. package/core/cpp-conversions.js +414 -0
  26. package/core/cpp-scope.js +245 -44
  27. package/core/deadcode.js +35 -15
  28. package/core/decorator-binding.js +137 -3
  29. package/core/enum-reference-syntax.js +134 -0
  30. package/core/execute.js +17 -1
  31. package/core/external-macros.js +42 -10
  32. package/core/go-packages.js +171 -0
  33. package/core/graph-build.js +2 -0
  34. package/core/graph.js +75 -59
  35. package/core/ignore-rules.js +2 -1
  36. package/core/imports.js +145 -19
  37. package/core/index-ir.js +41 -4
  38. package/core/ir.js +27 -6
  39. package/core/macro-expansion.js +38 -2
  40. package/core/out-of-class-members.js +182 -0
  41. package/core/output/analysis-ext.js +4 -2
  42. package/core/output/analysis.js +89 -20
  43. package/core/output/graph.js +12 -3
  44. package/core/output/lines.js +4 -3
  45. package/core/output/public.js +1 -0
  46. package/core/output/reporting.js +6 -1
  47. package/core/output/shared.js +2 -0
  48. package/core/output/tracing.js +28 -22
  49. package/core/output-budget.js +2 -2
  50. package/core/parallel-build.js +52 -1
  51. package/core/project.js +217 -112
  52. package/core/provenance-constructor-base.js +161 -0
  53. package/core/provenance-facts.js +257 -27
  54. package/core/provenance-overload.js +458 -54
  55. package/core/provenance-type.js +487 -0
  56. package/core/provenance.js +234 -15
  57. package/core/public-command.js +17 -2
  58. package/core/query-bodies.js +159 -0
  59. package/core/reflection.js +255 -2
  60. package/core/rename-token-syntax.js +111 -0
  61. package/core/reporting.js +13 -7
  62. package/core/rust-macro-expansion.js +151 -19
  63. package/core/rust-modules.js +1 -1
  64. package/core/rust-names.js +607 -0
  65. package/core/search.js +70 -17
  66. package/core/shared.js +98 -1
  67. package/core/source-memo.js +201 -0
  68. package/core/text-usage-syntax.js +38 -0
  69. package/core/tracing.js +7 -1
  70. package/core/type-denotation.js +124 -37
  71. package/core/type-references.js +615 -246
  72. package/core/value-references.js +2777 -0
  73. package/core/verify.js +1097 -281
  74. package/languages/adapter.js +28 -4
  75. package/languages/c-family.js +1118 -164
  76. package/languages/constructor-bases.js +105 -0
  77. package/languages/cpp-platform.js +26 -0
  78. package/languages/cpp-site-syntax.js +83 -0
  79. package/languages/csharp-scope.js +439 -0
  80. package/languages/csharp.js +428 -68
  81. package/languages/go.js +385 -207
  82. package/languages/html.js +4 -2
  83. package/languages/index.js +397 -2
  84. package/languages/java-platform.js +226 -0
  85. package/languages/java-unicode.js +269 -0
  86. package/languages/java.js +461 -117
  87. package/languages/javascript.js +418 -66
  88. package/languages/lexical-scope.js +190 -23
  89. package/languages/python.js +404 -54
  90. package/languages/rust.js +510 -82
  91. package/languages/utils.js +67 -8
  92. package/mcp/server.js +8 -2
  93. package/package.json +2 -2
@@ -14,6 +14,7 @@ const {
14
14
  containsOwnNode,
15
15
  parseErrorRegions,
16
16
  genericArityOf,
17
+ hasParameterizedQualifier,
17
18
  } = require('./utils');
18
19
 
19
20
  // An iterator method (`yield return` in its OWN body) returns an enumerable /
@@ -203,7 +204,12 @@ function csBuildViews(parser, code) {
203
204
  });
204
205
  if (resolved.length === 0) return null;
205
206
  for (const group of resolved) group.primary = csPrimaryBranch(group, lines);
206
- return csViewsFromGroups(parser, code, lines, resolved, damaged.errors);
207
+ const views = csViewsFromGroups(parser, code, lines, resolved, damaged.errors);
208
+ // The raw parse the views read from: its type-parameter names are the
209
+ // file's (fix #421).
210
+ if (views) Object.defineProperty(views, 'typeParameterNames', {
211
+ value: require('./utils').declaredTypeParameterNames(literal), enumerable: false });
212
+ return views;
207
213
  }
208
214
 
209
215
  // Rows of branch b of a group (between its directive and the next).
@@ -237,7 +243,7 @@ function csPrimaryBranch(group, lines) {
237
243
  * index recorded for this content from `groups` (persisted per file as
238
244
  * [branch rows..., end row, primary branch]) without the literal parse.
239
245
  */
240
- function csViewsFromGroups(parser, code, lines, resolved, literalErrors = null) {
246
+ function csViewsFromGroups(parser, code, lines, resolved, literalErrors = null, transform = null) {
241
247
  // One view per alternative position: view 0 keeps each group's primary
242
248
  // branch, view k its k-th other branch (the primary one when it has no
243
249
  // more). A lone `#if` branch is kept in every view.
@@ -265,7 +271,8 @@ function csViewsFromGroups(parser, code, lines, resolved, literalErrors = null)
265
271
  };
266
272
  };
267
273
  const reparse = source => safeParse(parser, source, undefined, PARSE_OPTIONS);
268
- const primaryTree = reparse(viewSource(0).source);
274
+ const primarySource = viewSource(0).source;
275
+ const primaryTree = reparse(transform ? transform(primarySource, null) : primarySource);
269
276
  if (literalErrors != null && primaryTree.rootNode.hasError &&
270
277
  csCountErrors(primaryTree.rootNode) >= literalErrors) return null;
271
278
  const secondary = [];
@@ -274,7 +281,7 @@ function csViewsFromGroups(parser, code, lines, resolved, literalErrors = null)
274
281
  // Only rows that hold code are worth a parse.
275
282
  const owns = new Set([...view.owns].filter(row => /\w/.test(lines[row])));
276
283
  if (owns.size === 0) continue;
277
- secondary.push({ tree: reparse(view.source), owns, reaches: csRowReach(owns) });
284
+ secondary.push({ tree: reparse(transform ? transform(view.source, owns) : view.source), owns, reaches: csRowReach(owns) });
278
285
  }
279
286
  return {
280
287
  primary: { tree: primaryTree },
@@ -361,6 +368,26 @@ function queryTreeIsPlain(code, entry = null) {
361
368
  crypto.createHash('md5').update(code).digest('hex') === entry.hash;
362
369
  }
363
370
 
371
+ function queryBodyViews(tree) {
372
+ const views = tree && csViewsByTree.get(tree)?.views;
373
+ return views ? [views.primary, ...views.secondary] : [{ tree }];
374
+ }
375
+
376
+ /** Rebuild every recorded configuration, with a source-local body transform. */
377
+ function queryBodyTree(code, parser, entry, transform) {
378
+ if (!Array.isArray(entry.conditionalViews)) {
379
+ const tree = safeParse(parser, transform(code, null), undefined, PARSE_OPTIONS);
380
+ if (tree) csViewsByTree.set(tree, { code, views: null });
381
+ return tree;
382
+ }
383
+ const lines = code.match(/.*(?:\r\n|\n|\r|$)/g)?.filter(Boolean) || [];
384
+ const resolved = entry.conditionalViews.map(group => ({ branches: group.slice(0, -2),
385
+ end: group[group.length - 2], primary: group[group.length - 1] }));
386
+ const views = csViewsFromGroups(parser, code, lines, resolved, null, transform);
387
+ for (const view of [views.primary, ...views.secondary]) csViewsByTree.set(view.tree, { code, views });
388
+ return views.primary.tree;
389
+ }
390
+
364
391
  function namespaceDeclarationName(declaration) {
365
392
  const nameNode = declaration.childForFieldName('name') ||
366
393
  declaration.namedChildren.find(child =>
@@ -414,16 +441,7 @@ function namespaceOf(node, tree) {
414
441
  // The file-scoped namespace declaration of a compilation unit, also when a
415
442
  // conditional wraps the whole file (`#if X` ... `namespace N;` ... `#endif`,
416
443
  // fix #391).
417
- function fileScopedNamespace(root) {
418
- for (const child of root?.namedChildren || []) {
419
- if (child.type === 'file_scoped_namespace_declaration') return child;
420
- if (child.type.startsWith('preproc_')) {
421
- const nested = fileScopedNamespace(child);
422
- if (nested) return nested;
423
- }
424
- }
425
- return null;
426
- }
444
+ const { fileScopedNamespace } = require('./csharp-scope');
427
445
 
428
446
  function modifiersOf(node) {
429
447
  return (node.namedChildren || [])
@@ -551,7 +569,7 @@ function memberFromNode(node, className, lines) {
551
569
  if (!name) return null;
552
570
  const paramsNode = node.childForFieldName('parameters');
553
571
  const returnNode = node.childForFieldName('returns') || node.childForFieldName('type');
554
- const { startLine, endLine, indent } = nodeToLocation(node, lines);
572
+ const { startLine, endLine, indent, sourceSpan } = nodeToLocation(node, lines);
555
573
  const attrs = attributeData(node);
556
574
  const modifiers = modifiersOf(node);
557
575
  const isConstructor = node.type === 'constructor_declaration';
@@ -562,27 +580,33 @@ function memberFromNode(node, className, lines) {
562
580
  const explicitInterface = explicitInterfaceOf(node);
563
581
  const paramsStructured = structuredParams(paramsNode);
564
582
  const generics = typeParameterNames(node);
583
+ const typeParameterConstraints = generics ? typeParameterConstraintMap(node) : null;
565
584
  // fix #380: an extension receiver typed by a METHOD type parameter
566
585
  // (`static T Ext<T>(this T e) where T : Exception`) accepts any type
567
586
  // satisfying the parameter's constraints.
568
- const receiverParam = paramsStructured[0];
569
- if (receiverParam?.extensionReceiver && receiverParam.type) {
570
- const typeParams = node.childForFieldName('type_parameters');
571
- const isTypeParam = (typeParams?.namedChildren || []).some(child =>
572
- child.type === 'type_parameter' && child.childForFieldName('name')?.text === receiverParam.type);
573
- if (isTypeParam) {
587
+ // fix #434: every parameter typed by a method type parameter carries
588
+ // that parameter's type constraints (`Send<TRequest>(TRequest r) where
589
+ // TRequest : IRequest`): an argument of a type that satisfies none of
590
+ // them makes the method inapplicable (C# 7.3 candidate pruning).
591
+ const typeParams = node.childForFieldName('type_parameters');
592
+ const ownTypeParams = new Set((typeParams?.namedChildren || []).filter(child => child.type === 'type_parameter')
593
+ .map(child => child.childForFieldName('name')?.text).filter(Boolean));
594
+ if (ownTypeParams.size > 0) {
595
+ paramsStructured.forEach((param, position) => {
596
+ const type = String(param?.type || '').replace(/\?$/, '');
597
+ if (!ownTypeParams.has(type)) return;
574
598
  const constraints = [];
575
599
  for (const clause of node.namedChildren) {
576
600
  if (clause.type !== 'type_parameter_constraints_clause') continue;
577
- if (clause.namedChild(0)?.text !== receiverParam.type) continue;
601
+ if (clause.namedChild(0)?.text !== type) continue;
578
602
  for (const constraint of clause.namedChildren.slice(1)) {
579
603
  const typeNode = constraint.childForFieldName('type');
580
604
  if (typeNode) constraints.push(nodeTextWithoutComments(typeNode));
581
605
  }
582
606
  }
583
- paramsStructured[0] = { ...receiverParam, typeParameter: true,
607
+ paramsStructured[position] = { ...param, typeParameter: true,
584
608
  ...(constraints.length > 0 && { constraints }) };
585
- }
609
+ });
586
610
  }
587
611
  return {
588
612
  name,
@@ -591,10 +615,12 @@ function memberFromNode(node, className, lines) {
591
615
  returnType: isConstructor ? null : nodeTextWithoutComments(returnNode).trim() || null,
592
616
  startLine,
593
617
  endLine,
618
+ sourceSpan,
594
619
  ...nameLineOf(nameNode, startLine),
595
620
  indent,
596
621
  modifiers,
597
622
  ...(generics && { generics }),
623
+ ...(typeParameterConstraints && { typeParameterConstraints }),
598
624
  memberType: isConstructor ? 'constructor' : 'method',
599
625
  isMethod: true,
600
626
  isConstructor,
@@ -623,11 +649,12 @@ function fieldMembers(node, lines) {
623
649
  if (declarator.type !== 'variable_declarator') continue;
624
650
  const nameNode = declarator.childForFieldName('name');
625
651
  if (!nameNode?.text) continue;
626
- const { startLine, endLine, indent } = nodeToLocation(declarator, lines);
652
+ const { startLine, endLine, indent, sourceSpan } = nodeToLocation(declarator, lines);
627
653
  members.push({
628
654
  name: nameNode.text,
629
655
  startLine,
630
656
  endLine,
657
+ sourceSpan,
631
658
  indent,
632
659
  modifiers: modifiersOf(node),
633
660
  memberType: 'field',
@@ -651,7 +678,7 @@ function indexerMember(node, className, lines) {
651
678
  if (node.type !== 'indexer_declaration') return null;
652
679
  const paramsNode = node.childForFieldName('parameters');
653
680
  const typeNode = node.childForFieldName('type');
654
- const { startLine, endLine, indent } = nodeToLocation(node, lines);
681
+ const { startLine, endLine, indent, sourceSpan } = nodeToLocation(node, lines);
655
682
  const explicitInterface = explicitInterfaceOf(node);
656
683
  return {
657
684
  name: 'this[]',
@@ -660,6 +687,7 @@ function indexerMember(node, className, lines) {
660
687
  returnType: nodeTextWithoutComments(typeNode).trim() || null,
661
688
  startLine,
662
689
  endLine,
690
+ sourceSpan,
663
691
  ...nameLineOf((node.children || []).find(child => child.type === 'this') || null, startLine),
664
692
  indent,
665
693
  modifiers: modifiersOf(node),
@@ -680,12 +708,13 @@ function propertyMember(node, lines) {
680
708
  // The real declaration is already indexed; reject the missing-node
681
709
  // artifact instead of letting one invalid symbol discard the whole file.
682
710
  if (!nameNode?.text) return null;
683
- const { startLine, endLine, indent } = nodeToLocation(node, lines);
711
+ const { startLine, endLine, indent, sourceSpan } = nodeToLocation(node, lines);
684
712
  const explicitInterface = explicitInterfaceOf(node);
685
713
  return {
686
714
  name: nameNode.text,
687
715
  startLine,
688
716
  endLine,
717
+ sourceSpan,
689
718
  ...nameLineOf(nameNode, startLine),
690
719
  indent,
691
720
  modifiers: modifiersOf(node),
@@ -703,6 +732,26 @@ function typeParameterCount(node) {
703
732
  return list.namedChildren.filter(child => child.type === 'type_parameter').length;
704
733
  }
705
734
 
735
+ // The constructed type is the terminal AST name. A generic containing
736
+ // type keeps its written arguments in the qualifier for identity lookup.
737
+ function constructionType(node) {
738
+ let name = node, qualifier = null;
739
+ if (name?.type === 'qualified_name') {
740
+ qualifier = name.childForFieldName('qualifier');
741
+ name = name.childForFieldName('name');
742
+ } else if (name?.type === 'alias_qualified_name') {
743
+ qualifier = name.childForFieldName('alias');
744
+ name = name.childForFieldName('name');
745
+ }
746
+ const typeArgs = name?.type === 'generic_name'
747
+ ? name.namedChildren.find(child => child.type === 'type_argument_list')?.namedChildCount || 0 : 0;
748
+ if (name?.type === 'generic_name') name = name.namedChild(0);
749
+ // Predefined keywords (`new object()`) cannot name project types.
750
+ // Escaped names (`new @object()`) are identifiers and keep their records.
751
+ if (name?.type !== 'identifier') return null;
752
+ return { name: name.text, node: name, qualifier: qualifier?.text, typeArgs };
753
+ }
754
+
706
755
  // fix #390: the declared type parameter NAMES, in order (`<TState, TResult>`;
707
756
  // variance and attributes dropped), so a base clause `: Visitor<TextWriter,
708
757
  // bool>` can be read as a substitution of those names.
@@ -720,6 +769,28 @@ function typeParameterNames(node) {
720
769
  return names.length > 0 ? `<${names.join(', ')}>` : null;
721
770
  }
722
771
 
772
+ /**
773
+ * The type constraints a declaration's `where` clauses give its type
774
+ * parameters (fix #434): { T: ['IParsable<T>'] }, types only (`class`,
775
+ * `struct`, `new()` and `notnull` constrain no member lookup), or null.
776
+ */
777
+ function typeParameterConstraintMap(node) {
778
+ const out = {};
779
+ let any = false;
780
+ for (const clause of node?.namedChildren || []) {
781
+ if (clause.type !== 'type_parameter_constraints_clause') continue;
782
+ const target = clause.namedChild(0)?.text;
783
+ if (!target) continue;
784
+ for (const constraint of clause.namedChildren.slice(1)) {
785
+ const typeNode = constraint.childForFieldName('type');
786
+ if (!typeNode) continue;
787
+ (out[target] ||= []).push(nodeTextWithoutComments(typeNode));
788
+ any = true;
789
+ }
790
+ }
791
+ return any ? out : null;
792
+ }
793
+
723
794
  // fix #390: the line of the declared name when attributes on their own lines
724
795
  // start the declaration (`[Obsolete]` above `void M()`): definition edits and
725
796
  // definition-line classification use it.
@@ -815,7 +886,7 @@ function findClassesInTree(code, tree) {
815
886
  // function-pointer typedef.
816
887
  const delegateName = node.childForFieldName('name');
817
888
  if (delegateName) {
818
- const { startLine, endLine, indent } = nodeToLocation(node, lines);
889
+ const { startLine, endLine, indent, sourceSpan } = nodeToLocation(node, lines);
819
890
  const delegateArity = typeParameterCount(node);
820
891
  const delegateGenerics = typeParameterNames(node);
821
892
  // fix #393: the delegate's parameter count, the count a
@@ -833,6 +904,7 @@ function findClassesInTree(code, tree) {
833
904
  ...(Number.isInteger(delegateParams) && { delegateParams }),
834
905
  startLine,
835
906
  endLine,
907
+ sourceSpan,
836
908
  ...nameLineOf(delegateName, startLine),
837
909
  indent,
838
910
  modifiers: modifiersOf(node),
@@ -868,11 +940,12 @@ function findClassesInTree(code, tree) {
868
940
  const enumName = child.childForFieldName('name') ||
869
941
  child.namedChildren.find(item => item.type === 'identifier');
870
942
  if (enumName) {
871
- const { startLine, endLine, indent } = nodeToLocation(child, lines);
943
+ const { startLine, endLine, indent, sourceSpan } = nodeToLocation(child, lines);
872
944
  members.push({
873
945
  name: enumName.text,
874
946
  startLine,
875
947
  endLine,
948
+ sourceSpan,
876
949
  ...nameLineOf(enumName, startLine),
877
950
  indent,
878
951
  modifiers: ['public', 'static'],
@@ -893,7 +966,7 @@ function findClassesInTree(code, tree) {
893
966
  }
894
967
  const baseList = node.namedChildren.find(child => child.type === 'base_list');
895
968
  const bases = baseList?.namedChildren.map(child => child.text) || [];
896
- const { startLine, endLine, indent } = nodeToLocation(node, lines);
969
+ const { startLine, endLine, indent, sourceSpan } = nodeToLocation(node, lines);
897
970
  const attrs = attributeData(node);
898
971
  let enclosingType;
899
972
  for (let parent = node.parent; parent; parent = parent.parent) {
@@ -906,6 +979,7 @@ function findClassesInTree(code, tree) {
906
979
  for (const member of members) member.ownerTypeArity = typeArity;
907
980
  }
908
981
  const generics = typeParameterNames(node);
982
+ const typeParameterConstraints = generics ? typeParameterConstraintMap(node) : null;
909
983
  // A primary constructor's parameters (`class C(int a)`, positional
910
984
  // records): the constructor the type declares in its header (fix #400).
911
985
  const primaryList = node.namedChildren.find(child => child.type === 'parameter_list');
@@ -915,10 +989,12 @@ function findClassesInTree(code, tree) {
915
989
  ...(primaryList && { primaryParams: structuredParams(primaryList) }),
916
990
  startLine,
917
991
  endLine,
992
+ sourceSpan,
918
993
  ...nameLineOf(nameNode, startLine),
919
994
  indent,
920
995
  modifiers: modifiersOf(node),
921
996
  ...(typeArity > 0 && { typeArity }),
997
+ ...(typeParameterConstraints && { typeParameterConstraints }),
922
998
  ...(generics && { generics }),
923
999
  ...(enclosingType && { enclosingType }),
924
1000
  ...(namespaceOf(node, tree) && { namespace: namespaceOf(node, tree) }),
@@ -991,7 +1067,7 @@ function findFunctionsInTree(code, tree, reaches = null) {
991
1067
  }
992
1068
  const paramsNode = node.childForFieldName('parameters');
993
1069
  const returnNode = node.childForFieldName('returns') || node.childForFieldName('type');
994
- const { startLine, endLine, indent } = nodeToLocation(node, lines);
1070
+ const { startLine, endLine, indent, sourceSpan } = nodeToLocation(node, lines);
995
1071
  const modifiers = modifiersOf(node);
996
1072
  const localGenerics = typeParameterNames(node);
997
1073
  functions.push({
@@ -1001,6 +1077,7 @@ function findFunctionsInTree(code, tree, reaches = null) {
1001
1077
  returnType: nodeTextWithoutComments(returnNode).trim() || null,
1002
1078
  startLine,
1003
1079
  endLine,
1080
+ sourceSpan,
1004
1081
  ...nameLineOf(nameNode, startLine),
1005
1082
  indent,
1006
1083
  modifiers,
@@ -1051,11 +1128,12 @@ function findStateObjectsInTree(code, tree, reaches = null) {
1051
1128
  if (declarator.type !== 'variable_declarator') continue;
1052
1129
  const nameNode = declarator.childForFieldName('name');
1053
1130
  if (!nameNode) continue;
1054
- const { startLine, endLine, indent } = nodeToLocation(node, lines);
1131
+ const { startLine, endLine, indent, sourceSpan } = nodeToLocation(node, lines);
1055
1132
  states.push({
1056
1133
  name: nameNode.text,
1057
1134
  startLine,
1058
1135
  endLine,
1136
+ sourceSpan,
1059
1137
  indent,
1060
1138
  modifiers: [],
1061
1139
  });
@@ -1433,18 +1511,57 @@ const CALLABLE_SCOPE_NODES = new Set([
1433
1511
  'local_function_statement', 'accessor_declaration',
1434
1512
  ]);
1435
1513
 
1436
- function variableScopeKey(node) {
1514
+ // Names a local function declares itself (parameters, locals, pattern
1515
+ // and foreach variables, catch and query variables): they hide the
1516
+ // enclosing callable's (fix #434).
1517
+ const CS_DECLARING_NODES = new Set(['parameter', 'variable_declarator', 'single_variable_designation',
1518
+ 'foreach_statement', 'catch_declaration', 'from_clause', 'let_clause', 'join_clause']);
1519
+ function csDeclaredNames(scopeNode) {
1520
+ const names = new Set();
1521
+ traverseTree(scopeNode, node => {
1522
+ if (!CS_DECLARING_NODES.has(node.type)) return true;
1523
+ const name = node.type === 'single_variable_designation'
1524
+ ? node.namedChild(0) || node
1525
+ : node.childForFieldName('name') || node.childForFieldName('left') ||
1526
+ node.namedChildren.find(child => child.type === 'identifier');
1527
+ if (name?.type === 'identifier') names.add(name.text);
1528
+ return true;
1529
+ });
1530
+ return names;
1531
+ }
1532
+
1533
+ /**
1534
+ * The variable types in scope at a node (fix #434): a local function
1535
+ * also reads the typed locals of the callables around it (C# closures
1536
+ * capture them), except the names it declares itself.
1537
+ */
1538
+ function scopedVariableTypes(byScope, node, memo) {
1539
+ let scopeNode = null;
1437
1540
  for (let parent = node?.parent; parent; parent = parent.parent) {
1438
- if (CALLABLE_SCOPE_NODES.has(parent.type)) {
1439
- if (isControlFlowLocalArtifact(parent)) continue;
1440
- return parent.startPosition.row + 1;
1441
- }
1442
- if (parent.type === 'global_statement') return 'global';
1443
- }
1444
- return 'global';
1541
+ if (CALLABLE_SCOPE_NODES.has(parent.type) && !isControlFlowLocalArtifact(parent)) { scopeNode = parent; break; }
1542
+ if (parent.type === 'global_statement') break;
1543
+ }
1544
+ const own = (scopeNode && byScope.get(scopeNode.startPosition.row + 1)) || byScope.get('global');
1545
+ if (scopeNode?.type !== 'local_function_statement') return own;
1546
+ const key = scopeNode.startPosition.row + 1;
1547
+ if (memo.has(key)) return memo.get(key);
1548
+ const outer = scopedVariableTypes(byScope, scopeNode, memo);
1549
+ let merged = own;
1550
+ if (outer && outer !== own && outer.size > 0) {
1551
+ const declared = csDeclaredNames(scopeNode);
1552
+ merged = new ReceiverTypeMap();
1553
+ for (const [name, type] of outer) {
1554
+ if (!declared.has(name) && !own?.has(name)) merged.set(name, type, outer.origins.get(name) || 'unknown');
1555
+ }
1556
+ for (const [name, type] of own || []) merged.set(name, type, own.origins.get(name) || 'unknown');
1557
+ }
1558
+ memo.set(key, merged);
1559
+ return merged;
1445
1560
  }
1446
1561
 
1447
1562
  const localBindingFactsByTree = new WeakMap();
1563
+ // Nodes whose identifier children can be deconstruction designations.
1564
+ const DECONSTRUCTION_PARENTS = new Set(['tuple_pattern', 'parenthesized_variable_designation', 'argument']);
1448
1565
 
1449
1566
  /**
1450
1567
  * Local binding facts of one callable (fix #381): how many times each name
@@ -1475,6 +1592,10 @@ function csharpLocalBindingFacts(fnNode) {
1475
1592
  }
1476
1593
  } else if (child.type === 'declaration_pattern' || child.type === 'declaration_expression') {
1477
1594
  declare(child.childForFieldName('name')?.text || child.namedChildren.at(-1)?.text);
1595
+ } else if (child.type === 'identifier' && DECONSTRUCTION_PARENTS.has(node.type) &&
1596
+ require('./csharp-scope').deconstructionRoot(child)) {
1597
+ // `var (a, b) = t;`, `case var (a, b):` (fix #419).
1598
+ declare(child.text);
1478
1599
  } else if (child.type === 'assignment_expression') {
1479
1600
  const left = child.childForFieldName('left');
1480
1601
  if (left?.type === 'identifier') facts.assigned.add(left.text);
@@ -1573,7 +1694,10 @@ function buildVariableTypes(tree, parser) {
1573
1694
  const conflictsByScope = new Map([['global', new Set()]]);
1574
1695
  const scopeStack = [];
1575
1696
  const setType = (scope, name, type, source = 'unknown', node = null) => {
1576
- if (!name || !type) return;
1697
+ // An implicitly typed declaration (`out var f`, `is var x` in a
1698
+ // recovered condition) writes no type: the name is a local of
1699
+ // unknown type, never a value of a type named `var` (fix #426).
1700
+ if (!name || !type || type === 'var') return;
1577
1701
  if (!conflictsByScope.has(scope)) conflictsByScope.set(scope, new Set());
1578
1702
  const conflicts = conflictsByScope.get(scope);
1579
1703
  if (conflicts.has(name)) return;
@@ -1738,6 +1862,21 @@ function buildVariableTypes(tree, parser) {
1738
1862
  return byScope;
1739
1863
  }
1740
1864
 
1865
+ // Type spellings held in local bindings have no retained AST node. Parse
1866
+ // them on demand and keep bounded data, never nodes, across worker files.
1867
+ const constructedTypeSyntax = new Map();
1868
+ function constructionTypeText(text) {
1869
+ if (constructedTypeSyntax.has(text)) return constructedTypeSyntax.get(text);
1870
+ const tree = safeParse(require('./index').getParser('csharp'), `class TypeSlot { ${text} field; }`);
1871
+ const type = tree && !tree.rootNode.hasError
1872
+ ? tree.rootNode.descendantsOfType('variable_declaration')[0]?.childForFieldName('type') : null;
1873
+ const shape = constructionType(type);
1874
+ const result = shape ? { name: shape.name, qualifier: shape.qualifier, typeArgs: shape.typeArgs } : null;
1875
+ if (constructedTypeSyntax.size >= 256) constructedTypeSyntax.delete(constructedTypeSyntax.keys().next().value);
1876
+ constructedTypeSyntax.set(text, result);
1877
+ return result;
1878
+ }
1879
+
1741
1880
  function normalizeReceiverType(raw) {
1742
1881
  if (!raw) return null;
1743
1882
  let value = String(raw).trim().replace(/\?$/, '');
@@ -1746,6 +1885,10 @@ function normalizeReceiverType(raw) {
1746
1885
  // this index cannot attribute (fix #401): no receiver type.
1747
1886
  if (/^(?!global::)[A-Za-z_]\w*::/.test(value)) return null;
1748
1887
  value = value.replace(/^global::/, '').replace(/::/g, '.');
1888
+ if (hasParameterizedQualifier(value)) {
1889
+ const shape = constructionTypeText(value);
1890
+ return shape ? { name: shape.name, namespace: shape.qualifier, typeArgs: shape.typeArgs } : null;
1891
+ }
1749
1892
  const match = value.match(/^([A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*)\s*(?:<.*>)?$/s);
1750
1893
  if (!match) return null;
1751
1894
  const parts = match[1].split('.');
@@ -1911,6 +2054,31 @@ function receiverFieldPath(node, variableTypes, enclosingClass) {
1911
2054
  return null;
1912
2055
  }
1913
2056
 
2057
+ /**
2058
+ * A receiver expression's text with the null-forgiving operator removed
2059
+ * (fix #430): `TraceWriter!.Trace(..)` calls `Trace` on `TraceWriter`, the
2060
+ * operator changes no type or lookup.
2061
+ */
2062
+ function nullForgivingFreeText(node) {
2063
+ if (!node) return node?.text;
2064
+ const cuts = [];
2065
+ const visit = current => {
2066
+ if (current.type === 'postfix_unary_expression' && current.childCount === 2 &&
2067
+ current.child(1).type === '!') cuts.push(current.child(1));
2068
+ if (current.type === 'argument_list' || current.type === 'lambda_expression') return;
2069
+ for (let i = 0; i < current.namedChildCount; i++) visit(current.namedChild(i));
2070
+ };
2071
+ visit(node);
2072
+ if (cuts.length === 0) return node.text;
2073
+ let text = '', at = node.startIndex;
2074
+ const source = node.text;
2075
+ for (const cut of cuts.sort((a, b) => a.startIndex - b.startIndex)) {
2076
+ text += source.slice(at - node.startIndex, cut.startIndex - node.startIndex);
2077
+ at = cut.endIndex;
2078
+ }
2079
+ return text + source.slice(at - node.startIndex);
2080
+ }
2081
+
1914
2082
  function invocationIdentity(node) {
1915
2083
  if (!node) return {};
1916
2084
  if (node.type === 'identifier' || node.type === 'generic_name') {
@@ -1924,7 +2092,7 @@ function invocationIdentity(node) {
1924
2092
  return {
1925
2093
  name: nameNode?.type === 'generic_name' ? nameNode.namedChild(0)?.text : nameNode?.text,
1926
2094
  nameNode,
1927
- receiver: expression?.text,
2095
+ receiver: nullForgivingFreeText(expression),
1928
2096
  isMethod: true,
1929
2097
  };
1930
2098
  }
@@ -1939,7 +2107,7 @@ function invocationIdentity(node) {
1939
2107
  name: nameNode?.type === 'generic_name'
1940
2108
  ? nameNode.namedChild(0)?.text : nameNode?.text,
1941
2109
  nameNode,
1942
- receiver: expression?.text,
2110
+ receiver: nullForgivingFreeText(expression),
1943
2111
  isMethod: true,
1944
2112
  };
1945
2113
  }
@@ -1987,6 +2155,15 @@ function staticMemberPath(node, variableTypes) {
1987
2155
  function staticArgKind(node, variableTypes) {
1988
2156
  if (!node) return 'expr';
1989
2157
  switch (node.type) {
2158
+ case 'this': {
2159
+ // `this` is an instance of the enclosing type (fix #430); a
2160
+ // generic type's own instance stays unknown.
2161
+ let owner = node.parent;
2162
+ while (owner && !TYPE_DECLARATIONS.has(owner.type)) owner = owner.parent;
2163
+ const name = owner?.childForFieldName('name')?.text;
2164
+ return name && !owner.childForFieldName('type_parameters') &&
2165
+ !owner.namedChildren.some(child => child.type === 'type_parameter_list') ? `type:${name}` : 'expr';
2166
+ }
1990
2167
  case 'string_literal':
1991
2168
  case 'verbatim_string_literal':
1992
2169
  case 'interpolated_string_expression':
@@ -2036,6 +2213,11 @@ function staticArgKind(node, variableTypes) {
2036
2213
  }
2037
2214
  case 'invocation_expression': {
2038
2215
  const identity = invocationIdentity(node.childForFieldName('function'));
2216
+ // An untyped local or member remains a value binding. Its
2217
+ // spelling cannot identify a same-named producer class.
2218
+ if (identity.receiver && !variableTypes?.has(identity.receiver) &&
2219
+ (csharpBareNameShadowedByLocal(node, identity.receiver) ||
2220
+ enclosingTypeDeclaresMember(node, identity.receiver))) return 'expr';
2039
2221
  if (identity.name === 'GetValueOrDefault' && identity.receiver) {
2040
2222
  const rawType = variableTypes?.get(identity.receiver);
2041
2223
  if (rawType?.trim().endsWith('?')) {
@@ -2109,11 +2291,16 @@ function staticArgKind(node, variableTypes) {
2109
2291
  case 'anonymous_method_expression': {
2110
2292
  // Its parameter count selects among delegate overloads (fix
2111
2293
  // #391); `delegate { }` without a list converts to any count.
2294
+ // An implicitly typed lambda (`x => ..`, `(a, b) => ..`) is
2295
+ // marked `:i`: its parameter types come from the target
2296
+ // delegate, never the other way (fix #428).
2112
2297
  const params = node.childForFieldName('parameters');
2113
2298
  if (!params) return 'lambda';
2114
- return params.type === 'parameter_list'
2115
- ? `lambda:${params.namedChildren.filter(child => child.type === 'parameter').length}`
2116
- : 'lambda:1';
2299
+ if (params.type !== 'parameter_list') return 'lambda:1:i';
2300
+ const list = params.namedChildren.filter(child => child.type === 'parameter');
2301
+ const implicit = node.type === 'lambda_expression' && list.length > 0 &&
2302
+ list.every(child => !child.childForFieldName('type'));
2303
+ return `lambda:${list.length}${implicit ? ':i' : ''}`;
2117
2304
  }
2118
2305
  case 'binary_expression': {
2119
2306
  // String concatenation: `+` with a string operand is a string
@@ -2134,7 +2321,8 @@ function callArgs(node, variableTypes) {
2134
2321
  node.namedChildren.find(child => child.type === 'argument_list');
2135
2322
  const args = (argsNode?.namedChildren || []).filter(child => child.type === 'argument');
2136
2323
  const argKinds = args.map(argument =>
2137
- staticArgKind(argument.namedChild(0), variableTypes));
2324
+ staticArgKind(argument.namedChild(0) ||
2325
+ (argument.childCount === 1 && argument.child(0)?.type === 'this' ? argument.child(0) : null), variableTypes));
2138
2326
  return {
2139
2327
  argCount: args.length,
2140
2328
  ...(argKinds.some(kind => kind !== 'expr') && { argKinds }),
@@ -2272,13 +2460,22 @@ function findCallsInCode(code, parser) {
2272
2460
  (a.callSite?.column ?? 0) - (b.callSite?.column ?? 0));
2273
2461
  }
2274
2462
 
2463
+ /** Omitted base initializer calls of one class, read as findCallsInCode reads them (fix #421). */
2464
+ function implicitBaseCallsOf(code, parser, ownerLine, ownerEndLine) {
2465
+ const extract = (tree, reaches) => require('./constructor-bases').implicitBaseCallsOfOwner(tree.rootNode, 'csharp',
2466
+ reaches, ownerLine, ownerEndLine);
2467
+ const views = csharpViews(parser, code);
2468
+ if (!views) return extract(safeParse(parser, code, undefined, PARSE_OPTIONS), null);
2469
+ return csMergeViewItems(views, extract, call => call.line);
2470
+ }
2471
+
2275
2472
  function findCallsInTree(code, parser, tree, reaches = null) {
2276
2473
  const variableTypesByScope = buildVariableTypes(tree, parser);
2277
2474
  const calls = [];
2278
2475
  // The variable types in scope at a call (only call nodes ask: the scope
2279
2476
  // walk climbs parents).
2280
- const typesAt = node => variableTypesByScope.get(variableScopeKey(node)) ||
2281
- variableTypesByScope.get('global');
2477
+ const scopeMemo = new Map();
2478
+ const typesAt = node => scopedVariableTypes(variableTypesByScope, node, scopeMemo);
2282
2479
  traverseTree(tree.rootNode, node => {
2283
2480
  if (reaches && !reaches(node)) return false;
2284
2481
  if (isControlFlowLocalArtifact(node)) {
@@ -2300,6 +2497,26 @@ function findCallsInTree(code, parser, tree, reaches = null) {
2300
2497
  // contains valid invocation AST nodes.
2301
2498
  return true;
2302
2499
  }
2500
+ if (node.type === 'constructor_initializer') {
2501
+ let owner = node.parent;
2502
+ while (owner && !TYPE_DECLARATIONS.has(owner.type)) owner = owner.parent;
2503
+ const isBase = node.children.some(child => child.type === 'base');
2504
+ const baseList = owner?.namedChildren.find(child => child.type === 'base_list');
2505
+ const type = isBase ? baseList?.namedChild(0) : owner?.childForFieldName('name');
2506
+ if (!type) return true;
2507
+ const constructed = constructionType(type);
2508
+ if (!constructed) return true;
2509
+ const typeArgs = isBase ? constructed.typeArgs : typeParameterCount(owner);
2510
+ const args = callArgs(node, typesAt(node));
2511
+ calls.push({ callSite: typeOrigin('call', node), name: constructed.name,
2512
+ line: node.startPosition.row + 1, isMethod: false, isConstructor: true,
2513
+ constructorSyntax: 'delegation', ctorDelegation: isBase ? 'super' : 'this',
2514
+ ...(constructed.qualifier && { constructorQualifier: constructed.qualifier }),
2515
+ ...(typeArgs > 0 && { typeArgs }),
2516
+ argCount: args.argCount, ...(args.argKinds && { argKinds: args.argKinds }),
2517
+ enclosingFunction: enclosingFunctionOf(node) });
2518
+ return true;
2519
+ }
2303
2520
  if (node.type === 'invocation_expression') {
2304
2521
  const identity = invocationIdentity(node.childForFieldName('function'));
2305
2522
  if (!identity.name) return true;
@@ -2515,13 +2732,13 @@ function findCallsInTree(code, parser, tree, reaches = null) {
2515
2732
  }
2516
2733
  if (!typeNode) return true;
2517
2734
  const args = callArgs(node, typesAt(node));
2518
- const raw = typeNode.text.replace(/<.*>$/, '');
2519
- const name = raw.split('.').pop();
2520
- const typeArgs = genericArityOf(typeNode.text);
2735
+ const constructed = typeNode.type ? constructionType(typeNode) : constructionTypeText(typeNode.text);
2736
+ if (!constructed) return true;
2737
+ const { name, typeArgs } = constructed;
2521
2738
  // A target-typed site is the `new` token; its type is written
2522
2739
  // elsewhere.
2523
2740
  const targetTyped = !node.childForFieldName('type');
2524
- const siteNode = targetTyped ? (node.child(0) || node) : typeNode;
2741
+ const siteNode = targetTyped ? (node.child(0) || node) : constructed.node;
2525
2742
  calls.push({
2526
2743
  callSite: typeOrigin('call', siteNode),
2527
2744
  name,
@@ -2529,6 +2746,7 @@ function findCallsInTree(code, parser, tree, reaches = null) {
2529
2746
  ...(targetTyped && { targetTyped: true }),
2530
2747
  isMethod: false,
2531
2748
  isConstructor: true,
2749
+ ...(constructed.qualifier && { constructorQualifier: constructed.qualifier }),
2532
2750
  ...(typeArgs > 0 && { typeArgs }),
2533
2751
  argCount: args.argCount,
2534
2752
  ...(args.argKinds && { argKinds: args.argKinds }),
@@ -2537,6 +2755,7 @@ function findCallsInTree(code, parser, tree, reaches = null) {
2537
2755
  }
2538
2756
  return true;
2539
2757
  });
2758
+ calls.push(...require('./constructor-bases').implicitBaseCalls(tree.rootNode, 'csharp', reaches));
2540
2759
  return calls;
2541
2760
  }
2542
2761
 
@@ -2547,9 +2766,9 @@ function findImportsInCode(code, parser) {
2547
2766
  .sort((a, b) => a.line - b.line);
2548
2767
  }
2549
2768
 
2550
- function findImportsInTree(tree) {
2769
+ function findImportsInTree(tree, queryOnly = false) {
2551
2770
  const imports = [];
2552
- traverseTreeCached(tree.rootNode, node => {
2771
+ const collect = node => {
2553
2772
  if (node.type !== 'using_directive') return true;
2554
2773
  const nameNode = node.childForFieldName('name');
2555
2774
  const named = node.namedChildren || [];
@@ -2572,9 +2791,17 @@ function findImportsInTree(tree) {
2572
2791
  ...(isGlobal && { global: true }),
2573
2792
  ...(isStatic && { static: true }),
2574
2793
  ...(enclosing && !rooted && { namespace: enclosing }),
2794
+ // csMergeViewItems passes a reach predicate as its second arg.
2795
+ // Only this explicit query mode adds facts; build records keep
2796
+ // their existing persisted shape.
2797
+ ...(queryOnly === true && { rooted,
2798
+ usingScopes: require('./csharp-scope').usingScopesOf(node) }),
2575
2799
  });
2576
2800
  return false;
2577
- });
2801
+ };
2802
+ if (queryOnly === true) {
2803
+ for (const node of tree.rootNode.descendantsOfType('using_directive')) collect(node);
2804
+ } else traverseTreeCached(tree.rootNode, collect);
2578
2805
  return imports;
2579
2806
  }
2580
2807
 
@@ -2591,15 +2818,98 @@ function findUsagesInCode(code, name, parser, existingTree) {
2591
2818
  return usagesInTree(code, name, parser, existingTree || safeParse(parser, code, undefined, PARSE_OPTIONS));
2592
2819
  }
2593
2820
 
2821
+ // Lift only the terminal name, never a receiver or type argument, through
2822
+ // the grammar's generic/member wrappers (fix #411).
2823
+ function usageNameExpression(node) {
2824
+ let current = node;
2825
+ let parent = current.parent;
2826
+ if (parent?.type === 'generic_name' && sameNode(parent.namedChild(0), current)) {
2827
+ current = parent;
2828
+ parent = current.parent;
2829
+ }
2830
+ if ((parent?.type === 'member_access_expression' || parent?.type === 'member_binding_expression') &&
2831
+ sameNode(parent.childForFieldName('name') || parent.namedChild(0), current)) {
2832
+ current = parent;
2833
+ parent = current.parent;
2834
+ if (current.type === 'member_binding_expression' && parent?.type === 'conditional_access_expression') {
2835
+ current = parent;
2836
+ }
2837
+ }
2838
+ return current;
2839
+ }
2840
+
2841
+ function methodGroupReceiver(node) {
2842
+ const expression = usageNameExpression(node);
2843
+ const receiver = expression.childForFieldName('expression') || expression.childForFieldName('condition');
2844
+ const type = require('./csharp-scope').namedType(receiver, node);
2845
+ if (!type) return null;
2846
+ const argumentsNode = type.node.parent?.type === 'generic_name'
2847
+ ? type.node.parent.namedChildren.find(child => child.type === 'type_argument_list') : null;
2848
+ return { name: type.name, qualifier: type.qualifierText,
2849
+ arity: argumentsNode?.namedChildCount || 0 };
2850
+ }
2851
+
2852
+ function invokesNameExpression(expression) {
2853
+ return expression.parent?.type === 'invocation_expression' &&
2854
+ sameNode(expression.parent.childForFieldName('function'), expression);
2855
+ }
2856
+
2857
+ function isInvokedName(node, options = {}) {
2858
+ const expression = usageNameExpression(node);
2859
+ if (options.unqualified && expression.type !== 'identifier' && expression.type !== 'generic_name') return false;
2860
+ return invokesNameExpression(expression);
2861
+ }
2862
+
2863
+ /** Only declarations can give the local-type map a receiver's name. */
2864
+ function possibleReceiverBinding(node, name, memo) {
2865
+ const member = csharpEnclosingCallable(node);
2866
+ if (!member || member.hasError) return true;
2867
+ const key = `${member.id}\0${name}`;
2868
+ if (memo.has(key)) return memo.get(key);
2869
+ const text = member.text;
2870
+ let possible = false;
2871
+ for (let at = text.indexOf(name); at >= 0; at = text.indexOf(name, at + name.length)) {
2872
+ const token = member.descendantForIndex(member.startIndex + at, member.startIndex + at + name.length);
2873
+ if (!token || token.text !== name) continue;
2874
+ const parent = token.parent;
2875
+ if (token.type !== 'identifier' || parent?.type !== 'member_access_expression' ||
2876
+ !sameNode(parent.childForFieldName('expression'), token)) {
2877
+ possible = true;
2878
+ break;
2879
+ }
2880
+ }
2881
+ if (!possible) {
2882
+ let rows = memo.get('scope-rows');
2883
+ if (!rows) {
2884
+ rows = new Map();
2885
+ for (const scope of node.tree.rootNode.descendantsOfType([...CALLABLE_SCOPE_NODES])) {
2886
+ if (isControlFlowLocalArtifact(scope)) continue;
2887
+ const row = scope.startPosition.row;
2888
+ rows.set(row, (rows.get(row) || 0) + 1);
2889
+ }
2890
+ memo.set('scope-rows', rows);
2891
+ }
2892
+ // Scopes sharing a declaration row contribute to the same local-type
2893
+ // map, so another callable on that row can carry the binding.
2894
+ if (rows.get(member.startPosition.row) !== 1) possible = true;
2895
+ }
2896
+ memo.set(key, possible);
2897
+ return possible;
2898
+ }
2899
+
2594
2900
  function usagesInTree(code, name, parser, tree) {
2595
2901
  const usages = [];
2596
- const variableTypesByScope = buildVariableTypes(tree, parser);
2902
+ const receiverBindings = new Map();
2903
+ let variableTypesByScope;
2904
+ const scopeMemo = new Map();
2597
2905
  visitNameNodes(tree, code, name, node => {
2598
2906
  if (!IDENTIFIER_NODES.has(node.type) || node.text !== name) return;
2599
2907
  let usageType = 'reference';
2600
2908
  const parent = node.parent;
2909
+ const expression = usageNameExpression(node);
2601
2910
  if (parent) {
2602
2911
  if ((parent.type === 'method_declaration' ||
2912
+ parent.type === 'local_function_statement' ||
2603
2913
  parent.type === 'constructor_declaration' ||
2604
2914
  TYPE_DECLARATIONS.has(parent.type) ||
2605
2915
  parent.type === 'property_declaration' ||
@@ -2608,21 +2918,23 @@ function usagesInTree(code, name, parser, tree) {
2608
2918
  parent.type === 'variable_declarator') &&
2609
2919
  (sameNode(parent.childForFieldName('name'), node))) {
2610
2920
  usageType = 'definition';
2611
- } else if (parent.type === 'invocation_expression' ||
2612
- parent.parent?.type === 'invocation_expression') {
2921
+ } else if (invokesNameExpression(expression)) {
2613
2922
  usageType = 'call';
2614
2923
  } else if (parent.type === 'using_directive') {
2615
2924
  usageType = 'import';
2616
2925
  }
2617
- if (parent.type === 'member_access_expression' &&
2618
- sameNode(parent.childForFieldName('name'), node)) {
2619
- const receiverNode = parent.childForFieldName('expression') ||
2620
- parent.namedChild(0);
2621
- const receiver = receiverNode?.text;
2622
- const scopeTypes = variableTypesByScope.get(variableScopeKey(node)) ||
2623
- variableTypesByScope.get('global');
2624
- const declared = receiverNode?.type === 'identifier'
2625
- ? normalizeReceiverType(scopeTypes?.get(receiver)) : null;
2926
+ const identity = invocationIdentity(expression);
2927
+ if (identity.receiver) {
2928
+ const receiverNode = expression.childForFieldName('expression') ||
2929
+ expression.childForFieldName('condition') || expression.namedChild(0);
2930
+ const receiver = identity.receiver;
2931
+ let declared = null;
2932
+ if (receiverNode?.type === 'identifier' &&
2933
+ (variableTypesByScope || possibleReceiverBinding(node, receiver, receiverBindings))) {
2934
+ variableTypesByScope ||= buildVariableTypes(tree, parser);
2935
+ const scopeTypes = scopedVariableTypes(variableTypesByScope, node, scopeMemo);
2936
+ declared = normalizeReceiverType(scopeTypes?.get(receiver));
2937
+ }
2626
2938
  const sameClass = ['this', 'base'].includes(receiver)
2627
2939
  ? enclosingClassName(node) : null;
2628
2940
  usages.push({
@@ -2632,6 +2944,8 @@ function usagesInTree(code, name, parser, tree) {
2632
2944
  ...(receiver && { receiver }),
2633
2945
  ...((declared?.name || sameClass) && {
2634
2946
  receiverType: declared?.name || sameClass,
2947
+ ...(declared?.namespace && { receiverTypeQualifier: declared.namespace }),
2948
+ ...(Number.isInteger(declared?.typeArgs) && { receiverTypeArgs: declared.typeArgs }),
2635
2949
  }),
2636
2950
  });
2637
2951
  return true;
@@ -2771,6 +3085,10 @@ function parse(code, parser) {
2771
3085
  }),
2772
3086
  ...(features.length > 0 && { languageFeatures: features }),
2773
3087
  ...(views && { conditionalViews: views.groups.map(group => [...group]) }),
3088
+ // The raw parse's names: configuration views parse rewritten sources.
3089
+ ...((!views || views.typeParameterNames) && {
3090
+ typeParameterNames: views ? views.typeParameterNames : require('./utils').declaredTypeParameterNames(tree),
3091
+ }),
2774
3092
  };
2775
3093
  }
2776
3094
 
@@ -2790,7 +3108,49 @@ function exportsOfClasses(classes) {
2790
3108
  return exports;
2791
3109
  }
2792
3110
 
3111
+ const scopedUsingsByTree = new WeakMap();
3112
+ function queryUsingDirectives(tree) {
3113
+ if (scopedUsingsByTree.has(tree)) return scopedUsingsByTree.get(tree);
3114
+ const views = csViewsByTree.get(tree)?.views;
3115
+ const read = view => findImportsInTree(view, true);
3116
+ const result = views ? csMergeViewItems(views, read, item => item.line) : read(tree);
3117
+ scopedUsingsByTree.set(tree, result);
3118
+ return result;
3119
+ }
3120
+
3121
+ function accessorReferenceShape(tree, usage) {
3122
+ const views = tree && csViewsByTree.get(tree)?.views;
3123
+ const owner = views?.secondary.find(view => view.owns.has(usage.line - 1));
3124
+ return require('./csharp-scope').accessorReferenceShape(owner?.tree || tree, usage, implicitCreationTarget);
3125
+ }
3126
+
3127
+ function accessorTypeSyntax(text) {
3128
+ const parser = require('./index').getParser('csharp');
3129
+ const tree = safeParse(parser, `class TypeSlot { ${text} field; }`);
3130
+ if (!tree || tree.rootNode.hasError) return null;
3131
+ const declaration = tree.rootNode.descendantsOfType('variable_declaration')[0];
3132
+ const type = declaration?.childForFieldName('type');
3133
+ return type ? require('./csharp-scope').namedType(type, type) : null;
3134
+ }
3135
+
2793
3136
  module.exports = {
3137
+ implicitBaseCallsOf,
3138
+ implicitOwnerScope(node) {
3139
+ let enclosingType = null;
3140
+ for (let parent = node.parent; parent; parent = parent.parent) {
3141
+ if (!TYPE_DECLARATIONS.has(parent.type)) continue;
3142
+ enclosingType = parent.childForFieldName('name')?.text || null;
3143
+ break;
3144
+ }
3145
+ return { namespace: namespaceOf(node, node.tree), enclosingType };
3146
+ },
3147
+ queryBodyViews,
3148
+ queryBodyTree,
3149
+ methodGroupReceiver,
3150
+ isInvokedName,
3151
+ queryUsingDirectives,
3152
+ accessorReferenceShape,
3153
+ accessorTypeSyntax,
2794
3154
  queryTree,
2795
3155
  queryTreeIsPlain,
2796
3156
  analysisTree: queryTree,