@cplieger/deadset-ts 8.0.0-dev.20 → 8.0.0-dev.22
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/dist/src/alias-chain.js +33 -2
- package/dist/src/analysis.js +18 -77
- package/dist/src/conformance-record.js +2 -2
- package/dist/src/contract/contract.generated.js +5 -4
- package/dist/src/dependencies.js +1 -1
- package/dist/src/exemptions/argument-places.js +131 -0
- package/dist/src/{calls.js → exemptions/calls.js} +126 -179
- package/dist/src/{exempt-classes.js → exemptions/classes.js} +1 -1
- package/dist/src/{decorator.js → exemptions/decorator.js} +19 -30
- package/dist/src/exemptions/detectors.js +48 -0
- package/dist/src/{enum-group.js → exemptions/enum-group.js} +7 -7
- package/dist/src/{erased-stores.js → exemptions/erased-stores.js} +4 -6
- package/dist/src/{framework-lifecycle.js → exemptions/framework-lifecycle.js} +31 -36
- package/dist/src/{generated-file.js → exemptions/generated-file.js} +4 -4
- package/dist/src/{injection-container.js → exemptions/injection-container.js} +19 -25
- package/dist/src/exemptions/installed-globals.js +173 -0
- package/dist/src/{interface-satisfaction.js → exemptions/interface-satisfaction.js} +52 -121
- package/dist/src/exemptions/lifecycle.js +150 -0
- package/dist/src/{reflective-lookup.js → exemptions/reflective-lookup.js} +3 -3
- package/dist/src/{serialization-contract.js → exemptions/serialization-contract.js} +415 -329
- package/dist/src/exemptions/sites.js +182 -0
- package/dist/src/{template-field.js → exemptions/template-field.js} +7 -7
- package/dist/src/{value-reach.js → exemptions/value-reach.js} +14 -7
- package/dist/src/findings/intra-function.js +50 -37
- package/dist/src/index/sites.js +1 -1
- package/dist/src/index/values.js +124 -0
- package/dist/src/intra-function-parts.js +1149 -222
- package/dist/src/liveness/sweep.js +2 -2
- package/dist/src/query.js +48 -0
- package/dist/src/roots/configured-declarations.js +1 -1
- package/dist/src/verbs/print-retained.js +23 -1
- package/jsr.json +1 -1
- package/package.json +2 -2
- package/src/alias-chain.ts +38 -2
- package/src/analysis.ts +19 -100
- package/src/bindings.ts +1 -1
- package/src/conformance-record.ts +2 -2
- package/src/contract/contract.generated.ts +5 -4
- package/src/dependencies.ts +1 -1
- package/src/exemptions/argument-places.ts +198 -0
- package/src/{calls.ts → exemptions/calls.ts} +190 -224
- package/src/{exempt-classes.ts → exemptions/classes.ts} +1 -1
- package/src/exemptions/decorator.ts +56 -0
- package/src/exemptions/detectors.ts +75 -0
- package/src/{enum-group.ts → exemptions/enum-group.ts} +9 -9
- package/src/{erased-stores.ts → exemptions/erased-stores.ts} +5 -8
- package/src/{framework-lifecycle.ts → exemptions/framework-lifecycle.ts} +39 -48
- package/src/{generated-file.ts → exemptions/generated-file.ts} +6 -6
- package/src/{injection-container.ts → exemptions/injection-container.ts} +28 -33
- package/src/exemptions/installed-globals.ts +255 -0
- package/src/{interface-satisfaction.ts → exemptions/interface-satisfaction.ts} +69 -160
- package/src/exemptions/lifecycle.ts +286 -0
- package/src/{reflective-lookup.ts → exemptions/reflective-lookup.ts} +5 -5
- package/src/{serialization-contract.ts → exemptions/serialization-contract.ts} +563 -429
- package/src/exemptions/sites.ts +293 -0
- package/src/{template-field.ts → exemptions/template-field.ts} +11 -11
- package/src/{value-reach.ts → exemptions/value-reach.ts} +16 -8
- package/src/facts.ts +1 -1
- package/src/findings/intra-function.ts +59 -37
- package/src/index/sites.ts +1 -1
- package/src/index/values.ts +170 -0
- package/src/intra-function-parts.ts +1435 -247
- package/src/liveness/sweep.ts +2 -2
- package/src/query.ts +109 -10
- package/src/roots/configured-declarations.ts +1 -2
- package/src/stores.ts +1 -1
- package/src/verbs/print-retained.ts +29 -1
- package/dist/src/exempt.js +0 -164
- package/dist/src/value-flow.js +0 -74
- package/src/decorator.ts +0 -78
- package/src/exempt.ts +0 -295
- package/src/value-flow.ts +0 -104
|
@@ -6,26 +6,22 @@
|
|
|
6
6
|
* the project handing such a parameter to a destination is one, to a fixpoint, and a
|
|
7
7
|
* function invoked through `call`, `apply` or `bind` is the callee.
|
|
8
8
|
*/
|
|
9
|
-
import {
|
|
10
|
-
import { parenthesesRemoved } from "./unwrap.js";
|
|
9
|
+
import { isArrowFunction, isAsExpression, isAwaitExpression, isCallExpression, isClassDeclaration, isClassExpression, isElementAccessExpression, isExpressionStatement, isFunctionDeclaration, isFunctionExpression, isFunctionLikeDeclaration, isGetAccessorDeclaration, isIdentifier, isInterfaceDeclaration, isMethodDeclaration, isNonNullExpression, isNoSubstitutionTemplateLiteral, isNumericLiteral, isObjectLiteralExpression, isParenthesizedExpression, isForInStatement, isPropertyAccessExpression, isPropertyDeclaration, isReturnStatement, isSatisfiesExpression, isSetAccessorDeclaration, isSourceFile, isSpreadAssignment, isSpreadElement, isStringLiteral, isTypeAssertion, isTypeLiteralNode, isVariableDeclaration, NodeFlags, SyntaxKind, } from "@typescript/native/ast";
|
|
11
10
|
import { SignatureKind, SymbolFlags, TypeFlags, } from "@typescript/native/sync";
|
|
12
|
-
import { aliasChains } from "
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
11
|
+
import { aliasChains } from "../alias-chain.js";
|
|
12
|
+
import { carriedValues, valuesOf } from "../index/values.js";
|
|
13
|
+
import { parenthesesRemoved } from "../unwrap.js";
|
|
14
|
+
import { fills, knownElements, layoutOf, placesIn, spansOf, } from "./argument-places.js";
|
|
15
|
+
import { classMembers, memberComponent, typesAt } from "../class-members.js";
|
|
16
|
+
import { names, namesADeclaration, resolveEntries, spelledEntry, } from "../roots/configured-declarations.js";
|
|
17
|
+
import { renderPosition } from "../position.js";
|
|
18
|
+
import { must, Unanswerable } from "../query.js";
|
|
19
19
|
import { erasedStores } from "./erased-stores.js";
|
|
20
|
-
import { checkedTypes, CONVERSION_MEMBERS, valueReach } from "./value-reach.js";
|
|
20
|
+
import { checkedTypes, CONVERSION_MEMBERS, CONVERSION_NAMES, ERASED, valueReach, } from "./value-reach.js";
|
|
21
21
|
/** The serializer every run reads, whatever the configuration names. */
|
|
22
22
|
const JSON_STRINGIFY = { global: "JSON.stringify" };
|
|
23
|
-
/** The methods a serializer and a formatter resolve by name on a value that left the analysis. */
|
|
24
|
-
const CONVERSIONS = new Set(["toJSON", "toString"]);
|
|
25
23
|
/** The method a serializer calls on a value that carries one, wherever the value goes. */
|
|
26
24
|
const SERIALIZED_CONVERSION = "toJSON";
|
|
27
|
-
/** The parameter types that keep nothing of the value they are given. */
|
|
28
|
-
const ERASED = TypeFlags.Any | TypeFlags.Unknown;
|
|
29
25
|
/** The standard library's promise types, whose value is their one type argument. */
|
|
30
26
|
const PROMISES = new Set(["Promise", "PromiseLike"]);
|
|
31
27
|
/** How deep a callee's result is searched for a type parameter: `Promise<Box<T>>` is two levels. */
|
|
@@ -34,39 +30,6 @@ const MENTION_DEPTH = 3;
|
|
|
34
30
|
function wider(a, b) {
|
|
35
31
|
return a === "conversions" || b === "conversions" ? "conversions" : (a ?? b);
|
|
36
32
|
}
|
|
37
|
-
/** Whether one node declares a static member, whose `this` is the class itself. */
|
|
38
|
-
function isStatic(node) {
|
|
39
|
-
const flags = node.modifierFlags ?? 0;
|
|
40
|
-
return (flags & ModifierFlags.Static) !== 0;
|
|
41
|
-
}
|
|
42
|
-
/** Whether one node opens a body whose `this` is not an instance of the enclosing class. */
|
|
43
|
-
function resetsThis(node) {
|
|
44
|
-
return (isFunctionDeclaration(node) ||
|
|
45
|
-
isFunctionExpression(node) ||
|
|
46
|
-
isClassStaticBlockDeclaration(node) ||
|
|
47
|
-
isStatic(node) ||
|
|
48
|
-
((isMethodDeclaration(node) ||
|
|
49
|
-
isGetAccessorDeclaration(node) ||
|
|
50
|
-
isSetAccessorDeclaration(node)) &&
|
|
51
|
-
isObjectLiteralExpression(node.parent)));
|
|
52
|
-
}
|
|
53
|
-
/** The parameters and declaring node of one function-like declaration of the project. */
|
|
54
|
-
function functionOf(node) {
|
|
55
|
-
if (isFunctionDeclaration(node) ||
|
|
56
|
-
(isMethodDeclaration(node) && !isObjectLiteralExpression(node.parent))) {
|
|
57
|
-
return { declared: node, parameters: node.parameters };
|
|
58
|
-
}
|
|
59
|
-
if (isVariableDeclaration(node) &&
|
|
60
|
-
node.initializer !== undefined &&
|
|
61
|
-
(isArrowFunction(node.initializer) || isFunctionExpression(node.initializer))) {
|
|
62
|
-
return { declared: node, parameters: node.initializer.parameters };
|
|
63
|
-
}
|
|
64
|
-
return undefined;
|
|
65
|
-
}
|
|
66
|
-
/** Whether one parameter is a `this` parameter, which types `this` and binds no argument. */
|
|
67
|
-
function isThisParameter(parameter) {
|
|
68
|
-
return isIdentifier(parameter.name) && parameter.name.text === "this";
|
|
69
|
-
}
|
|
70
33
|
/** Whether an argument is written in a form that holds no class instance, which saves a type. */
|
|
71
34
|
function holdsNoInstance(argument) {
|
|
72
35
|
return (isArrowFunction(argument) ||
|
|
@@ -76,108 +39,6 @@ function holdsNoInstance(argument) {
|
|
|
76
39
|
isNoSubstitutionTemplateLiteral(argument) ||
|
|
77
40
|
argument.kind === SyntaxKind.TemplateExpression);
|
|
78
41
|
}
|
|
79
|
-
/** The expression an argument evaluates, through parentheses and a spread. */
|
|
80
|
-
function evaluated(argument) {
|
|
81
|
-
const value = isSpreadElement(argument) ? argument.expression : argument;
|
|
82
|
-
return isParenthesizedExpression(value) ? evaluated(value.expression) : value;
|
|
83
|
-
}
|
|
84
|
-
/** The methods of `Function.prototype` that invoke a function with arguments of their own. */
|
|
85
|
-
const FUNCTION_METHODS = new Set(["call", "apply", "bind"]);
|
|
86
|
-
/** The interfaces of the standard library that declare those methods for a function value. */
|
|
87
|
-
const FUNCTION_INTERFACES = new Set([
|
|
88
|
-
"Function",
|
|
89
|
-
"CallableFunction",
|
|
90
|
-
"NewableFunction",
|
|
91
|
-
]);
|
|
92
|
-
/**
|
|
93
|
-
* The invocation a call writes through a method of `Function.prototype`, read from
|
|
94
|
-
* the syntax alone: `f.call(self, a)`, `f.apply(self, [a])`, `f.bind(self, a)`, which
|
|
95
|
-
* binds `a`, and `f.bind(self, a)(b)`, which passes `b` after it. An `apply` argument
|
|
96
|
-
* that is no array literal is the value of the first parameter, whose elements it
|
|
97
|
-
* carries. Whether the method is the prototype's is resolution's to say.
|
|
98
|
-
*/
|
|
99
|
-
function throughMethod(call) {
|
|
100
|
-
const expression = parenthesesRemoved(call.expression);
|
|
101
|
-
if (isPropertyAccessExpression(expression) && FUNCTION_METHODS.has(expression.name.text)) {
|
|
102
|
-
const [, ...rest] = call.arguments;
|
|
103
|
-
let passed = rest;
|
|
104
|
-
if (expression.name.text === "apply") {
|
|
105
|
-
const list = rest[0] === undefined ? undefined : parenthesesRemoved(rest[0]);
|
|
106
|
-
passed = list === undefined ? [] : isArrayLiteralExpression(list) ? list.elements : [list];
|
|
107
|
-
}
|
|
108
|
-
return {
|
|
109
|
-
method: expression.name,
|
|
110
|
-
invocation: { callee: expression.expression, arguments: passed, indirect: true },
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
if (isCallExpression(expression)) {
|
|
114
|
-
const bound = parenthesesRemoved(expression.expression);
|
|
115
|
-
if (isPropertyAccessExpression(bound) && bound.name.text === "bind") {
|
|
116
|
-
return {
|
|
117
|
-
method: bound.name,
|
|
118
|
-
invocation: {
|
|
119
|
-
callee: bound.expression,
|
|
120
|
-
arguments: [...expression.arguments.slice(1), ...call.arguments],
|
|
121
|
-
indirect: true,
|
|
122
|
-
},
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
return undefined;
|
|
127
|
-
}
|
|
128
|
-
/**
|
|
129
|
-
* Every call of the project's own files with the class `this` stands for there, and
|
|
130
|
-
* every identifier-named parameter of every function, method and function-valued
|
|
131
|
-
* variable the project declares, at the position of the argument it binds.
|
|
132
|
-
*/
|
|
133
|
-
function walkProject(project, held) {
|
|
134
|
-
const sites = [];
|
|
135
|
-
const parameters = [];
|
|
136
|
-
const initialized = new Set();
|
|
137
|
-
for (const file of project.ownSourceFiles()) {
|
|
138
|
-
const visit = (node, self) => {
|
|
139
|
-
let inner = self;
|
|
140
|
-
if (isPropertyDeclaration(node) && node.initializer !== undefined) {
|
|
141
|
-
const id = held.declarations.get(nodeKey(file, node));
|
|
142
|
-
if (id !== undefined) {
|
|
143
|
-
initialized.add(id);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
if (isClassDeclaration(node) || isClassExpression(node)) {
|
|
147
|
-
inner = held.declarations.get(nodeKey(file, node));
|
|
148
|
-
}
|
|
149
|
-
else if (resetsThis(node)) {
|
|
150
|
-
inner = undefined;
|
|
151
|
-
}
|
|
152
|
-
if (node.kind === SyntaxKind.CallExpression) {
|
|
153
|
-
sites.push({ call: node, self });
|
|
154
|
-
}
|
|
155
|
-
const fn = functionOf(node);
|
|
156
|
-
const id = fn === undefined ? undefined : held.declarations.get(nodeKey(file, fn.declared));
|
|
157
|
-
if (fn !== undefined && id !== undefined) {
|
|
158
|
-
fn.parameters
|
|
159
|
-
.filter((parameter) => !isThisParameter(parameter))
|
|
160
|
-
.forEach((parameter, index) => {
|
|
161
|
-
if (isIdentifier(parameter.name)) {
|
|
162
|
-
parameters.push({
|
|
163
|
-
fn: id,
|
|
164
|
-
index,
|
|
165
|
-
rest: parameter.dotDotDotToken !== undefined,
|
|
166
|
-
name: parameter.name,
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
|
-
node.forEachChild((child) => {
|
|
172
|
-
visit(child, inner);
|
|
173
|
-
});
|
|
174
|
-
};
|
|
175
|
-
file.forEachChild((child) => {
|
|
176
|
-
visit(child, undefined);
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
return { sites, parameters, initialized };
|
|
180
|
-
}
|
|
181
42
|
/** The calls of `Object` that read every own property of their argument by name. */
|
|
182
43
|
const ENUMERATIONS = new Set([
|
|
183
44
|
"keys",
|
|
@@ -202,7 +63,9 @@ function keepsNoName(project, type) {
|
|
|
202
63
|
}
|
|
203
64
|
/**
|
|
204
65
|
* The parameters that keep no member name of the value they are given, as `keepsNoName`
|
|
205
|
-
* reads it, and a rest parameter whose
|
|
66
|
+
* reads it, and a rest parameter whose number index holds a type that keeps none, an
|
|
67
|
+
* element of an array, a tuple, an array-like interface or a union or an intersection of
|
|
68
|
+
* them, each by its symbol. A rest a type parameter types is left to its calls' flows.
|
|
206
69
|
*/
|
|
207
70
|
function erasedParameters(project, parameters) {
|
|
208
71
|
const types = typesAt(project, parameters.map((parameter) => parameter.name));
|
|
@@ -214,8 +77,11 @@ function erasedParameters(project, parameters) {
|
|
|
214
77
|
if (!parameter.rest) {
|
|
215
78
|
return keepsNoName(project, type);
|
|
216
79
|
}
|
|
217
|
-
const
|
|
218
|
-
|
|
80
|
+
const element = (type.flags & TypeFlags.Instantiable) === 0
|
|
81
|
+
? must(project.queries.numberIndexOf(type))
|
|
82
|
+
: undefined;
|
|
83
|
+
return (element !== undefined &&
|
|
84
|
+
must(project.queries.constituents(element)).some((part) => keepsNoName(project, part)));
|
|
219
85
|
});
|
|
220
86
|
const bySymbol = new Map();
|
|
221
87
|
project
|
|
@@ -229,6 +95,14 @@ function erasedParameters(project, parameters) {
|
|
|
229
95
|
});
|
|
230
96
|
return bySymbol;
|
|
231
97
|
}
|
|
98
|
+
/**
|
|
99
|
+
* The type any place of a signature's rest may take, the number index of the rest's type;
|
|
100
|
+
* none where it has no rest or the type no number index.
|
|
101
|
+
*/
|
|
102
|
+
function restElementOf(project, signature) {
|
|
103
|
+
const rest = must(project.queries.restTypeOf(signature));
|
|
104
|
+
return rest === undefined ? undefined : must(project.queries.numberIndexOf(rest));
|
|
105
|
+
}
|
|
232
106
|
/** Whether one node is a function, whose body runs only where something calls it. */
|
|
233
107
|
function isFunctionNode(node) {
|
|
234
108
|
return (isFunctionDeclaration(node) ||
|
|
@@ -299,7 +173,7 @@ function enumeratedParameters(project, erased) {
|
|
|
299
173
|
else if (isForInStatement(node)) {
|
|
300
174
|
read.push(node.expression);
|
|
301
175
|
}
|
|
302
|
-
for (const value of read.
|
|
176
|
+
for (const value of read.flatMap(valuesOf)) {
|
|
303
177
|
if (isIdentifier(value) && value.text === spelled) {
|
|
304
178
|
asked.push({ node: value, parameter, object });
|
|
305
179
|
}
|
|
@@ -327,11 +201,13 @@ function enumeratedParameters(project, erased) {
|
|
|
327
201
|
];
|
|
328
202
|
}
|
|
329
203
|
/** Each place a call's argument passes one of the erased parameters it can see. */
|
|
330
|
-
function handedOn(project, sites, erased
|
|
204
|
+
function handedOn(project, sites, erased) {
|
|
331
205
|
const spellings = new Set([...erased.values()].map((parameter) => parameter.name.getText()));
|
|
332
|
-
const candidates = sites.flatMap((site) =>
|
|
333
|
-
.
|
|
334
|
-
.
|
|
206
|
+
const candidates = sites.calls.flatMap((site) => sites.invocations
|
|
207
|
+
.of(site.call)
|
|
208
|
+
.arguments.flatMap((argument, index) => carriedValues(argument).flatMap((value) => value.name !== undefined && spellings.has(value.name.getText())
|
|
209
|
+
? [{ site, argument: index, value: { ...value, name: value.name } }]
|
|
210
|
+
: [])));
|
|
335
211
|
const plain = candidates.filter((one) => !one.value.shorthand);
|
|
336
212
|
const resolved = project.symbolsAt(plain.map((one) => project.handle(one.value.name))).map(must);
|
|
337
213
|
const symbolOf = new Map(plain.map((one, index) => [one, resolved[index]]));
|
|
@@ -373,11 +249,11 @@ function implementedOwners(project, inAnalysis) {
|
|
|
373
249
|
else if ((isVariableDeclaration(node) || isPropertyDeclaration(node)) &&
|
|
374
250
|
node.type !== undefined &&
|
|
375
251
|
node.initializer !== undefined &&
|
|
376
|
-
isObjectLiteralExpression(
|
|
252
|
+
isObjectLiteralExpression(parenthesesRemoved(node.initializer))) {
|
|
377
253
|
naming.push(node.type);
|
|
378
254
|
}
|
|
379
255
|
else if ((isSatisfiesExpression(node) || isAsExpression(node)) &&
|
|
380
|
-
isObjectLiteralExpression(
|
|
256
|
+
isObjectLiteralExpression(parenthesesRemoved(node.expression))) {
|
|
381
257
|
naming.push(node.type);
|
|
382
258
|
}
|
|
383
259
|
node.forEachChild(visit);
|
|
@@ -412,26 +288,13 @@ function implementedOwners(project, inAnalysis) {
|
|
|
412
288
|
* parameter the function enumerates or hands on to a destination, holding back the widest
|
|
413
289
|
* of what those hold back, settled by widening until no parameter moves.
|
|
414
290
|
*/
|
|
415
|
-
function destinations(input, chains
|
|
416
|
-
const { project, held } = input;
|
|
291
|
+
function destinations(input, chains) {
|
|
292
|
+
const { project, held, sites } = input;
|
|
417
293
|
const queries = project.queries;
|
|
418
294
|
const own = project.ownPaths();
|
|
419
295
|
const named = resolveEntries(project, held, [JSON_STRINGIFY, ...input.ts.serializers]).filter(namesADeclaration);
|
|
420
|
-
const
|
|
421
|
-
|
|
422
|
-
const form = throughMethod(site.call);
|
|
423
|
-
if (form !== undefined) {
|
|
424
|
-
forms.set(site.call, form);
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
const callees = resolvedTargets(project, [
|
|
428
|
-
...walked.sites.flatMap((site) => calleeName(site.call.expression) ?? []),
|
|
429
|
-
...[...forms.values()].flatMap((form) => {
|
|
430
|
-
const name = calleeName(form.invocation.callee);
|
|
431
|
-
return name === undefined ? [form.method] : [form.method, name];
|
|
432
|
-
}),
|
|
433
|
-
]);
|
|
434
|
-
const erased = erasedParameters(project, walked.parameters);
|
|
296
|
+
const { of: invocation, callee: calleeOf } = sites.invocations;
|
|
297
|
+
const erased = erasedParameters(project, sites.parameters);
|
|
435
298
|
const byFunction = new Map();
|
|
436
299
|
for (const parameter of erased.values()) {
|
|
437
300
|
byFunction.set(parameter.fn, [...(byFunction.get(parameter.fn) ?? []), parameter]);
|
|
@@ -466,37 +329,16 @@ function destinations(input, chains, walked) {
|
|
|
466
329
|
* library's beside it, so a call can still resolve to the library's.
|
|
467
330
|
*/
|
|
468
331
|
const mayBeOutside = (target) => target.declarations.some((handle) => outside(target, false, [handle]));
|
|
469
|
-
const
|
|
470
|
-
const form = forms.get(call);
|
|
471
|
-
const method = form === undefined ? undefined : callees.get(form.method);
|
|
472
|
-
// Only the standard library's function methods pass their arguments on: the
|
|
473
|
-
// program's own `call` and `Reflect.apply` invoke nothing here.
|
|
474
|
-
if (form !== undefined &&
|
|
475
|
-
method !== undefined &&
|
|
476
|
-
mayBeOutside(method) &&
|
|
477
|
-
FUNCTION_INTERFACES.has(must(project.queries.parentOf(method))?.name ?? "")) {
|
|
478
|
-
return form.invocation;
|
|
479
|
-
}
|
|
480
|
-
return { callee: call.expression, arguments: call.arguments, indirect: false };
|
|
481
|
-
};
|
|
482
|
-
const calleeOf = (call) => {
|
|
483
|
-
const name = calleeName(invocation(call).callee);
|
|
484
|
-
return name === undefined ? undefined : callees.get(name);
|
|
485
|
-
};
|
|
486
|
-
const signatures = new Map();
|
|
487
|
-
const resolvedOf = (call) => {
|
|
488
|
-
if (!signatures.has(call)) {
|
|
489
|
-
signatures.set(call, must(queries.resolvedSignature(call)));
|
|
490
|
-
}
|
|
491
|
-
return signatures.get(call);
|
|
492
|
-
};
|
|
332
|
+
const bindings = callBindings(project, invocation);
|
|
493
333
|
/**
|
|
494
334
|
* The declarations of a callee that the declaration of a direct call's resolved
|
|
495
335
|
* signature lies in, or all of them where it lies in none: a declaration merged into
|
|
496
336
|
* the callee's symbol that the call does not resolve to does not decide where it runs.
|
|
497
337
|
*/
|
|
498
338
|
const calledDeclarations = (call, target) => {
|
|
499
|
-
const declaration = invocation(call).indirect
|
|
339
|
+
const declaration = invocation(call).indirect
|
|
340
|
+
? undefined
|
|
341
|
+
: bindings.of(call)[0]?.signature.declaration;
|
|
500
342
|
const signature = declaration === undefined ? undefined : project.declarationAt(declaration)?.node;
|
|
501
343
|
if (declaration === undefined || signature === undefined) {
|
|
502
344
|
return target.declarations;
|
|
@@ -529,54 +371,111 @@ function destinations(input, chains, walked) {
|
|
|
529
371
|
];
|
|
530
372
|
return inner.some((one) => mentions(one, parameter, depth + 1));
|
|
531
373
|
};
|
|
532
|
-
const
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
//
|
|
546
|
-
//
|
|
547
|
-
//
|
|
548
|
-
const
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
const parameter = declared === undefined ? undefined : must(queries.parameterType(declared, index));
|
|
552
|
-
const parts = parameter === undefined
|
|
553
|
-
? []
|
|
554
|
-
: parameter.isUnionType()
|
|
555
|
-
? must(queries.constituents(parameter))
|
|
556
|
-
: [parameter];
|
|
557
|
-
const result = declared === undefined ? undefined : must(queries.returnTypeOf(declared));
|
|
558
|
-
return parts.some((part) => {
|
|
559
|
-
if (!part.isTypeParameter() || (result !== undefined && mentions(result, part, 0))) {
|
|
560
|
-
return false;
|
|
561
|
-
}
|
|
562
|
-
const constraint = must(queries.constraintOf(part));
|
|
563
|
-
return constraint === undefined || (constraint.flags & ERASED) !== 0;
|
|
564
|
-
});
|
|
374
|
+
const erasedPlaces = new Map();
|
|
375
|
+
/**
|
|
376
|
+
* Whether an argument of a call is erased: where `element` is given, that element of a
|
|
377
|
+
* span of known length at the one place it fills, and otherwise at any place the
|
|
378
|
+
* argument fills, in any signature the call binds. An argument no signature the call
|
|
379
|
+
* binds has a place for says nothing of what it keeps, so it counts as erased.
|
|
380
|
+
*/
|
|
381
|
+
const erasedAt = (call, argument, element) => {
|
|
382
|
+
let erased = erasedPlaces.get(call);
|
|
383
|
+
if (erased === undefined) {
|
|
384
|
+
// A type parameter or an indexed access over one keeps nothing of the value where its
|
|
385
|
+
// base constraint is erased or absent, unless the result names the type parameter:
|
|
386
|
+
// the instantiated result is then the value's own type, handed back. A mapped type
|
|
387
|
+
// over type parameters keeps nothing where any of theirs is erased, whatever the
|
|
388
|
+
// result names, because the checker infers through it a copy without the class's
|
|
389
|
+
// non-public members, and which keys the mapping keeps is not read.
|
|
390
|
+
const erasedConstraint = (generic) => {
|
|
391
|
+
const constraint = must(queries.baseConstraintOf(generic));
|
|
392
|
+
return constraint === undefined || (constraint.flags & ERASED) !== 0;
|
|
565
393
|
};
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
return
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
394
|
+
const keepsNothing = (part, result) => {
|
|
395
|
+
const mapped = must(queries.mappedParametersOf(part));
|
|
396
|
+
if (mapped.length > 0) {
|
|
397
|
+
return mapped.some(erasedConstraint);
|
|
398
|
+
}
|
|
399
|
+
if ((part.flags & TypeFlags.Instantiable) === 0) {
|
|
400
|
+
return false;
|
|
401
|
+
}
|
|
402
|
+
const free = part.isTypeParameter() ? part : must(queries.indexedAccessOf(part))?.object;
|
|
403
|
+
if (free?.isTypeParameter() === true && result !== undefined && mentions(result, free, 0)) {
|
|
404
|
+
return false;
|
|
405
|
+
}
|
|
406
|
+
return erasedConstraint(part);
|
|
407
|
+
};
|
|
408
|
+
// At a rest placement the declaration's type is its rest's element, the number index,
|
|
409
|
+
// which the checker reads through the base constraint of a rest a type parameter
|
|
410
|
+
// types, so an element erased there is one the declaration leaves generic.
|
|
411
|
+
const unconstrained = (declared, placed) => {
|
|
412
|
+
if (declared === undefined) {
|
|
413
|
+
return false;
|
|
414
|
+
}
|
|
415
|
+
const result = must(queries.returnTypeOf(declared));
|
|
416
|
+
if (!placed.rest) {
|
|
417
|
+
const parameter = must(queries.parameterType(declared, placed.place));
|
|
418
|
+
return (parameter === undefined ? [] : must(queries.constituents(parameter))).some((part) => keepsNothing(part, result));
|
|
419
|
+
}
|
|
420
|
+
const rest = must(queries.restTypeOf(declared));
|
|
421
|
+
const element = restElementOf(project, declared);
|
|
422
|
+
if (rest?.isTypeParameter() === true && result !== undefined && mentions(result, rest, 0)) {
|
|
423
|
+
return false;
|
|
424
|
+
}
|
|
425
|
+
return (element === undefined ||
|
|
426
|
+
must(queries.constituents(element)).some((part) => (part.flags & ERASED) !== 0 || keepsNothing(part, result)));
|
|
427
|
+
};
|
|
428
|
+
const found = new Set();
|
|
429
|
+
const placedAt = new Set();
|
|
430
|
+
const unplaced = new Map();
|
|
431
|
+
for (const one of bindings.of(call)) {
|
|
432
|
+
const asked = new Set();
|
|
433
|
+
for (const placed of one.placed()) {
|
|
434
|
+
const { argument: at, element: given, place, uncovered, rest } = placed;
|
|
435
|
+
const key = `${String(at)}:${String(given)}`;
|
|
436
|
+
if (uncovered) {
|
|
437
|
+
unplaced.set(key, at);
|
|
438
|
+
continue;
|
|
439
|
+
}
|
|
440
|
+
placedAt.add(key);
|
|
441
|
+
const where = `${key}:${rest ? "rest" : String(place)}`;
|
|
442
|
+
if (found.has(key) || asked.has(where)) {
|
|
443
|
+
continue;
|
|
444
|
+
}
|
|
445
|
+
asked.add(where);
|
|
446
|
+
// A signature that is its own declaration, as one an indirect call binds, has its
|
|
447
|
+
// rest's element judged as the declaration's alone.
|
|
448
|
+
const own = rest && one.signature === one.declared;
|
|
449
|
+
const parameter = own
|
|
450
|
+
? undefined
|
|
451
|
+
: rest
|
|
452
|
+
? restElementOf(project, one.signature)
|
|
453
|
+
: must(queries.parameterType(one.signature, place));
|
|
454
|
+
if ((rest && !own && parameter === undefined) ||
|
|
455
|
+
(parameter !== undefined && (parameter.flags & ERASED) !== 0) ||
|
|
456
|
+
unconstrained(one.declared, placed)) {
|
|
457
|
+
found.add(key);
|
|
458
|
+
found.add(String(at));
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
for (const [key, at] of unplaced) {
|
|
463
|
+
if (!placedAt.has(key)) {
|
|
464
|
+
found.add(key);
|
|
465
|
+
found.add(String(at));
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
erased = found;
|
|
469
|
+
erasedPlaces.set(call, erased);
|
|
574
470
|
}
|
|
575
|
-
|
|
471
|
+
const span = bindings.spans(call).find((one) => one.argument === argument);
|
|
472
|
+
return element === undefined || span?.open !== false
|
|
473
|
+
? erased.has(String(argument))
|
|
474
|
+
: erased.has(`${String(argument)}:${String(element)}`);
|
|
576
475
|
};
|
|
577
476
|
const forwards = new Map();
|
|
578
477
|
const keyOf = (parameter) => `${parameter.fn}\u0000${String(parameter.index)}`;
|
|
579
|
-
const at = (call, argument) => {
|
|
478
|
+
const at = (call, argument, element) => {
|
|
580
479
|
const target = calleeOf(call);
|
|
581
480
|
if (target === undefined) {
|
|
582
481
|
return undefined;
|
|
@@ -586,16 +485,18 @@ function destinations(input, chains, walked) {
|
|
|
586
485
|
return { reach: "properties", callee: spelledEntry(entry.entry, held) };
|
|
587
486
|
}
|
|
588
487
|
if (outside(target, true, calledDeclarations(call, target))) {
|
|
589
|
-
return erasedAt(call, argument)
|
|
488
|
+
return erasedAt(call, argument, element)
|
|
590
489
|
? {
|
|
591
490
|
reach: "conversions",
|
|
592
491
|
callee: invocation(call).callee.getText().replace(/\s+/gu, ""),
|
|
593
492
|
}
|
|
594
493
|
: undefined;
|
|
595
494
|
}
|
|
495
|
+
const spans = bindings.spans(call).filter((span) => span.argument === argument);
|
|
596
496
|
for (const fn of chains.declarationsOf(target)) {
|
|
597
|
-
const
|
|
598
|
-
|
|
497
|
+
const reach = (byFunction.get(fn) ?? [])
|
|
498
|
+
.filter((one) => spans.some((span) => fills(span, one.index, one.rest, element)))
|
|
499
|
+
.reduce((widest, one) => wider(widest, forwards.get(keyOf(one))), undefined);
|
|
599
500
|
if (reach !== undefined) {
|
|
600
501
|
return { reach, callee: held.symbols.find((symbol) => symbol.id === fn)?.name ?? fn };
|
|
601
502
|
}
|
|
@@ -605,7 +506,7 @@ function destinations(input, chains, walked) {
|
|
|
605
506
|
for (const parameter of enumeratedParameters(project, erased)) {
|
|
606
507
|
forwards.set(keyOf(parameter), "properties");
|
|
607
508
|
}
|
|
608
|
-
const forwarded = handedOn(project,
|
|
509
|
+
const forwarded = handedOn(project, sites, erased);
|
|
609
510
|
for (let moved = true; moved;) {
|
|
610
511
|
moved = false;
|
|
611
512
|
for (const one of forwarded) {
|
|
@@ -630,7 +531,7 @@ function destinations(input, chains, walked) {
|
|
|
630
531
|
.declarationsOf(target)
|
|
631
532
|
.some((fn) => (byFunction.get(fn) ?? []).some((parameter) => forwards.has(keyOf(parameter))));
|
|
632
533
|
};
|
|
633
|
-
return { invocation, at, mayReceive };
|
|
534
|
+
return { invocation, bindings, at, mayReceive };
|
|
634
535
|
}
|
|
635
536
|
/** What two reaches carry together. */
|
|
636
537
|
function joined(a, b) {
|
|
@@ -640,51 +541,86 @@ function joined(a, b) {
|
|
|
640
541
|
function flowsOf(project, held, sites, to, stores) {
|
|
641
542
|
const reach = valueReach(project, held, stores.inside);
|
|
642
543
|
const arguments_ = sites.flatMap((site) => to.mayReceive(site.call)
|
|
643
|
-
? to
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
})
|
|
544
|
+
? to
|
|
545
|
+
.invocation(site.call)
|
|
546
|
+
.arguments.flatMap((argument, index) => valuesOf(argument).flatMap((value) => holdsNoInstance(value) ? [] : [{ site, argument: index, value }]))
|
|
647
547
|
: []);
|
|
648
548
|
const asked = arguments_.filter((one) => one.value.kind !== SyntaxKind.ThisKeyword);
|
|
649
549
|
const types = typesAt(project, asked.map((one) => one.value));
|
|
650
550
|
const typeOf = new Map(asked.map((one, index) => [one.value, types[index]]));
|
|
651
551
|
const checked = checkedTypes(project, arguments_.map((one) => one.value));
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
552
|
+
/**
|
|
553
|
+
* The types one argument's value carries, each with the element of the argument's span
|
|
554
|
+
* it fills. A spread's value is its elements, which a container other than an array does
|
|
555
|
+
* not walk to, so the span's element types join. A spread of known length gives each
|
|
556
|
+
* element at its own place, as does any type read for it that is a tuple of that length,
|
|
557
|
+
* and every other type fills every place the argument does.
|
|
558
|
+
*/
|
|
559
|
+
const partsOf = (one, types) => {
|
|
560
|
+
const span = to.bindings.spans(one.site.call).find((at) => at.argument === one.argument);
|
|
561
|
+
const given = span?.spread === true ? span.given.filter((type) => type !== undefined) : [];
|
|
562
|
+
if (span === undefined || !span.spread || span.open) {
|
|
563
|
+
return [{ element: undefined, types: [...types, ...given] }];
|
|
663
564
|
}
|
|
664
|
-
|
|
665
|
-
|
|
565
|
+
const whole = [];
|
|
566
|
+
const elements = span.given.map((type) => (type === undefined ? [] : [type]));
|
|
567
|
+
for (const type of types) {
|
|
568
|
+
const known = knownElements(project, type, elements.length);
|
|
569
|
+
if (known === undefined) {
|
|
570
|
+
whole.push(type);
|
|
571
|
+
}
|
|
572
|
+
else {
|
|
573
|
+
known.forEach((part, element) => {
|
|
574
|
+
if (part !== undefined) {
|
|
575
|
+
elements[element]?.push(part);
|
|
576
|
+
}
|
|
577
|
+
});
|
|
578
|
+
}
|
|
666
579
|
}
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
580
|
+
return [
|
|
581
|
+
{ element: undefined, types: whole },
|
|
582
|
+
...elements.map((parts, element) => ({ element, types: parts })),
|
|
583
|
+
];
|
|
584
|
+
};
|
|
585
|
+
const passed = arguments_.flatMap((one, index) => {
|
|
586
|
+
const type = typeOf.get(one.value);
|
|
587
|
+
const self = {
|
|
588
|
+
classes: new Set(one.value.kind === SyntaxKind.ThisKeyword && one.site.self !== undefined
|
|
589
|
+
? [one.site.self]
|
|
590
|
+
: []),
|
|
591
|
+
members: [],
|
|
592
|
+
};
|
|
593
|
+
const types = [
|
|
594
|
+
...(type === undefined ? [] : [type]),
|
|
595
|
+
...(checked[index] ?? []),
|
|
596
|
+
...(stores.ofVariable(one.value) ?? []),
|
|
597
|
+
];
|
|
598
|
+
return partsOf(one, types).flatMap(({ element, types: parts }) => {
|
|
599
|
+
const reached = parts.reduce((all, part) => joined(all, reach.reachedFrom(part)), element === undefined ? self : { classes: new Set(), members: [] });
|
|
600
|
+
const carries = reached.classes.size > 0 || reached.members.length > 0;
|
|
601
|
+
const destination = carries ? to.at(one.site.call, one.argument, element) : undefined;
|
|
602
|
+
return destination === undefined
|
|
603
|
+
? []
|
|
604
|
+
: [
|
|
605
|
+
{
|
|
606
|
+
site: one.value,
|
|
607
|
+
classes: reached.classes,
|
|
608
|
+
members: reached.members,
|
|
609
|
+
to: destination,
|
|
610
|
+
detail: `passed to ${destination.callee}`,
|
|
611
|
+
},
|
|
612
|
+
];
|
|
613
|
+
});
|
|
680
614
|
});
|
|
681
|
-
const generic = typeParameterFlows(project, reach, sites, arguments_.flatMap((one) => {
|
|
615
|
+
const generic = typeParameterFlows(project, reach, sites, to.bindings, arguments_.flatMap((one) => {
|
|
682
616
|
const type = typeOf.get(one.value);
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
617
|
+
return partsOf(one, type === undefined ? [] : [type]).flatMap(({ element, types }) => {
|
|
618
|
+
const parameters = types.flatMap((part) => typeParametersOf(project, part, 0));
|
|
619
|
+
const destination = parameters.length === 0 ? undefined : to.at(one.site.call, one.argument, element);
|
|
620
|
+
return destination === undefined
|
|
621
|
+
? []
|
|
622
|
+
: parameters.map((parameter) => ({ parameter, to: destination }));
|
|
623
|
+
});
|
|
688
624
|
}));
|
|
689
625
|
const left = stores.outside.flatMap((store) => {
|
|
690
626
|
const reached = reach.reachedFrom(store.type);
|
|
@@ -702,10 +638,14 @@ function flowsOf(project, held, sites, to, stores) {
|
|
|
702
638
|
});
|
|
703
639
|
return [...passed, ...generic, ...left];
|
|
704
640
|
}
|
|
705
|
-
/**
|
|
641
|
+
/**
|
|
642
|
+
* The type parameters one type names, through constituents, type arguments and the type
|
|
643
|
+
* parameters a mapped type is over. A class's polymorphic `this` type is no parameter a
|
|
644
|
+
* call binds: the reach walks it as its class.
|
|
645
|
+
*/
|
|
706
646
|
function typeParametersOf(project, type, depth) {
|
|
707
647
|
if (type.isTypeParameter()) {
|
|
708
|
-
return [type];
|
|
648
|
+
return type.isThisType === true ? [] : [type];
|
|
709
649
|
}
|
|
710
650
|
if (depth >= MENTION_DEPTH) {
|
|
711
651
|
return [];
|
|
@@ -714,6 +654,7 @@ function typeParametersOf(project, type, depth) {
|
|
|
714
654
|
return [
|
|
715
655
|
...(type.isUnionType() || type.isIntersectionType() ? must(queries.constituents(type)) : []),
|
|
716
656
|
...must(queries.typeArguments(type)),
|
|
657
|
+
...must(queries.mappedParametersOf(type)),
|
|
717
658
|
].flatMap((one) => typeParametersOf(project, one, depth + 1));
|
|
718
659
|
}
|
|
719
660
|
/**
|
|
@@ -757,6 +698,11 @@ function bound(project, declared, instantiated, parameter, depth) {
|
|
|
757
698
|
return [];
|
|
758
699
|
}
|
|
759
700
|
const { queries } = project;
|
|
701
|
+
// A place of a rest the parameter types is declared as an indexed access over it, and
|
|
702
|
+
// what the call passes there is one element of the type it binds.
|
|
703
|
+
if (must(queries.indexedAccessOf(declared))?.object.id === parameter.id) {
|
|
704
|
+
return [instantiated];
|
|
705
|
+
}
|
|
760
706
|
if (declared.isUnionType()) {
|
|
761
707
|
const parts = must(queries.constituents(declared));
|
|
762
708
|
if (parts.some((part) => part.id === parameter.id)) {
|
|
@@ -776,68 +722,208 @@ function bound(project, declared, instantiated, parameter, depth) {
|
|
|
776
722
|
return given === undefined ? [] : bound(project, one, given, parameter, depth + 1);
|
|
777
723
|
});
|
|
778
724
|
}
|
|
725
|
+
/** One value read once, at its first use. */
|
|
726
|
+
function once(read) {
|
|
727
|
+
let held;
|
|
728
|
+
return () => (held ??= { value: read() }).value;
|
|
729
|
+
}
|
|
730
|
+
/**
|
|
731
|
+
* The signatures a project's calls bind, and the place each argument binds in each. A
|
|
732
|
+
* direct call binds the signature the checker resolves, its places taking the types the
|
|
733
|
+
* checker instantiates there. Through `call`, `apply` or `bind` a call binds every call
|
|
734
|
+
* signature of the function, a place taking the type of the argument that fills it: the
|
|
735
|
+
* checker instantiates those methods from the arguments, not from the function's own
|
|
736
|
+
* parameters, so no instantiation of them says what the function keeps.
|
|
737
|
+
*/
|
|
738
|
+
function callBindings(project, invocation) {
|
|
739
|
+
const { queries } = project;
|
|
740
|
+
const known = new Map();
|
|
741
|
+
const passed = new Map();
|
|
742
|
+
const spanned = new Map();
|
|
743
|
+
const layouts = new Map();
|
|
744
|
+
const valueOf = (argument) => isSpreadElement(argument) ? argument.expression : argument;
|
|
745
|
+
// An indirect call's places take its arguments' types, and a spread's type says how
|
|
746
|
+
// many places it fills; a direct call with neither reads no argument's type.
|
|
747
|
+
const typed = (call) => {
|
|
748
|
+
const invoked = invocation(call);
|
|
749
|
+
return invoked.indirect || invoked.arguments.some(isSpreadElement);
|
|
750
|
+
};
|
|
751
|
+
const typesPassed = (call) => {
|
|
752
|
+
let types = passed.get(call);
|
|
753
|
+
if (types === undefined) {
|
|
754
|
+
types = typesAt(project, invocation(call).arguments.map(valueOf));
|
|
755
|
+
passed.set(call, types);
|
|
756
|
+
}
|
|
757
|
+
return types;
|
|
758
|
+
};
|
|
759
|
+
const spans = (call) => {
|
|
760
|
+
let found = spanned.get(call);
|
|
761
|
+
if (found === undefined) {
|
|
762
|
+
const invoked = invocation(call);
|
|
763
|
+
found = spansOf(project, invoked.arguments, invoked.listed, typed(call) ? typesPassed(call) : []);
|
|
764
|
+
spanned.set(call, found);
|
|
765
|
+
}
|
|
766
|
+
return found;
|
|
767
|
+
};
|
|
768
|
+
const layout = (signature) => {
|
|
769
|
+
let found = layouts.get(signature.id);
|
|
770
|
+
if (found === undefined) {
|
|
771
|
+
found = layoutOf(project, signature);
|
|
772
|
+
layouts.set(signature.id, found);
|
|
773
|
+
}
|
|
774
|
+
return found;
|
|
775
|
+
};
|
|
776
|
+
const distinct = (pairs) => [
|
|
777
|
+
...new Map(pairs.map((pair) => [`${String(pair.from.id)}:${String(pair.into.id)}`, pair])).values(),
|
|
778
|
+
];
|
|
779
|
+
// The checker infers a type parameter a mapped type is over by reversing the mapping,
|
|
780
|
+
// into a copy that names no class, so at a place declared as one the value passed is
|
|
781
|
+
// what each of its type parameters carries.
|
|
782
|
+
const mappedFrom = (from, value) => value === undefined
|
|
783
|
+
? []
|
|
784
|
+
: must(queries.constituents(from)).flatMap((part) => must(queries.mappedParametersOf(part)).map((source) => ({ from: source, into: value })));
|
|
785
|
+
const direct = (call, resolved) => {
|
|
786
|
+
if (resolved === undefined) {
|
|
787
|
+
return [];
|
|
788
|
+
}
|
|
789
|
+
const declared = must(queries.genericOf(resolved));
|
|
790
|
+
const placed = once(() => placesIn(spans(call), layout(resolved)));
|
|
791
|
+
const places = () => {
|
|
792
|
+
if (declared === undefined) {
|
|
793
|
+
return [];
|
|
794
|
+
}
|
|
795
|
+
return distinct(placed().flatMap((one) => {
|
|
796
|
+
const from = one.uncovered ? undefined : must(queries.parameterType(declared, one.place));
|
|
797
|
+
if (from === undefined) {
|
|
798
|
+
return [];
|
|
799
|
+
}
|
|
800
|
+
const into = must(queries.parameterType(resolved, one.place));
|
|
801
|
+
const element = one.rest ? restElementOf(project, declared) : undefined;
|
|
802
|
+
const elements = one.rest ? restElementOf(project, resolved) : undefined;
|
|
803
|
+
return [
|
|
804
|
+
...(into === undefined ? [] : [{ from, into }]),
|
|
805
|
+
...(element === undefined || elements === undefined
|
|
806
|
+
? []
|
|
807
|
+
: [{ from: element, into: elements }]),
|
|
808
|
+
...mappedFrom(from, one.given ?? typesPassed(call)[one.argument]),
|
|
809
|
+
];
|
|
810
|
+
}));
|
|
811
|
+
};
|
|
812
|
+
return [{ call, signature: resolved, declared, placed, places, written: call.typeArguments }];
|
|
813
|
+
};
|
|
814
|
+
const indirect = (call, callee) => (callee === undefined ? [] : must(queries.signaturesOf(callee, SignatureKind.Call))).map((signature) => {
|
|
815
|
+
const placed = once(() => placesIn(spans(call), layout(signature)));
|
|
816
|
+
return {
|
|
817
|
+
call,
|
|
818
|
+
signature,
|
|
819
|
+
declared: signature,
|
|
820
|
+
placed,
|
|
821
|
+
places: () => distinct(placed().flatMap(({ place, given, uncovered, rest }) => {
|
|
822
|
+
const from = uncovered ? undefined : must(queries.parameterType(signature, place));
|
|
823
|
+
const element = rest ? restElementOf(project, signature) : undefined;
|
|
824
|
+
return from === undefined || given === undefined
|
|
825
|
+
? []
|
|
826
|
+
: [
|
|
827
|
+
{ from, into: given },
|
|
828
|
+
...(element === undefined ? [] : [{ from: element, into: given }]),
|
|
829
|
+
...mappedFrom(from, given),
|
|
830
|
+
];
|
|
831
|
+
})),
|
|
832
|
+
written: undefined,
|
|
833
|
+
};
|
|
834
|
+
});
|
|
835
|
+
const of = (call) => {
|
|
836
|
+
let bound = known.get(call);
|
|
837
|
+
if (bound === undefined) {
|
|
838
|
+
const invoked = invocation(call);
|
|
839
|
+
bound = invoked.indirect
|
|
840
|
+
? indirect(call, must(queries.typeAt(invoked.callee)))
|
|
841
|
+
: direct(call, must(queries.resolvedSignature(call)));
|
|
842
|
+
known.set(call, bound);
|
|
843
|
+
}
|
|
844
|
+
return bound;
|
|
845
|
+
};
|
|
846
|
+
const all = (calls) => {
|
|
847
|
+
const asked = calls.filter((call) => !known.has(call));
|
|
848
|
+
const plain = asked.filter((call) => !invocation(call).indirect);
|
|
849
|
+
queries
|
|
850
|
+
.resolvedSignatures(plain)
|
|
851
|
+
.map(must)
|
|
852
|
+
.forEach((resolved, at) => {
|
|
853
|
+
const call = plain[at];
|
|
854
|
+
if (call !== undefined) {
|
|
855
|
+
known.set(call, direct(call, resolved));
|
|
856
|
+
}
|
|
857
|
+
});
|
|
858
|
+
const through = asked.filter((call) => invocation(call).indirect);
|
|
859
|
+
const callees = typesAt(project, through.map((call) => invocation(call).callee));
|
|
860
|
+
const typing = asked.filter((call) => typed(call) && !passed.has(call));
|
|
861
|
+
const values = typesAt(project, typing.flatMap((call) => invocation(call).arguments.map(valueOf)));
|
|
862
|
+
let offset = 0;
|
|
863
|
+
for (const call of typing) {
|
|
864
|
+
const count = invocation(call).arguments.length;
|
|
865
|
+
passed.set(call, values.slice(offset, offset + count));
|
|
866
|
+
offset += count;
|
|
867
|
+
}
|
|
868
|
+
through.forEach((call, at) => {
|
|
869
|
+
known.set(call, indirect(call, callees[at]));
|
|
870
|
+
});
|
|
871
|
+
return calls.flatMap(of);
|
|
872
|
+
};
|
|
873
|
+
return { of, all, spans };
|
|
874
|
+
}
|
|
779
875
|
/**
|
|
780
876
|
* The flows a type parameter of a function of the project carries where a value its type
|
|
781
877
|
* names reaches a destination: each call of the function passes its type argument there,
|
|
782
878
|
* written or inferred from the arguments, and a type argument that is a type parameter
|
|
783
879
|
* of the calling function is followed on to that function's calls, to a fixpoint.
|
|
784
880
|
*/
|
|
785
|
-
function typeParameterFlows(project, reach, sites, pending) {
|
|
881
|
+
function typeParameterFlows(project, reach, sites, bindings, pending) {
|
|
786
882
|
const { queries } = project;
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
const
|
|
790
|
-
|
|
791
|
-
|
|
883
|
+
let calls;
|
|
884
|
+
const byDeclaration = () => {
|
|
885
|
+
const found = new Map();
|
|
886
|
+
for (const one of bindings.all(sites.map((site) => site.call))) {
|
|
887
|
+
const handle = one.declared?.declaration;
|
|
888
|
+
const node = handle === undefined ? undefined : project.declarationAt(handle)?.node;
|
|
889
|
+
if (node !== undefined) {
|
|
890
|
+
found.set(node, [...(found.get(node) ?? []), one]);
|
|
891
|
+
}
|
|
792
892
|
}
|
|
793
|
-
|
|
893
|
+
return found;
|
|
894
|
+
};
|
|
794
895
|
const found = [];
|
|
896
|
+
// One type parameter reaches each of its destinations, each holding back its own.
|
|
795
897
|
const seen = new Set();
|
|
796
898
|
for (let next = pending.pop(); next !== undefined; next = pending.pop()) {
|
|
797
899
|
const { parameter, to } = next;
|
|
798
|
-
|
|
900
|
+
const key = `${String(parameter.id)}\u0000${to.reach}\u0000${to.callee}`;
|
|
901
|
+
if (seen.has(key)) {
|
|
799
902
|
continue;
|
|
800
903
|
}
|
|
801
|
-
seen.add(
|
|
904
|
+
seen.add(key);
|
|
802
905
|
const symbol = must(queries.symbolOfType(parameter));
|
|
803
906
|
const [handle] = symbol?.declarations ?? [];
|
|
804
907
|
const declaration = handle === undefined ? undefined : project.declarationAt(handle)?.node;
|
|
805
908
|
const fn = declaration?.parent;
|
|
806
|
-
|
|
807
|
-
? -1
|
|
808
|
-
: (fn.typeParameters ?? []).findIndex((one) => one === declaration);
|
|
809
|
-
const name = fn === undefined
|
|
810
|
-
? undefined
|
|
811
|
-
: isMethodDeclaration(fn) && isIdentifier(fn.name)
|
|
812
|
-
? fn.name.text
|
|
813
|
-
: nestedName(fn);
|
|
814
|
-
if (symbol === undefined || fn === undefined || index < 0 || name === undefined) {
|
|
909
|
+
if (symbol === undefined || fn === undefined || !isFunctionLikeDeclaration(fn)) {
|
|
815
910
|
continue;
|
|
816
911
|
}
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
continue;
|
|
825
|
-
}
|
|
826
|
-
const written = site.call.typeArguments?.[index];
|
|
912
|
+
const index = (fn.typeParameters ?? []).findIndex((one) => one === declaration);
|
|
913
|
+
if (index < 0) {
|
|
914
|
+
continue;
|
|
915
|
+
}
|
|
916
|
+
calls ??= byDeclaration();
|
|
917
|
+
for (const one of calls.get(fn) ?? []) {
|
|
918
|
+
const written = one.written?.[index];
|
|
827
919
|
const given = written === undefined
|
|
828
|
-
?
|
|
829
|
-
const from = must(queries.parameterType(declared, at));
|
|
830
|
-
const into = must(queries.parameterType(resolved, at));
|
|
831
|
-
return from === undefined || into === undefined
|
|
832
|
-
? []
|
|
833
|
-
: bound(project, from, into, parameter, 0);
|
|
834
|
-
})
|
|
920
|
+
? one.places().flatMap(({ from, into }) => bound(project, from, into, parameter, 0))
|
|
835
921
|
: typesAt(project, [written]).filter((type) => type !== undefined);
|
|
836
922
|
for (const type of given) {
|
|
837
923
|
const reached = reach.reachedFrom(type);
|
|
838
924
|
if (reached.classes.size > 0 || reached.members.length > 0) {
|
|
839
925
|
found.push({
|
|
840
|
-
site:
|
|
926
|
+
site: one.call,
|
|
841
927
|
classes: reached.classes,
|
|
842
928
|
members: reached.members,
|
|
843
929
|
to,
|
|
@@ -857,10 +943,9 @@ function typeParameterFlows(project, reach, sites, pending) {
|
|
|
857
943
|
* the callee named.
|
|
858
944
|
*/
|
|
859
945
|
export function serializationContract(input) {
|
|
860
|
-
const { project, held, targetRoot } = input;
|
|
861
|
-
const walked = walkProject(project, held);
|
|
946
|
+
const { project, held, sites, targetRoot } = input;
|
|
862
947
|
const chains = aliasChains(project, held);
|
|
863
|
-
const flows = flowsOf(project, held,
|
|
948
|
+
const flows = flowsOf(project, held, sites.calls, destinations(input, chains), erasedStores(project));
|
|
864
949
|
if (flows.length === 0) {
|
|
865
950
|
return [];
|
|
866
951
|
}
|
|
@@ -870,9 +955,10 @@ export function serializationContract(input) {
|
|
|
870
955
|
// the value left the analysis.
|
|
871
956
|
const holdsBack = (flags, name, to) => (flags & SymbolFlags.Property) !== 0 ||
|
|
872
957
|
((flags & CONVERSION_MEMBERS) !== 0 &&
|
|
873
|
-
(name === SERIALIZED_CONVERSION ||
|
|
958
|
+
(name === SERIALIZED_CONVERSION ||
|
|
959
|
+
(to.reach === "conversions" && CONVERSION_NAMES.has(name))));
|
|
874
960
|
// The receiver reads only what the program wrote, so a data member holds while written.
|
|
875
|
-
const evidence = (id, flags, detail, site) => (flags & SymbolFlags.Property) !== 0 && !
|
|
961
|
+
const evidence = (id, flags, detail, site) => (flags & SymbolFlags.Property) !== 0 && !sites.initialized.has(id)
|
|
876
962
|
? { id, detail, site, whileWritten: true }
|
|
877
963
|
: { id, detail, site };
|
|
878
964
|
const found = [];
|
|
@@ -1112,7 +1198,7 @@ function readTypes(project, values) {
|
|
|
1112
1198
|
const parse = parses[at];
|
|
1113
1199
|
const parsed = parse !== undefined && standard.has(parse);
|
|
1114
1200
|
const type = valueTypes[at];
|
|
1115
|
-
const untyped = type !== undefined && (type.flags &
|
|
1201
|
+
const untyped = type !== undefined && (type.flags & ERASED) !== 0;
|
|
1116
1202
|
return parsed || untyped ? [{ ...readAs(value), parsed }] : [];
|
|
1117
1203
|
});
|
|
1118
1204
|
// Each type node and each outermost expression, with whether a `JSON.parse` call is read as it.
|