@scriptc/compiler 0.3.5 → 0.3.6
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/ambient/scriptc-node-fallback.d.ts +278 -32
- package/dist/backend/llvm/dyn.js +31 -1
- package/dist/backend/llvm/dyn.js.map +1 -1
- package/dist/backend/llvm/emitter.d.ts +45 -1
- package/dist/backend/llvm/emitter.js +412 -104
- package/dist/backend/llvm/emitter.js.map +1 -1
- package/dist/backend/llvm/expr-dispatch.js +1 -0
- package/dist/backend/llvm/expr-dispatch.js.map +1 -1
- package/dist/backend/llvm/expr-dynamic.js +25 -1
- package/dist/backend/llvm/expr-dynamic.js.map +1 -1
- package/dist/backend/llvm/expr-primitives.d.ts +1 -1
- package/dist/backend/llvm/expr-primitives.js +11 -0
- package/dist/backend/llvm/expr-primitives.js.map +1 -1
- package/dist/backend/llvm/lib-dispatch.js +28 -1
- package/dist/backend/llvm/lib-dispatch.js.map +1 -1
- package/dist/backend/llvm/lib-filesystem.js +61 -1
- package/dist/backend/llvm/lib-filesystem.js.map +1 -1
- package/dist/backend/llvm/lib-network.js +21 -3
- package/dist/backend/llvm/lib-network.js.map +1 -1
- package/dist/backend/llvm/lib-process.js +10 -0
- package/dist/backend/llvm/lib-process.js.map +1 -1
- package/dist/backend/llvm/lib-shared.js +43 -0
- package/dist/backend/llvm/lib-shared.js.map +1 -1
- package/dist/backend/llvm/lib-test.js +16 -3
- package/dist/backend/llvm/lib-test.js.map +1 -1
- package/dist/backend/llvm/nullable-unions.d.ts +6 -3
- package/dist/backend/llvm/nullable-unions.js +2 -0
- package/dist/backend/llvm/nullable-unions.js.map +1 -1
- package/dist/backend/llvm/record-results.d.ts +1 -1
- package/dist/backend/llvm/record-results.js +2 -2
- package/dist/backend/llvm/record-results.js.map +1 -1
- package/dist/backend/llvm/reference-writes.js +1 -0
- package/dist/backend/llvm/reference-writes.js.map +1 -1
- package/dist/backend/llvm/shapes.d.ts +3 -0
- package/dist/backend/llvm/shapes.js +5 -0
- package/dist/backend/llvm/shapes.js.map +1 -1
- package/dist/backend/llvm/speculative-integers.d.ts +74 -0
- package/dist/backend/llvm/speculative-integers.js +571 -0
- package/dist/backend/llvm/speculative-integers.js.map +1 -0
- package/dist/backend/llvm/speculative-integers.test.d.ts +1 -0
- package/dist/backend/llvm/speculative-integers.test.js +148 -0
- package/dist/backend/llvm/speculative-integers.test.js.map +1 -0
- package/dist/backend/may-throw.js +2 -0
- package/dist/backend/may-throw.js.map +1 -1
- package/dist/backend/native/executable.js +3 -0
- package/dist/backend/native/executable.js.map +1 -1
- package/dist/backend/native/runtime-inputs.d.ts +1 -1
- package/dist/backend/native/runtime-inputs.js +1 -0
- package/dist/backend/native/runtime-inputs.js.map +1 -1
- package/dist/backend/runtime-pack.d.ts +7 -3
- package/dist/backend/runtime-pack.js +12 -6
- package/dist/backend/runtime-pack.js.map +1 -1
- package/dist/backend/runtime-pack.test.js +15 -6
- package/dist/backend/runtime-pack.test.js.map +1 -1
- package/dist/contracts/check.js +1 -0
- package/dist/contracts/check.js.map +1 -1
- package/dist/coverage/surface-manifest.js +3 -3
- package/dist/coverage/surface-manifest.js.map +1 -1
- package/dist/diagnostics/diagnostic.d.ts +1 -1
- package/dist/diagnostics/diagnostic.js +6 -2
- package/dist/diagnostics/diagnostic.js.map +1 -1
- package/dist/frontend/lowering/builtin-calls.js +10 -0
- package/dist/frontend/lowering/builtin-calls.js.map +1 -1
- package/dist/frontend/lowering/builtins/callable-aliases.js +31 -3
- package/dist/frontend/lowering/builtins/callable-aliases.js.map +1 -1
- package/dist/frontend/lowering/builtins/compression.d.ts +9 -0
- package/dist/frontend/lowering/builtins/compression.js +145 -30
- package/dist/frontend/lowering/builtins/compression.js.map +1 -1
- package/dist/frontend/lowering/builtins/crypto.js +12 -1
- package/dist/frontend/lowering/builtins/crypto.js.map +1 -1
- package/dist/frontend/lowering/builtins/errors.js +5 -3
- package/dist/frontend/lowering/builtins/errors.js.map +1 -1
- package/dist/frontend/lowering/builtins/filesystem-calls.d.ts +4 -0
- package/dist/frontend/lowering/builtins/filesystem-calls.js +11 -0
- package/dist/frontend/lowering/builtins/filesystem-calls.js.map +1 -1
- package/dist/frontend/lowering/builtins/filesystem.d.ts +8 -0
- package/dist/frontend/lowering/builtins/filesystem.js +139 -2
- package/dist/frontend/lowering/builtins/filesystem.js.map +1 -1
- package/dist/frontend/lowering/builtins/hash-stream.d.ts +12 -0
- package/dist/frontend/lowering/builtins/hash-stream.js +80 -0
- package/dist/frontend/lowering/builtins/hash-stream.js.map +1 -0
- package/dist/frontend/lowering/builtins/module-calls.js +13 -3
- package/dist/frontend/lowering/builtins/module-calls.js.map +1 -1
- package/dist/frontend/lowering/builtins/process.js +9 -2
- package/dist/frontend/lowering/builtins/process.js.map +1 -1
- package/dist/frontend/lowering/builtins/readline.d.ts +45 -13
- package/dist/frontend/lowering/builtins/readline.js +812 -47
- package/dist/frontend/lowering/builtins/readline.js.map +1 -1
- package/dist/frontend/lowering/builtins/workers.d.ts +4 -0
- package/dist/frontend/lowering/builtins/workers.js +58 -0
- package/dist/frontend/lowering/builtins/workers.js.map +1 -1
- package/dist/frontend/lowering/class-protocols.d.ts +55 -0
- package/dist/frontend/lowering/class-protocols.js +735 -0
- package/dist/frontend/lowering/class-protocols.js.map +1 -1
- package/dist/frontend/lowering/containers/array-from-async.d.ts +18 -0
- package/dist/frontend/lowering/containers/array-from-async.js +282 -0
- package/dist/frontend/lowering/containers/array-from-async.js.map +1 -0
- package/dist/frontend/lowering/containers/collection-grouping.js +2 -1
- package/dist/frontend/lowering/containers/collection-grouping.js.map +1 -1
- package/dist/frontend/lowering/containers/collection-methods.js +27 -23
- package/dist/frontend/lowering/containers/collection-methods.js.map +1 -1
- package/dist/frontend/lowering/divergences.js +188 -15
- package/dist/frontend/lowering/divergences.js.map +1 -1
- package/dist/frontend/lowering/lower-calls.js +71 -22
- package/dist/frontend/lowering/lower-calls.js.map +1 -1
- package/dist/frontend/lowering/lower-classes.js +39 -1
- package/dist/frontend/lowering/lower-classes.js.map +1 -1
- package/dist/frontend/lowering/lower-event-emitter.js +10 -1
- package/dist/frontend/lowering/lower-event-emitter.js.map +1 -1
- package/dist/frontend/lowering/lower-exprs.js +107 -13
- package/dist/frontend/lowering/lower-exprs.js.map +1 -1
- package/dist/frontend/lowering/lower-inspect.js +23 -3
- package/dist/frontend/lowering/lower-inspect.js.map +1 -1
- package/dist/frontend/lowering/lower-mock.d.ts +11 -1
- package/dist/frontend/lowering/lower-mock.js +377 -61
- package/dist/frontend/lowering/lower-mock.js.map +1 -1
- package/dist/frontend/lowering/lower-modules.js +1 -0
- package/dist/frontend/lowering/lower-modules.js.map +1 -1
- package/dist/frontend/lowering/lower-server.js +161 -16
- package/dist/frontend/lowering/lower-server.js.map +1 -1
- package/dist/frontend/lowering/lower-stmts.js +48 -7
- package/dist/frontend/lowering/lower-stmts.js.map +1 -1
- package/dist/frontend/lowering/lower-stream.d.ts +1 -3
- package/dist/frontend/lowering/lower-stream.js +317 -20
- package/dist/frontend/lowering/lower-stream.js.map +1 -1
- package/dist/frontend/lowering/lower-test.js +147 -48
- package/dist/frontend/lowering/lower-test.js.map +1 -1
- package/dist/frontend/lowering/lowerer.d.ts +10 -0
- package/dist/frontend/lowering/lowerer.js +8 -0
- package/dist/frontend/lowering/lowerer.js.map +1 -1
- package/dist/frontend/lowering/null-prototype.d.ts +13 -0
- package/dist/frontend/lowering/null-prototype.js +33 -0
- package/dist/frontend/lowering/null-prototype.js.map +1 -0
- package/dist/frontend/lowering/surfaces.d.ts +3 -3
- package/dist/frontend/lowering/surfaces.js +27 -5
- package/dist/frontend/lowering/surfaces.js.map +1 -1
- package/dist/frontend/mock-shapes.d.ts +5 -0
- package/dist/frontend/mock-shapes.js +27 -2
- package/dist/frontend/mock-shapes.js.map +1 -1
- package/dist/frontend/program.js +5 -4
- package/dist/frontend/program.js.map +1 -1
- package/dist/frontend/type-mapper.js +95 -4
- package/dist/frontend/type-mapper.js.map +1 -1
- package/dist/index.js +18 -8
- package/dist/index.js.map +1 -1
- package/dist/ir/builtin-effects.js +36 -0
- package/dist/ir/builtin-effects.js.map +1 -1
- package/dist/ir/builtin-signatures.d.ts +1 -1
- package/dist/ir/builtin-signatures.js +167 -6
- package/dist/ir/builtin-signatures.js.map +1 -1
- package/dist/ir/function-references.test.js +1 -0
- package/dist/ir/function-references.test.js.map +1 -1
- package/dist/ir/int32-slots.js +22 -0
- package/dist/ir/int32-slots.js.map +1 -1
- package/dist/ir/int32-slots.test.js +28 -0
- package/dist/ir/int32-slots.test.js.map +1 -1
- package/dist/ir/integer-loop-facts.d.ts +14 -6
- package/dist/ir/integer-loop-facts.js +119 -35
- package/dist/ir/integer-loop-facts.js.map +1 -1
- package/dist/ir/integer-ranges.js +1 -5
- package/dist/ir/integer-ranges.js.map +1 -1
- package/dist/ir/integer-ranges.test.js +60 -0
- package/dist/ir/integer-ranges.test.js.map +1 -1
- package/dist/ir/ir.d.ts +13 -0
- package/dist/ir/ir.js +4 -0
- package/dist/ir/ir.js.map +1 -1
- package/dist/ir/runtime-features.d.ts +3 -1
- package/dist/ir/runtime-features.js +96 -6
- package/dist/ir/runtime-features.js.map +1 -1
- package/dist/ir/runtime-features.test.js +62 -0
- package/dist/ir/runtime-features.test.js.map +1 -1
- package/dist/ir/traverse.js +4 -0
- package/dist/ir/traverse.js.map +1 -1
- package/dist/ir/validate.js +45 -3
- package/dist/ir/validate.js.map +1 -1
- package/dist/ir/validate.test.js +38 -0
- package/dist/ir/validate.test.js.map +1 -1
- package/dist/library/ir-policy.js +5 -0
- package/dist/library/ir-policy.js.map +1 -1
- package/package.json +20 -20
- package/surface-manifest.json +31 -19
|
@@ -8,6 +8,8 @@ export class ClassProtocols {
|
|
|
8
8
|
implemented = null;
|
|
9
9
|
methods = new Map();
|
|
10
10
|
answers = new Map();
|
|
11
|
+
/** What markIdentityFlows learned about the program, while it runs. */
|
|
12
|
+
facts = null;
|
|
11
13
|
constructor(checker, sources) {
|
|
12
14
|
this.checker = checker;
|
|
13
15
|
this.sources = sources;
|
|
@@ -65,8 +67,643 @@ export class ClassProtocols {
|
|
|
65
67
|
for (const source of this.sources)
|
|
66
68
|
if (!source.isDeclarationFile)
|
|
67
69
|
visit(source);
|
|
70
|
+
this.markIdentityFlows(protocols);
|
|
68
71
|
return protocols;
|
|
69
72
|
}
|
|
73
|
+
/** Structural types that receive a value with identity: a program class
|
|
74
|
+
* instance, or a value of a type already marked here. Natively such a
|
|
75
|
+
* type would be a record, and the conversion into it a field copy, so a
|
|
76
|
+
* write or identity check through the record would miss the original
|
|
77
|
+
* (`function bump(o: { n: number }) { o.n++ }` called with a class
|
|
78
|
+
* instance). Marking keeps every value of the type a live, checked view.
|
|
79
|
+
* Flows are scanned syntactically, and only sites whose source carries
|
|
80
|
+
* identity query their target type.
|
|
81
|
+
*
|
|
82
|
+
* A copy differs from the original only when something observes the
|
|
83
|
+
* difference, so a type is marked only when the program can: values of
|
|
84
|
+
* the type are written through, compared by identity (`===`, `switch`,
|
|
85
|
+
* assert), used as Set/Map keys or array search needles, given to
|
|
86
|
+
* whole-object observers (console, JSON, Object, util, structuredClone,
|
|
87
|
+
* spreads, for-in, templates, `unknown` parameters) or tested with
|
|
88
|
+
* instanceof; or a field the type
|
|
89
|
+
* exposes is written on the source class after construction, so a copy
|
|
90
|
+
* could read a stale value. A view of readonly data that nothing
|
|
91
|
+
* observes stays a native record. */
|
|
92
|
+
markIdentityFlows(protocols) {
|
|
93
|
+
const sites = [];
|
|
94
|
+
/** Expressions whose values are observed as whole objects. */
|
|
95
|
+
const observed = [];
|
|
96
|
+
/** Receivers whose type arguments or elements are observed (Set and Map
|
|
97
|
+
* keys, array search haystacks). */
|
|
98
|
+
const observedContainers = [];
|
|
99
|
+
/** Arguments observed when their parameter type accepts any value
|
|
100
|
+
* (`unknown`, `any`, `object`, `{}`, an unresolved type parameter) or
|
|
101
|
+
* the callee is a generic function or class that observes its type
|
|
102
|
+
* parameter. */
|
|
103
|
+
const looseArguments = [];
|
|
104
|
+
/** The call each loose argument belongs to. */
|
|
105
|
+
const looseCalls = [];
|
|
106
|
+
/** Member writes: the receiver and the written name (null: any name). */
|
|
107
|
+
const writes = [];
|
|
108
|
+
/** Annotated callback parameters: the caller's argument (the contextual
|
|
109
|
+
* signature's parameter) flows into the declared parameter type. */
|
|
110
|
+
const callbackParams = [];
|
|
111
|
+
const contextual = (expr) => () => this.checker.getContextualType(expr);
|
|
112
|
+
const flow = (expr) => {
|
|
113
|
+
if (expr !== undefined)
|
|
114
|
+
sites.push({ source: expr, target: contextual(expr) });
|
|
115
|
+
};
|
|
116
|
+
const write = (target) => {
|
|
117
|
+
while (ts.isParenthesizedExpression(target) || ts.isNonNullExpression(target))
|
|
118
|
+
target = target.expression;
|
|
119
|
+
if (ts.isPropertyAccessExpression(target)) {
|
|
120
|
+
// `this.f = v` in a constructor initializes the instance before any
|
|
121
|
+
// view of it can exist.
|
|
122
|
+
if (target.expression.kind !== ts.SyntaxKind.ThisKeyword || !inConstructor(target))
|
|
123
|
+
writes.push({ receiver: target.expression, name: target.name.text });
|
|
124
|
+
}
|
|
125
|
+
else if (ts.isElementAccessExpression(target)) {
|
|
126
|
+
const key = target.argumentExpression;
|
|
127
|
+
writes.push({
|
|
128
|
+
receiver: target.expression,
|
|
129
|
+
name: ts.isStringLiteralLike(key) || ts.isNumericLiteral(key) ? key.text : null,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
const observe = (node) => {
|
|
134
|
+
if (ts.isBinaryExpression(node)) {
|
|
135
|
+
const op = node.operatorToken.kind;
|
|
136
|
+
if ((op === ts.SyntaxKind.EqualsEqualsEqualsToken ||
|
|
137
|
+
op === ts.SyntaxKind.ExclamationEqualsEqualsToken ||
|
|
138
|
+
op === ts.SyntaxKind.EqualsEqualsToken ||
|
|
139
|
+
op === ts.SyntaxKind.ExclamationEqualsToken) &&
|
|
140
|
+
!isNullishLiteral(node.left) &&
|
|
141
|
+
!isNullishLiteral(node.right) &&
|
|
142
|
+
!isPrimitiveLiteral(node.left) &&
|
|
143
|
+
!isPrimitiveLiteral(node.right))
|
|
144
|
+
observed.push(node.left, node.right);
|
|
145
|
+
else if (op === ts.SyntaxKind.InstanceOfKeyword)
|
|
146
|
+
observed.push(node.left);
|
|
147
|
+
else if (op === ts.SyntaxKind.InKeyword)
|
|
148
|
+
observed.push(node.right);
|
|
149
|
+
else if (op >= ts.SyntaxKind.FirstAssignment && op <= ts.SyntaxKind.LastAssignment)
|
|
150
|
+
write(node.left);
|
|
151
|
+
}
|
|
152
|
+
else if ((ts.isPrefixUnaryExpression(node) || ts.isPostfixUnaryExpression(node)) &&
|
|
153
|
+
(node.operator === ts.SyntaxKind.PlusPlusToken ||
|
|
154
|
+
node.operator === ts.SyntaxKind.MinusMinusToken)) {
|
|
155
|
+
write(node.operand);
|
|
156
|
+
}
|
|
157
|
+
else if (ts.isDeleteExpression(node)) {
|
|
158
|
+
write(node.expression);
|
|
159
|
+
}
|
|
160
|
+
else if (ts.isSpreadAssignment(node) || ts.isTemplateSpan(node)) {
|
|
161
|
+
observed.push(node.expression);
|
|
162
|
+
}
|
|
163
|
+
else if (ts.isForInStatement(node)) {
|
|
164
|
+
observed.push(node.expression);
|
|
165
|
+
}
|
|
166
|
+
else if (ts.isSwitchStatement(node)) {
|
|
167
|
+
// `switch` matches its cases with ===.
|
|
168
|
+
const cases = node.caseBlock.clauses
|
|
169
|
+
.filter(ts.isCaseClause)
|
|
170
|
+
.map((clause) => clause.expression)
|
|
171
|
+
.filter((expr) => !isNullishLiteral(expr) && !isPrimitiveLiteral(expr));
|
|
172
|
+
if (cases.length > 0)
|
|
173
|
+
observed.push(node.expression, ...cases);
|
|
174
|
+
}
|
|
175
|
+
else if (ts.isCallExpression(node) || ts.isNewExpression(node)) {
|
|
176
|
+
const callee = node.expression;
|
|
177
|
+
const args = node.arguments ?? [];
|
|
178
|
+
const member = ts.isPropertyAccessExpression(callee) ? callee.name.text : null;
|
|
179
|
+
const owner = ts.isPropertyAccessExpression(callee) && ts.isIdentifier(callee.expression)
|
|
180
|
+
? callee.expression.text
|
|
181
|
+
: null;
|
|
182
|
+
if ((owner !== null && OBSERVER_NAMESPACES.has(owner)) ||
|
|
183
|
+
(ts.isIdentifier(callee) &&
|
|
184
|
+
(OBSERVER_FUNCTIONS.has(callee.text) ||
|
|
185
|
+
(ts.isNewExpression(node) && IDENTITY_CONSTRUCTORS.has(callee.text))))) {
|
|
186
|
+
observed.push(...args.filter((arg) => !ts.isSpreadElement(arg)));
|
|
187
|
+
// Object.assign(target, ...) and Object.defineProperty write the target.
|
|
188
|
+
if (owner === "Object" && args[0] !== undefined && OBJECT_WRITERS.has(member ?? ""))
|
|
189
|
+
writes.push({ receiver: args[0], name: null });
|
|
190
|
+
}
|
|
191
|
+
else if (member !== null && IDENTITY_METHODS.has(member)) {
|
|
192
|
+
observed.push(...args.filter((arg) => !ts.isSpreadElement(arg)));
|
|
193
|
+
observedContainers.push(callee.expression);
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
for (const arg of args) {
|
|
197
|
+
if (ts.isSpreadElement(arg))
|
|
198
|
+
continue;
|
|
199
|
+
looseArguments.push(arg);
|
|
200
|
+
looseCalls.push(node);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
const visit = (node) => {
|
|
206
|
+
observe(node);
|
|
207
|
+
if (ts.isCallExpression(node) || ts.isNewExpression(node))
|
|
208
|
+
node.arguments?.forEach(flow);
|
|
209
|
+
else if (ts.isVariableDeclaration(node) && node.type !== undefined)
|
|
210
|
+
flow(node.initializer);
|
|
211
|
+
else if (ts.isPropertyDeclaration(node) && node.type !== undefined)
|
|
212
|
+
flow(node.initializer);
|
|
213
|
+
else if (ts.isParameter(node) && node.type !== undefined)
|
|
214
|
+
flow(node.initializer);
|
|
215
|
+
else if (ts.isReturnStatement(node))
|
|
216
|
+
flow(node.expression);
|
|
217
|
+
else if (ts.isArrowFunction(node) && !ts.isBlock(node.body))
|
|
218
|
+
flow(node.body);
|
|
219
|
+
else if (ts.isYieldExpression(node))
|
|
220
|
+
flow(node.expression);
|
|
221
|
+
else if (ts.isArrayLiteralExpression(node))
|
|
222
|
+
node.elements.forEach((element) => {
|
|
223
|
+
if (!ts.isSpreadElement(element) && !ts.isOmittedExpression(element))
|
|
224
|
+
flow(element);
|
|
225
|
+
});
|
|
226
|
+
else if (ts.isPropertyAssignment(node))
|
|
227
|
+
flow(node.initializer);
|
|
228
|
+
else if (ts.isShorthandPropertyAssignment(node) && ts.isIdentifier(node.name))
|
|
229
|
+
flow(node.name);
|
|
230
|
+
else if (ts.isBinaryExpression(node) &&
|
|
231
|
+
node.operatorToken.kind === ts.SyntaxKind.EqualsToken &&
|
|
232
|
+
!ts.isObjectLiteralExpression(node.left) &&
|
|
233
|
+
!ts.isArrayLiteralExpression(node.left)) {
|
|
234
|
+
const left = node.left;
|
|
235
|
+
sites.push({ source: node.right, target: () => this.checker.getTypeAtLocation(left) });
|
|
236
|
+
}
|
|
237
|
+
else if ((ts.isAsExpression(node) || ts.isTypeAssertion(node)) && !accessedInPlace(node)) {
|
|
238
|
+
const typeNode = node.type;
|
|
239
|
+
sites.push({
|
|
240
|
+
source: node.expression,
|
|
241
|
+
target: () => this.checker.getTypeFromTypeNode(typeNode),
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
if (ts.isArrowFunction(node) || ts.isFunctionExpression(node))
|
|
245
|
+
node.parameters.forEach((param, index) => {
|
|
246
|
+
if (param.type !== undefined)
|
|
247
|
+
callbackParams.push({ fn: node, index, name: param.name });
|
|
248
|
+
});
|
|
249
|
+
ts.forEachChild(node, visit);
|
|
250
|
+
};
|
|
251
|
+
for (const source of this.sources)
|
|
252
|
+
if (!source.isDeclarationFile)
|
|
253
|
+
visit(source);
|
|
254
|
+
if (sites.length === 0 && callbackParams.length === 0)
|
|
255
|
+
return;
|
|
256
|
+
// One batched checker round trip for every node the scan reads: flow
|
|
257
|
+
// sites sit in bodies whose reachability is not known yet, so the
|
|
258
|
+
// facade has not prefetched them.
|
|
259
|
+
const sourceNodes = sites.map((site) => this.flowSourceNode(site.source));
|
|
260
|
+
const observedNodes = observed.map((expr) => this.flowSourceNode(expr));
|
|
261
|
+
const containerNodes = observedContainers.map((expr) => this.flowSourceNode(expr));
|
|
262
|
+
const looseNodes = looseArguments.map((expr) => this.flowSourceNode(expr));
|
|
263
|
+
const writeNodes = writes.map((w) => this.flowSourceNode(w.receiver));
|
|
264
|
+
attempt(() => this.checker.prefetchClassCollection([
|
|
265
|
+
...sourceNodes,
|
|
266
|
+
...callbackParams.map((param) => param.name),
|
|
267
|
+
...observedNodes,
|
|
268
|
+
...containerNodes,
|
|
269
|
+
...looseNodes,
|
|
270
|
+
...writeNodes,
|
|
271
|
+
], []));
|
|
272
|
+
const marked = protocols;
|
|
273
|
+
const facts = {
|
|
274
|
+
observed: new Set(),
|
|
275
|
+
written: new Map(),
|
|
276
|
+
loose: [],
|
|
277
|
+
bases: new Map(),
|
|
278
|
+
origins: new Map(),
|
|
279
|
+
targets: new Map(),
|
|
280
|
+
flows: new Map(),
|
|
281
|
+
genericObservers: new Set(),
|
|
282
|
+
};
|
|
283
|
+
const typeOf = (node) => attempt(() => this.checker.getTypeAtLocation(node));
|
|
284
|
+
for (const node of [...observedNodes, ...containerNodes]) {
|
|
285
|
+
const type = typeOf(node);
|
|
286
|
+
if (type === undefined)
|
|
287
|
+
continue;
|
|
288
|
+
this.collectSymbols(type, facts.observed, 0);
|
|
289
|
+
// A type parameter observed in a generic function or class: calls of
|
|
290
|
+
// it observe whatever their arguments hold.
|
|
291
|
+
const parts = type.isUnionType() ? ts.constituentTypes(type) : [type];
|
|
292
|
+
for (const part of parts) {
|
|
293
|
+
if ((part.flags & ts.TypeFlags.TypeParameter) === 0)
|
|
294
|
+
continue;
|
|
295
|
+
const symbol = part.getSymbol();
|
|
296
|
+
for (const declaration of symbol ? this.checker.declarationsOf(symbol) : [])
|
|
297
|
+
if (ts.isTypeParameterDeclaration(declaration) && declaration.parent !== undefined)
|
|
298
|
+
facts.genericObservers.add(declaration.parent);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
for (let i = 0; i < writes.length; i++) {
|
|
302
|
+
const symbols = new Set();
|
|
303
|
+
// `this` has the polymorphic this type: the write lands on the
|
|
304
|
+
// enclosing class's instance.
|
|
305
|
+
const owner = writeNodes[i].kind === ts.SyntaxKind.ThisKeyword ? thisClass(writeNodes[i]) : undefined;
|
|
306
|
+
const ownerSymbol = owner?.name ? this.checker.getSymbolAtLocation(owner.name) : undefined;
|
|
307
|
+
if (ownerSymbol !== undefined)
|
|
308
|
+
symbols.add(ownerSymbol);
|
|
309
|
+
else {
|
|
310
|
+
const type = typeOf(writeNodes[i]);
|
|
311
|
+
if (type === undefined)
|
|
312
|
+
continue;
|
|
313
|
+
this.collectSymbols(type, symbols, 2);
|
|
314
|
+
}
|
|
315
|
+
const name = writes[i].name;
|
|
316
|
+
for (const symbol of symbols) {
|
|
317
|
+
const names = facts.written.get(symbol);
|
|
318
|
+
if (name === null)
|
|
319
|
+
facts.written.set(symbol, "all");
|
|
320
|
+
else if (names === undefined)
|
|
321
|
+
facts.written.set(symbol, new Set([name]));
|
|
322
|
+
else if (names !== "all")
|
|
323
|
+
names.add(name);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
for (let i = 0; i < looseArguments.length; i++) {
|
|
327
|
+
const type = typeOf(looseNodes[i]);
|
|
328
|
+
if (type === undefined)
|
|
329
|
+
continue;
|
|
330
|
+
const symbols = new Set();
|
|
331
|
+
this.collectSymbols(type, symbols, 0);
|
|
332
|
+
if (symbols.size > 0)
|
|
333
|
+
facts.loose.push({ node: looseArguments[i], call: looseCalls[i], symbols });
|
|
334
|
+
}
|
|
335
|
+
this.facts = facts;
|
|
336
|
+
const pending = [];
|
|
337
|
+
for (let i = 0; i < sites.length; i++) {
|
|
338
|
+
const site = sites[i];
|
|
339
|
+
const source = sourceNodes[i];
|
|
340
|
+
const sourceType = attempt(() => this.checker.getTypeAtLocation(source));
|
|
341
|
+
if (sourceType !== undefined)
|
|
342
|
+
pending.push({ sourceType, target: site.target });
|
|
343
|
+
}
|
|
344
|
+
for (const { fn, index, name } of callbackParams) {
|
|
345
|
+
attempt(() => {
|
|
346
|
+
// Only structural declared types can lose identity; the contextual
|
|
347
|
+
// signature is a checker round trip, so query it for those alone.
|
|
348
|
+
const declared = this.checker.getTypeAtLocation(name);
|
|
349
|
+
const parts = declared.isUnionType() ? ts.constituentTypes(declared) : [declared];
|
|
350
|
+
if (!parts.some((part) => this.isStructuralTarget(part)))
|
|
351
|
+
return;
|
|
352
|
+
const context = this.checker.getContextualType(fn);
|
|
353
|
+
if (context === undefined)
|
|
354
|
+
return;
|
|
355
|
+
for (const signature of this.checker.getCallSignatures(context)) {
|
|
356
|
+
const parameter = signature.getParameters()[index];
|
|
357
|
+
if (parameter === undefined)
|
|
358
|
+
continue;
|
|
359
|
+
pending.push({
|
|
360
|
+
sourceType: this.checker.getTypeOfSymbol(parameter),
|
|
361
|
+
target: () => declared,
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
// Every site is revisited until nothing new is marked, so each mark's
|
|
367
|
+
// flows and origins are complete.
|
|
368
|
+
const before = new Set(marked);
|
|
369
|
+
const targets = new Map();
|
|
370
|
+
let changed = true;
|
|
371
|
+
while (changed) {
|
|
372
|
+
changed = false;
|
|
373
|
+
for (let i = pending.length - 1; i >= 0; i--) {
|
|
374
|
+
const entry = pending[i];
|
|
375
|
+
if (attempt(() => this.carriesIdentity(entry.sourceType, marked)) !== true)
|
|
376
|
+
continue;
|
|
377
|
+
if (!targets.has(i))
|
|
378
|
+
targets.set(i, attempt(entry.target));
|
|
379
|
+
const target = targets.get(i);
|
|
380
|
+
if (target !== undefined &&
|
|
381
|
+
attempt(() => this.markTarget(entry.sourceType, target, marked)) === true)
|
|
382
|
+
changed = true;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
// Keep the marks a copy would be told apart from, and the marks whose
|
|
386
|
+
// values flow into a kept view or another live protocol.
|
|
387
|
+
const added = [...marked].filter((symbol) => !before.has(symbol));
|
|
388
|
+
const kept = new Set(added.filter((symbol) => this.needsView(symbol, marked, facts)));
|
|
389
|
+
let grew = true;
|
|
390
|
+
while (grew) {
|
|
391
|
+
grew = false;
|
|
392
|
+
for (const symbol of added) {
|
|
393
|
+
if (kept.has(symbol))
|
|
394
|
+
continue;
|
|
395
|
+
for (const into of facts.flows.get(symbol) ?? NO_SYMBOLS) {
|
|
396
|
+
if (kept.has(into) || before.has(into)) {
|
|
397
|
+
kept.add(symbol);
|
|
398
|
+
grew = true;
|
|
399
|
+
break;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
for (const symbol of added)
|
|
405
|
+
if (!kept.has(symbol))
|
|
406
|
+
marked.delete(symbol);
|
|
407
|
+
this.facts = null;
|
|
408
|
+
}
|
|
409
|
+
/** The symbols of `type`'s object parts: union and intersection members,
|
|
410
|
+
* and to `depth` levels the elements and type arguments they hold. */
|
|
411
|
+
collectSymbols(type, out, depth) {
|
|
412
|
+
if (type.isUnionType() || type.isIntersectionType()) {
|
|
413
|
+
for (const part of ts.constituentTypes(type))
|
|
414
|
+
this.collectSymbols(part, out, depth);
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
if ((type.flags & ts.TypeFlags.Object) === 0)
|
|
418
|
+
return;
|
|
419
|
+
const symbol = type.getSymbol();
|
|
420
|
+
if (symbol !== undefined)
|
|
421
|
+
out.add(symbol);
|
|
422
|
+
if (depth >= 2 || !type.isTypeReference())
|
|
423
|
+
return;
|
|
424
|
+
for (const argument of this.checker.getTypeArguments(type))
|
|
425
|
+
this.collectSymbols(argument, out, depth + 1);
|
|
426
|
+
}
|
|
427
|
+
/** Program classes whose instances can be values of `source`: a class,
|
|
428
|
+
* its bases and the classes extending it, or the classes behind a marked
|
|
429
|
+
* structural type. */
|
|
430
|
+
originsOf(source, facts) {
|
|
431
|
+
const out = new Set();
|
|
432
|
+
const add = (type) => {
|
|
433
|
+
if (type.isUnionType() || type.isIntersectionType()) {
|
|
434
|
+
for (const part of ts.constituentTypes(type))
|
|
435
|
+
add(part);
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
const symbol = type.getSymbol();
|
|
439
|
+
if (symbol === undefined)
|
|
440
|
+
return;
|
|
441
|
+
if (this.isProgramClassInstance(type)) {
|
|
442
|
+
for (const related of this.classFamily(symbol, facts))
|
|
443
|
+
out.add(related);
|
|
444
|
+
}
|
|
445
|
+
else {
|
|
446
|
+
for (const origin of facts.origins.get(symbol) ?? NO_SYMBOLS)
|
|
447
|
+
out.add(origin);
|
|
448
|
+
}
|
|
449
|
+
};
|
|
450
|
+
if (this.checker.isArrayType(source)) {
|
|
451
|
+
const [element] = this.checker.getTypeArguments(source);
|
|
452
|
+
if (element !== undefined)
|
|
453
|
+
add(element);
|
|
454
|
+
}
|
|
455
|
+
else
|
|
456
|
+
add(source);
|
|
457
|
+
return out;
|
|
458
|
+
}
|
|
459
|
+
/** A class symbol with its base classes and the program classes that
|
|
460
|
+
* extend it. */
|
|
461
|
+
classFamily(symbol, facts) {
|
|
462
|
+
const known = facts.bases.get(symbol);
|
|
463
|
+
if (known !== undefined)
|
|
464
|
+
return known;
|
|
465
|
+
const family = new Set([symbol]);
|
|
466
|
+
const addBases = (sym) => {
|
|
467
|
+
const declared = this.checker.getDeclaredTypeOfSymbol(sym);
|
|
468
|
+
const target = declared.isTypeReference() ? declared.getTarget() : declared;
|
|
469
|
+
if (!target?.isClassOrInterface())
|
|
470
|
+
return;
|
|
471
|
+
for (const base of this.checker.getBaseTypes(target)) {
|
|
472
|
+
const baseSymbol = base.getSymbol();
|
|
473
|
+
if (baseSymbol !== undefined && !family.has(baseSymbol)) {
|
|
474
|
+
family.add(baseSymbol);
|
|
475
|
+
addBases(baseSymbol);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
};
|
|
479
|
+
addBases(symbol);
|
|
480
|
+
const declared = this.checker.getDeclaredTypeOfSymbol(symbol);
|
|
481
|
+
for (const entries of this.methods.values())
|
|
482
|
+
for (const declaration of entries) {
|
|
483
|
+
const other = declaration.name
|
|
484
|
+
? this.checker.getSymbolAtLocation(declaration.name)
|
|
485
|
+
: undefined;
|
|
486
|
+
if (other !== undefined &&
|
|
487
|
+
!family.has(other) &&
|
|
488
|
+
this.checker.isTypeAssignableTo(this.checker.getDeclaredTypeOfSymbol(other), declared))
|
|
489
|
+
family.add(other);
|
|
490
|
+
}
|
|
491
|
+
const result = [...family];
|
|
492
|
+
facts.bases.set(symbol, result);
|
|
493
|
+
return result;
|
|
494
|
+
}
|
|
495
|
+
/** Whether the program can tell a copy of a value of the marked type
|
|
496
|
+
* `symbol` from the original: see markIdentityFlows. */
|
|
497
|
+
needsView(symbol, marked, facts) {
|
|
498
|
+
if (facts.observed.has(symbol) || facts.written.has(symbol))
|
|
499
|
+
return true;
|
|
500
|
+
const target = facts.targets.get(symbol);
|
|
501
|
+
if (target === undefined)
|
|
502
|
+
return true;
|
|
503
|
+
const names = new Set(this.checker.getPropertiesOfType(target).map((p) => p.name));
|
|
504
|
+
const origins = facts.origins.get(symbol) ?? new Set();
|
|
505
|
+
const writesNames = (written) => written === "all" || [...written].some((name) => names.has(name));
|
|
506
|
+
for (const origin of origins) {
|
|
507
|
+
const written = facts.written.get(origin);
|
|
508
|
+
if (written !== undefined && writesNames(written))
|
|
509
|
+
return true;
|
|
510
|
+
// An accessor runs on every read of the instance: a copy would keep
|
|
511
|
+
// the value of one call.
|
|
512
|
+
if (this.hasAccessorFor(origin, names))
|
|
513
|
+
return true;
|
|
514
|
+
}
|
|
515
|
+
// Writes through another live view of the same instances.
|
|
516
|
+
for (const [view, written] of facts.written) {
|
|
517
|
+
if (view === symbol || !marked.has(view) || !writesNames(written))
|
|
518
|
+
continue;
|
|
519
|
+
for (const origin of facts.origins.get(view) ?? NO_SYMBOLS)
|
|
520
|
+
if (origins.has(origin))
|
|
521
|
+
return true;
|
|
522
|
+
}
|
|
523
|
+
// An argument holding the type, passed where any value is accepted or
|
|
524
|
+
// to a generic function or class that observes its type parameter.
|
|
525
|
+
return facts.loose.some((arg) => arg.symbols.has(symbol) &&
|
|
526
|
+
attempt(() => {
|
|
527
|
+
const context = this.checker.getContextualType(arg.node);
|
|
528
|
+
if (context !== undefined && this.acceptsAnyValue(context))
|
|
529
|
+
return true;
|
|
530
|
+
if (facts.genericObservers.size === 0)
|
|
531
|
+
return false;
|
|
532
|
+
const signature = this.checker.getResolvedSignature(arg.call);
|
|
533
|
+
const declaration = signature && this.checker.signatureDeclaration(signature);
|
|
534
|
+
return (declaration !== undefined &&
|
|
535
|
+
(facts.genericObservers.has(declaration) ||
|
|
536
|
+
(declaration.parent !== undefined && facts.genericObservers.has(declaration.parent))));
|
|
537
|
+
}) === true);
|
|
538
|
+
}
|
|
539
|
+
/** Whether class `symbol` declares a get or set accessor for one of
|
|
540
|
+
* `names`. */
|
|
541
|
+
hasAccessorFor(symbol, names) {
|
|
542
|
+
const declared = this.checker.getDeclaredTypeOfSymbol(symbol);
|
|
543
|
+
return this.checker
|
|
544
|
+
.getPropertiesOfType(declared)
|
|
545
|
+
.some((property) => names.has(property.name) &&
|
|
546
|
+
this.checker
|
|
547
|
+
.declarationsOf(property)
|
|
548
|
+
.some((declaration) => ts.isGetAccessorDeclaration(declaration) ||
|
|
549
|
+
ts.isSetAccessorDeclaration(declaration)));
|
|
550
|
+
}
|
|
551
|
+
/** `unknown`, `any`, `object`, `{}` and type-parameter parameters, alone
|
|
552
|
+
* or in a union. */
|
|
553
|
+
acceptsAnyValue(type) {
|
|
554
|
+
if (type.isUnionType())
|
|
555
|
+
return ts.constituentTypes(type).some((p) => this.acceptsAnyValue(p));
|
|
556
|
+
if ((type.flags &
|
|
557
|
+
(ts.TypeFlags.Any |
|
|
558
|
+
ts.TypeFlags.Unknown |
|
|
559
|
+
ts.TypeFlags.NonPrimitive |
|
|
560
|
+
ts.TypeFlags.TypeParameter)) !==
|
|
561
|
+
0)
|
|
562
|
+
return true;
|
|
563
|
+
return ((type.flags & ts.TypeFlags.Object) !== 0 &&
|
|
564
|
+
this.checker.getPropertiesOfType(type).length === 0 &&
|
|
565
|
+
this.checker.getCallSignatures(type).length === 0 &&
|
|
566
|
+
this.checker.getConstructSignatures(type).length === 0 &&
|
|
567
|
+
!this.checker.isArrayType(type));
|
|
568
|
+
}
|
|
569
|
+
/** The value a flow site hands over: casts through `unknown` or `any`
|
|
570
|
+
* keep the original value (`t as unknown as { v: number }`). */
|
|
571
|
+
/** The expression whose type a flow site carries: parentheses, non-null
|
|
572
|
+
* assertions and casts through `unknown`/`any` keep the value itself. */
|
|
573
|
+
flowSourceNode(expr) {
|
|
574
|
+
let inner = expr;
|
|
575
|
+
for (;;) {
|
|
576
|
+
if (ts.isParenthesizedExpression(inner) || ts.isNonNullExpression(inner)) {
|
|
577
|
+
inner = inner.expression;
|
|
578
|
+
continue;
|
|
579
|
+
}
|
|
580
|
+
if ((ts.isAsExpression(inner) || ts.isTypeAssertion(inner)) &&
|
|
581
|
+
(inner.type.kind === ts.SyntaxKind.UnknownKeyword ||
|
|
582
|
+
inner.type.kind === ts.SyntaxKind.AnyKeyword)) {
|
|
583
|
+
inner = inner.expression;
|
|
584
|
+
continue;
|
|
585
|
+
}
|
|
586
|
+
return inner;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
isProgramClassInstance(type) {
|
|
590
|
+
const symbol = type.getSymbol();
|
|
591
|
+
return (symbol !== undefined &&
|
|
592
|
+
(symbol.flags & ts.SymbolFlags.Class) !== 0 &&
|
|
593
|
+
this.checker
|
|
594
|
+
.declarationsOf(symbol)
|
|
595
|
+
.some((declaration) => !declaration.getSourceFile().isDeclarationFile) &&
|
|
596
|
+
// `typeof C` (the class value) shares the class symbol.
|
|
597
|
+
this.checker.getConstructSignatures(type).length === 0);
|
|
598
|
+
}
|
|
599
|
+
carriesIdentity(type, marked) {
|
|
600
|
+
if (type.isUnionType() || type.isIntersectionType())
|
|
601
|
+
return ts.constituentTypes(type).some((part) => this.carriesIdentity(part, marked));
|
|
602
|
+
if ((type.flags & ts.TypeFlags.Object) === 0)
|
|
603
|
+
return false;
|
|
604
|
+
if (this.checker.isArrayType(type)) {
|
|
605
|
+
const [element] = this.checker.getTypeArguments(type);
|
|
606
|
+
return element !== undefined && this.carriesIdentity(element, marked);
|
|
607
|
+
}
|
|
608
|
+
if (this.isProgramClassInstance(type))
|
|
609
|
+
return true;
|
|
610
|
+
const symbol = type.getSymbol();
|
|
611
|
+
if (symbol === undefined)
|
|
612
|
+
return false;
|
|
613
|
+
if (marked.has(symbol))
|
|
614
|
+
return true;
|
|
615
|
+
// A program interface a class matches by its methods (protocol below).
|
|
616
|
+
return (this.methods.size > 0 &&
|
|
617
|
+
this.checker
|
|
618
|
+
.declarationsOf(symbol)
|
|
619
|
+
.some((declaration) => ts.isInterfaceDeclaration(declaration) &&
|
|
620
|
+
!declaration.getSourceFile().isDeclarationFile) &&
|
|
621
|
+
this.protocol(type));
|
|
622
|
+
}
|
|
623
|
+
/** A structural target that natively becomes a record: a program object
|
|
624
|
+
* type that is not a class instance, array, tuple, function or library
|
|
625
|
+
* type. */
|
|
626
|
+
isStructuralTarget(type) {
|
|
627
|
+
if ((type.flags & ts.TypeFlags.Object) === 0)
|
|
628
|
+
return false;
|
|
629
|
+
if (this.checker.isArrayType(type) || this.checker.isTupleType(type))
|
|
630
|
+
return false;
|
|
631
|
+
if (this.checker.getCallSignatures(type).length > 0)
|
|
632
|
+
return false;
|
|
633
|
+
if (this.checker.getConstructSignatures(type).length > 0)
|
|
634
|
+
return false;
|
|
635
|
+
const symbol = type.getSymbol();
|
|
636
|
+
if (symbol === undefined || (symbol.flags & ts.SymbolFlags.Class) !== 0)
|
|
637
|
+
return false;
|
|
638
|
+
const declarations = this.checker.declarationsOf(symbol);
|
|
639
|
+
return (declarations.length > 0 &&
|
|
640
|
+
declarations.every((declaration) => !declaration.getSourceFile().isDeclarationFile));
|
|
641
|
+
}
|
|
642
|
+
/** Marks the structural parts of `target` that the identity-carrying
|
|
643
|
+
* parts of `source` flow into. Answers whether anything new was marked. */
|
|
644
|
+
markTarget(source, target, marked) {
|
|
645
|
+
if (source.isUnionType()) {
|
|
646
|
+
let changed = false;
|
|
647
|
+
for (const part of ts.constituentTypes(source))
|
|
648
|
+
if (this.carriesIdentity(part, marked) && this.markTarget(part, target, marked))
|
|
649
|
+
changed = true;
|
|
650
|
+
return changed;
|
|
651
|
+
}
|
|
652
|
+
if (target.isUnionType()) {
|
|
653
|
+
// Only the arms that accept the value receive it. An accepting arm
|
|
654
|
+
// that holds it as itself (its own type, its class, or another
|
|
655
|
+
// representation with identity) needs no structural view.
|
|
656
|
+
const accepting = [];
|
|
657
|
+
for (const part of ts.constituentTypes(target)) {
|
|
658
|
+
if ((part.flags & (ts.TypeFlags.Undefined | ts.TypeFlags.Null)) !== 0)
|
|
659
|
+
continue;
|
|
660
|
+
if (part === source || part.getSymbol() === source.getSymbol())
|
|
661
|
+
return false;
|
|
662
|
+
if (!this.checker.isTypeAssignableTo(source, part))
|
|
663
|
+
continue;
|
|
664
|
+
if (!this.isStructuralTarget(part))
|
|
665
|
+
return false;
|
|
666
|
+
accepting.push(part);
|
|
667
|
+
}
|
|
668
|
+
let changed = false;
|
|
669
|
+
for (const part of accepting)
|
|
670
|
+
if (this.markTarget(source, part, marked))
|
|
671
|
+
changed = true;
|
|
672
|
+
return changed;
|
|
673
|
+
}
|
|
674
|
+
if (this.checker.isArrayType(source) && this.checker.isArrayType(target)) {
|
|
675
|
+
const [from] = this.checker.getTypeArguments(source);
|
|
676
|
+
const [to] = this.checker.getTypeArguments(target);
|
|
677
|
+
return from !== undefined && to !== undefined && this.markTarget(from, to, marked);
|
|
678
|
+
}
|
|
679
|
+
if (!this.isStructuralTarget(target))
|
|
680
|
+
return false;
|
|
681
|
+
const symbol = target.getSymbol();
|
|
682
|
+
if (symbol === undefined)
|
|
683
|
+
return false;
|
|
684
|
+
const from = source.getSymbol();
|
|
685
|
+
if (from === symbol)
|
|
686
|
+
return false;
|
|
687
|
+
const facts = this.facts;
|
|
688
|
+
if (facts !== null) {
|
|
689
|
+
// The flow: the source's classes and marked type reach `symbol`.
|
|
690
|
+
const origins = facts.origins.get(symbol) ?? new Set();
|
|
691
|
+
for (const origin of this.originsOf(source, facts))
|
|
692
|
+
origins.add(origin);
|
|
693
|
+
facts.origins.set(symbol, origins);
|
|
694
|
+
if (!facts.targets.has(symbol))
|
|
695
|
+
facts.targets.set(symbol, target);
|
|
696
|
+
if (from !== undefined && marked.has(from)) {
|
|
697
|
+
const flows = facts.flows.get(from) ?? new Set();
|
|
698
|
+
flows.add(symbol);
|
|
699
|
+
facts.flows.set(from, flows);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
if (marked.has(symbol))
|
|
703
|
+
return false;
|
|
704
|
+
marked.add(symbol);
|
|
705
|
+
return true;
|
|
706
|
+
}
|
|
70
707
|
/** Class-backed protocols require live receiver dispatch. Other interfaces,
|
|
71
708
|
* including factories backed by static methods and ordinary records of
|
|
72
709
|
* closures, retain their fixed native layouts. */
|
|
@@ -123,4 +760,102 @@ export class ClassProtocols {
|
|
|
123
760
|
return false;
|
|
124
761
|
}
|
|
125
762
|
}
|
|
763
|
+
/** The empty answer of the symbol maps above. */
|
|
764
|
+
const NO_SYMBOLS = new Set();
|
|
765
|
+
/** Calls of these namespaces and functions observe whole objects. */
|
|
766
|
+
const OBSERVER_NAMESPACES = new Set(["console", "JSON", "Object", "Reflect", "util", "assert"]);
|
|
767
|
+
const OBSERVER_FUNCTIONS = new Set([
|
|
768
|
+
"structuredClone",
|
|
769
|
+
"String",
|
|
770
|
+
"inspect",
|
|
771
|
+
"format",
|
|
772
|
+
"isDeepStrictEqual",
|
|
773
|
+
// node:assert's named exports.
|
|
774
|
+
"strictEqual",
|
|
775
|
+
"notStrictEqual",
|
|
776
|
+
"deepStrictEqual",
|
|
777
|
+
"notDeepStrictEqual",
|
|
778
|
+
"deepEqual",
|
|
779
|
+
"notDeepEqual",
|
|
780
|
+
"equal",
|
|
781
|
+
"notEqual",
|
|
782
|
+
]);
|
|
783
|
+
/** Constructors that key their arguments' values by identity. */
|
|
784
|
+
const IDENTITY_CONSTRUCTORS = new Set(["Set", "Map", "WeakSet", "WeakMap", "WeakRef"]);
|
|
785
|
+
/** Object statics that write their first argument. */
|
|
786
|
+
const OBJECT_WRITERS = new Set(["assign", "defineProperty", "defineProperties", "setPrototypeOf"]);
|
|
787
|
+
/** Methods that compare their arguments by identity (Set, Map, WeakMap,
|
|
788
|
+
* WeakSet and array searches). */
|
|
789
|
+
const IDENTITY_METHODS = new Set([
|
|
790
|
+
"add",
|
|
791
|
+
"has",
|
|
792
|
+
"delete",
|
|
793
|
+
"set",
|
|
794
|
+
"get",
|
|
795
|
+
"indexOf",
|
|
796
|
+
"lastIndexOf",
|
|
797
|
+
"includes",
|
|
798
|
+
]);
|
|
799
|
+
function isNullishLiteral(node) {
|
|
800
|
+
while (ts.isParenthesizedExpression(node))
|
|
801
|
+
node = node.expression;
|
|
802
|
+
return (node.kind === ts.SyntaxKind.NullKeyword ||
|
|
803
|
+
(ts.isIdentifier(node) && node.text === "undefined") ||
|
|
804
|
+
ts.isVoidExpression(node));
|
|
805
|
+
}
|
|
806
|
+
/** A literal or `typeof` operand, which cannot hold an object. */
|
|
807
|
+
function isPrimitiveLiteral(node) {
|
|
808
|
+
while (ts.isParenthesizedExpression(node))
|
|
809
|
+
node = node.expression;
|
|
810
|
+
return (ts.isStringLiteralLike(node) ||
|
|
811
|
+
ts.isNumericLiteral(node) ||
|
|
812
|
+
ts.isBigIntLiteral(node) ||
|
|
813
|
+
ts.isTypeOfExpression(node) ||
|
|
814
|
+
node.kind === ts.SyntaxKind.TrueKeyword ||
|
|
815
|
+
node.kind === ts.SyntaxKind.FalseKeyword ||
|
|
816
|
+
ts.isTemplateExpression(node));
|
|
817
|
+
}
|
|
818
|
+
/** The class whose instance `this` is at `node`: the nearest enclosing
|
|
819
|
+
* class, unless a non-arrow function rebinds `this` first. */
|
|
820
|
+
function thisClass(node) {
|
|
821
|
+
for (let cur = node.parent; cur !== undefined; cur = cur.parent) {
|
|
822
|
+
if (ts.isClassDeclaration(cur) || ts.isClassExpression(cur))
|
|
823
|
+
return cur;
|
|
824
|
+
if (ts.isFunctionDeclaration(cur) || ts.isFunctionExpression(cur))
|
|
825
|
+
return undefined;
|
|
826
|
+
}
|
|
827
|
+
return undefined;
|
|
828
|
+
}
|
|
829
|
+
/** Whether `node` sits directly in a constructor body (not in a nested
|
|
830
|
+
* function). */
|
|
831
|
+
function inConstructor(node) {
|
|
832
|
+
for (let cur = node.parent; cur !== undefined; cur = cur.parent) {
|
|
833
|
+
if (ts.isConstructorDeclaration(cur))
|
|
834
|
+
return true;
|
|
835
|
+
if (ts.isFunctionLike(cur) || ts.isClassDeclaration(cur) || ts.isClassExpression(cur))
|
|
836
|
+
return false;
|
|
837
|
+
}
|
|
838
|
+
return false;
|
|
839
|
+
}
|
|
840
|
+
/** A checker query that may hit an upstream checker panic: the flow scan
|
|
841
|
+
* skips a site it cannot type rather than failing the build. */
|
|
842
|
+
function attempt(query) {
|
|
843
|
+
try {
|
|
844
|
+
return query();
|
|
845
|
+
}
|
|
846
|
+
catch {
|
|
847
|
+
return undefined;
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
/** `(value as T).member` and `(value as T)[key]`: the assertion is read or
|
|
851
|
+
* written member-wise in place, which lowers to the instance's own fields
|
|
852
|
+
* and creates no view that could outlive it. */
|
|
853
|
+
function accessedInPlace(node) {
|
|
854
|
+
let outer = node;
|
|
855
|
+
while (ts.isParenthesizedExpression(outer.parent))
|
|
856
|
+
outer = outer.parent;
|
|
857
|
+
const parent = outer.parent;
|
|
858
|
+
return ((ts.isPropertyAccessExpression(parent) || ts.isElementAccessExpression(parent)) &&
|
|
859
|
+
parent.expression === outer);
|
|
860
|
+
}
|
|
126
861
|
//# sourceMappingURL=class-protocols.js.map
|