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
@@ -307,9 +307,12 @@ function protocolMemberCertainty(index, def) {
307
307
  const lang = index.files.get(def.file)?.language;
308
308
  const predicate = langTraits(lang)?.languageProtocolMember;
309
309
  if (typeof predicate !== 'function') return null;
310
- const isMember = !!def.className;
310
+ // A C/C++ member type alias names its class as `enclosingType` (fix #427).
311
+ const isTypeMember = def.type === 'type' && !!(def.className || def.enclosingType);
312
+ const isMember = !!def.className || isTypeMember;
311
313
  const verdict = predicate(def.name, {
312
314
  isMember,
315
+ isTypeMember,
313
316
  isField: def.type === 'field' || def.type === 'property' || def.type === 'constant',
314
317
  modifiers: def.modifiers || [],
315
318
  paramTypes: Array.isArray(def.paramsStructured)
@@ -480,6 +483,18 @@ function externalContractOf(index, def) {
480
483
  line: def.nameLine || def.startLine }],
481
484
  };
482
485
  }
486
+ // A member type alias the standard library reads by name (fix #427).
487
+ if (def && !def.className && def.type === 'type' && def.enclosingType) {
488
+ const certainty = protocolMemberCertainty(index, def);
489
+ if (!certainty) return null;
490
+ return {
491
+ certainty,
492
+ via: ['standard library traits (iterator_traits, pointer_traits, container)'],
493
+ reason: 'language-protocol-member',
494
+ sites: [{ file: def.file, relativePath: def.relativePath || _rel(index, def.file),
495
+ line: def.nameLine || def.startLine }],
496
+ };
497
+ }
483
498
  if (!def || !def.className) return null;
484
499
  const defSite = {
485
500
  file: def.file,
@@ -683,8 +698,10 @@ function _typeParams(...defs) {
683
698
  /**
684
699
  * Could `member` override (occupy the same slot as) `pin` by parameter
685
700
  * types? Types compare by their terminal name; a type parameter of either
686
- * side's method or class matches anything (`m(T)` overridden as
687
- * `m(String)`).
701
+ * side's class matches anything (`m(T)` overridden as `m(String)`). Method
702
+ * type parameters correspond by position (fix #426, JLS 8.4.2 / C#
703
+ * signature identity): `Send<TRequest>(TRequest)` never takes the slot of
704
+ * `Send<TResponse>(IRequest<TResponse>)`.
688
705
  */
689
706
  function _slotParamsCompatible(index, member, pin) {
690
707
  const params = d => (d.paramsStructured || []).filter(p => !p?.extensionReceiver &&
@@ -692,13 +709,16 @@ function _slotParamsCompatible(index, member, pin) {
692
709
  const a = params(member);
693
710
  const b = params(pin);
694
711
  if (a.length !== b.length) return false;
712
+ const correspondence = _methodParamCorrespondence(index, member, pin);
713
+ if (!correspondence) return false;
695
714
  const owners = d => (d.className ? (index.symbols.get(d.className) || [])
696
715
  .filter(c => c.file === d.file && CLASS_KINDS.has(c.type)) : []);
697
- const variables = _typeParams(member, pin, ...owners(member), ...owners(pin));
716
+ const variables = _typeParams(...owners(member), ...owners(pin));
698
717
  for (let i = 0; i < a.length; i++) {
699
- const ta = _slotTypeName(_paramTypeText(a[i]));
700
- const tb = _slotTypeName(_paramTypeText(b[i]));
718
+ const ta = _slotTypeName(_substituteType(_paramTypeText(a[i]), correspondence.member));
719
+ const tb = _slotTypeName(_substituteType(_paramTypeText(b[i]), correspondence.slot));
701
720
  if (!ta || !tb || ta === tb) continue;
721
+ if (ta.startsWith('$M') || tb.startsWith('$M')) return false;
702
722
  if (variables.has(ta) || variables.has(tb)) continue;
703
723
  if (/^[A-Z]$/.test(ta) || /^[A-Z]$/.test(tb)) continue;
704
724
  return false;
@@ -706,6 +726,47 @@ function _slotParamsCompatible(index, member, pin) {
706
726
  return true;
707
727
  }
708
728
 
729
+ /** A method's own type parameters in order, with each one's erasure (its
730
+ * first bound's head, `Object` without one). */
731
+ function _methodTypeParamList(def) {
732
+ const raw = String(def?.generics || '').trim().replace(/^<|>$/g, '');
733
+ if (!raw) return [];
734
+ return _splitTypeArgs(raw).map(part => {
735
+ const text = part.replace(/@[\w.]+(?:\([^)]*\))?/g, ' ').trim();
736
+ const tokens = text.split(/[\s:=]+/).filter(Boolean);
737
+ const name = (tokens.find(token => !TYPE_PARAM_PREFIXES.has(token)) || '_').replace(/\.\.\.$/, '');
738
+ const bound = /\bextends\b/.test(text)
739
+ ? text.split(/\bextends\b/)[1].split('&')[0].replace(/<.*$/s, '').trim() : null;
740
+ return { name, erasure: bound || 'Object' };
741
+ });
742
+ }
743
+
744
+ /**
745
+ * How the method type parameters of a member and of the slot it may take
746
+ * correspond (fix #426): position by position into shared placeholders
747
+ * (`$M0`, ..), so `<U> m(U)` takes the slot `<V> m(V)` and `<Q> m(Q)` never
748
+ * takes `<R> m(Box<R>)`'s. Unequal counts: Java lets a non-generic method
749
+ * take a generic slot through the slot's erasure (JLS 8.4.2 subsignature);
750
+ * anything else is another slot (null).
751
+ */
752
+ function _methodParamCorrespondence(index, member, slot) {
753
+ const own = _methodTypeParamList(member);
754
+ const other = _methodTypeParamList(slot);
755
+ const memberMap = new Map();
756
+ const slotMap = new Map();
757
+ if (own.length === other.length) {
758
+ own.forEach((param, i) => memberMap.set(param.name, `$M${i}`));
759
+ other.forEach((param, i) => slotMap.set(param.name, `$M${i}`));
760
+ return { member: memberMap, slot: slotMap };
761
+ }
762
+ const lang = index.files.get(member.file)?.language;
763
+ if (own.length === 0 && !langTraits(lang)?.genericArityIsIdentity) {
764
+ for (const param of other) slotMap.set(param.name, param.erasure);
765
+ return { member: memberMap, slot: slotMap };
766
+ }
767
+ return null;
768
+ }
769
+
709
770
  // ---------------------------------------------------------------------------
710
771
  // Generic base clauses as type-argument substitutions (fix #390)
711
772
  // ---------------------------------------------------------------------------
@@ -837,21 +898,19 @@ function _slotParams(def) {
837
898
 
838
899
  /**
839
900
  * Does `member` take the slot whose parameter types, in the member's own
840
- * class terms, are `slotTypes`? Method-level type parameters of either
841
- * side match any type (`<U> m(U)` overrides `<V> m(V)`).
901
+ * class terms, are `slotTypes`? Method type parameters correspond by
902
+ * position (fix #426) with those of `slotDef`, the method `slotTypes` were
903
+ * read from.
842
904
  */
843
- function _slotTypesMatch(member, slotTypes, slotMethodParams) {
905
+ function _slotTypesMatch(index, member, slotTypes, slotDef) {
844
906
  const params = _slotParams(member);
845
907
  if (params.length !== slotTypes.length) return false;
846
- const wild = new Set([...(_orderedTypeParams({ generics: member.generics }) || []),
847
- ...slotMethodParams]);
908
+ const correspondence = _methodParamCorrespondence(index, member, slotDef);
909
+ if (!correspondence) return false;
848
910
  for (let i = 0; i < params.length; i++) {
849
- const a = _slotTypeKey(params[i]?.type);
850
- const b = _slotTypeKey(slotTypes[i]);
911
+ const a = _slotTypeKey(_substituteType(params[i]?.type, correspondence.member));
912
+ const b = _slotTypeKey(_substituteType(slotTypes[i], correspondence.slot));
851
913
  if (!a || !b || a === b) continue;
852
- const baseA = a.replace(/[^\w$].*$/s, '');
853
- const baseB = b.replace(/[^\w$].*$/s, '');
854
- if (wild.has(baseA) || wild.has(baseB)) continue;
855
914
  return false;
856
915
  }
857
916
  return true;
@@ -880,11 +939,18 @@ function _explicitInterfaceName(member) {
880
939
  ? String(member.explicitInterface).replace(/<.*$/s, '').split('.').pop().trim() : null;
881
940
  }
882
941
 
883
- function _slotMembersOf(index, name, ref, pin, overloads, frame = null, slotInterfaces = null) {
942
+ // A value slot (fix #420): fields and properties of one name, and the
943
+ // accessors that implement them.
944
+ const VALUE_SLOT_KINDS = new Set(['field', 'private field', 'constant', 'state', 'get', 'set', 'property']);
945
+
946
+ function _slotMembersOf(index, name, ref, pin, overloads, frame = null, slotInterfaces = null, valueSlot = false,
947
+ byClass = null) {
884
948
  const { ownedBy } = require('./class-identity');
885
949
  const refFile = ref.def?.file || null;
886
- let defs = (index.symbols.get(name) || []).filter(member => {
887
- if (NON_CALLABLE_TYPES.has(member.type) || member.className !== ref.name) return false;
950
+ let defs = (byClass ? byClass.get(ref.name) || [] : index.symbols.get(name) || []).filter(member => {
951
+ if (member.className !== ref.name) return false;
952
+ if (valueSlot ? !VALUE_SLOT_KINDS.has(member.type) && !VALUE_SLOT_KINDS.has(member.memberType)
953
+ : NON_CALLABLE_TYPES.has(member.type)) return false;
888
954
  const owned = ownedBy(index, member, ref);
889
955
  return owned === 'yes' || (owned === 'maybe' && refFile && member.file === refFile);
890
956
  });
@@ -912,18 +978,19 @@ function _slotMembersOf(index, name, ref, pin, overloads, frame = null, slotInte
912
978
  same = same.filter(member => methodArity(member) === pinMethodArity);
913
979
  }
914
980
  if (frame && arity != null) {
915
- const pinMethodParams = _orderedTypeParams({ generics: pin.generics }) || [];
916
981
  if (frame.slotTypes) {
982
+ const slotDef = frame.slotDef || pin;
917
983
  return same.filter(member => _arity(member) == null ||
918
- _slotTypesMatch(member, frame.slotTypes, pinMethodParams));
984
+ _slotTypesMatch(index, member, frame.slotTypes, slotDef));
919
985
  }
920
986
  if (frame.memberSubst) {
921
- const pinTypes = _slotParams(pin).map(p => p?.type);
987
+ const targetTypes = frame.targetTypes || _slotParams(pin).map(p => p?.type);
988
+ const targetDef = frame.targetDef || pin;
922
989
  return same.filter(member => {
923
990
  if (_arity(member) == null) return true;
924
991
  const inPinTerms = { ...member, paramsStructured: _slotParams(member)
925
992
  .map(p => ({ ...p, type: _substituteType(p?.type, frame.memberSubst) })) };
926
- return _slotTypesMatch(inPinTerms, pinTypes, pinMethodParams);
993
+ return _slotTypesMatch(index, inPinTerms, targetTypes, targetDef);
927
994
  });
928
995
  }
929
996
  }
@@ -952,12 +1019,21 @@ function _slotMembersOf(index, name, ref, pin, overloads, frame = null, slotInte
952
1019
  * @returns {{pinRef, ancestors: {ref, members}[], descendants: {ref, members,
953
1020
  * viaImplements}[], unresolved: string[], external: string[]}|null}
954
1021
  */
955
- function hierarchySlotClosure(index, def, startRef = null) {
1022
+ function hierarchySlotClosure(index, def, startRef = null, options = {}) {
1023
+ const valueSlot = options.values === true;
1024
+ // The name's definitions by class, read once for every class visited.
1025
+ const byClass = new Map();
1026
+ for (const member of index.symbols.get(def.name) || []) {
1027
+ if (!member.className) continue;
1028
+ if (!byClass.has(member.className)) byClass.set(member.className, []);
1029
+ byClass.get(member.className).push(member);
1030
+ }
956
1031
  const { ownerRefOf, supertypeRefsOf, classDefsNamed } = require('./class-identity');
957
1032
  const pinRef = startRef || ownerRefOf(index, def);
958
1033
  if (!pinRef || (!pinRef.key && !pinRef.def)) return null;
959
1034
  const lang = index.files.get(def.file)?.language;
960
- const overloads = !!langTraits(lang)?.hasArityOverloads;
1035
+ const traits = langTraits(lang);
1036
+ const overloads = !!traits?.hasArityOverloads;
961
1037
  const name = def.name;
962
1038
  // Only a member declared inside the class body is constrained by the
963
1039
  // class's implements clause (a Rust inherent method never fills the
@@ -974,15 +1050,19 @@ function hierarchySlotClosure(index, def, startRef = null) {
974
1050
  .find(e => e.def.file === file);
975
1051
  return entry ? { name: parent.name, key: entry.key, def: entry.def } : null;
976
1052
  };
1053
+ // A member that hides the inherited one (C# `new`, fix #426) starts a
1054
+ // slot of its own: it never joins the inherited member's slot.
1055
+ const hides = member => !!traits?.memberHidingModifier &&
1056
+ (member?.modifiers || []).includes(traits.memberHidingModifier);
977
1057
 
978
1058
  const ancestors = [];
979
1059
  const unresolved = [];
980
1060
  const external = [];
981
- const ancestorSeen = new Set([refKey(pinRef)]);
982
- // Each class carries the substitution of its type parameters into the
983
- // pin's terms, composed over the generic base clauses walked (fix #390):
984
- // `Mid<T> : Base<T, bool>` maps Base's S to the pin class's T. null when
985
- // a base clause on the way cannot be read.
1061
+ // Classes that hold the slot (declare a member of it, or inherit one).
1062
+ const inSlot = new Set([refKey(pinRef)]);
1063
+ // Classes whose supertypes some climb already walked: a later climb
1064
+ // reaching one stops there, everything above it is known.
1065
+ const explored = new Set();
986
1066
  // Interfaces whose member of this name a class on the upward path
987
1067
  // implements explicitly (C#).
988
1068
  const explicitViaOf = (ref, inherited) => {
@@ -991,45 +1071,86 @@ function hierarchySlotClosure(index, def, startRef = null) {
991
1071
  if (own.length === 0) return inherited || null;
992
1072
  return new Set([...(inherited || []), ...own]);
993
1073
  };
994
- const upQueue = [{ ref: pinRef, contextFile: pinRef.def?.file || def.file,
995
- subst: overloads ? new Map() : null, explicitVia: explicitViaOf(pinRef, null) }];
996
- while (upQueue.length > 0 && ancestorSeen.size < 256) {
997
- const current = upQueue.shift();
998
- for (const parent of supertypeRefsOf(index, current.ref,
999
- { implements: followImplements })) {
1000
- if (parent.external) {
1001
- if (!external.includes(parent.name)) external.push(parent.name);
1002
- continue;
1003
- }
1004
- const resolved = resolveLoose(parent, current.contextFile);
1005
- if (!resolved) continue;
1006
- const key = refKey(resolved);
1007
- if (ancestorSeen.has(key)) continue;
1008
- ancestorSeen.add(key);
1009
- let subst = null;
1010
- if (current.subst && resolved.def) {
1011
- const step = _baseSubstitution(index, current.ref, resolved);
1012
- if (step) {
1013
- subst = new Map();
1014
- for (const [param, arg] of step) subst.set(param, _substituteType(arg, current.subst));
1074
+ // Each class carries the substitution of its type parameters into the
1075
+ // origin's terms, composed over the generic base clauses walked (fix
1076
+ // #390): `Mid<T> : Base<T, bool>` maps Base's S to the origin's T. null
1077
+ // when a base clause on the way cannot be read.
1078
+ const pendingRoots = [];
1079
+ // Where a class member is overridden only when it is virtual (C++
1080
+ // `virtual`, C# `virtual`/`abstract`/`override`; trait
1081
+ // virtualMemberModifiers), a holder's climb joins a class's member only
1082
+ // when it is (fix #426: `struct D : Pool, Plain` overrides Pool's
1083
+ // virtual, never Plain's plain member). Interface members always are.
1084
+ const virtualModifiers = traits?.virtualMemberModifiers || null;
1085
+ const overridable = (ref, member) => !virtualModifiers || ref.def?.type === 'interface' ||
1086
+ (member.modifiers || []).some(modifier => virtualModifiers.includes(modifier));
1087
+ const climb = (origin, originTypes, originDef, interfacesOnly, virtualOnly = false) => {
1088
+ const queue = [{ ref: origin, contextFile: origin.def?.file || def.file,
1089
+ subst: overloads ? new Map() : null, explicitVia: explicitViaOf(origin, null) }];
1090
+ explored.add(refKey(origin));
1091
+ let steps = 0;
1092
+ while (queue.length > 0 && steps++ < 256) {
1093
+ const current = queue.shift();
1094
+ for (const parent of supertypeRefsOf(index, current.ref,
1095
+ { implements: followImplements })) {
1096
+ if (parent.external) {
1097
+ if (!external.includes(parent.name)) external.push(parent.name);
1098
+ continue;
1015
1099
  }
1100
+ const resolved = resolveLoose(parent, current.contextFile);
1101
+ if (!resolved) continue;
1102
+ const key = refKey(resolved);
1103
+ if (explored.has(key)) continue;
1104
+ // A hiding member's slot reaches only the interfaces its own
1105
+ // class declares.
1106
+ if (interfacesOnly && current.ref === origin && resolved.def?.type !== 'interface') continue;
1107
+ let subst = null;
1108
+ if (current.subst && resolved.def) {
1109
+ const step = _baseSubstitution(index, current.ref, resolved);
1110
+ if (step) {
1111
+ subst = new Map();
1112
+ for (const [param, arg] of step) subst.set(param, _substituteType(arg, current.subst));
1113
+ }
1114
+ }
1115
+ // A class on the way that implements this interface's member
1116
+ // explicitly keeps the slot out of the interface (fix #395,
1117
+ // C#): `string IValidator.M()` beside `protected virtual M()`
1118
+ // are two slots.
1119
+ if (!_explicitInterfaceName(originDef) && current.explicitVia?.has(resolved.name)) continue;
1120
+ // An interface member is implemented by public members only (C#,
1121
+ // Java, TS): a `protected`/`private`/`internal` member is never
1122
+ // its slot (fix #395).
1123
+ if (resolved.def?.type === 'interface' && !_explicitInterfaceName(originDef) &&
1124
+ _nonPublicMember(originDef)) continue;
1125
+ explored.add(key);
1126
+ // Types compare in the origin's terms; without them (a base
1127
+ // clause below that could not be read) they compare loosely
1128
+ // with the pin's.
1129
+ const frame = subst && originTypes
1130
+ ? { memberSubst: subst, targetTypes: originTypes, targetDef: originDef } : null;
1131
+ const declared = inSlot.has(key) ? [] : _slotMembersOf(index, name, resolved, def, overloads,
1132
+ frame, null, valueSlot, byClass);
1133
+ // A member hiding the inherited one (C# `new virtual M`) is
1134
+ // the top of the slot below it: it joins, and what is above
1135
+ // it is another slot.
1136
+ const hiding = declared.length > 0 && declared.every(hides);
1137
+ const members = declared.filter(member => !virtualOnly || overridable(resolved, member));
1138
+ if (members.length > 0) {
1139
+ inSlot.add(key);
1140
+ const entry = { ref: resolved, members };
1141
+ ancestors.push(entry);
1142
+ pendingRoots.push(entry);
1143
+ }
1144
+ if (hiding) continue;
1145
+ queue.push({ ref: resolved, contextFile: resolved.def?.file || current.contextFile, subst,
1146
+ explicitVia: explicitViaOf(resolved, current.explicitVia) });
1016
1147
  }
1017
- // A class on the way that implements this interface's member
1018
- // explicitly keeps the pin's slot out of the interface (fix
1019
- // #395, C#): `string IValidator.M()` beside `protected virtual
1020
- // M()` are two slots.
1021
- if (!_explicitInterfaceName(def) && current.explicitVia?.has(resolved.name)) continue;
1022
- // An interface member is implemented by public members only (C#,
1023
- // Java, TS): a `protected`/`private`/`internal` member is never
1024
- // its slot (fix #395).
1025
- if (resolved.def?.type === 'interface' && !_explicitInterfaceName(def) && _nonPublicMember(def)) continue;
1026
- const members = _slotMembersOf(index, name, resolved, def, overloads,
1027
- subst ? { memberSubst: subst } : null);
1028
- if (members.length > 0) ancestors.push({ ref: resolved, members });
1029
- upQueue.push({ ref: resolved, contextFile: resolved.def?.file || current.contextFile, subst,
1030
- explicitVia: explicitViaOf(resolved, current.explicitVia) });
1031
1148
  }
1032
- }
1149
+ };
1150
+ climb(pinRef, overloads ? _slotParams(def).map(p => p?.type) : null, def, hides(def));
1151
+ // The pin's own climb found the ancestors; descendants' climbs (below)
1152
+ // add the supertypes a slot holder reaches beside the pin's line.
1153
+ pendingRoots.length = 0;
1033
1154
 
1034
1155
  // Children spelled their parent with a qualifier the inheritance graph
1035
1156
  // kept (`extends Outer.Inner<T>`): look those buckets up too.
@@ -1049,8 +1170,7 @@ function hierarchySlotClosure(index, def, startRef = null) {
1049
1170
  // Descendants of every slot owner: the pin's class and each declaring
1050
1171
  // ancestor (siblings of the pin override the same slot).
1051
1172
  const descendants = [];
1052
- const roots = [pinRef, ...ancestors.map(a => a.ref)];
1053
- const downSeen = new Set(roots.map(refKey));
1173
+ const downSeen = new Set([pinRef, ...ancestors.map(a => a.ref)].map(refKey));
1054
1174
  // Each class carries the slot's parameter types in its own terms, read
1055
1175
  // through the generic base clauses from the root that declares the slot
1056
1176
  // (fix #390): below `Fmt : Visitor<TextWriter, bool>` the slot
@@ -1061,84 +1181,141 @@ function hierarchySlotClosure(index, def, startRef = null) {
1061
1181
  // virtual (fix #395); a subclass re-implementing the interface is found
1062
1182
  // through the interface.
1063
1183
  const onlyExplicit = members => members.length > 0 && members.every(m => m.explicitInterface);
1064
- const interfaceSlot = pinRef.def?.type === 'interface' || !!def.explicitInterface ||
1065
- ancestors.some(a => a.ref.def?.type === 'interface' && a.members.length > 0);
1066
- // The interfaces whose member is this slot: their explicit
1067
- // implementations below are slot members.
1068
- const slotInterfaces = new Set([
1069
- ...(pinRef.def?.type === 'interface' ? [pinRef.name] : []),
1070
- ...ancestors.filter(a => a.ref.def?.type === 'interface' && a.members.length > 0).map(a => a.ref.name),
1071
- ...(_explicitInterfaceName(def) ? [_explicitInterfaceName(def)] : []),
1072
- ]);
1184
+ let interfaceSlot = false;
1185
+ const slotInterfaces = new Set(_explicitInterfaceName(def) ? [_explicitInterfaceName(def)] : []);
1186
+ const noteInterfaces = () => {
1187
+ interfaceSlot = pinRef.def?.type === 'interface' || !!def.explicitInterface ||
1188
+ ancestors.some(a => a.ref.def?.type === 'interface' && a.members.length > 0);
1189
+ if (pinRef.def?.type === 'interface') slotInterfaces.add(pinRef.name);
1190
+ for (const a of ancestors) {
1191
+ if (a.ref.def?.type === 'interface' && a.members.length > 0) slotInterfaces.add(a.ref.name);
1192
+ }
1193
+ };
1194
+ noteInterfaces();
1195
+ const rootEntry = (ref, members) => ({ ref,
1196
+ slotTypes: overloads && members.length === 1 ? slotTypesOf(members[0]) : null,
1197
+ slotDef: members.length === 1 ? members[0] : def,
1198
+ explicitOnly: onlyExplicit(members) });
1073
1199
  const downQueue = [
1074
- { ref: pinRef, slotTypes: overloads ? slotTypesOf(def) : null, explicitOnly: !!def.explicitInterface },
1075
- ...ancestors.map(a => ({ ref: a.ref,
1076
- slotTypes: overloads && a.members.length === 1 ? slotTypesOf(a.members[0]) : null,
1077
- explicitOnly: onlyExplicit(a.members) })),
1200
+ { ref: pinRef, slotTypes: overloads ? slotTypesOf(def) : null, slotDef: def,
1201
+ explicitOnly: !!def.explicitInterface },
1202
+ ...ancestors.map(a => rootEntry(a.ref, a.members)),
1078
1203
  ];
1079
- // The slot's parameter types in each class's own terms (null: unknown).
1204
+ // The slot's parameter types in each class's own terms (null: unknown),
1205
+ // and the method whose type parameters they are spelled with.
1080
1206
  const slotTypesByKey = new Map(downQueue.map(entry => [refKey(entry.ref), entry.slotTypes]));
1081
- while (downQueue.length > 0 && descendants.length < 5000) {
1082
- const currentEntry = downQueue.shift();
1083
- const current = currentEntry.ref;
1084
- const children = [
1085
- ...(currentEntry.explicitOnly ? [] : qualifiedChildKeys(current.name).flatMap(key =>
1086
- index.extendedByGraph.get(key) || []).map(child =>
1087
- typeof child === 'string' ? { name: child, file: null }
1088
- : { name: child.name, file: child.file || null })),
1089
- ...implementedBy(index, current.name),
1090
- ];
1091
- for (const child of children) {
1092
- if (!child.name) continue;
1093
- const entries = classDefsNamed(index, child.name).entries
1094
- .filter(e => !child.file || e.def.file === child.file);
1095
- for (const entry of entries) {
1096
- if (downSeen.has(entry.key)) continue;
1097
- const childRef = { name: child.name, key: entry.key, def: entry.def };
1098
- const link = supertypeRefsOf(index, childRef, { implements: true }).find(p => {
1099
- if (p.external || _slotTypeName(p.name) !== current.name) return false;
1100
- if (p.key) return p.key === current.key;
1101
- return !!current.def?.file &&
1102
- index._resolveClassFile(current.name, entry.def.file) === current.def.file;
1103
- });
1104
- if (!link) continue;
1105
- downSeen.add(entry.key);
1106
- let slotTypes = null;
1107
- let instantiations = null;
1108
- if (currentEntry.slotTypes && current.def) {
1109
- const steps = _baseSubstitutions(index, childRef, current);
1110
- if (steps?.length === 1) {
1111
- slotTypes = currentEntry.slotTypes.map(type => _substituteType(type, steps[0]));
1112
- } else if (steps?.length > 1) {
1113
- instantiations = steps.map(step => currentEntry.slotTypes.map(type => _substituteType(type, step)));
1207
+ const slotDefsByKey = new Map(downQueue.map(entry => [refKey(entry.ref), entry.slotDef]));
1208
+ // Supertypes of a slot holder beside the pin's line fill the slot too
1209
+ // (fix #426): a class's member implements the same-signature member of
1210
+ // every interface it implements (`GenericPool implements Pool,
1211
+ // PoolMXBean`), and where the language lets an inherited member
1212
+ // implement (Java, C#, TS), a class inheriting the slot member fills
1213
+ // its own interfaces with it (`Impl extends Base implements Bean`).
1214
+ const climbsFromHolders = !!traits?.memberFillsSupertypeSlots;
1215
+ const climbsFromInheritors = !!traits?.inheritedMemberFillsSlots;
1216
+ let rounds = 0;
1217
+ while (downQueue.length > 0 && rounds++ < 64) {
1218
+ while (downQueue.length > 0 && descendants.length < 5000) {
1219
+ const currentEntry = downQueue.shift();
1220
+ const current = currentEntry.ref;
1221
+ const children = [
1222
+ ...(currentEntry.explicitOnly ? [] : qualifiedChildKeys(current.name).flatMap(key =>
1223
+ index.extendedByGraph.get(key) || []).map(child =>
1224
+ typeof child === 'string' ? { name: child, file: null }
1225
+ : { name: child.name, file: child.file || null })),
1226
+ ...implementedBy(index, current.name),
1227
+ ];
1228
+ for (const child of children) {
1229
+ if (!child.name) continue;
1230
+ const entries = classDefsNamed(index, child.name).entries
1231
+ .filter(e => !child.file || e.def.file === child.file);
1232
+ for (const entry of entries) {
1233
+ if (downSeen.has(entry.key)) continue;
1234
+ const childRef = { name: child.name, key: entry.key, def: entry.def };
1235
+ const link = supertypeRefsOf(index, childRef, { implements: true }).find(p => {
1236
+ if (p.external || _slotTypeName(p.name) !== current.name) return false;
1237
+ if (p.key) return p.key === current.key;
1238
+ return !!current.def?.file &&
1239
+ index._resolveClassFile(current.name, entry.def.file) === current.def.file;
1240
+ });
1241
+ if (!link) continue;
1242
+ downSeen.add(entry.key);
1243
+ let slotTypes = null;
1244
+ let instantiations = null;
1245
+ if (currentEntry.slotTypes && current.def) {
1246
+ const steps = _baseSubstitutions(index, childRef, current);
1247
+ if (steps?.length === 1) {
1248
+ slotTypes = currentEntry.slotTypes.map(type => _substituteType(type, steps[0]));
1249
+ } else if (steps?.length > 1) {
1250
+ instantiations = steps.map(step => currentEntry.slotTypes.map(type => _substituteType(type, step)));
1251
+ }
1252
+ }
1253
+ const slotDef = currentEntry.slotDef || def;
1254
+ // A class implementing the generic base more than once fills
1255
+ // the slot once per instantiation (fix #400).
1256
+ let members = instantiations
1257
+ ? [...new Set(instantiations.flatMap(types => _slotMembersOf(index, name, childRef, def,
1258
+ overloads, { slotTypes: types, slotDef }, slotInterfaces, valueSlot, byClass)))]
1259
+ : _slotMembersOf(index, name, childRef, def, overloads,
1260
+ slotTypes ? { slotTypes, slotDef } : null, slotInterfaces, valueSlot, byClass);
1261
+ if (link.viaImplements) members = members.filter(m => memberInClassBody(index, m));
1262
+ // In an interface member's slot a class takes part with a
1263
+ // public member or an explicit implementation only (fix
1264
+ // #395): a protected override below it overrides another
1265
+ // slot.
1266
+ if (interfaceSlot && childRef.def?.type !== 'interface') {
1267
+ members = members.filter(m => !!m.explicitInterface || !_nonPublicMember(m));
1268
+ }
1269
+ // A member hiding the slot member (C# `new`) is another
1270
+ // slot, and so is everything below it.
1271
+ if (members.length > 0 && members.every(hides)) continue;
1272
+ members = members.filter(m => !hides(m));
1273
+ inSlot.add(entry.key);
1274
+ descendants.push({ ref: childRef, members, viaImplements: !!link.viaImplements });
1275
+ downQueue.push({ ref: childRef, slotTypes, slotDef, explicitOnly: onlyExplicit(members) });
1276
+ slotTypesByKey.set(entry.key, slotTypes);
1277
+ slotDefsByKey.set(entry.key, slotDef);
1278
+ if (!onlyExplicit(members) &&
1279
+ (members.length > 0 ? climbsFromHolders : climbsFromInheritors)) {
1280
+ // A declared member overrides; an inherited one is the
1281
+ // implementation its provider declares, virtual or not.
1282
+ climb(childRef, slotTypes, members.length === 1 ? members[0] : def, false,
1283
+ members.length > 0);
1114
1284
  }
1115
1285
  }
1116
- // A class implementing the generic base more than once fills
1117
- // the slot once per instantiation (fix #400).
1118
- let members = instantiations
1119
- ? [...new Set(instantiations.flatMap(types => _slotMembersOf(index, name, childRef, def,
1120
- overloads, { slotTypes: types }, slotInterfaces)))]
1121
- : _slotMembersOf(index, name, childRef, def, overloads,
1122
- slotTypes ? { slotTypes } : null, slotInterfaces);
1123
- if (link.viaImplements) members = members.filter(m => memberInClassBody(index, m));
1124
- // In an interface member's slot a class takes part with a
1125
- // public member or an explicit implementation only (fix
1126
- // #395): a protected override below it overrides another
1127
- // slot.
1128
- if (interfaceSlot && childRef.def?.type !== 'interface') {
1129
- members = members.filter(m => !!m.explicitInterface || !_nonPublicMember(m));
1130
- }
1131
- descendants.push({ ref: childRef, members, viaImplements: !!link.viaImplements });
1132
- downQueue.push({ ref: childRef, slotTypes, explicitOnly: onlyExplicit(members) });
1133
- slotTypesByKey.set(entry.key, slotTypes);
1286
+ }
1287
+ }
1288
+ // Ancestors a holder's climb joined: their descendants hold the slot.
1289
+ if (pendingRoots.length > 0) {
1290
+ noteInterfaces();
1291
+ for (const root of pendingRoots.splice(0)) {
1292
+ downSeen.add(refKey(root.ref));
1293
+ const entry = rootEntry(root.ref, root.members);
1294
+ downQueue.push(entry);
1295
+ slotTypesByKey.set(refKey(root.ref), entry.slotTypes);
1296
+ slotDefsByKey.set(refKey(root.ref), entry.slotDef);
1134
1297
  }
1135
1298
  }
1136
1299
  }
1137
1300
  if (startRef) {
1138
- const own = _slotMembersOf(index, name, startRef, def, overloads);
1301
+ const own = _slotMembersOf(index, name, startRef, def, overloads, null, null, valueSlot, byClass);
1139
1302
  if (own.length > 0) ancestors.unshift({ ref: startRef, members: own });
1140
1303
  }
1141
- return { pinRef, ancestors, descendants, unresolved, external, slotTypesByKey };
1304
+ return { pinRef, ancestors, descendants, unresolved, external, slotTypesByKey, slotDefsByKey };
1305
+ }
1306
+
1307
+ function sourceSyntaxRecord(index, file, name, content, kind, positions, accept) {
1308
+ const cache = require('./cache');
1309
+ const { indexedContent, parseViewParts, syntaxMode } = require('./source-memo');
1310
+ const entry = index.files.get(file);
1311
+ const eligible = entry?.hash && index._usageResultCache && !index.isProvenanceFactIndex &&
1312
+ indexedContent(index, file, entry, content);
1313
+ const mode = eligible ? syntaxMode('contractSyntax', [...parseViewParts(entry), positions], kind) : null;
1314
+ const stored = eligible ? cache.getUsageResult(index, file, name, mode) : null;
1315
+ return {
1316
+ value: Array.isArray(stored) && accept(stored) ? stored : null,
1317
+ save(value) { if (eligible) cache.storeUsageResult(index, file, name, mode, value); },
1318
+ };
1142
1319
  }
1143
1320
 
1144
1321
  /**
@@ -1157,7 +1334,6 @@ function anonymousSlotMembers(index, name, perFile, closure, pin) {
1157
1334
  ...closure.descendants.map(d => d.ref)].filter(ref => ref?.key).map(ref => ref.key));
1158
1335
  if (slotKeys.size === 0) return [];
1159
1336
  const overloads = !!langTraits(index.files.get(pin.file)?.language)?.hasArityOverloads;
1160
- const pinMethodParams = _orderedTypeParams({ generics: pin.generics }) || [];
1161
1337
  const out = [];
1162
1338
  for (const [file, lineNos] of perFile) {
1163
1339
  const fileEntry = index.files.get(file);
@@ -1168,12 +1344,16 @@ function anonymousSlotMembers(index, name, perFile, closure, pin) {
1168
1344
  if (!javaLike && !classExpressionLanguage) continue;
1169
1345
  let content;
1170
1346
  try { content = index._readFile(file); } catch { continue; }
1347
+ const lines = [...lineNos].sort((a, b) => a - b);
1348
+ const syntax = sourceSyntaxRecord(index, file, name, content, 'anonymous', lines,
1349
+ value => value.length === 1 && value[0] === 0);
1350
+ if (syntax.value) continue;
1171
1351
  const tree = index._getParsedTree?.(file, content, language);
1172
1352
  if (!tree) continue;
1173
1353
  const namespace = (fileEntry.symbols || []).find(s => s.namespace)?.namespace || null;
1174
- const lines = [...lineNos].sort((a, b) => a - b);
1175
1354
  const fileLines = index._getFileLines(file);
1176
1355
  const seen = new Set();
1356
+ let hasAnonymousMember = false;
1177
1357
  for (const line of lines) {
1178
1358
  const text = fileLines[line - 1] || '';
1179
1359
  for (let at = text.indexOf(name); at >= 0; at = text.indexOf(name, at + 1)) {
@@ -1201,6 +1381,9 @@ function anonymousSlotMembers(index, name, perFile, closure, pin) {
1201
1381
  typeText = value ? `${value.text}${extendsClause.namedChildren.find(child => child.type === 'type_arguments')?.text || ''}` : null;
1202
1382
  } else continue;
1203
1383
  if (!typeText) continue;
1384
+ // Only syntactic absence is cached. An unbound or external
1385
+ // owner may bind to a project slot after another file changes.
1386
+ hasAnonymousMember = true;
1204
1387
  const key = `${file}:${nameNode.startIndex}`;
1205
1388
  if (seen.has(key)) continue;
1206
1389
  seen.add(key);
@@ -1237,9 +1420,9 @@ function anonymousSlotMembers(index, name, perFile, closure, pin) {
1237
1420
  slotTypes = null; // diamond `new X<>() {..}`: arguments inferred
1238
1421
  }
1239
1422
  }
1240
- const member = { paramsStructured: params, generics: null };
1423
+ const member = { paramsStructured: params, generics: null, file };
1241
1424
  const fits = slotTypes
1242
- ? _slotTypesMatch(member, slotTypes, pinMethodParams)
1425
+ ? _slotTypesMatch(index, member, slotTypes, closure.slotDefsByKey?.get(ref.key) || pin)
1243
1426
  : params.length === _slotParams(pin).length;
1244
1427
  if (!fits) continue;
1245
1428
  if (!slotTypes) {
@@ -1250,6 +1433,7 @@ function anonymousSlotMembers(index, name, perFile, closure, pin) {
1250
1433
  out.push({ ...site, verdict: 'edit' });
1251
1434
  }
1252
1435
  }
1436
+ if (!hasAnonymousMember) syntax.save([0]);
1253
1437
  }
1254
1438
  return out;
1255
1439
  }
@@ -2111,6 +2295,14 @@ function rustMacroTemplateSites(index, name, files) {
2111
2295
  // Only files that define a macro can hold template tokens; the
2112
2296
  // AST below decides everything, this only skips needless parses.
2113
2297
  if (!content.includes('macro_rules!')) continue;
2298
+ const syntax = sourceSyntaxRecord(index, file, name, content, 'rust-template', null,
2299
+ validRustTemplateSyntax);
2300
+ if (syntax.value) {
2301
+ for (const fact of syntax.value) sites.push({ file, relativePath: entry.relativePath || file,
2302
+ ...fact, owners: fact.owners.map(owner => ({ ...owner })) });
2303
+ continue;
2304
+ }
2305
+ const siteStart = sites.length;
2114
2306
  const tree = index._getParsedTree?.(file, content, 'rust') || safeParse(parser, content);
2115
2307
  if (!tree) continue;
2116
2308
  const macros = [];
@@ -2122,7 +2314,7 @@ function rustMacroTemplateSites(index, name, files) {
2122
2314
  if (node.type === 'macro_invocation') invocations.push(node);
2123
2315
  for (let i = node.namedChildCount - 1; i >= 0; i--) stack.push(node.namedChild(i));
2124
2316
  }
2125
- if (macros.length === 0) continue;
2317
+ if (macros.length === 0) { syntax.save([]); continue; }
2126
2318
  const invocationContexts = (macroName) => {
2127
2319
  const contexts = [];
2128
2320
  for (const invocation of invocations) {
@@ -2200,12 +2392,26 @@ function rustMacroTemplateSites(index, name, files) {
2200
2392
  };
2201
2393
  walk(macro, false);
2202
2394
  }
2395
+ syntax.save(sites.slice(siteStart).map(({ file: _file, relativePath: _relativePath, ...fact }) =>
2396
+ ({ ...fact, owners: fact.owners.map(owner => ({ ...owner })) })));
2203
2397
  }
2204
2398
  sites.sort((a, b) => (a.relativePath < b.relativePath ? -1
2205
2399
  : a.relativePath > b.relativePath ? 1 : a.line - b.line || a.column - b.column));
2206
2400
  return sites;
2207
2401
  }
2208
2402
 
2403
+ function validRustTemplateSyntax(value) {
2404
+ const optionalString = text => text == null || typeof text === 'string';
2405
+ return value.every(fact => fact && Number.isInteger(fact.line) && fact.line > 0 &&
2406
+ Number.isInteger(fact.column) && fact.column >= 0 &&
2407
+ ['definition', 'method-call', 'path-call', 'call', 'other'].includes(fact.shape) &&
2408
+ optionalString(fact.macroName) && optionalString(fact.enclosingFn) &&
2409
+ optionalString(fact.pathQualifier) && Array.isArray(fact.owners) &&
2410
+ fact.owners.every(owner => owner && ['impl', 'fn-body', 'module'].includes(owner.kind) &&
2411
+ optionalString(owner.trait) && ['line', 'invocationLine'].every(key =>
2412
+ owner[key] == null || Number.isInteger(owner[key]) && owner[key] > 0)));
2413
+ }
2414
+
2209
2415
  /**
2210
2416
  * Project Go types and interfaces whose selector `T.<memberName>` resolves to
2211
2417
  * a project declaration (fix #383): methods (own and promoted through