@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.
Files changed (72) hide show
  1. package/dist/src/alias-chain.js +33 -2
  2. package/dist/src/analysis.js +18 -77
  3. package/dist/src/conformance-record.js +2 -2
  4. package/dist/src/contract/contract.generated.js +5 -4
  5. package/dist/src/dependencies.js +1 -1
  6. package/dist/src/exemptions/argument-places.js +131 -0
  7. package/dist/src/{calls.js → exemptions/calls.js} +126 -179
  8. package/dist/src/{exempt-classes.js → exemptions/classes.js} +1 -1
  9. package/dist/src/{decorator.js → exemptions/decorator.js} +19 -30
  10. package/dist/src/exemptions/detectors.js +48 -0
  11. package/dist/src/{enum-group.js → exemptions/enum-group.js} +7 -7
  12. package/dist/src/{erased-stores.js → exemptions/erased-stores.js} +4 -6
  13. package/dist/src/{framework-lifecycle.js → exemptions/framework-lifecycle.js} +31 -36
  14. package/dist/src/{generated-file.js → exemptions/generated-file.js} +4 -4
  15. package/dist/src/{injection-container.js → exemptions/injection-container.js} +19 -25
  16. package/dist/src/exemptions/installed-globals.js +173 -0
  17. package/dist/src/{interface-satisfaction.js → exemptions/interface-satisfaction.js} +52 -121
  18. package/dist/src/exemptions/lifecycle.js +150 -0
  19. package/dist/src/{reflective-lookup.js → exemptions/reflective-lookup.js} +3 -3
  20. package/dist/src/{serialization-contract.js → exemptions/serialization-contract.js} +415 -329
  21. package/dist/src/exemptions/sites.js +182 -0
  22. package/dist/src/{template-field.js → exemptions/template-field.js} +7 -7
  23. package/dist/src/{value-reach.js → exemptions/value-reach.js} +14 -7
  24. package/dist/src/findings/intra-function.js +50 -37
  25. package/dist/src/index/sites.js +1 -1
  26. package/dist/src/index/values.js +124 -0
  27. package/dist/src/intra-function-parts.js +1149 -222
  28. package/dist/src/liveness/sweep.js +2 -2
  29. package/dist/src/query.js +48 -0
  30. package/dist/src/roots/configured-declarations.js +1 -1
  31. package/dist/src/verbs/print-retained.js +23 -1
  32. package/jsr.json +1 -1
  33. package/package.json +2 -2
  34. package/src/alias-chain.ts +38 -2
  35. package/src/analysis.ts +19 -100
  36. package/src/bindings.ts +1 -1
  37. package/src/conformance-record.ts +2 -2
  38. package/src/contract/contract.generated.ts +5 -4
  39. package/src/dependencies.ts +1 -1
  40. package/src/exemptions/argument-places.ts +198 -0
  41. package/src/{calls.ts → exemptions/calls.ts} +190 -224
  42. package/src/{exempt-classes.ts → exemptions/classes.ts} +1 -1
  43. package/src/exemptions/decorator.ts +56 -0
  44. package/src/exemptions/detectors.ts +75 -0
  45. package/src/{enum-group.ts → exemptions/enum-group.ts} +9 -9
  46. package/src/{erased-stores.ts → exemptions/erased-stores.ts} +5 -8
  47. package/src/{framework-lifecycle.ts → exemptions/framework-lifecycle.ts} +39 -48
  48. package/src/{generated-file.ts → exemptions/generated-file.ts} +6 -6
  49. package/src/{injection-container.ts → exemptions/injection-container.ts} +28 -33
  50. package/src/exemptions/installed-globals.ts +255 -0
  51. package/src/{interface-satisfaction.ts → exemptions/interface-satisfaction.ts} +69 -160
  52. package/src/exemptions/lifecycle.ts +286 -0
  53. package/src/{reflective-lookup.ts → exemptions/reflective-lookup.ts} +5 -5
  54. package/src/{serialization-contract.ts → exemptions/serialization-contract.ts} +563 -429
  55. package/src/exemptions/sites.ts +293 -0
  56. package/src/{template-field.ts → exemptions/template-field.ts} +11 -11
  57. package/src/{value-reach.ts → exemptions/value-reach.ts} +16 -8
  58. package/src/facts.ts +1 -1
  59. package/src/findings/intra-function.ts +59 -37
  60. package/src/index/sites.ts +1 -1
  61. package/src/index/values.ts +170 -0
  62. package/src/intra-function-parts.ts +1435 -247
  63. package/src/liveness/sweep.ts +2 -2
  64. package/src/query.ts +109 -10
  65. package/src/roots/configured-declarations.ts +1 -2
  66. package/src/stores.ts +1 -1
  67. package/src/verbs/print-retained.ts +29 -1
  68. package/dist/src/exempt.js +0 -164
  69. package/dist/src/value-flow.js +0 -74
  70. package/src/decorator.ts +0 -78
  71. package/src/exempt.ts +0 -295
  72. 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 { isArrayLiteralExpression, isArrowFunction, isAsExpression, isAwaitExpression, isCallExpression, isClassDeclaration, isClassExpression, isClassStaticBlockDeclaration, 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, ModifierFlags, NodeFlags, SyntaxKind, } from "@typescript/native/ast";
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 "./alias-chain.js";
13
- import { calleeName, resolvedTargets, valuesPassed } from "./calls.js";
14
- import { classMembers, memberComponent, typesAt } from "./class-members.js";
15
- import { names, namesADeclaration, resolveEntries, spelledEntry, } from "./roots/configured-declarations.js";
16
- import { nodeKey } from "./inventory.js";
17
- import { renderPosition } from "./position.js";
18
- import { must, Unanswerable } from "./query.js";
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 elements keep none, each by its symbol.
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 [element] = must(project.queries.typeArguments(type));
218
- return element !== undefined && keepsNoName(project, element);
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.map(evaluated)) {
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, invocationOf) {
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) => invocationOf(site.call).arguments.flatMap((argument, index) => valuesPassed(argument)
333
- .filter((value) => spellings.has(value.name.getText()))
334
- .map((value) => ({ site, argument: index, value }))));
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(evaluated(node.initializer))) {
252
+ isObjectLiteralExpression(parenthesesRemoved(node.initializer))) {
377
253
  naming.push(node.type);
378
254
  }
379
255
  else if ((isSatisfiesExpression(node) || isAsExpression(node)) &&
380
- isObjectLiteralExpression(evaluated(node.expression))) {
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, walked) {
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 forms = new Map();
421
- for (const site of walked.sites) {
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 invocation = (call) => {
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 ? undefined : resolvedOf(call)?.declaration;
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 erasedArguments = new Map();
533
- const erasedAt = (call, argument) => {
534
- let indexes = erasedArguments.get(call);
535
- if (indexes === undefined) {
536
- const invoked = invocation(call);
537
- // Through a method of the prototype the resolved signature is the method's, so
538
- // the parameters are read off the callee's own signatures, any one erasing.
539
- const type = invoked.indirect ? must(queries.typeAt(invoked.callee)) : undefined;
540
- const signatures = invoked.indirect
541
- ? type === undefined
542
- ? []
543
- : must(queries.signaturesOf(type, SignatureKind.Call))
544
- : [resolvedOf(call)].filter((one) => one !== undefined);
545
- // A type parameter of the callee that nothing constrains and its result does not name
546
- // keeps nothing of the value either: the instantiated signature names the value's own
547
- // type there instead, and a result that names it hands the value back.
548
- const generic = invoked.indirect ? undefined : resolvedOf(call);
549
- const declared = generic === undefined ? undefined : must(queries.genericOf(generic));
550
- const unconstrained = (index) => {
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
- indexes = new Set(invoked.arguments.flatMap((_unused, index) => unconstrained(index) ||
567
- signatures.some((signature) => {
568
- const parameter = must(queries.parameterType(signature, index));
569
- return parameter !== undefined && (parameter.flags & ERASED) !== 0;
570
- })
571
- ? [index]
572
- : []));
573
- erasedArguments.set(call, indexes);
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
- return indexes.has(argument);
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 parameter = (byFunction.get(fn) ?? []).find((one) => one.index === argument || (one.rest && one.index <= argument));
598
- const reach = parameter === undefined ? undefined : forwards.get(keyOf(parameter));
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, walked.sites, erased, invocation);
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.invocation(site.call).arguments.flatMap((argument, index) => {
644
- const value = evaluated(argument);
645
- return holdsNoInstance(value) ? [] : [{ site, argument: index, value }];
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
- const passed = arguments_.flatMap((one, index) => {
653
- const type = typeOf.get(one.value);
654
- let reached = { classes: new Set(), members: [] };
655
- if (one.value.kind === SyntaxKind.ThisKeyword) {
656
- reached = {
657
- classes: new Set(one.site.self === undefined ? [] : [one.site.self]),
658
- members: [],
659
- };
660
- }
661
- else if (type !== undefined) {
662
- reached = reach.reachedFrom(type);
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
- for (const stored of [...(checked[index] ?? []), ...(stores.ofVariable(one.value) ?? [])]) {
665
- reached = joined(reached, reach.reachedFrom(stored));
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
- const carries = reached.classes.size > 0 || reached.members.length > 0;
668
- const destination = carries ? to.at(one.site.call, one.argument) : undefined;
669
- return destination === undefined
670
- ? []
671
- : [
672
- {
673
- site: one.value,
674
- classes: reached.classes,
675
- members: reached.members,
676
- to: destination,
677
- detail: `passed to ${destination.callee}`,
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
- const parameters = type === undefined ? [] : typeParametersOf(project, type, 0);
684
- const destination = parameters.length === 0 ? undefined : to.at(one.site.call, one.argument);
685
- return destination === undefined
686
- ? []
687
- : parameters.map((parameter) => ({ parameter, to: destination }));
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
- /** The type parameters one type names, through constituents and type arguments. */
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
- const byName = new Map();
788
- for (const site of sites) {
789
- const name = calleeName(site.call.expression)?.getText();
790
- if (name !== undefined) {
791
- byName.set(name, [...(byName.get(name) ?? []), site]);
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
- if (seen.has(parameter.id)) {
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(parameter.id);
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
- const index = fn === undefined || !isFunctionLikeDeclaration(fn)
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
- for (const site of byName.get(name) ?? []) {
818
- const resolved = must(queries.resolvedSignature(site.call));
819
- const declared = resolved === undefined ? undefined : must(queries.genericOf(resolved));
820
- const called = declared?.declaration === undefined
821
- ? undefined
822
- : project.declarationAt(declared.declaration)?.node;
823
- if (resolved === undefined || declared === undefined || called !== fn) {
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
- ? site.call.arguments.flatMap((_argument, at) => {
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: site.call,
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, walked.sites, destinations(input, chains, walked), erasedStores(project));
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 || (to.reach === "conversions" && CONVERSIONS.has(name))));
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 && !walked.initialized.has(id)
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 & (TypeFlags.Any | TypeFlags.Unknown)) !== 0;
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.