ucn 5.5.3 → 5.6.1

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 (77) hide show
  1. package/.claude/skills/ucn/SKILL.md +37 -12
  2. package/.claude/skills/ucn/references/commands.md +18 -14
  3. package/.claude/skills/ucn/references/resolution-rules.md +4 -0
  4. package/.claude/skills/ucn/references/trust-contract.md +4 -4
  5. package/CHANGELOG.md +485 -0
  6. package/README.md +66 -29
  7. package/cli/flags.js +184 -0
  8. package/cli/index.js +137 -324
  9. package/core/account.js +44 -87
  10. package/core/analysis.js +274 -108
  11. package/core/ast-analysis.js +2 -0
  12. package/core/bridge.js +14 -0
  13. package/core/cache.js +103 -16
  14. package/core/call-fit.js +130 -0
  15. package/core/callers.js +353 -71
  16. package/core/capture.js +363 -0
  17. package/core/change-kinds.js +1107 -0
  18. package/core/check.js +1150 -139
  19. package/core/command-contracts.js +8 -7
  20. package/core/constructed-class.js +1195 -0
  21. package/core/contract-membership.js +142 -0
  22. package/core/deadcode.js +455 -106
  23. package/core/deletion.js +421 -0
  24. package/core/discovery.js +104 -9
  25. package/core/entrypoint-toml.js +110 -0
  26. package/core/entrypoints.js +12 -6
  27. package/core/evidence-bands.js +68 -0
  28. package/core/evidence-bands.json +44 -0
  29. package/core/execute.js +172 -161
  30. package/core/hot-summary.js +241 -0
  31. package/core/index-ir.js +5 -3
  32. package/core/ir.js +6 -2
  33. package/core/member-signatures.js +789 -0
  34. package/core/outgoing-check.js +116 -0
  35. package/core/output/analysis-ext.js +20 -10
  36. package/core/output/analysis.js +98 -60
  37. package/core/output/check.js +101 -37
  38. package/core/output/doctor.js +34 -1
  39. package/core/output/find.js +2 -1
  40. package/core/output/lines.js +63 -3
  41. package/core/output/public.js +29 -11
  42. package/core/output/refactoring.js +32 -0
  43. package/core/output/reporting.js +19 -2
  44. package/core/output/search.js +14 -2
  45. package/core/output/tracing.js +36 -8
  46. package/core/output-budget.js +146 -61
  47. package/core/parallel-build.js +3 -3
  48. package/core/project.js +106 -23
  49. package/core/provenance-type.js +12 -5
  50. package/core/public-command.js +35 -9
  51. package/core/python-binding-identity.js +707 -0
  52. package/core/python-modules.js +6 -0
  53. package/core/reflection.js +248 -31
  54. package/core/registry.js +623 -72
  55. package/core/reporting.js +69 -34
  56. package/core/review-items.js +75 -0
  57. package/core/runtime-entrypoints.js +159 -0
  58. package/core/search.js +148 -164
  59. package/core/shared.js +0 -45
  60. package/core/snapshot.js +350 -0
  61. package/core/source-universe.js +707 -0
  62. package/core/test-units.js +3698 -0
  63. package/core/tracing.js +270 -658
  64. package/core/verify.js +63 -22
  65. package/languages/adapter.js +8 -3
  66. package/languages/c-family.js +12 -0
  67. package/languages/csharp.js +634 -0
  68. package/languages/go.js +120 -0
  69. package/languages/index.js +466 -4
  70. package/languages/java.js +712 -0
  71. package/languages/javascript.js +1498 -6
  72. package/languages/lexical-scope.js +68 -0
  73. package/languages/python.js +1708 -3
  74. package/languages/rust.js +209 -104
  75. package/languages/static-js.js +88 -0
  76. package/mcp/server.js +44 -110
  77. package/package.json +11 -5
package/core/callers.js CHANGED
@@ -52,6 +52,8 @@ let lazyProvenanceOverloadModuleCache = null;
52
52
  const lazyProvenanceOverloadModule = () => lazyProvenanceOverloadModuleCache || (lazyProvenanceOverloadModuleCache = require('./provenance-overload'));
53
53
  let lazyConstructorCallsModuleCache = null;
54
54
  const lazyConstructorCallsModule = () => lazyConstructorCallsModuleCache || (lazyConstructorCallsModuleCache = require('./constructor-calls'));
55
+ let lazyConstructedClassModuleCache = null;
56
+ const lazyConstructedClassModule = () => lazyConstructedClassModuleCache || (lazyConstructedClassModuleCache = require('./constructed-class'));
55
57
  let lazySourceMemoModuleCache = null;
56
58
  const lazySourceMemoModule = () => lazySourceMemoModuleCache || (lazySourceMemoModuleCache = require('./source-memo'));
57
59
  let lazyMacroExpansionModuleCache = null;
@@ -113,6 +115,24 @@ function _instanceAttributeCall(index, receiver, definitions, targets = definiti
113
115
  return { wrapsTarget, reason: readsPrevious ? 'instance-attribute-wraps-method' : 'callable-value' };
114
116
  }
115
117
 
118
+ /**
119
+ * The unverified reason of a call whose name an instance attribute of the
120
+ * receiver's class holds, for these targets; null when the targets are not
121
+ * concerned. The attribute may wrap a target (`self.m = wrap(self.m)`), or
122
+ * the class that declares it declares a target method too: which of the two
123
+ * the call runs is then the instance's state. An attribute of a subclass
124
+ * that replaces an inherited method is that subclass's member, not a target.
125
+ */
126
+ function _instanceAttributeRoute(index, classRef, definitions, targets) {
127
+ const attribute = _instanceAttributeCall(index, classRef, definitions, targets);
128
+ if (!attribute) return null;
129
+ if (attribute.wrapsTarget) return attribute.reason;
130
+ const declaring = findDeclaringClass(index, classRef, definitions);
131
+ return declaring && !declaring.uncertain && declaring.members.some(member => !member.instanceAttribute &&
132
+ targets.some(target => sameDeclaration(declarationIdentity(member), declarationIdentity(target))))
133
+ ? 'callable-value' : null;
134
+ }
135
+
116
136
  const CONSTRUCTABLE_BINDING_KINDS = new Set([
117
137
  'class', 'struct', 'record', 'enum', 'function',
118
138
  ]);
@@ -2506,46 +2526,26 @@ function findCallers(index, name, options = {}) {
2506
2526
  }
2507
2527
  }
2508
2528
 
2509
- // An unqualified constructor name can still have exact import
2510
- // provenance: `from io import StringIO; out = StringIO()`.
2511
- // A resolved project import pins the type to that file; a
2512
- // clearly external import blocks project-local single-owner
2513
- // confirmation. Annotation-only receivers do not take this
2514
- // path because they may hold a project subtype.
2515
- if (call.isMethod && call.receiverConstructed && call.receiverType &&
2516
- !call.receiverTypeQualifier &&
2529
+ // A receiver made by a constructor expression (`out =
2530
+ // StringIO()`, `new User(..).save()`) has the class the
2531
+ // expression's name denotes where it is written: the file's
2532
+ // own binding of the local name decides, never a same-named
2533
+ // class or import elsewhere. Annotation-only receivers do
2534
+ // not take this path because they may hold a project subtype.
2535
+ if (call.isMethod && call.receiverType && call.receiverTypeSource === 'constructor' &&
2517
2536
  langTraits(fileEntry.language)?.typeSystem === 'structural' &&
2518
2537
  !_functionLocalTypeInScope(index, filePath, call.receiverType,
2519
2538
  call.receiverTypeEvidence?.line ?? call.line)) {
2520
- const bindings = (fileEntry.importBindings || []).filter(b =>
2521
- b.name === call.receiverType || b.alias === call.receiverType);
2522
- if (bindings.length > 0) {
2523
- const project = bindings.map(b => ({
2524
- binding: b,
2525
- rel: fileEntry.moduleResolved && fileEntry.moduleResolved[b.module],
2526
- })).find(x => x.rel);
2527
- if (project) {
2528
- call = { ...call,
2529
- receiverType: project.binding.name,
2530
- receiverTypeFlowFile: path.join(index.root, project.rel),
2531
- };
2532
- } else {
2533
- const projectish = bindings.some(b =>
2534
- _unresolvedModuleIsGap(index, b.module, b));
2535
- const via = `${bindings[0].module}.${bindings[0].name}`;
2536
- if (BUILTIN_RECEIVER_TYPES.has(call.receiverType)) {
2537
- // Stable stdlib runtime classes (StringIO,
2538
- // BytesIO, etc.) retain their concrete builtin
2539
- // identity even though their module lies
2540
- // outside the project index.
2541
- call = { ...call, receiverTypePlatform: true };
2542
- } else {
2543
- call = { ...call, receiverType: undefined,
2544
- ...(projectish
2545
- ? { receiverQualifiedFlow: `${via} — unresolved module` }
2546
- : { receiverExternalFlow: via,
2547
- receiverExternalConcreteFlow: true }) };
2548
- }
2539
+ call = _constructedReceiverClass(index, fileEntry, filePath, call);
2540
+ // An instance attribute of the constructed class holds
2541
+ // the name: the call runs that value, not the method.
2542
+ if (call.receiverConstructedClass) {
2543
+ const attributeRoute = _instanceAttributeRoute(index,
2544
+ _classRefOfDefinition(index, call.receiverConstructedClass), definitions,
2545
+ options.targetDefinitions || definitions);
2546
+ if (attributeRoute) {
2547
+ routeUnverified(filePath, fileEntry, call, attributeRoute, calledAs);
2548
+ continue;
2549
2549
  }
2550
2550
  }
2551
2551
  }
@@ -2603,7 +2603,9 @@ function findCallers(index, name, options = {}) {
2603
2603
  call = { ...call, receiverType: folded.type,
2604
2604
  receiverTypeSource: 'flow',
2605
2605
  receiverTypeEvidence: { source: 'flow', ...folded },
2606
- ...(folded.fromFile && { receiverTypeFlowFile: folded.fromFile }) };
2606
+ ...(folded.fromFile && { receiverTypeFlowFile: folded.fromFile }),
2607
+ ...(folded.exactConstructor && folded.definition &&
2608
+ { receiverConstructedClass: folded.definition }) };
2607
2609
  } else if (folded && folded.externalVia) {
2608
2610
  call = {
2609
2611
  ...call,
@@ -2619,6 +2621,19 @@ function findCallers(index, name, options = {}) {
2619
2621
  receiverTypeEvidence: { source: 'flow', ...chainedType },
2620
2622
  ...(chainedType.fromFile && { receiverTypeFlowFile: chainedType.fromFile }) };
2621
2623
  }
2624
+ // An instance attribute of the constructed class holds
2625
+ // the name (`self.save = other` in its initializer): the
2626
+ // call runs that value, not the method (the callee
2627
+ // side's rule).
2628
+ if (call.receiverConstructedClass) {
2629
+ const attributeRoute = _instanceAttributeRoute(index,
2630
+ _classRefOfDefinition(index, call.receiverConstructedClass), definitions,
2631
+ options.targetDefinitions || definitions);
2632
+ if (attributeRoute) {
2633
+ routeUnverified(filePath, fileEntry, call, attributeRoute, calledAs);
2634
+ continue;
2635
+ }
2636
+ }
2622
2637
  } else if (call.isMethod && (!call.receiver || call.receiverIsChainRoot) &&
2623
2638
  !call.receiverType && call.receiverCall &&
2624
2639
  collectAccount && !call.isPotentialCallback &&
@@ -4087,9 +4102,10 @@ function findCallers(index, name, options = {}) {
4087
4102
  }
4088
4103
  const declaring = callerClassRef?.external ? null
4089
4104
  : findDeclaringClass(index, callerClassRef, definitions, { parentsOnly });
4090
- if (!parentsOnly && _instanceAttributeCall(index, callerClassRef, definitions,
4091
- options.targetDefinitions || definitions)?.wrapsTarget) {
4092
- routeUnverified(filePath, fileEntry, call, 'instance-attribute-wraps-method', calledAs);
4105
+ const attributeRoute = parentsOnly ? null : _instanceAttributeRoute(index,
4106
+ callerClassRef, definitions, options.targetDefinitions || definitions);
4107
+ if (attributeRoute) {
4108
+ routeUnverified(filePath, fileEntry, call, attributeRoute, calledAs);
4093
4109
  continue;
4094
4110
  }
4095
4111
  const matchedClass = declaring ? declaring.ref.name : null;
@@ -8930,15 +8946,6 @@ function findCallees(index, definition, options = {}) {
8930
8946
  },
8931
8947
  options: { sameClass: !!call.receiver && _isReservedReceiver(language, call.receiver) },
8932
8948
  });
8933
- const shadowedClassReceiver = structuralCallee &&
8934
- _unprovenLocalClassReceiver(index, fileEntry, def.file, call);
8935
- if (shadowedClassReceiver) {
8936
- const evidence = siteEvidence.get(siteId);
8937
- evidence.call = shadowedClassReceiver;
8938
- evidence.evidence.hasReceiverType = false;
8939
- noteUnverified(siteId, shadowedClassReceiver, 'ambiguous-binding');
8940
- continue;
8941
- }
8942
8949
  if (language === 'csharp') {
8943
8950
  // fix #353: `Beta.Helper.Widget()` — namespace-qualified type
8944
8951
  // receiver (see the findCallers twin).
@@ -9007,6 +9014,41 @@ function findCallees(index, definition, options = {}) {
9007
9014
  if (call.isFunctionReference && call.isPathCall &&
9008
9015
  !(index.symbols.get(call.name) || []).some(d => CALLABLE_SYMBOL_KINDS.has(d.type))) continue;
9009
9016
  if (calleeAccount) calleeAccount.totalSites++;
9017
+ // A refused local construction is still a call in this
9018
+ // function's ground set. Enroll it only after scope filtering;
9019
+ // an inner class method must not enter the surrounding answer.
9020
+ const shadowedClassReceiver = structuralCallee &&
9021
+ _unprovenLocalClassReceiver(index, fileEntry, def.file, call);
9022
+ if (shadowedClassReceiver) {
9023
+ const evidence = siteEvidence.get(siteId);
9024
+ evidence.call = shadowedClassReceiver;
9025
+ evidence.evidence.hasReceiverType = false;
9026
+ noteUnverified(siteId, shadowedClassReceiver, 'ambiguous-binding');
9027
+ continue;
9028
+ }
9029
+ // The class a constructor expression makes (the findCallers twin):
9030
+ // the file's own binding of the name decides the receiver's class.
9031
+ if (structuralCallee && call.isMethod && call.receiverType &&
9032
+ call.receiverTypeSource === 'constructor' &&
9033
+ !_functionLocalTypeInScope(index, def.file, call.receiverType,
9034
+ call.receiverTypeEvidence?.line ?? call.line)) {
9035
+ const typed = _constructedReceiverClass(index, fileEntry, def.file, call);
9036
+ if (typed !== call) {
9037
+ call = typed;
9038
+ const evidence = siteEvidence.get(siteId);
9039
+ evidence.call = call;
9040
+ evidence.evidence.hasReceiverType = !!call.receiverType;
9041
+ if (!call.receiverType && call.receiverExternalConcreteFlow) {
9042
+ noteSite(siteId, 'external', null, call);
9043
+ continue;
9044
+ }
9045
+ if (!call.receiverType && call.receiverQualifiedFlow) {
9046
+ if (_calleeZeroCandidateName(index, call)) noteSite(siteId, 'external', null, call);
9047
+ else noteUnverified(siteId, call, 'possible-dispatch', { dispatchVia: call.receiverQualifiedFlow });
9048
+ continue;
9049
+ }
9050
+ }
9051
+ }
9010
9052
  // A paren-less member access is always a field where method
9011
9053
  // values are path-only (fix #422, the caller side's #220 rule):
9012
9054
  // `take(self.len)` reads the field, never the method `len`.
@@ -9980,7 +10022,19 @@ function findCallees(index, definition, options = {}) {
9980
10022
  });
9981
10023
  continue;
9982
10024
  }
9983
- if (!qualifiedType && _isDispatchContractType(index, typeName)) {
10025
+ // A constructor expression made this receiver: one class
10026
+ // definition, never an interface or a subclass of its name.
10027
+ const constructedClass = structuralCallee && call.receiverConstructedClass &&
10028
+ call.receiverConstructedClass.name === typeName ? call.receiverConstructedClass : null;
10029
+ // A type imported under another name (`import { User as
10030
+ // U }`) is the class its own import leads to, whatever
10031
+ // else this file imports under the original name.
10032
+ const aliasedClass = structuralCallee && !constructedClass && renamedType?.fromFile &&
10033
+ !call.receiverTypeFlowFile && renamedType.name === typeName
10034
+ ? _moduleClassNamed(index,
10035
+ _resolveFlowTypeOrigin(index, renamedType.fromFile, typeName)?.fromFile, typeName)
10036
+ : null;
10037
+ if (!qualifiedType && !constructedClass && _isDispatchContractType(index, typeName)) {
9984
10038
  noteUnverified(siteId, call, 'possible-dispatch', {
9985
10039
  dispatchVia: typeName,
9986
10040
  dispatchCandidates: _countDispatchCandidates(index, typeName, symbols || []),
@@ -10009,7 +10063,7 @@ function findCallees(index, definition, options = {}) {
10009
10063
  ? _scopedTypeDefinitionAt(index, receiverOriginFile, typeName,
10010
10064
  receiverOriginFile === def.file ? call.line : null)
10011
10065
  : null;
10012
- const acceptsReceiverDefinition = symbol =>
10066
+ const acceptsReceiverDefinition = constructedClass || aliasedClass ? isCallableRT : symbol =>
10013
10067
  isCallableRT(symbol) &&
10014
10068
  (!receiverTypeDefinition || !ownerDefinitionOf(index, symbol) ||
10015
10069
  classKeyOf(index, ownerDefinitionOf(index, symbol)) ===
@@ -10059,16 +10113,20 @@ function findCallees(index, definition, options = {}) {
10059
10113
  const sel = rustTypedProbe?.match ? { match: rustTypedProbe.match }
10060
10114
  : _calleeSelectReceiverMethod(
10061
10115
  index, call, symbols || [], typeName, language, def.file,
10062
- acceptsReceiverDefinition, receiverOriginFile && {
10063
- fromFile: receiverOriginFile, definition: receiverTypeDefinition,
10064
- });
10116
+ acceptsReceiverDefinition, constructedClass || aliasedClass
10117
+ ? { fromFile: (constructedClass || aliasedClass).file,
10118
+ definition: constructedClass || aliasedClass, constructed: true,
10119
+ classRef: _classRefOfDefinition(index, constructedClass || aliasedClass) }
10120
+ : receiverOriginFile && {
10121
+ fromFile: receiverOriginFile, definition: receiverTypeDefinition,
10122
+ });
10065
10123
  if (sel.ambiguous) {
10066
10124
  noteUnverified(siteId, call, sel.reason || 'overload-ambiguous');
10067
10125
  continue;
10068
10126
  }
10069
10127
  const match = sel.match;
10070
10128
  if (match) {
10071
- if (routeVirtualOverride(siteId, call, typeName, match)) continue;
10129
+ if (!constructedClass && routeVirtualOverride(siteId, call, typeName, match)) continue;
10072
10130
  const key = match.bindingId || `${typeName}.${call.name}`;
10073
10131
  const existing = callees.get(key);
10074
10132
  if (existing) {
@@ -10144,6 +10202,18 @@ function findCallees(index, definition, options = {}) {
10144
10202
  });
10145
10203
  continue;
10146
10204
  }
10205
+ // A constructed class whose own ancestry declares no such
10206
+ // member: the member is an outside base's, or one set at
10207
+ // run time. A same-named method of another class is never it.
10208
+ if (constructedClass) {
10209
+ if (_calleeZeroCandidateName(index, call) ||
10210
+ _classAncestry(index, constructedClass) === 'outside') {
10211
+ noteSite(siteId, 'external', null, call);
10212
+ } else {
10213
+ noteUnverified(siteId, call, 'uncertain-receiver');
10214
+ }
10215
+ continue;
10216
+ }
10147
10217
  // Structural legacy mode retains its historical fallback.
10148
10218
  } else if (call.receiverCall && (!call.receiver || call.receiverIsChainRoot) &&
10149
10219
  !call.receiverIsModule && !call.isConstructor) {
@@ -10173,7 +10243,14 @@ function findCallees(index, definition, options = {}) {
10173
10243
  (s.type === 'field' && s.fieldType && /^func\b/.test(s.fieldType));
10174
10244
  const scopedConstructor = chained.exactConstructor &&
10175
10245
  classDefsNamed(index, chained.type).hasScoped;
10176
- const acceptsChainedDefinition = symbol =>
10246
+ // A constructor expression makes one class definition:
10247
+ // its members are the ones its own ancestry declares.
10248
+ const constructedClass = structuralCallee && chained.exactConstructor
10249
+ ? chained.definition || _moduleClassNamed(index,
10250
+ _resolveFlowTypeOrigin(index, chained.fromFile || def.file, chained.type)?.fromFile,
10251
+ chained.type)
10252
+ : null;
10253
+ const acceptsChainedDefinition = constructedClass ? isCallableCh : symbol =>
10177
10254
  isCallableCh(symbol) &&
10178
10255
  (scopedConstructor || !chained.fromFile || !symbol.file ||
10179
10256
  ((language === 'java' || language === 'csharp')
@@ -10182,7 +10259,9 @@ function findCallees(index, definition, options = {}) {
10182
10259
  path.dirname(chained.fromFile)));
10183
10260
  const sel = _calleeSelectReceiverMethod(
10184
10261
  index, call, symbols || [], chained.type, language,
10185
- def.file, acceptsChainedDefinition, chained);
10262
+ def.file, acceptsChainedDefinition, constructedClass
10263
+ ? { ...chained, definition: constructedClass, constructed: true,
10264
+ classRef: _classRefOfDefinition(index, constructedClass) } : chained);
10186
10265
  if (sel.match) {
10187
10266
  const match = sel.match;
10188
10267
  // A direct constructor expression fixes the
@@ -10211,6 +10290,17 @@ function findCallees(index, definition, options = {}) {
10211
10290
  noteSite(siteId, 'external', null, call);
10212
10291
  } else if (chained?.externalVia) {
10213
10292
  noteUnverified(siteId, call, 'possible-dispatch');
10293
+ } else if (structuralCallee && chained?.exactConstructor && chained.type) {
10294
+ // No member of that name in the constructed class's
10295
+ // ancestry: an outside base's member, or unknown.
10296
+ const constructedClass = chained.definition || _moduleClassNamed(index,
10297
+ _resolveFlowTypeOrigin(index, chained.fromFile || def.file, chained.type)?.fromFile,
10298
+ chained.type);
10299
+ if (constructedClass && _classAncestry(index, constructedClass) === 'outside') {
10300
+ noteSite(siteId, 'external', null, call);
10301
+ } else {
10302
+ noteUnverified(siteId, call, 'uncertain-receiver');
10303
+ }
10214
10304
  } else {
10215
10305
  noteUnverified(siteId, call, 'uncertain-receiver');
10216
10306
  }
@@ -17886,9 +17976,11 @@ function _importRenamedTypeName(index, file, typeName, options = {}) {
17886
17976
  (d.type === 'type' || IDENTITY_TYPE_KINDS.has(d.type)))) return null;
17887
17977
  let original = null;
17888
17978
  let qualifier;
17979
+ let module;
17889
17980
  for (const binding of fileEntry.importBindings || []) {
17890
17981
  if (binding.alias === typeName && binding.name && binding.name !== typeName) {
17891
17982
  original = binding.name;
17983
+ module = binding.module;
17892
17984
  } else if (!binding.alias && binding.kind === 'using' && binding.name === typeName) {
17893
17985
  // C# alias directive: the module is the aliased type.
17894
17986
  const head = String(binding.module || '').split('<', 1)[0].trim();
@@ -17907,7 +17999,13 @@ function _importRenamedTypeName(index, file, typeName, options = {}) {
17907
17999
  if (rename) original = String(rename.original).split(/::|\./).pop();
17908
18000
  }
17909
18001
  if (!original) return null;
17910
- if (options.withQualifier) return { name: original, qualifier };
18002
+ if (options.withQualifier) {
18003
+ // The module the alias was imported from: the original name is
18004
+ // looked up there, never among this file's other imports of it.
18005
+ const relative = module != null ? fileEntry.moduleResolved?.[module] : null;
18006
+ return { name: original, qualifier,
18007
+ ...(relative && { fromFile: path.isAbsolute(relative) ? relative : path.join(index.root, relative) }) };
18008
+ }
17911
18009
  return original;
17912
18010
  }
17913
18011
 
@@ -19279,7 +19377,9 @@ function _calleeSelectReceiverMethod(index, call, symbols, typeName, language,
19279
19377
  index, symbols, typeName, language, contextFile, accepts, call?.line, ref),
19280
19378
  language);
19281
19379
  }
19282
- if (classDefsNamed(index, typeName).hasScoped) {
19380
+ if (classDefsNamed(index, typeName).hasScoped || typeOrigin?.constructed) {
19381
+ // A constructed receiver is one class definition: its members are
19382
+ // the ones its own ancestry declares, nearest first.
19283
19383
  // A returned/field type is bound where it originated. Re-resolving
19284
19384
  // its bare name at the consumer can pick an unrelated local class
19285
19385
  // (or fail when the caller imported only its producer).
@@ -19288,6 +19388,12 @@ function _calleeSelectReceiverMethod(index, call, symbols, typeName, language,
19288
19388
  : resolveClassRef(index, typeName, typeOrigin?.fromFile || contextFile,
19289
19389
  { line: typeOrigin?.fromFile ? 0 : call?.line }));
19290
19390
  const declaring = ref.key && findDeclaringClass(index, ref, (symbols || []).filter(accepts));
19391
+ // A constructed class none of whose project ancestors declares the
19392
+ // member: an outside or unresolved base may. The caller reads the
19393
+ // ancestry and says which.
19394
+ if (typeOrigin?.constructed && ref.key && declaring?.uncertain && declaring.members.length === 0) {
19395
+ return { match: null };
19396
+ }
19291
19397
  if (!ref.key || declaring?.uncertain) return { ambiguous: true };
19292
19398
  // Name lookup precedes arity: a local override hides its base even
19293
19399
  // if its signature no longer accepts the call's arguments.
@@ -31533,7 +31639,9 @@ function _unprovenLocalClassReceiver(index, fileEntry, file, call) {
31533
31639
  const recordedRow = constructed
31534
31640
  ? (call.receiverTypeLocalClassRef?.start === start ? call.receiverTypeLocalClassRef.row : undefined)
31535
31641
  : call.receiverLocalClassRow;
31536
- if (_localClassReferenceBinds(index, fileEntry, file, start, name, local, recordedRow)) return null;
31642
+ if (_localClassReferenceBinds(index, fileEntry, file, start, name, local, recordedRow) &&
31643
+ (!constructed || (lazyConstructedClassModule().constructionKeepsClass(index, local) &&
31644
+ (!call.receiverWithBinding || lazyConstructedClassModule().contextBindingKeepsInstance(index, local))))) return null;
31537
31645
  // Neither the type name nor the qualifier spelling is a declaration
31538
31646
  // witness once a value binding shadows it. Do not attach that false
31539
31647
  // proof to the unverified entry either.
@@ -31542,6 +31650,157 @@ function _unprovenLocalClassReceiver(index, fileEntry, file, call) {
31542
31650
  receiverTypeFlowFile: undefined, receiverIsTypeQualified: undefined };
31543
31651
  }
31544
31652
 
31653
+ /** The class reference of a definition: its name with the key its namesakes are told apart by. */
31654
+ function _classRefOfDefinition(index, definition) {
31655
+ const entry = classDefsNamed(index, definition.name).entries.find(candidate => candidate.def === definition);
31656
+ return { name: definition.name, key: entry?.key || classKeyOf(index, definition), def: definition };
31657
+ }
31658
+
31659
+ /** The one module-level class of `name` a file declares, or null. */
31660
+ function _moduleClassNamed(index, file, name) {
31661
+ if (!file) return null;
31662
+ const defs = (index.symbols.get(name) || []).filter(definition =>
31663
+ IDENTITY_TYPE_KINDS.has(definition.type) && definition.file === file &&
31664
+ !_isFunctionLocalType(definition) && !definition.enclosingType);
31665
+ return defs.length === 1 ? defs[0] : null;
31666
+ }
31667
+
31668
+ /**
31669
+ * Where the ancestors of a class are declared: 'closed' when every one is a
31670
+ * project class, 'outside' when one is declared outside the project and the
31671
+ * rest are project classes, 'unknown' when a base is not resolved or is
31672
+ * computed (`extends mixin(Base)`).
31673
+ */
31674
+ function _classAncestry(index, definition) {
31675
+ const start = _classRefOfDefinition(index, definition);
31676
+ const visited = new Set([start.key]);
31677
+ const queue = [start];
31678
+ let outside = false;
31679
+ while (queue.length > 0 && visited.size < 128) {
31680
+ for (const parent of parentRefsOfClass(index, queue.shift())) {
31681
+ if (parent.external) { outside = true; continue; }
31682
+ if (parent.importUnresolved || !parent.key) return 'unknown';
31683
+ if (!parent.def) {
31684
+ // A base no project class is named after is outside the
31685
+ // project, unless a project symbol of the name computes it.
31686
+ if ((index.symbols.get(parent.name) || []).length > 0) return 'unknown';
31687
+ outside = true;
31688
+ continue;
31689
+ }
31690
+ if (visited.has(parent.key)) continue;
31691
+ visited.add(parent.key);
31692
+ queue.push(parent);
31693
+ }
31694
+ }
31695
+ return outside ? 'outside' : 'closed';
31696
+ }
31697
+
31698
+ /**
31699
+ * A receiver typed by the constructor expression that made it (`u =
31700
+ * User(..)` then `u.m()`, `new User(..).m()`): its class is the one the
31701
+ * expression's name denotes there (core/constructed-class.js). A name the
31702
+ * file imports from outside the project names an outside class; a name the
31703
+ * file binds without settling on one class types nothing.
31704
+ */
31705
+ function _constructedReceiverClass(index, fileEntry, filePath, call) {
31706
+ const constructedClasses = lazyConstructedClassModule();
31707
+ const record = constructedClasses.constructionRecordOf(index, filePath, call);
31708
+ if (record?.isMethod && record.receiver &&
31709
+ constructedClasses.qualifiedBindingChanged(index, filePath, record, record.receiver)) {
31710
+ return { ...call, receiverType: undefined, receiverTypeSource: undefined,
31711
+ receiverTypeEvidence: undefined, receiverTypeQualifier: undefined, receiverTypeFlowFile: undefined,
31712
+ receiverQualifiedFlow: `${record.receiver}.${record.name} — written namespace binding` };
31713
+ }
31714
+ const constructed = record ? constructedClasses.constructedClass(index, filePath, record) : undefined;
31715
+ if (constructed?.definition) {
31716
+ // `with Name(..) as v`: `v` is what the manager's enter method returns.
31717
+ if (call.receiverWithBinding &&
31718
+ !constructedClasses.contextBindingKeepsInstance(index, constructed.definition)) {
31719
+ return { ...call, receiverType: undefined, receiverTypeSource: undefined,
31720
+ receiverTypeEvidence: undefined, receiverTypeQualifier: undefined,
31721
+ receiverQualifiedFlow: `${constructed.definition.name} — context manager result` };
31722
+ }
31723
+ // `module.Name(..)` the module-path rule has already placed keeps
31724
+ // that origin (the module the name was read from is what its
31725
+ // evidence replays); the class found serves the member lookup.
31726
+ if (call.receiverTypeQualifier && call.receiverTypeFlowFile) {
31727
+ return { ...call, receiverConstructedClass: constructed.definition };
31728
+ }
31729
+ return { ...call, receiverType: constructed.definition.name,
31730
+ receiverTypeFlowFile: constructed.definition.file,
31731
+ receiverTypeQualifier: undefined,
31732
+ receiverConstructedClass: constructed.definition };
31733
+ }
31734
+ // `registry.User(..)` where `registry` is a value: the class is whatever
31735
+ // that attribute holds.
31736
+ const qualifierRoot = record?.isMethod ? String(record.receiver || record.receiverRoot || '').split('.')[0] : null;
31737
+ const rootIsImport = !!qualifierRoot && (fileEntry.importBindings || []).some(binding =>
31738
+ (binding.alias || binding.name) === qualifierRoot ||
31739
+ (!binding.alias && String(binding.name || '').startsWith(`${qualifierRoot}.`)));
31740
+ if (record && record.isMethod && !record.receiverIsModule && !record.receiverModuleSpecifier &&
31741
+ (record.receiverLocalBinding || !rootIsImport)) {
31742
+ return { ...call, receiverType: undefined, receiverTypeSource: undefined,
31743
+ receiverTypeEvidence: undefined, receiverTypeQualifier: undefined,
31744
+ receiverQualifiedFlow: `${record.receiver || 'value'}.${record.name} — attribute of a value` };
31745
+ }
31746
+ // `module.Name(..)`: the module-path rules of a qualified receiver decide
31747
+ // what this lookup did not. The construction record names the module
31748
+ // also where the receiver's own record does not carry the qualifier.
31749
+ if (constructed === null) {
31750
+ const qualifier = record?.receiver || record?.receiverModuleSpecifier;
31751
+ const origin = record?.isMethod && qualifier && _structuralQualifiedReceiverOrigin(index, fileEntry,
31752
+ qualifier, record.name, record.callSite || null);
31753
+ if (origin?.kind !== 'external') {
31754
+ return { ...call, receiverType: undefined, receiverTypeSource: undefined,
31755
+ receiverTypeEvidence: undefined, receiverTypeQualifier: undefined,
31756
+ receiverTypeFlowFile: undefined, receiverConstructedClass: undefined,
31757
+ receiverQualifiedFlow: `${record?.name || call.receiverType} — construction result is not one class` };
31758
+ }
31759
+ }
31760
+ if (call.receiverTypeQualifier) return call;
31761
+ if (record && record.isMethod && (record.receiverIsModule || record.receiverModuleSpecifier)) {
31762
+ const origin = _structuralQualifiedReceiverOrigin(index, fileEntry,
31763
+ record.receiver || record.receiverModuleSpecifier, record.name,
31764
+ call.receiverTypeEvidence || record.callSite || null);
31765
+ if (origin?.kind === 'project') {
31766
+ return { ...call, receiverTypeFlowFile: origin.fromFile,
31767
+ receiverTypeQualifier: record.receiver || record.receiverModuleSpecifier };
31768
+ }
31769
+ if (origin?.kind === 'external') {
31770
+ return BUILTIN_RECEIVER_TYPES.has(call.receiverType) ? { ...call, receiverTypePlatform: true }
31771
+ : { ...call, receiverType: undefined, receiverExternalFlow: origin.via,
31772
+ receiverExternalConcreteFlow: true };
31773
+ }
31774
+ return origin ? { ...call, receiverType: undefined, receiverQualifiedFlow: origin.via } : call;
31775
+ }
31776
+ const localName = record?.name || call.receiverTypeAlias || call.receiverTypeEvidence?.type || call.receiverType;
31777
+ const bindings = constructedClasses.localImportBindings(fileEntry, localName);
31778
+ const project = bindings.filter(binding => fileEntry.moduleResolved?.[binding.module]);
31779
+ if (bindings.length > 0 && project.length === 0) {
31780
+ // Stable platform classes (StringIO, BytesIO, ..) keep their
31781
+ // concrete builtin identity although their module is not indexed.
31782
+ if (BUILTIN_RECEIVER_TYPES.has(call.receiverType)) return { ...call, receiverTypePlatform: true };
31783
+ const via = `${bindings[0].module}.${bindings[0].name}`;
31784
+ const gap = bindings.some(binding => _unresolvedModuleIsGap(index, binding.module, binding));
31785
+ return { ...call, receiverType: undefined,
31786
+ ...(gap ? { receiverQualifiedFlow: `${via} — unresolved module` }
31787
+ : { receiverExternalFlow: via, receiverExternalConcreteFlow: true }) };
31788
+ }
31789
+ // No construction record to look the name up at: its import, when every
31790
+ // import of the name here leads to one module and name.
31791
+ const imported = new Set(project.map(binding => `${fileEntry.moduleResolved[binding.module]}\0${binding.name}`));
31792
+ if (!record && imported.size === 1 && project.length === bindings.length) {
31793
+ return { ...call, receiverType: project[0].name,
31794
+ receiverTypeFlowFile: path.join(index.root, fileEntry.moduleResolved[project[0].module]) };
31795
+ }
31796
+ if (constructed === null || (!record && imported.size > 1)) {
31797
+ return { ...call, receiverType: undefined, receiverTypeSource: undefined,
31798
+ receiverTypeEvidence: undefined,
31799
+ receiverQualifiedFlow: `${localName} — the name's binding is not one class here` };
31800
+ }
31801
+ return call;
31802
+ }
31803
+
31545
31804
  function _typeOfCallResultFoldInner(index, fileEntry, filePath, record, ctx, consumerAwaited) {
31546
31805
  const language = fileEntry.language;
31547
31806
  const traits = langTraits(language);
@@ -31572,23 +31831,46 @@ function _typeOfCallResultFoldInner(index, fileEntry, filePath, record, ctx, con
31572
31831
  !NON_CALLABLE_TYPES.has(definition.type));
31573
31832
  const local = _functionLocalTypeInScope(index, filePath, name, record.line);
31574
31833
  if (local) {
31575
- return _localConstructorBinding(index, fileEntry, filePath, record, local)
31834
+ return _localConstructorBinding(index, fileEntry, filePath, record, local) &&
31835
+ lazyConstructedClassModule().constructionKeepsClass(index, local)
31576
31836
  ? { type: name, fromFile: filePath, definition: local, exactConstructor: true } : null;
31577
31837
  }
31838
+ // The class the name denotes where it is written: this file's own
31839
+ // binding of it decides, whichever classes of the name the project
31840
+ // declares elsewhere (several `User` classes, one imported here).
31841
+ const constructed = lazyConstructedClassModule().constructedClass(index, filePath, record);
31842
+ if (constructed?.definition) {
31843
+ return { type: constructed.definition.name, fromFile: constructed.definition.file,
31844
+ definition: constructed.definition, exactConstructor: true };
31845
+ }
31846
+ // A name the file binds nowhere is a global of another script: the
31847
+ // one class of that name is it when no callable shares the name.
31578
31848
  const moduleTypes = typeDefs.filter(definition => !_isFunctionLocalType(definition));
31579
- if (!record.localShadow && moduleTypes.length === 1 && callableDefs.length === 0) {
31849
+ if (constructed?.unread && !record.localShadow && moduleTypes.length === 1 && callableDefs.length === 0) {
31580
31850
  const origin = _resolveFlowTypeOrigin(index, filePath, name, undefined, record.line);
31581
- if (origin?.fromFile === moduleTypes[0].file) {
31851
+ if (origin?.fromFile === moduleTypes[0].file &&
31852
+ lazyConstructedClassModule().constructionKeepsClass(index, moduleTypes[0])) {
31582
31853
  return { type: name, fromFile: origin.fromFile,
31583
31854
  exactConstructor: true };
31584
31855
  }
31585
31856
  }
31586
- // A module value alias of a class (fix #392): `Alias().m()`
31587
- // constructs the class the alias names where it is bound.
31588
- if (!record.localShadow && callableDefs.length === 0 && typeDefs.length === 0 &&
31589
- _moduleValueAliasNames(index).has(name)) {
31590
- const aliased = _moduleValueAliasType(index, filePath, name);
31591
- if (aliased) return { type: aliased.type, fromFile: aliased.fromFile, exactConstructor: true };
31857
+ }
31858
+ // `module.Name(...)`: the class that module binds under the name, by
31859
+ // the rule a variable made from the same expression is typed with.
31860
+ if (!nominal && record.isMethod && record.receiverIsModule && record.receiver &&
31861
+ !record.isFunctionReference && !record.isPotentialCallback &&
31862
+ (index.symbols.get(name) || []).some(definition =>
31863
+ IDENTITY_TYPE_KINDS.has(definition.type) && !_isFunctionLocalType(definition))) {
31864
+ const constructed = lazyConstructedClassModule().constructedClass(index, filePath, record);
31865
+ if (constructed?.definition) {
31866
+ return { type: constructed.definition.name, fromFile: constructed.definition.file,
31867
+ definition: constructed.definition, exactConstructor: true };
31868
+ }
31869
+ if (constructed === null) return null;
31870
+ const origin = _structuralQualifiedReceiverOrigin(index, fileEntry, record.receiver, name,
31871
+ record.callSite || null);
31872
+ if (origin?.kind === 'project') {
31873
+ return { type: name, fromFile: origin.fromFile, exactConstructor: true };
31592
31874
  }
31593
31875
  }
31594
31876
 
@@ -32292,6 +32574,6 @@ function findCallbackUsages(index, name) {
32292
32574
  return usages;
32293
32575
  }
32294
32576
 
32295
- module.exports = { rustPathQualifierVerdict: _pathQualifierVerdict, rustTraitDeclPathProbe: _rustTraitDeclPathProbe, _recordIsNoCall, _cppMemberUsingBases, _cppTargetVisibility, rustTypeNameDenotes, _textualIncludeClosures, _textualLinkVerdict, isProvenanceBuiltinReceiver, provenanceParameterIdentity: _overloadTypeIdentity, selectProvenanceOverload, _unresolvedModuleIsGap, _nonSourceModuleFile, _moduleValueAliasType, _importReaches, _sameNominalPackageDir, getCachedCalls, getCachedCallsBearing, findCallers, findCallees, getInstanceAttributeTypes, findCallbackUsages, _nameBindingReaches, _moduleAttributeBindingReaches, _declaredFieldType, _projectTopLevelNames, _callArityCompatible, _closeCallableIdentityGroup, _overloadDiscipline, _overloadApplicable, _buildReturnTypeFlowMap, _lookupReturnTypeFlow, _cFamilySignatureKey,
32577
+ module.exports = { argumentProvablyConverts: _argProvablyConverts, cppArgumentProvablyConverts: _cppArgProvablyConverts, rustPathQualifierVerdict: _pathQualifierVerdict, rustTraitDeclPathProbe: _rustTraitDeclPathProbe, _recordIsNoCall, _cppMemberUsingBases, _cppTargetVisibility, rustTypeNameDenotes, _textualIncludeClosures, _textualLinkVerdict, isProvenanceBuiltinReceiver, provenanceParameterIdentity: _overloadTypeIdentity, selectProvenanceOverload, _unresolvedModuleIsGap, _nonSourceModuleFile, _moduleValueAliasType, _importReaches, _sameNominalPackageDir, getCachedCalls, getCachedCallsBearing, findCallers, findCallees, getInstanceAttributeTypes, findCallbackUsages, _nameBindingReaches, _moduleAttributeBindingReaches, _declaredFieldType, _projectTopLevelNames, _callArityCompatible, _closeCallableIdentityGroup, _overloadDiscipline, _overloadApplicable, _buildReturnTypeFlowMap, _lookupReturnTypeFlow, _cFamilySignatureKey,
32296
32578
  _isConfigurationAlternative, _isCrossFileConfigurationMember, _configurationAlternativeFiles, _sameOwnerPath,
32297
32579
  _goFileConstraint, _typeQueryRecords };