storm-lua-minify 0.2.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +157 -37
  3. package/dist/aggregateSpecialization.js +406 -0
  4. package/dist/ast2lua.js +223 -82
  5. package/dist/astWalk.js +162 -0
  6. package/dist/callGraph.js +372 -0
  7. package/dist/cli.js +53 -32
  8. package/dist/cliOptions.js +36 -0
  9. package/dist/cliProgress.js +87 -0
  10. package/dist/config.js +73 -0
  11. package/dist/constantFold.js +798 -0
  12. package/dist/controlFlow.js +266 -0
  13. package/dist/functionRewrites.js +580 -0
  14. package/dist/generatedAst.js +108 -0
  15. package/dist/generatedNode.js +23 -0
  16. package/dist/globalRename.js +35 -0
  17. package/dist/interproceduralAnalysis.js +842 -0
  18. package/dist/interproceduralConstants.js +120 -0
  19. package/dist/keywordLocator.js +68 -0
  20. package/dist/linker.js +13 -0
  21. package/dist/luaString.js +157 -0
  22. package/dist/minifier.js +1243 -35
  23. package/dist/optimizerAnalysis.js +43 -0
  24. package/dist/optimizerDiagnostics.js +65 -0
  25. package/dist/optimizerFacts.js +529 -0
  26. package/dist/optimizerPass.js +96 -0
  27. package/dist/optimizerTransaction.js +56 -0
  28. package/dist/optimizerValueDomain.js +180 -0
  29. package/dist/options.js +233 -0
  30. package/dist/progress.js +2 -0
  31. package/dist/removeUnused.js +145 -0
  32. package/dist/renamer.js +236 -53
  33. package/dist/resolver.js +37 -14
  34. package/dist/runtimeEnvironment.js +105 -0
  35. package/dist/sourceMetadata.js +314 -0
  36. package/dist/statementDataflow.js +259 -0
  37. package/dist/statementScheduler.js +595 -0
  38. package/dist/symbolLiveness.js +92 -0
  39. package/dist/tableEffects.js +356 -0
  40. package/dist/transform.js +40 -0
  41. package/dist/valueFlow.js +409 -0
  42. package/dist/wholeProgramExports.js +646 -0
  43. package/dist/wholeProgramFieldRenames.js +583 -0
  44. package/dist/wholeProgramFields.js +672 -0
  45. package/dist/wholeProgramObjects.js +783 -0
  46. package/package.json +50 -33
  47. package/.github/workflows/ci.yml +0 -39
  48. package/.github/workflows/publish.yml +0 -39
  49. package/.prettierignore +0 -3
  50. package/.prettierrc.json +0 -1
  51. package/dist/index.js +0 -27
  52. package/eslint.config.js +0 -29
  53. package/src/ast2lua.ts +0 -940
  54. package/src/cli.ts +0 -86
  55. package/src/linker.ts +0 -108
  56. package/src/minifier.ts +0 -284
  57. package/src/output.ts +0 -71
  58. package/src/renamer.ts +0 -134
  59. package/src/resolver.ts +0 -378
  60. package/test/circular-require.test.ts +0 -19
  61. package/test/fixtures/bare-require/main.lua +0 -2
  62. package/test/fixtures/bare-require/mod.lua +0 -1
  63. package/test/fixtures/bitwise-precedence/main.lua +0 -10
  64. package/test/fixtures/circular-require/a.lua +0 -2
  65. package/test/fixtures/circular-require/b.lua +0 -2
  66. package/test/fixtures/circular-require/main.lua +0 -2
  67. package/test/fixtures/dofile/greet.lua +0 -1
  68. package/test/fixtures/dofile/main.lua +0 -2
  69. package/test/fixtures/entry-scope-many-requires/dep_alpha.lua +0 -2
  70. package/test/fixtures/entry-scope-many-requires/dep_bravo.lua +0 -2
  71. package/test/fixtures/entry-scope-many-requires/dep_charlie.lua +0 -2
  72. package/test/fixtures/entry-scope-many-requires/dep_delta.lua +0 -2
  73. package/test/fixtures/entry-scope-many-requires/dep_echo.lua +0 -2
  74. package/test/fixtures/entry-scope-many-requires/dep_foxtrot.lua +0 -2
  75. package/test/fixtures/entry-scope-many-requires/dep_golf.lua +0 -2
  76. package/test/fixtures/entry-scope-many-requires/dep_hotel.lua +0 -2
  77. package/test/fixtures/entry-scope-many-requires/dep_india.lua +0 -2
  78. package/test/fixtures/entry-scope-many-requires/dep_juliet.lua +0 -2
  79. package/test/fixtures/entry-scope-many-requires/dep_kilo.lua +0 -2
  80. package/test/fixtures/entry-scope-many-requires/main.lua +0 -25
  81. package/test/fixtures/multi-require/common.lua +0 -1
  82. package/test/fixtures/multi-require/main.lua +0 -3
  83. package/test/fixtures/nested-module/main.lua +0 -2
  84. package/test/fixtures/nested-module/sub/deep.lua +0 -1
  85. package/test/fixtures/require-call/main.lua +0 -2
  86. package/test/fixtures/require-call/mod.lua +0 -5
  87. package/test/fixtures/require-in-expression/main.lua +0 -1
  88. package/test/fixtures/require-in-expression/mod.lua +0 -1
  89. package/test/fixtures/require-string-call/main.lua +0 -2
  90. package/test/fixtures/require-string-call/mod.lua +0 -5
  91. package/test/fixtures/single-file/main.lua +0 -15
  92. package/test/identifier-collision.test.ts +0 -28
  93. package/test/lib/collision.ts +0 -131
  94. package/test/lib/helpers.ts +0 -124
  95. package/test/no-rename.test.ts +0 -19
  96. package/test/output.test.ts +0 -95
  97. package/test/precedence.test.ts +0 -28
  98. package/test/renamer.test.ts +0 -130
  99. package/test/resolver.test.ts +0 -206
  100. package/test/roundtrip.test.ts +0 -26
  101. package/test/snapshot.test.ts +0 -27
  102. package/test/snapshots/bare-require.sl.lua +0 -1
  103. package/test/snapshots/bitwise-precedence.sl.lua +0 -2
  104. package/test/snapshots/dofile.sl.lua +0 -1
  105. package/test/snapshots/entry-scope-many-requires.m.lua +0 -14
  106. package/test/snapshots/multi-require.m.lua +0 -4
  107. package/test/snapshots/multi-require.sl.lua +0 -1
  108. package/test/snapshots/nested-module.m.lua +0 -4
  109. package/test/snapshots/require-call.m.lua +0 -5
  110. package/test/snapshots/require-call.sl.lua +0 -1
  111. package/test/snapshots/require-in-expression.sl.lua +0 -1
  112. package/test/snapshots/require-string-call.m.lua +0 -5
  113. package/test/snapshots/single-file.sl.lua +0 -6
  114. package/test/sourcemap.test.ts +0 -105
  115. package/tsconfig.eslint.json +0 -8
  116. package/tsconfig.json +0 -111
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OPTIMIZER_ANALYSIS_CACHE_KEY = void 0;
4
+ exports.analyzeOptimizer = analyzeOptimizer;
5
+ exports.analyzeOptimizerAtGeneration = analyzeOptimizerAtGeneration;
6
+ const optimizerFacts_1 = require("./optimizerFacts");
7
+ const valueFlow_1 = require("./valueFlow");
8
+ const tableEffects_1 = require("./tableEffects");
9
+ const statementDataflow_1 = require("./statementDataflow");
10
+ const callGraph_1 = require("./callGraph");
11
+ const interproceduralAnalysis_1 = require("./interproceduralAnalysis");
12
+ exports.OPTIMIZER_ANALYSIS_CACHE_KEY = {};
13
+ /**
14
+ * 一つのAST世代に属するoptimizer解析をまとめて構築する。
15
+ *
16
+ * sub-analysisをpass側で個別に生成させないことが重要である。これによりtable解析と
17
+ * value flowが同じallocation identity・CFG世代を共有し、AST変更時には
18
+ * PassOrchestratorがsnapshot全体を一度に破棄できる。
19
+ */
20
+ function analyzeOptimizer(chunk, resolved, options = {}) {
21
+ const generation = options.generation ?? 0;
22
+ const facts = (0, optimizerFacts_1.analyzeOptimizerFacts)(chunk, resolved, {
23
+ ...options,
24
+ generation,
25
+ });
26
+ const callGraph = (0, callGraph_1.analyzeCallGraph)(chunk, resolved, facts);
27
+ const interprocedural = (0, interproceduralAnalysis_1.analyzeInterprocedural)(chunk, resolved, callGraph, options.interprocedural);
28
+ const valueFlow = (0, valueFlow_1.analyzeValueFlow)(chunk, resolved, facts, generation, interprocedural);
29
+ const tableEffects = (0, tableEffects_1.analyzeTableEffects)(chunk, resolved, valueFlow, facts, interprocedural);
30
+ const statementDataflow = (0, statementDataflow_1.analyzeStatementDataflow)(chunk, facts, valueFlow);
31
+ return {
32
+ generation,
33
+ facts,
34
+ callGraph,
35
+ interprocedural,
36
+ valueFlow,
37
+ tableEffects,
38
+ statementDataflow,
39
+ };
40
+ }
41
+ function analyzeOptimizerAtGeneration(chunk, resolved, generation) {
42
+ return analyzeOptimizer(chunk, resolved, { generation });
43
+ }
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OptimizationDiagnosticCollector = void 0;
4
+ exports.summarizeOptimizationDiagnostics = summarizeOptimizationDiagnostics;
5
+ class OptimizationDiagnosticCollector {
6
+ diagnosticsValue = [];
7
+ get diagnostics() {
8
+ return this.diagnosticsValue;
9
+ }
10
+ record(diagnostic) {
11
+ this.diagnosticsValue.push({ ...diagnostic });
12
+ }
13
+ }
14
+ exports.OptimizationDiagnosticCollector = OptimizationDiagnosticCollector;
15
+ function summarizeOptimizationDiagnostics(diagnostics) {
16
+ const buckets = new Map();
17
+ let acceptedCandidates = 0;
18
+ let rejectedCandidates = 0;
19
+ let estimatedByteSavings = 0;
20
+ let estimatedOpportunityBytes = 0;
21
+ diagnostics.forEach((diagnostic) => {
22
+ const count = diagnostic.candidateSize ?? 1;
23
+ if (diagnostic.decision === "accepted") {
24
+ acceptedCandidates += count;
25
+ estimatedByteSavings += diagnostic.estimatedByteSavings ?? 0;
26
+ }
27
+ else {
28
+ rejectedCandidates += count;
29
+ estimatedOpportunityBytes += diagnostic.estimatedOpportunityBytes ?? 0;
30
+ }
31
+ const runtimeProfile = diagnostic.runtimeProfile ?? "unknown";
32
+ const moduleName = diagnostic.moduleName ?? "unknown";
33
+ const key = [
34
+ runtimeProfile,
35
+ moduleName,
36
+ diagnostic.pass,
37
+ diagnostic.decision,
38
+ diagnostic.reason,
39
+ ].join("\u0000");
40
+ const current = buckets.get(key) ?? {
41
+ runtimeProfile,
42
+ moduleName,
43
+ pass: diagnostic.pass,
44
+ decision: diagnostic.decision,
45
+ reason: diagnostic.reason,
46
+ candidateCount: 0,
47
+ estimatedByteSavings: 0,
48
+ estimatedOpportunityBytes: 0,
49
+ };
50
+ current.candidateCount += count;
51
+ current.estimatedByteSavings += diagnostic.estimatedByteSavings ?? 0;
52
+ current.estimatedOpportunityBytes +=
53
+ diagnostic.estimatedOpportunityBytes ?? 0;
54
+ buckets.set(key, current);
55
+ });
56
+ return {
57
+ acceptedCandidates,
58
+ rejectedCandidates,
59
+ estimatedByteSavings,
60
+ estimatedOpportunityBytes,
61
+ buckets: [...buckets]
62
+ .sort(([left], [right]) => left.localeCompare(right))
63
+ .map(([, bucket]) => ({ ...bucket })),
64
+ };
65
+ }
@@ -0,0 +1,529 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OPTIMIZER_FACTS_CACHE_KEY = void 0;
4
+ exports.analyzeOptimizerFactsAtGeneration = analyzeOptimizerFactsAtGeneration;
5
+ exports.analyzeOptimizerFacts = analyzeOptimizerFacts;
6
+ const luaString_1 = require("./luaString");
7
+ /** Stable cache identity used by PassOrchestrator for language-level facts. */
8
+ exports.OPTIMIZER_FACTS_CACHE_KEY = {};
9
+ function analyzeOptimizerFactsAtGeneration(chunk, resolved, generation) {
10
+ return analyzeOptimizerFacts(chunk, resolved, { generation });
11
+ }
12
+ const LANGUAGE_NO_EFFECT = {
13
+ mayError: {
14
+ value: "no",
15
+ evidence: { kind: "language", reason: "literal-or-function-construction" },
16
+ },
17
+ mayInvokeMetamethod: {
18
+ value: "no",
19
+ evidence: { kind: "language", reason: "literal-or-function-construction" },
20
+ },
21
+ };
22
+ const LANGUAGE_MAY_EFFECT = {
23
+ mayError: {
24
+ value: "may",
25
+ evidence: { kind: "language", reason: "operation-may-raise" },
26
+ },
27
+ mayInvokeMetamethod: {
28
+ value: "may",
29
+ evidence: { kind: "language", reason: "operation-may-dispatch-metamethod" },
30
+ },
31
+ };
32
+ /** Collects syntax-level optimizer facts. It deliberately performs no CFG or interprocedural inference. */
33
+ function analyzeOptimizerFacts(chunk, resolved, options = {}) {
34
+ const operations = [];
35
+ const unknowns = [];
36
+ const byOwner = new WeakMap();
37
+ const withinOwner = new WeakMap();
38
+ const byOrigin = new WeakMap();
39
+ const bySymbol = new Map();
40
+ const expressionFacts = new WeakMap();
41
+ const valueSlots = new WeakMap();
42
+ let nextOperationId = 0;
43
+ let nextGroupId = 0;
44
+ const conservativeEffects = () => ({
45
+ mayError: LANGUAGE_MAY_EFFECT.mayError,
46
+ mayInvokeMetamethod: options.runtime?.semantics.mutableMetatables === false
47
+ ? {
48
+ value: "no",
49
+ evidence: {
50
+ kind: "runtime",
51
+ profile: options.runtime.profile,
52
+ reason: "runtime-has-no-mutable-metatables",
53
+ },
54
+ }
55
+ : LANGUAGE_MAY_EFFECT.mayInvokeMetamethod,
56
+ });
57
+ const record = (operation) => {
58
+ const sourceRange = operation.origin
59
+ .range;
60
+ const complete = {
61
+ ...operation,
62
+ ...(sourceRange ? { sourceRange } : {}),
63
+ id: nextOperationId++,
64
+ };
65
+ operations.push(complete);
66
+ if ("location" in complete && complete.location.kind === "external") {
67
+ unknowns.push({
68
+ origin: complete.origin,
69
+ domain: "location",
70
+ reason: complete.location.reason,
71
+ });
72
+ }
73
+ byOrigin.set(complete.origin, complete);
74
+ const owned = byOwner.get(complete.owner) ?? [];
75
+ owned.push(complete);
76
+ byOwner.set(complete.owner, owned);
77
+ if ("location" in complete &&
78
+ (complete.location.kind === "local" ||
79
+ complete.location.kind === "parameter" ||
80
+ complete.location.kind === "upvalue")) {
81
+ const symbolOperations = bySymbol.get(complete.location.symbol) ?? [];
82
+ symbolOperations.push(complete);
83
+ bySymbol.set(complete.location.symbol, symbolOperations);
84
+ }
85
+ };
86
+ function nearestFunctionScope(scope) {
87
+ for (let current = scope; current; current = current.parent) {
88
+ if (current.kind === "function")
89
+ return current;
90
+ }
91
+ return undefined;
92
+ }
93
+ function bindingLocation(identifier, currentFunction) {
94
+ const symbol = resolved.symbolOf(identifier);
95
+ if (symbol) {
96
+ if (nearestFunctionScope(symbol.scope) !== currentFunction)
97
+ return { kind: "upvalue", symbol };
98
+ if (symbol.kind === "param")
99
+ return { kind: "parameter", symbol };
100
+ return { kind: "local", symbol };
101
+ }
102
+ if (resolved.isGlobalReference(identifier)) {
103
+ const binding = resolved.globals.get(identifier.name);
104
+ if (binding)
105
+ return { kind: "global", binding };
106
+ }
107
+ return { kind: "external", reason: "unresolved-identifier" };
108
+ }
109
+ function tableLocation(expression) {
110
+ let key = { kind: "dynamic" };
111
+ if (expression.type === "MemberExpression") {
112
+ key = {
113
+ kind: "static",
114
+ value: (0, luaString_1.luaByteStringKey)((0, luaString_1.luaByteStringOfText)(expression.identifier.name)),
115
+ };
116
+ }
117
+ else if (expression.index.type === "StringLiteral") {
118
+ const decoded = (0, luaString_1.decodeLuaStringLiteral)(expression.index);
119
+ if (decoded.ok)
120
+ key = { kind: "static", value: (0, luaString_1.luaByteStringKey)(decoded.value) };
121
+ }
122
+ return { kind: "table", base: expression.base, key };
123
+ }
124
+ function effectsOf(expression) {
125
+ switch (expression.type) {
126
+ case "NilLiteral":
127
+ case "BooleanLiteral":
128
+ case "NumericLiteral":
129
+ case "StringLiteral":
130
+ case "VarargLiteral":
131
+ case "Identifier":
132
+ case "FunctionDeclaration":
133
+ return LANGUAGE_NO_EFFECT;
134
+ case "TableConstructorExpression":
135
+ return mergeEffects(expression.fields.flatMap((field) => [
136
+ ...(field.type === "TableKey" ? [field.key] : []),
137
+ field.value,
138
+ ]));
139
+ case "UnaryExpression":
140
+ return expression.operator === "-" &&
141
+ expression.argument.type === "NumericLiteral"
142
+ ? LANGUAGE_NO_EFFECT
143
+ : conservativeEffects();
144
+ default:
145
+ return conservativeEffects();
146
+ }
147
+ }
148
+ function mergeEffects(expressions) {
149
+ const effects = expressions.map(effectsOf);
150
+ return {
151
+ mayError: effects.find((effect) => effect.mayError.value === "may")?.mayError ??
152
+ LANGUAGE_NO_EFFECT.mayError,
153
+ mayInvokeMetamethod: effects.find((effect) => effect.mayInvokeMetamethod.value === "may")
154
+ ?.mayInvokeMetamethod ?? LANGUAGE_NO_EFFECT.mayInvokeMetamethod,
155
+ };
156
+ }
157
+ function cardinalityOf(expression) {
158
+ switch (expression.type) {
159
+ case "CallExpression":
160
+ case "TableCallExpression":
161
+ case "StringCallExpression":
162
+ case "VarargLiteral":
163
+ return { kind: "tuple", knownPrefixLength: 0, tail: "unknown" };
164
+ default:
165
+ return { kind: "single" };
166
+ }
167
+ }
168
+ function valueOf(expression) {
169
+ switch (expression.type) {
170
+ case "NilLiteral":
171
+ return { kind: "nil" };
172
+ case "BooleanLiteral":
173
+ return { kind: "boolean", value: expression.value };
174
+ case "NumericLiteral":
175
+ return { kind: "number", raw: expression.raw };
176
+ case "StringLiteral":
177
+ return { kind: "string", raw: expression.raw };
178
+ case "TableConstructorExpression":
179
+ return {
180
+ kind: "allocation",
181
+ allocationKind: "table",
182
+ origin: expression,
183
+ };
184
+ case "FunctionDeclaration":
185
+ return {
186
+ kind: "allocation",
187
+ allocationKind: "function",
188
+ origin: expression,
189
+ };
190
+ case "Identifier":
191
+ return bindingLocation(expression, undefined).kind === "global"
192
+ ? { kind: "external", reason: "global-binding" }
193
+ : { kind: "unknown", reason: "binding-value-requires-flow" };
194
+ case "CallExpression":
195
+ case "TableCallExpression":
196
+ case "StringCallExpression":
197
+ return { kind: "unknown", reason: "call-result" };
198
+ case "VarargLiteral":
199
+ return { kind: "unknown", reason: "vararg-value" };
200
+ default:
201
+ return { kind: "unknown", reason: "computed-expression" };
202
+ }
203
+ }
204
+ function recordValueSlots(owner, expressions, targetCount) {
205
+ const slots = [];
206
+ const last = expressions.at(-1);
207
+ const lastExpands = last !== undefined && cardinalityOf(last).kind === "tuple";
208
+ for (let index = 0; index < targetCount; index++) {
209
+ if (index < expressions.length - 1 ||
210
+ (index < expressions.length && !lastExpands)) {
211
+ slots.push({
212
+ index,
213
+ source: { kind: "expression", expression: expressions[index] },
214
+ });
215
+ }
216
+ else if (last && lastExpands && index >= expressions.length - 1) {
217
+ slots.push({
218
+ index,
219
+ source: {
220
+ kind: "tail-expansion",
221
+ expression: last,
222
+ offset: index - (expressions.length - 1),
223
+ },
224
+ });
225
+ }
226
+ else {
227
+ slots.push({ index, source: { kind: "nil-padding" } });
228
+ }
229
+ }
230
+ valueSlots.set(owner, slots);
231
+ }
232
+ function visitExpression(expression, owner, currentFunction, group) {
233
+ const value = valueOf(expression);
234
+ const cardinality = cardinalityOf(expression);
235
+ expressionFacts.set(expression, {
236
+ expression,
237
+ value,
238
+ cardinality,
239
+ effects: effectsOf(expression),
240
+ });
241
+ if (value.kind === "unknown") {
242
+ unknowns.push({
243
+ origin: expression,
244
+ domain: "value",
245
+ reason: value.reason,
246
+ });
247
+ }
248
+ if (cardinality.kind === "unknown") {
249
+ unknowns.push({
250
+ origin: expression,
251
+ domain: "cardinality",
252
+ reason: cardinality.reason,
253
+ });
254
+ }
255
+ const position = { group, ordering: "unordered" };
256
+ switch (expression.type) {
257
+ case "Identifier":
258
+ record({
259
+ kind: "read",
260
+ location: bindingLocation(expression, currentFunction),
261
+ owner,
262
+ origin: expression,
263
+ position,
264
+ });
265
+ return;
266
+ case "NilLiteral":
267
+ case "BooleanLiteral":
268
+ case "NumericLiteral":
269
+ case "StringLiteral":
270
+ case "VarargLiteral":
271
+ return;
272
+ case "UnaryExpression":
273
+ visitExpression(expression.argument, owner, currentFunction, group);
274
+ return;
275
+ case "BinaryExpression":
276
+ case "LogicalExpression":
277
+ visitExpression(expression.left, owner, currentFunction, group);
278
+ visitExpression(expression.right, owner, currentFunction, group);
279
+ return;
280
+ case "TableConstructorExpression":
281
+ record({
282
+ kind: "allocate",
283
+ allocationKind: "table",
284
+ owner,
285
+ origin: expression,
286
+ position,
287
+ });
288
+ expression.fields.forEach((field) => {
289
+ if (field.type === "TableKey")
290
+ visitExpression(field.key, owner, currentFunction, group);
291
+ visitExpression(field.value, owner, currentFunction, group);
292
+ });
293
+ return;
294
+ case "FunctionDeclaration":
295
+ record({
296
+ kind: "allocate",
297
+ allocationKind: "function",
298
+ owner,
299
+ origin: expression,
300
+ position,
301
+ });
302
+ visitFunction(expression);
303
+ return;
304
+ case "MemberExpression":
305
+ case "IndexExpression": {
306
+ visitExpression(expression.base, owner, currentFunction, group);
307
+ if (expression.type === "IndexExpression")
308
+ visitExpression(expression.index, owner, currentFunction, group);
309
+ record({
310
+ kind: "table-read",
311
+ location: tableLocation(expression),
312
+ owner,
313
+ origin: expression,
314
+ position,
315
+ });
316
+ return;
317
+ }
318
+ case "CallExpression":
319
+ case "TableCallExpression":
320
+ case "StringCallExpression": {
321
+ visitExpression(expression.base, owner, currentFunction, group);
322
+ if (expression.type === "CallExpression")
323
+ expression.arguments.forEach((argument) => {
324
+ visitExpression(argument, owner, currentFunction, group);
325
+ });
326
+ else
327
+ visitExpression(expression.type === "TableCallExpression"
328
+ ? expression.arguments
329
+ : expression.argument, owner, currentFunction, group);
330
+ const target = expression.base.type === "Identifier"
331
+ ? bindingLocation(expression.base, currentFunction)
332
+ : { kind: "unknown" };
333
+ record({
334
+ kind: "call",
335
+ call: expression,
336
+ target,
337
+ owner,
338
+ origin: expression,
339
+ position,
340
+ });
341
+ return;
342
+ }
343
+ }
344
+ }
345
+ function declare(identifier, owner, currentFunction, group) {
346
+ record({
347
+ kind: "declare",
348
+ location: bindingLocation(identifier, currentFunction),
349
+ owner,
350
+ origin: identifier,
351
+ position: { group, ordering: "sequenced" },
352
+ });
353
+ }
354
+ function visitFunction(fn) {
355
+ const functionScope = resolved.scopeOfFunction(fn);
356
+ const owner = fn;
357
+ fn.parameters.forEach((parameter) => {
358
+ if (parameter.type === "Identifier")
359
+ declare(parameter, owner, functionScope, nextGroupId++);
360
+ });
361
+ visitBlock(fn.body, functionScope);
362
+ }
363
+ function visitStatementContents(statement, currentFunction) {
364
+ const group = nextGroupId++;
365
+ const expressions = (items) => {
366
+ items.forEach((item) => {
367
+ visitExpression(item, statement, currentFunction, group);
368
+ });
369
+ };
370
+ switch (statement.type) {
371
+ case "LocalStatement":
372
+ expressions(statement.init);
373
+ recordValueSlots(statement, statement.init, statement.variables.length);
374
+ statement.variables.forEach((id) => {
375
+ declare(id, statement, currentFunction, group);
376
+ });
377
+ return;
378
+ case "AssignmentStatement":
379
+ expressions(statement.init);
380
+ recordValueSlots(statement, statement.init, statement.variables.length);
381
+ statement.variables.forEach((target) => {
382
+ if (target.type === "Identifier")
383
+ record({
384
+ kind: "write",
385
+ location: bindingLocation(target, currentFunction),
386
+ owner: statement,
387
+ origin: target,
388
+ position: { group, ordering: "unordered" },
389
+ });
390
+ else {
391
+ visitExpression(target.base, statement, currentFunction, group);
392
+ if (target.type === "IndexExpression")
393
+ visitExpression(target.index, statement, currentFunction, group);
394
+ record({
395
+ kind: "table-write",
396
+ location: tableLocation(target),
397
+ owner: statement,
398
+ origin: target,
399
+ position: { group, ordering: "unordered" },
400
+ });
401
+ }
402
+ });
403
+ return;
404
+ case "CallStatement":
405
+ visitExpression(statement.expression, statement, currentFunction, group);
406
+ return;
407
+ case "ReturnStatement":
408
+ expressions(statement.arguments);
409
+ return;
410
+ case "WhileStatement":
411
+ visitExpression(statement.condition, statement, currentFunction, group);
412
+ visitBlock(statement.body, currentFunction);
413
+ return;
414
+ case "RepeatStatement":
415
+ visitBlock(statement.body, currentFunction);
416
+ visitExpression(statement.condition, statement, currentFunction, group);
417
+ return;
418
+ case "DoStatement":
419
+ visitBlock(statement.body, currentFunction);
420
+ return;
421
+ case "IfStatement":
422
+ statement.clauses.forEach((clause) => {
423
+ if (clause.type !== "ElseClause")
424
+ visitExpression(clause.condition, statement, currentFunction, group);
425
+ visitBlock(clause.body, currentFunction);
426
+ });
427
+ return;
428
+ case "ForNumericStatement":
429
+ expressions([
430
+ statement.start,
431
+ statement.end,
432
+ ...(statement.step ? [statement.step] : []),
433
+ ]);
434
+ declare(statement.variable, statement, currentFunction, group);
435
+ visitBlock(statement.body, currentFunction);
436
+ return;
437
+ case "ForGenericStatement":
438
+ expressions(statement.iterators);
439
+ statement.variables.forEach((id) => {
440
+ declare(id, statement, currentFunction, group);
441
+ });
442
+ visitBlock(statement.body, currentFunction);
443
+ return;
444
+ case "FunctionDeclaration":
445
+ if (statement.identifier) {
446
+ if (statement.identifier.type === "Identifier")
447
+ record({
448
+ kind: statement.isLocal ? "declare" : "write",
449
+ location: bindingLocation(statement.identifier, currentFunction),
450
+ owner: statement,
451
+ origin: statement.identifier,
452
+ position: { group, ordering: "sequenced" },
453
+ });
454
+ else {
455
+ visitExpression(statement.identifier.base, statement, currentFunction, group);
456
+ record({
457
+ kind: "table-write",
458
+ location: tableLocation(statement.identifier),
459
+ owner: statement,
460
+ origin: statement.identifier,
461
+ position: { group, ordering: "unordered" },
462
+ });
463
+ }
464
+ }
465
+ record({
466
+ kind: "allocate",
467
+ allocationKind: "function",
468
+ owner: statement,
469
+ origin: statement,
470
+ position: { group, ordering: "sequenced" },
471
+ });
472
+ visitFunction(statement);
473
+ return;
474
+ case "BreakStatement":
475
+ case "LabelStatement":
476
+ case "GotoStatement":
477
+ return;
478
+ }
479
+ }
480
+ function visitStatement(statement, currentFunction) {
481
+ const operationStart = operations.length;
482
+ visitStatementContents(statement, currentFunction);
483
+ withinOwner.set(statement, operations.slice(operationStart));
484
+ }
485
+ function visitBlock(body, currentFunction) {
486
+ body.forEach((statement) => {
487
+ visitStatement(statement, currentFunction);
488
+ });
489
+ }
490
+ visitBlock(chunk.body, undefined);
491
+ return {
492
+ generation: options.generation ?? 0,
493
+ policy: {
494
+ runtime: options.runtime,
495
+ assumptions: new Map(options.assumptions ?? []),
496
+ },
497
+ operations,
498
+ unknowns,
499
+ operationsOf: (owner) => byOwner.get(owner) ?? [],
500
+ operationsWithin: (owner) => withinOwner.get(owner) ?? [],
501
+ operationOf: (origin) => byOrigin.get(origin),
502
+ operationsOfSymbol: (symbol) => bySymbol.get(symbol) ?? [],
503
+ expressionFact: (expression) => expressionFacts.get(expression),
504
+ valueSlotsOf: (owner) => valueSlots.get(owner) ?? [],
505
+ discardabilityOf: (expression) => {
506
+ const fact = expressionFacts.get(expression);
507
+ if (!fact)
508
+ return { discardable: false, reason: "expression-unindexed" };
509
+ if (fact.effects.mayError.value === "may" ||
510
+ fact.effects.mayInvokeMetamethod.value === "may") {
511
+ return { discardable: false, reason: "observable-effect" };
512
+ }
513
+ const syntacticallyDiscardable = expression.type === "NilLiteral" ||
514
+ expression.type === "BooleanLiteral" ||
515
+ expression.type === "NumericLiteral" ||
516
+ expression.type === "StringLiteral" ||
517
+ expression.type === "FunctionDeclaration" ||
518
+ (expression.type === "UnaryExpression" &&
519
+ expression.operator === "-" &&
520
+ expression.argument.type === "NumericLiteral");
521
+ return syntacticallyDiscardable
522
+ ? {
523
+ discardable: true,
524
+ evidence: { kind: "language", reason: "nonobserving-value" },
525
+ }
526
+ : { discardable: false, reason: "value-or-allocation-observable" };
527
+ },
528
+ };
529
+ }