ucn 5.5.3 → 5.6.0
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.
- package/.claude/skills/ucn/SKILL.md +35 -12
- package/.claude/skills/ucn/references/commands.md +18 -14
- package/.claude/skills/ucn/references/resolution-rules.md +4 -0
- package/.claude/skills/ucn/references/trust-contract.md +4 -4
- package/CHANGELOG.md +474 -0
- package/README.md +69 -29
- package/cli/flags.js +184 -0
- package/cli/index.js +137 -324
- package/core/account.js +44 -87
- package/core/analysis.js +273 -107
- package/core/ast-analysis.js +2 -0
- package/core/bridge.js +14 -0
- package/core/cache.js +101 -16
- package/core/callers.js +352 -70
- package/core/capture.js +331 -0
- package/core/change-kinds.js +888 -0
- package/core/check.js +1117 -139
- package/core/command-contracts.js +7 -6
- package/core/constructed-class.js +1195 -0
- package/core/contract-membership.js +142 -0
- package/core/deadcode.js +362 -84
- package/core/deletion.js +401 -0
- package/core/discovery.js +104 -9
- package/core/entrypoints.js +12 -6
- package/core/evidence-bands.js +68 -0
- package/core/evidence-bands.json +44 -0
- package/core/execute.js +172 -161
- package/core/hot-summary.js +241 -0
- package/core/index-ir.js +3 -1
- package/core/ir.js +5 -1
- package/core/member-signatures.js +789 -0
- package/core/output/analysis-ext.js +20 -10
- package/core/output/analysis.js +98 -60
- package/core/output/check.js +98 -37
- package/core/output/doctor.js +34 -1
- package/core/output/find.js +2 -1
- package/core/output/lines.js +63 -3
- package/core/output/public.js +29 -11
- package/core/output/refactoring.js +32 -0
- package/core/output/reporting.js +15 -2
- package/core/output/search.js +11 -2
- package/core/output/tracing.js +35 -7
- package/core/output-budget.js +124 -57
- package/core/parallel-build.js +3 -3
- package/core/project.js +106 -23
- package/core/provenance-type.js +12 -5
- package/core/public-command.js +35 -9
- package/core/python-modules.js +6 -0
- package/core/reflection.js +151 -23
- package/core/registry.js +623 -72
- package/core/reporting.js +69 -34
- package/core/review-items.js +75 -0
- package/core/search.js +89 -128
- package/core/shared.js +0 -45
- package/core/snapshot.js +350 -0
- package/core/source-universe.js +707 -0
- package/core/test-units.js +3624 -0
- package/core/tracing.js +150 -96
- package/core/verify.js +27 -11
- package/languages/adapter.js +8 -3
- package/languages/c-family.js +12 -0
- package/languages/csharp.js +634 -0
- package/languages/go.js +120 -0
- package/languages/index.js +422 -4
- package/languages/java.js +712 -0
- package/languages/javascript.js +1460 -2
- package/languages/lexical-scope.js +68 -0
- package/languages/python.js +1672 -1
- package/languages/rust.js +91 -0
- package/mcp/server.js +44 -110
- package/package.json +10 -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
|
-
//
|
|
2510
|
-
//
|
|
2511
|
-
//
|
|
2512
|
-
//
|
|
2513
|
-
//
|
|
2514
|
-
// path because they may hold a project subtype.
|
|
2515
|
-
if (call.isMethod && call.
|
|
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
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
if (
|
|
2528
|
-
|
|
2529
|
-
|
|
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
|
-
|
|
4091
|
-
options.targetDefinitions || definitions)
|
|
4092
|
-
|
|
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
|
-
|
|
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,
|
|
10063
|
-
fromFile:
|
|
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
|
-
|
|
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,
|
|
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)
|
|
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)
|
|
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
|
-
|
|
31587
|
-
|
|
31588
|
-
|
|
31589
|
-
|
|
31590
|
-
|
|
31591
|
-
|
|
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
|
|