@markw65/monkeyc-optimizer 1.0.29 → 1.0.32

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/build/api.cjs CHANGED
@@ -1,4 +1,4 @@
1
- 0 && (module.exports = {collectNamespaces,findUsingForNode,formatAst,getApiMapping,hasProperty,isStateNode,sameLookupResult,traverseAst,variableDeclarationName,visitReferences});
1
+ 0 && (module.exports = {collectNamespaces,findUsingForNode,formatAst,getApiFunctionInfo,getApiMapping,hasProperty,isLookupCandidate,isStateNode,markInvokeClassMethod,sameLookupResult,traverseAst,variableDeclarationName,visitReferences});
2
2
  /******/ (() => { // webpackBootstrap
3
3
  /******/ var __webpack_modules__ = ({
4
4
 
@@ -261,9 +261,12 @@ __webpack_require__.d(__webpack_exports__, {
261
261
  "collectNamespaces": () => (/* binding */ api_collectNamespaces),
262
262
  "findUsingForNode": () => (/* binding */ findUsingForNode),
263
263
  "formatAst": () => (/* binding */ api_formatAst),
264
+ "getApiFunctionInfo": () => (/* binding */ api_getApiFunctionInfo),
264
265
  "getApiMapping": () => (/* binding */ api_getApiMapping),
265
266
  "hasProperty": () => (/* reexport */ ast_hasProperty),
267
+ "isLookupCandidate": () => (/* binding */ api_isLookupCandidate),
266
268
  "isStateNode": () => (/* binding */ api_isStateNode),
269
+ "markInvokeClassMethod": () => (/* binding */ api_markInvokeClassMethod),
267
270
  "sameLookupResult": () => (/* binding */ api_sameLookupResult),
268
271
  "traverseAst": () => (/* reexport */ ast_traverseAst),
269
272
  "variableDeclarationName": () => (/* binding */ api_variableDeclarationName),
@@ -464,21 +467,150 @@ function ast_withLoc(node, start, end) {
464
467
  }
465
468
  return node;
466
469
  }
467
- function ast_withLocDeep(node, start, end) {
468
- node = ast_withLoc({ ...node }, start, end);
470
+ function ast_withLocDeep(node, start, end, inplace) {
471
+ node = ast_withLoc(inplace ? node : { ...node }, start, end);
469
472
  for (const key of mctreeTypeInfo[node.type].keys) {
470
473
  const value = node[key];
471
474
  if (!value)
472
475
  continue;
473
- const fix = (v) => isMCTreeNode(v) ? ast_withLocDeep(v, start, end) : v;
476
+ const fix = (v) => isMCTreeNode(v) ? ast_withLocDeep(v, start, end, inplace) : v;
474
477
  const repl = Array.isArray(value) ? value.map(fix) : fix(value);
475
- node[key] = repl;
478
+ inplace || (node[key] = repl);
476
479
  }
477
480
  return node;
478
481
  }
482
+ function ast_cloneDeep(node) {
483
+ return ast_withLocDeep(node, null);
484
+ }
479
485
 
480
486
  ;// CONCATENATED MODULE: external "./api.cjs"
481
487
  const external_api_cjs_namespaceObject = require("./api.cjs");
488
+ ;// CONCATENATED MODULE: ./src/function-info.ts
489
+
490
+ function function_info_cloneSet(ae) {
491
+ return new Set(ae);
492
+ }
493
+ function function_info_mergeSet(a, b) {
494
+ b.forEach((event) => a.add(event));
495
+ }
496
+ function recordModifiedDecl(func, decl) {
497
+ if (!func.next_info) {
498
+ func.next_info = { modifiedDecls: new Set(), calledFuncs: new Set() };
499
+ }
500
+ func.next_info.modifiedDecls.add(decl);
501
+ return null;
502
+ }
503
+ function function_info_recordModifiedDecls(func, lookupDefs) {
504
+ lookupDefs.forEach((lookupDef) => lookupDef.results.forEach((result) => {
505
+ if (result.type == "VariableDeclarator" && result.node.kind === "var") {
506
+ recordModifiedDecl(func, result);
507
+ }
508
+ }));
509
+ }
510
+ function function_info_recordModifiedName(func, name) {
511
+ if (!func.next_info) {
512
+ func.next_info = { modifiedDecls: new Set(), calledFuncs: new Set() };
513
+ }
514
+ if (!func.next_info.modifiedNames) {
515
+ func.next_info.modifiedNames = new Set();
516
+ }
517
+ func.next_info.modifiedNames.add(name);
518
+ }
519
+ function function_info_recordModifiedUnknown(func) {
520
+ if (!func.next_info) {
521
+ func.next_info = { modifiedDecls: new Set(), calledFuncs: new Set() };
522
+ }
523
+ func.next_info.modifiedUnknown = true;
524
+ }
525
+ function recordCalledFunc(func, callee) {
526
+ if (!func.next_info) {
527
+ func.next_info = { modifiedDecls: new Set(), calledFuncs: new Set() };
528
+ }
529
+ func.next_info.calledFuncs.add(callee);
530
+ return null;
531
+ }
532
+ function function_info_recordCalledFuncs(func, callees) {
533
+ callees.forEach((callee) => {
534
+ recordCalledFunc(func, callee);
535
+ });
536
+ }
537
+ function function_info_functionMayModify(state, func, decl) {
538
+ const info = func.info;
539
+ if (!info || info.modifiedUnknown)
540
+ return true;
541
+ if (info.resolvedDecls) {
542
+ return info.resolvedDecls.has(decl);
543
+ }
544
+ if (info.modifiedNames?.has(decl.name))
545
+ return true;
546
+ if (info.modifiedDecls.has(decl))
547
+ return true;
548
+ const visited = new Set();
549
+ const resolved = new Set();
550
+ const resolveDecls = (f) => {
551
+ if (visited.has(f))
552
+ return true;
553
+ if (!f.info)
554
+ return false;
555
+ if (f.info.modifiedUnknown) {
556
+ info.modifiedUnknown = true;
557
+ return false;
558
+ }
559
+ if (f.info.modifiedNames) {
560
+ if (info.modifiedNames) {
561
+ function_info_mergeSet(info.modifiedNames, f.info.modifiedNames);
562
+ }
563
+ else {
564
+ info.modifiedNames = function_info_cloneSet(f.info.modifiedNames);
565
+ }
566
+ }
567
+ function_info_mergeSet(resolved, f.info.modifiedDecls);
568
+ visited.add(f);
569
+ const q = true;
570
+ if (q &&
571
+ f.info.callsExposed &&
572
+ state.exposed &&
573
+ !Object.keys(state.exposed).every((key) => !state.allFunctions[key] ||
574
+ state.allFunctions[key].every(resolveDecls))) {
575
+ return false;
576
+ }
577
+ return Array.from(f.info.calledFuncs).every(resolveDecls);
578
+ };
579
+ if (resolveDecls(func)) {
580
+ info.resolvedDecls = resolved;
581
+ return resolved.has(decl);
582
+ }
583
+ return true;
584
+ }
585
+ function function_info_findCallees(lookupDefs) {
586
+ const decls = lookupDefs.reduce((decls, r) => (decls ? decls.concat(r.results) : r.results), null);
587
+ return (decls &&
588
+ decls.filter((decl) => decl ? decl.type === "FunctionDeclaration" : false));
589
+ }
590
+ function function_info_findCalleesForNew(lookupDefs) {
591
+ const initializer = (decl) => {
592
+ if (hasProperty(decl.decls, "initialize")) {
593
+ return decl.decls["initialize"];
594
+ }
595
+ if (decl.superClass && decl.superClass !== true) {
596
+ return decl.superClass.reduce((cur, cls) => {
597
+ const init = initializer(cls);
598
+ if (init) {
599
+ if (!cur)
600
+ return init;
601
+ return cur.concat(init);
602
+ }
603
+ return cur;
604
+ }, null);
605
+ }
606
+ return null;
607
+ };
608
+ return lookupDefs.flatMap((r) => r.results
609
+ .filter((decl) => decl.type === "ClassDeclaration")
610
+ .flatMap(initializer)
611
+ .filter((decl) => decl ? decl.type === "FunctionDeclaration" : false));
612
+ }
613
+
482
614
  ;// CONCATENATED MODULE: ./src/variable-renamer.ts
483
615
 
484
616
 
@@ -539,6 +671,7 @@ function variable_renamer_renameVariable(state, locals, declName) {
539
671
 
540
672
 
541
673
 
674
+
542
675
  function getArgSafety(state, func, args, requireAll) {
543
676
  // determine whether decl might be changed by a function call
544
677
  // or assignment during the evaluation of FunctionStateNode.
@@ -571,8 +704,9 @@ function getArgSafety(state, func, args, requireAll) {
571
704
  }
572
705
  };
573
706
  const safeArgs = [];
707
+ const argDecls = [];
574
708
  let allSafe = true;
575
- if (!args.every((arg) => {
709
+ if (!args.every((arg, i) => {
576
710
  switch (arg.type) {
577
711
  case "Literal":
578
712
  safeArgs.push(true);
@@ -586,13 +720,17 @@ function getArgSafety(state, func, args, requireAll) {
586
720
  safeArgs.push(null);
587
721
  return !requireAll;
588
722
  }
589
- const safety = getSafety(results[0].results[0]);
723
+ const decl = results[0].results[0];
724
+ const safety = getSafety(decl);
590
725
  safeArgs.push(safety);
591
726
  if (!safety) {
592
727
  allSafe = false;
593
728
  if (safety === null) {
594
729
  return !requireAll;
595
730
  }
731
+ else if (decl.type === "VariableDeclarator") {
732
+ argDecls[i] = decl;
733
+ }
596
734
  }
597
735
  return true;
598
736
  }
@@ -605,34 +743,91 @@ function getArgSafety(state, func, args, requireAll) {
605
743
  }
606
744
  if (allSafe && requireAll)
607
745
  return true;
608
- let callSeen = false;
746
+ const callsSeen = new Set();
747
+ const modifiedDecls = new Set();
748
+ let modifiedUnknown = false;
609
749
  const params = Object.fromEntries(func.node.params.map((param, i) => [variableDeclarationName(param), i]));
610
750
  // look for uses of "unsafe" args that occur after a call.
611
751
  // use post to do the checking, because arguments are evaluated
612
752
  // prior to the call, so eg "return f(x.y);" is fine, but
613
753
  // "return f()+x.y" is not.
614
- traverseAst(func.node.body, null, (node) => {
615
- switch (node.type) {
616
- case "AssignmentExpression":
617
- case "UpdateExpression": {
618
- const v = node.type == "UpdateExpression" ? node.argument : node.left;
619
- if (v.type === "Identifier" && hasProperty(params, v.name)) {
620
- safeArgs[params[v.name]] = null;
754
+ const { pre, post, stack } = state;
755
+ try {
756
+ delete state.pre;
757
+ state.post = (node) => {
758
+ switch (node.type) {
759
+ case "AssignmentExpression":
760
+ case "UpdateExpression": {
761
+ const v = node.type == "UpdateExpression" ? node.argument : node.left;
762
+ if (v.type === "Identifier" && hasProperty(params, v.name)) {
763
+ // If a parameter is modified, we can't just substitute the
764
+ // argument wherever the parameter is used.
765
+ safeArgs[params[v.name]] = null;
766
+ break;
767
+ }
768
+ if (modifiedUnknown)
769
+ break;
770
+ const [, results] = state.lookup(v);
771
+ if (results) {
772
+ results.forEach((r) => r.results.forEach((decl) => decl.type === "VariableDeclarator" && modifiedDecls.add(decl)));
773
+ }
774
+ else {
775
+ modifiedUnknown = true;
776
+ }
777
+ break;
621
778
  }
779
+ case "CallExpression":
780
+ case "NewExpression":
781
+ if (!modifiedUnknown) {
782
+ const [, results] = state.lookup(node.callee, null,
783
+ // calls are looked up as non-locals, but new is not
784
+ node.type === "CallExpression" ? func.stack : state.stack);
785
+ if (!results) {
786
+ const callee_name = node.callee.type === "Identifier"
787
+ ? node.callee
788
+ : node.callee.type === "MemberExpression"
789
+ ? isLookupCandidate(node.callee)
790
+ : null;
791
+ if (callee_name) {
792
+ const callees = state.allFunctions[callee_name.name];
793
+ if (callees) {
794
+ callees.forEach((callee) => callsSeen.add(callee));
795
+ }
796
+ }
797
+ else {
798
+ modifiedUnknown = true;
799
+ }
800
+ }
801
+ else {
802
+ const callees = node.type === "CallExpression"
803
+ ? findCallees(results)
804
+ : findCalleesForNew(results);
805
+ if (callees) {
806
+ callees.forEach((callee) => callsSeen.add(callee));
807
+ }
808
+ }
809
+ }
810
+ break;
811
+ case "Identifier":
812
+ if (hasProperty(params, node.name) &&
813
+ !safeArgs[params[node.name]] &&
814
+ (modifiedUnknown ||
815
+ !argDecls[params[node.name]] ||
816
+ modifiedDecls.has(argDecls[params[node.name]]) ||
817
+ Array.from(callsSeen).some((callee) => functionMayModify(state, callee, argDecls[params[node.name]])))) {
818
+ safeArgs[params[node.name]] = null;
819
+ }
622
820
  }
623
- // fall through
624
- case "CallExpression":
625
- case "NewExpression":
626
- callSeen = true;
627
- break;
628
- case "Identifier":
629
- if (callSeen &&
630
- hasProperty(params, node.name) &&
631
- !safeArgs[params[node.name]]) {
632
- safeArgs[params[node.name]] = null;
633
- }
634
- }
635
- });
821
+ return null;
822
+ };
823
+ state.stack = func.stack;
824
+ state.traverse(func.node.body);
825
+ }
826
+ finally {
827
+ state.pre = pre;
828
+ state.post = post;
829
+ state.stack = stack;
830
+ }
636
831
  return safeArgs;
637
832
  }
638
833
  function canInline(state, func, args) {
@@ -737,9 +932,6 @@ function processInlineBody(state, func, call, root, params) {
737
932
  state.pre = (node) => {
738
933
  if (failed)
739
934
  return [];
740
- node.start = call.start;
741
- node.end = call.end;
742
- node.loc = call.loc;
743
935
  if (replacements.has(node))
744
936
  return false;
745
937
  const result = pre(node, state);
@@ -754,6 +946,7 @@ function processInlineBody(state, func, call, root, params) {
754
946
  if (params[paramName] >= 0)
755
947
  return null;
756
948
  const name = renameVariable(state, locals, paramName) || paramName;
949
+ locals.map[name] = true;
757
950
  return {
758
951
  type: "VariableDeclarator",
759
952
  id: { type: "Identifier", name },
@@ -772,31 +965,49 @@ function processInlineBody(state, func, call, root, params) {
772
965
  }
773
966
  return result;
774
967
  };
968
+ const fixId = (node) => {
969
+ if (state.inType)
970
+ return null;
971
+ if (hasProperty(params, node.name)) {
972
+ const ix = params[node.name];
973
+ if (ix >= 0) {
974
+ const replacement = { ...call.arguments[ix] };
975
+ replacements.add(replacement);
976
+ return replacement;
977
+ }
978
+ return null;
979
+ }
980
+ const replacement = fixNodeScope(state, node, func.stack);
981
+ if (!replacement) {
982
+ failed = true;
983
+ inlineDiagnostic(state, func, call, `Failed to resolve '${node.name}'`);
984
+ }
985
+ return replacement;
986
+ };
775
987
  state.post = (node) => {
776
988
  if (failed)
777
989
  return post(node, state);
778
990
  let replacement = null;
779
991
  switch (node.type) {
780
- case "Identifier": {
781
- if (state.inType)
782
- break;
783
- if (hasProperty(params, node.name)) {
784
- const ix = params[node.name];
785
- if (ix >= 0) {
786
- replacement = { ...call.arguments[ix] };
787
- replacements.add(replacement);
788
- return replacement;
992
+ case "AssignmentExpression":
993
+ if (node.left.type === "Identifier") {
994
+ const rep = fixId(node.left);
995
+ if (rep) {
996
+ node.left = rep;
789
997
  }
790
- break;
791
998
  }
792
- replacement = fixNodeScope(state, node, func.stack);
793
- if (!replacement) {
794
- failed = true;
795
- inlineDiagnostic(state, func, call, `Failed to resolve '${node.name}'`);
796
- return post(node, state);
999
+ break;
1000
+ case "UpdateExpression":
1001
+ if (node.argument.type === "Identifier") {
1002
+ const rep = fixId(node.argument);
1003
+ if (rep) {
1004
+ node.argument = rep;
1005
+ }
797
1006
  }
798
1007
  break;
799
- }
1008
+ case "Identifier":
1009
+ replacement = fixId(node);
1010
+ break;
800
1011
  }
801
1012
  const ret = post(replacement || node, state);
802
1013
  return ret === false || ret ? ret : replacement;
@@ -928,6 +1139,10 @@ function inlineWithArgs(state, func, call, context) {
928
1139
  if (!func.node || !func.node.body) {
929
1140
  return null;
930
1141
  }
1142
+ const lastStmt = (block) => {
1143
+ const last = block.body.slice(-1)[0];
1144
+ return last.type === "BlockStatement" ? lastStmt(last) : [last, block];
1145
+ };
931
1146
  let retStmtCount = 0;
932
1147
  if (context.type === "ReturnStatement") {
933
1148
  const last = func.node.body.body.slice(-1)[0];
@@ -950,7 +1165,7 @@ function inlineWithArgs(state, func, call, context) {
950
1165
  return null;
951
1166
  }
952
1167
  if (retStmtCount === 1) {
953
- const last = func.node.body.body.slice(-1)[0];
1168
+ const [last] = lastStmt(func.node.body);
954
1169
  if (!last ||
955
1170
  last.type !== "ReturnStatement" ||
956
1171
  ((context.type === "AssignmentExpression" ||
@@ -961,7 +1176,7 @@ function inlineWithArgs(state, func, call, context) {
961
1176
  }
962
1177
  }
963
1178
  }
964
- const body = JSON.parse(JSON.stringify(func.node.body));
1179
+ const body = cloneDeep(func.node.body);
965
1180
  const safeArgs = getArgSafety(state, func, call.arguments, false);
966
1181
  const params = Object.fromEntries(func.node.params.map((param, i) => {
967
1182
  const argnum = safeArgs === true || (safeArgs !== false && safeArgs[i] !== null)
@@ -975,21 +1190,21 @@ function inlineWithArgs(state, func, call, context) {
975
1190
  }
976
1191
  inliner_diagnostic(state, call.loc, null);
977
1192
  if (context.type !== "ReturnStatement" && retStmtCount) {
978
- const last = body.body[body.body.length - 1];
1193
+ const [last, block] = lastStmt(body);
979
1194
  if (last.type != "ReturnStatement") {
980
1195
  throw new Error("ReturnStatement got lost!");
981
1196
  }
982
1197
  if (last.argument) {
983
1198
  if (context.type === "AssignmentExpression") {
984
1199
  context.right = last.argument;
985
- body.body[body.body.length - 1] = {
1200
+ block.body[block.body.length - 1] = {
986
1201
  type: "ExpressionStatement",
987
1202
  expression: context,
988
1203
  };
989
1204
  }
990
1205
  else if (context.type === "VariableDeclarator") {
991
1206
  const { id, init: _init, kind: _kind, ...rest } = context;
992
- body.body[body.body.length - 1] = {
1207
+ block.body[block.body.length - 1] = {
993
1208
  ...rest,
994
1209
  type: "ExpressionStatement",
995
1210
  expression: {
@@ -1003,25 +1218,26 @@ function inlineWithArgs(state, func, call, context) {
1003
1218
  }
1004
1219
  else {
1005
1220
  const side_exprs = inliner_unused(last.argument);
1006
- body.body.splice(body.body.length - 1, 1, ...side_exprs);
1221
+ block.body.splice(block.body.length - 1, 1, ...side_exprs);
1007
1222
  }
1008
1223
  }
1009
1224
  else {
1010
- --body.body.length;
1225
+ --block.body.length;
1011
1226
  }
1012
1227
  }
1228
+ withLocDeep(body, context, context, true);
1013
1229
  return body;
1014
1230
  }
1015
1231
  function inliner_inlineFunction(state, func, call, context) {
1016
1232
  if (context) {
1017
1233
  return inlineWithArgs(state, func, call, context);
1018
1234
  }
1019
- const retArg = JSON.parse(JSON.stringify(func.node.body.body[0].argument));
1235
+ const retArg = cloneDeep(func.node.body.body[0].argument);
1020
1236
  const params = Object.fromEntries(func.node.params.map((param, i) => [variableDeclarationName(param), i]));
1021
1237
  const map = fixupLocalsMap(state);
1022
1238
  const ret = processInlineBody(state, func, call, retArg, params);
1023
1239
  state.localsStack[state.localsStack.length - 1].map = map;
1024
- return ret;
1240
+ return ret && withLocDeep(ret, call, call, true);
1025
1241
  }
1026
1242
  function applyTypeIfNeeded(node) {
1027
1243
  if ("enumType" in node && node.enumType) {
@@ -1126,6 +1342,121 @@ function fixNodeScope(state, lookupNode, nodeStack) {
1126
1342
  return null;
1127
1343
  }
1128
1344
 
1345
+ ;// CONCATENATED MODULE: ./src/pragma-checker.ts
1346
+
1347
+
1348
+
1349
+ function pragma_checker_pragmaChecker(state, ast, diagnostics) {
1350
+ const comments = ast.comments;
1351
+ if (!comments)
1352
+ return;
1353
+ diagnostics = diagnostics
1354
+ ?.slice()
1355
+ .sort((d1, d2) => d1.loc.start < d2.loc.start ? -1 : d1.loc.start == d2.loc.start ? 0 : 1);
1356
+ let diagIndex = 0;
1357
+ let index = -1;
1358
+ let comment;
1359
+ let matchers;
1360
+ const next = () => {
1361
+ while (++index < comments.length) {
1362
+ comment = comments[index];
1363
+ let match = comment.value.match(/^\s*@(match|expect)\s+(.+)/);
1364
+ if (!match)
1365
+ continue;
1366
+ const kind = match[1];
1367
+ let str = match[2];
1368
+ matchers = [];
1369
+ while ((match = str.match(/^([/%&#@"])(.+?(?<!\\)(?:\\{2})*)\1(\s+|$)/))) {
1370
+ matchers.push({ kind, quote: match[1], needle: match[2] });
1371
+ str = str.substring(match[0].length);
1372
+ if (!str.length)
1373
+ break;
1374
+ }
1375
+ if (!str.length)
1376
+ break;
1377
+ if (!matchers.length) {
1378
+ match = str.match(/^(\S+)\s+$/);
1379
+ if (match) {
1380
+ matchers.push({ kind, quote: '"', needle: match[1] });
1381
+ break;
1382
+ }
1383
+ }
1384
+ diagnostic(state, comment.loc, `Build pragma '${comment.value}' is invalid`, "ERROR");
1385
+ }
1386
+ };
1387
+ const matcher = (quote, needle, haystack) => {
1388
+ if (quote == '"') {
1389
+ return haystack.includes(needle);
1390
+ }
1391
+ const re = new RegExp(needle.replace(/@(\d+)/g, "(pre_)?$1(_\\d+)?"));
1392
+ return re.test(haystack);
1393
+ };
1394
+ next();
1395
+ traverseAst(ast, (node) => {
1396
+ if (index >= comments.length)
1397
+ return false;
1398
+ if (node.start && node.start >= (comment.end || Infinity)) {
1399
+ const { kind, quote, needle } = matchers.shift();
1400
+ if (kind === "match") {
1401
+ const haystack = formatAst(node).replace(/([\r\n]|\s)+/g, " ");
1402
+ if (!matcher(quote, needle, haystack)) {
1403
+ matcher(quote, needle, haystack);
1404
+ diagnostic(state, comment.loc, `Didn't find '${needle}' in '${haystack}'`, "ERROR");
1405
+ }
1406
+ }
1407
+ else if (kind === "expect") {
1408
+ const locCmp = (a, b) => {
1409
+ if (!b)
1410
+ return -1;
1411
+ if (a.start.line < b.start.line)
1412
+ return -1;
1413
+ if (a.start.line === b.start.line &&
1414
+ a.start.column < b.start.column) {
1415
+ return -1;
1416
+ }
1417
+ if (a.end.line > b.end.line)
1418
+ return 1;
1419
+ if (a.end.line === b.end.line && a.end.column >= b.end.column) {
1420
+ return 1;
1421
+ }
1422
+ return 0;
1423
+ };
1424
+ let found = false;
1425
+ if (diagnostics) {
1426
+ while (true) {
1427
+ if (diagIndex >= diagnostics.length) {
1428
+ diagnostics = null;
1429
+ break;
1430
+ }
1431
+ const diag = diagnostics[diagIndex];
1432
+ const cmp = locCmp(diag.loc, node.loc);
1433
+ if (cmp > 0) {
1434
+ break;
1435
+ }
1436
+ diagIndex++;
1437
+ if (cmp < 0)
1438
+ continue;
1439
+ if (matcher(quote, needle, diag.message)) {
1440
+ found = true;
1441
+ diag.type = "INFO";
1442
+ }
1443
+ }
1444
+ }
1445
+ if (!found) {
1446
+ diagnostic(state, comment.loc, `Missing error message '${needle}`, "ERROR");
1447
+ }
1448
+ }
1449
+ if (matchers.length) {
1450
+ // if we're checking a series of nodes, we need
1451
+ // to skip over this one.
1452
+ return false;
1453
+ }
1454
+ next();
1455
+ }
1456
+ return null;
1457
+ });
1458
+ }
1459
+
1129
1460
  ;// CONCATENATED MODULE: external "./util.cjs"
1130
1461
  const external_util_cjs_namespaceObject = require("./util.cjs");
1131
1462
  ;// CONCATENATED MODULE: ./src/control-flow.ts
@@ -1271,7 +1602,8 @@ function control_flow_buildReducedGraph(state, func, notice) {
1271
1602
  if (node.type === "WhileStatement") {
1272
1603
  head = localState.newBlock(top.continue);
1273
1604
  state.traverse(node.test);
1274
- localState.addEdge(localState.newBlock(), top.break);
1605
+ localState.addEdge(localState.curBlock, top.break);
1606
+ localState.newBlock();
1275
1607
  }
1276
1608
  else {
1277
1609
  head = localState.newBlock();
@@ -1592,6 +1924,8 @@ function cleanCfg(head) {
1592
1924
  }
1593
1925
  }));
1594
1926
  }
1927
+ if (!cur.node)
1928
+ cur.node = succ.node;
1595
1929
  }
1596
1930
  }
1597
1931
  });
@@ -1645,6 +1979,8 @@ var priorityqueuejs = __webpack_require__(2789);
1645
1979
 
1646
1980
 
1647
1981
 
1982
+
1983
+
1648
1984
  /**
1649
1985
  * This implements a pseudo Partial Redundancy Elimination
1650
1986
  * pass. It isn't quite like traditional PRE because we're
@@ -1690,6 +2026,20 @@ function declName(decl) {
1690
2026
  throw new Error(`Unexpected EventDecl type: ${decl.type}`);
1691
2027
  }
1692
2028
  }
2029
+ function logAntState(s, decl) {
2030
+ const defs = Array.from(s.ant).reduce((defs, event) => {
2031
+ if (event.type === "def" || event.type === "mod")
2032
+ defs++;
2033
+ return defs;
2034
+ }, 0);
2035
+ console.log(` - ${declFullName(decl)}: ${candidateCost(s)} bytes, ${s.ant.size - defs} refs, ${defs} defs, ${s.live ? "" : "!"}live, ${s.isIsolated ? "" : "!"}isolated`);
2036
+ console.log(` - members: ${Array.from(s.members)
2037
+ .map(([block, live]) => block.order + (live ? "t" : "f"))
2038
+ .join(", ")}`);
2039
+ }
2040
+ function logAntDecls(antDecls) {
2041
+ antDecls.forEach(logAntState);
2042
+ }
1693
2043
  function pre_sizeBasedPRE(state, func) {
1694
2044
  if (!func.node.body)
1695
2045
  return;
@@ -1700,18 +2050,11 @@ function pre_sizeBasedPRE(state, func) {
1700
2050
  return;
1701
2051
  }
1702
2052
  const { graph: head, identifiers } = buildPREGraph(state, func);
1703
- const candidates = computeAttributes(head);
2053
+ const candidates = computeAttributes(state, head);
1704
2054
  if (candidates) {
1705
2055
  if (logging) {
1706
2056
  console.log(`Found ${candidates.size} candidates in ${func.fullName}`);
1707
- candidates.forEach((s, decl) => {
1708
- const defs = Array.from(s.ant).reduce((defs, event) => {
1709
- if (event.type === "def")
1710
- defs++;
1711
- return defs;
1712
- }, 0);
1713
- console.log(` - ${declFullName(decl)}: ${candidateCost(s)} bytes, ${s.ant.size - defs} refs, ${defs} defs, ${s.live ? "" : "!"}live`);
1714
- });
2057
+ logAntDecls(candidates);
1715
2058
  }
1716
2059
  const nodeMap = new Map();
1717
2060
  const declMap = new Map();
@@ -1727,8 +2070,10 @@ function pre_sizeBasedPRE(state, func) {
1727
2070
  let i = 0;
1728
2071
  do {
1729
2072
  name = `pre_${declName(decl)}${i ? "_" + i : ""}`;
1730
- if (!identifiers.has(name))
2073
+ if (!identifiers.has(name)) {
2074
+ identifiers.add(name);
1731
2075
  break;
2076
+ }
1732
2077
  i++;
1733
2078
  } while (true);
1734
2079
  declMap.set(decl, name);
@@ -1819,11 +2164,14 @@ function buildPREGraph(state, func) {
1819
2164
  case "ParenthesizedExpression":
1820
2165
  break;
1821
2166
  case "Literal":
1822
- if (!node.value && refCost(node) > LocalRefCost) {
1823
- let decl = literals.get(node.value);
2167
+ if (refCost(node) > LocalRefCost) {
2168
+ const key = LiteralIntegerRe.test(node.raw)
2169
+ ? BigInt(node.value)
2170
+ : node.value;
2171
+ let decl = literals.get(key);
1824
2172
  if (!decl) {
1825
2173
  decl = node;
1826
- literals.set(node.value, decl);
2174
+ literals.set(key, decl);
1827
2175
  }
1828
2176
  return {
1829
2177
  type: "ref",
@@ -1903,10 +2251,18 @@ function buildPREGraph(state, func) {
1903
2251
  }
1904
2252
  break;
1905
2253
  }
1906
- case "NewExpression":
1907
- case "CallExpression":
2254
+ case "NewExpression": {
2255
+ const [, results] = state.lookup(node.callee);
2256
+ const callees = results ? findCalleesForNew(results) : null;
2257
+ liveDef(null, stmt);
2258
+ return { type: "mod", node, mayThrow, callees };
2259
+ }
2260
+ case "CallExpression": {
1908
2261
  liveDef(null, stmt);
1909
- return { type: "mod", node, mayThrow };
2262
+ const [, results] = state.lookup(node.callee);
2263
+ const callees = results ? findCallees(results) : null;
2264
+ return { type: "mod", node, mayThrow, callees };
2265
+ }
1910
2266
  default:
1911
2267
  if (!isExpression(node))
1912
2268
  break;
@@ -1922,12 +2278,6 @@ function buildPREGraph(state, func) {
1922
2278
  function anticipatedDecls() {
1923
2279
  return new Map();
1924
2280
  }
1925
- function cloneSet(ae) {
1926
- return new Set(ae);
1927
- }
1928
- function mergeSet(a, b) {
1929
- b.forEach((event) => a.add(event));
1930
- }
1931
2281
  function equalSet(a, b) {
1932
2282
  if (a.size != b.size)
1933
2283
  return false;
@@ -1957,21 +2307,28 @@ function cloneAnticipatedState(as) {
1957
2307
  members: new Map(as.members),
1958
2308
  };
1959
2309
  }
2310
+ function mergeAnticipatedState(ae, be) {
2311
+ mergeSet(ae.ant, be.ant);
2312
+ be.members.forEach((live, block) => ae.members.set(block, live));
2313
+ if (be.live)
2314
+ ae.live = true;
2315
+ }
1960
2316
  function cloneAnticipatedDecls(ad) {
1961
2317
  const copy = anticipatedDecls();
1962
2318
  for (const [k, v] of ad) {
1963
- copy.set(k, cloneAnticipatedState(v));
2319
+ if (!v.isIsolated) {
2320
+ copy.set(k, cloneAnticipatedState(v));
2321
+ }
1964
2322
  }
1965
2323
  return copy;
1966
2324
  }
1967
2325
  function mergeAnticipatedDecls(a, b) {
1968
2326
  for (const [k, v] of b) {
2327
+ if (v.isIsolated)
2328
+ continue;
1969
2329
  const ae = a.get(k);
1970
2330
  if (ae) {
1971
- mergeSet(ae.ant, v.ant);
1972
- v.members.forEach((live, block) => ae.members.set(block, live));
1973
- if (v.live)
1974
- ae.live = true;
2331
+ mergeAnticipatedState(ae, v);
1975
2332
  }
1976
2333
  else {
1977
2334
  a.set(k, cloneAnticipatedState(v));
@@ -1985,6 +2342,7 @@ function equalStates(a, b) {
1985
2342
  const be = b.get(k);
1986
2343
  if (!be ||
1987
2344
  be.live != ae.live ||
2345
+ be.isIsolated != ae.isIsolated ||
1988
2346
  !equalSet(ae.ant, be.ant) ||
1989
2347
  !equalMap(ae.members, be.members)) {
1990
2348
  return false;
@@ -1998,6 +2356,7 @@ function refCost(node) {
1998
2356
  switch (typeof node.value) {
1999
2357
  case "string":
2000
2358
  return 5;
2359
+ case "bigint":
2001
2360
  case "number":
2002
2361
  return 5;
2003
2362
  case "boolean":
@@ -2055,10 +2414,11 @@ function candidateCost(candState) {
2055
2414
  cost += defCost(candState.node);
2056
2415
  }
2057
2416
  });
2058
- cost += defCost(candState.node) * candidateBoundary(candState).size;
2417
+ const boundarySize = candidateBoundary(candState).size;
2418
+ cost += defCost(candState.node) * boundarySize;
2059
2419
  return cost;
2060
2420
  }
2061
- function computeAttributes(head) {
2421
+ function computeAttributes(state, head) {
2062
2422
  const order = getPostOrder(head);
2063
2423
  order.forEach((block, i) => {
2064
2424
  block.order = i;
@@ -2167,7 +2527,9 @@ function computeAttributes(head) {
2167
2527
  curState.forEach((candidates, decl) => {
2168
2528
  if (decl.type === "VariableDeclarator" &&
2169
2529
  decl.node.kind === "var" &&
2170
- candidates.live) {
2530
+ candidates.live &&
2531
+ (!event.callees ||
2532
+ event.callees.some((callee) => functionMayModify(state, callee, decl)))) {
2171
2533
  candidates.ant.add(getMod(event, decl, candidates.node));
2172
2534
  candidates.live = false;
2173
2535
  }
@@ -2193,9 +2555,7 @@ function computeAttributes(head) {
2193
2555
  if (isUpdate || candidates.live) {
2194
2556
  candidates.ant.add(event);
2195
2557
  }
2196
- if (!isUpdate) {
2197
- candidates.live = false;
2198
- }
2558
+ candidates.live = isUpdate;
2199
2559
  break;
2200
2560
  }
2201
2561
  }
@@ -2204,12 +2564,23 @@ function computeAttributes(head) {
2204
2564
  curState.forEach((antState) => {
2205
2565
  antState.head = top;
2206
2566
  antState.members.set(top, antState.live);
2567
+ if (!antState.live && candidateBoundary(antState).size === 0) {
2568
+ // we found a group that's isolated from the rest
2569
+ // of the function. Don't merge it with earlier
2570
+ // refs and defs, because we can take it or leave
2571
+ // it based on its own cost.
2572
+ antState.isIsolated = true;
2573
+ }
2207
2574
  });
2208
2575
  const oldState = blockStates[top.order];
2209
2576
  if (oldState && equalStates(oldState, curState)) {
2210
2577
  continue;
2211
2578
  }
2212
2579
  blockStates[top.order] = curState;
2580
+ if (logging) {
2581
+ console.log(`Updated block ${top.order}`);
2582
+ logAntDecls(curState);
2583
+ }
2213
2584
  if (top.preds) {
2214
2585
  top.preds.forEach((pred) => enqueue(pred));
2215
2586
  }
@@ -2222,7 +2593,9 @@ function computeAttributes(head) {
2222
2593
  if (cost >= 0)
2223
2594
  return;
2224
2595
  const existing = candidateDecls.get(decl);
2225
- if (!existing || candidateCost(existing) > cost) {
2596
+ if (!existing ||
2597
+ existing.isIsolated ||
2598
+ candidateCost(existing) > cost) {
2226
2599
  const boundary = candidateBoundary(events);
2227
2600
  if (!Array.from(boundary).every((block) => {
2228
2601
  if (block !== events.head && block.events) {
@@ -2262,7 +2635,11 @@ function computeAttributes(head) {
2262
2635
  return;
2263
2636
  }
2264
2637
  events.live = false;
2265
- if (candidateCost(events) != cost) {
2638
+ if (existing && existing.isIsolated) {
2639
+ delete existing.isIsolated;
2640
+ mergeAnticipatedState(events, existing);
2641
+ }
2642
+ else if (candidateCost(events) != cost) {
2266
2643
  throw new Error(`cost of block ${i} changed`);
2267
2644
  }
2268
2645
  candidateDecls.set(decl, events);
@@ -2341,20 +2718,26 @@ function applyReplacements(func, nodeMap, declMap) {
2341
2718
  stmtStack.pop();
2342
2719
  const events = nodeMap.get(node);
2343
2720
  if (events) {
2344
- if (events.length === 1) {
2345
- if (events[0].type === "ref") {
2721
+ const ret = events.reduce((ret, event) => {
2722
+ if (event.type === "ref") {
2723
+ if (ret) {
2724
+ throw new Error(`ref found when there was already a replacement for this node`);
2725
+ }
2346
2726
  if (node.type !== "Identifier" &&
2347
2727
  node.type !== "MemberExpression" &&
2348
2728
  node.type !== "Literal") {
2349
2729
  throw new Error(`Ref found, but wrong type of node: ${node.type}`);
2350
2730
  }
2351
- const name = declMap.get(events[0].decl);
2731
+ const name = declMap.get(event.decl);
2352
2732
  if (!name) {
2353
2733
  throw new Error(`No replacement found for "${formatAst(node)}"`);
2354
2734
  }
2355
2735
  return ident(name, node);
2356
2736
  }
2357
- else if (events[0].type === "def") {
2737
+ if (event.type === "def") {
2738
+ if (ret) {
2739
+ throw new Error(`def found when there was already a replacement for this node`);
2740
+ }
2358
2741
  if (node.type !== "AssignmentExpression" &&
2359
2742
  node.type !== "UpdateExpression") {
2360
2743
  throw new Error(`Def found, but wrong type of node: ${node.type}`);
@@ -2362,7 +2745,7 @@ function applyReplacements(func, nodeMap, declMap) {
2362
2745
  const target = node.type === "AssignmentExpression"
2363
2746
  ? node.left
2364
2747
  : node.argument;
2365
- const name = declMap.get(events[0].decl);
2748
+ const name = declMap.get(event.decl);
2366
2749
  if (!name) {
2367
2750
  throw new Error(`No replacement found for "${formatAst(target)}"`);
2368
2751
  }
@@ -2381,20 +2764,24 @@ function applyReplacements(func, nodeMap, declMap) {
2381
2764
  }
2382
2765
  return withLoc({ type: "SequenceExpression", expressions: [node, assign] }, node);
2383
2766
  }
2384
- }
2385
- events.forEach((event) => {
2386
- if (event.type !== "mod") {
2387
- throw new Error(`Unexpected ${event.type} found amongst multiple events`);
2388
- }
2389
- if (!event.decl) {
2390
- throw new Error(`Unexpected null decl on mod event`);
2767
+ if (event.type === "mod") {
2768
+ if (!event.decl) {
2769
+ throw new Error(`Unexpected null decl on mod event`);
2770
+ }
2771
+ let pending = pendingMap.get(stmt);
2772
+ if (!pending) {
2773
+ pendingMap.set(stmt, (pending = new Set()));
2774
+ }
2775
+ pending.add(event);
2391
2776
  }
2392
- let pending = pendingMap.get(stmt);
2393
- if (!pending) {
2394
- pendingMap.set(stmt, (pending = new Set()));
2777
+ else {
2778
+ throw new Error(`Unexpected ${event.type} found`);
2395
2779
  }
2396
- pending.add(event);
2397
- });
2780
+ return ret;
2781
+ }, null);
2782
+ if (ret) {
2783
+ return ret;
2784
+ }
2398
2785
  }
2399
2786
  const pending = pendingMap.get(node);
2400
2787
  if (node.type === "SequenceExpression") {
@@ -2468,6 +2855,148 @@ function applyReplacements(func, nodeMap, declMap) {
2468
2855
  });
2469
2856
  }
2470
2857
 
2858
+ ;// CONCATENATED MODULE: ./src/unused-exprs.ts
2859
+
2860
+
2861
+
2862
+ function unused_exprs_cleanupUnusedVars(state, node) {
2863
+ const [parent] = state.stack.slice(-1);
2864
+ if (parent.node !== node) {
2865
+ return;
2866
+ }
2867
+ if (parent.type != "BlockStatement") {
2868
+ throw new Error(`Unexpected parent type '${parent.type}' for local declaration`);
2869
+ }
2870
+ if (parent.decls) {
2871
+ let toRemove = null;
2872
+ Object.values(parent.decls).forEach((decls) => {
2873
+ if (decls.length === 1 &&
2874
+ decls[0].type === "VariableDeclarator" &&
2875
+ !decls[0].used) {
2876
+ if (!toRemove)
2877
+ toRemove = {};
2878
+ toRemove[decls[0].name] = decls[0];
2879
+ }
2880
+ });
2881
+ if (toRemove) {
2882
+ const varDeclarations = new Map();
2883
+ traverseAst(node, null, (node) => {
2884
+ switch (node.type) {
2885
+ case "VariableDeclaration": {
2886
+ node.declarations.forEach((decl, i) => {
2887
+ const name = variableDeclarationName(decl.id);
2888
+ if (hasProperty(toRemove, name)) {
2889
+ const indices = varDeclarations.get(node);
2890
+ if (indices) {
2891
+ indices.push(i);
2892
+ }
2893
+ else {
2894
+ varDeclarations.set(node, [i]);
2895
+ }
2896
+ }
2897
+ });
2898
+ break;
2899
+ }
2900
+ case "ExpressionStatement":
2901
+ if (node.expression.type === "AssignmentExpression") {
2902
+ if (node.expression.left.type === "Identifier" &&
2903
+ hasProperty(toRemove, node.expression.left.name)) {
2904
+ return unused(node.expression.right);
2905
+ }
2906
+ }
2907
+ else if (node.expression.type === "UpdateExpression" &&
2908
+ node.expression.argument.type === "Identifier" &&
2909
+ hasProperty(toRemove, node.expression.argument.name)) {
2910
+ return false;
2911
+ }
2912
+ break;
2913
+ case "SequenceExpression": {
2914
+ for (let i = node.expressions.length; i--;) {
2915
+ const expr = node.expressions[i];
2916
+ if (expr.type === "AssignmentExpression") {
2917
+ if (expr.left.type === "Identifier" &&
2918
+ hasProperty(toRemove, expr.left.name)) {
2919
+ const rep = unused(expr.right);
2920
+ if (!rep.length) {
2921
+ node.expressions.splice(i, 1);
2922
+ }
2923
+ else {
2924
+ // Sequence expressions can only be assignments
2925
+ // or update expressions. Even calls aren't allowed
2926
+ toRemove[expr.left.name] = null;
2927
+ expr.operator = "=";
2928
+ }
2929
+ }
2930
+ }
2931
+ else if (expr.type === "UpdateExpression" &&
2932
+ expr.argument.type === "Identifier" &&
2933
+ hasProperty(toRemove, expr.argument.name)) {
2934
+ node.expressions.splice(i, 1);
2935
+ }
2936
+ }
2937
+ break;
2938
+ }
2939
+ }
2940
+ return null;
2941
+ });
2942
+ varDeclarations.forEach((indices, decl) => {
2943
+ let index = -1;
2944
+ for (let ii = indices.length, j = decl.declarations.length; ii--;) {
2945
+ const i = indices[ii];
2946
+ const vdecl = decl.declarations[i];
2947
+ const name = variableDeclarationName(vdecl.id);
2948
+ if (hasProperty(toRemove, name)) {
2949
+ const rep = vdecl.init ? unused(vdecl.init) : [];
2950
+ if (rep.length) {
2951
+ if (parent.node.type === "ForStatement") {
2952
+ // declarations whose inits have side effects
2953
+ // can't be deleted from for statements.
2954
+ continue;
2955
+ }
2956
+ if (index < 0) {
2957
+ index = parent.node.body.findIndex((s) => s === decl);
2958
+ if (index < 0) {
2959
+ throw new Error(`Failed to find variable declaration for ${variableDeclarationName(vdecl.id)}`);
2960
+ }
2961
+ }
2962
+ if (j > i + 1) {
2963
+ const tail = {
2964
+ ...decl,
2965
+ declarations: decl.declarations.slice(i + 1, j),
2966
+ };
2967
+ if (decl.loc && vdecl.loc) {
2968
+ tail.loc = { ...decl.loc, start: vdecl.loc.end };
2969
+ tail.start = vdecl.end;
2970
+ }
2971
+ rep.push(tail);
2972
+ }
2973
+ if (decl.loc && vdecl.loc) {
2974
+ decl.loc = { ...decl.loc, end: vdecl.loc.start };
2975
+ decl.end = vdecl.start;
2976
+ }
2977
+ decl.declarations.splice(i);
2978
+ parent.node.body.splice(index + 1, 0, ...rep);
2979
+ j = i;
2980
+ continue;
2981
+ }
2982
+ if (toRemove[name]) {
2983
+ j--;
2984
+ decl.declarations.splice(i, 1);
2985
+ if (i === j && decl.loc && vdecl.loc) {
2986
+ decl.loc = { ...decl.loc, end: vdecl.loc.start };
2987
+ decl.end = vdecl.start;
2988
+ }
2989
+ }
2990
+ else {
2991
+ delete vdecl.init;
2992
+ }
2993
+ }
2994
+ }
2995
+ });
2996
+ }
2997
+ }
2998
+ }
2999
+
2471
3000
  ;// CONCATENATED MODULE: ./src/visitor.ts
2472
3001
 
2473
3002
  function visitor_visitReferences(state, ast, name, defn, callback) {
@@ -2532,14 +3061,16 @@ function visitor_visitReferences(state, ast, name, defn, callback) {
2532
3061
  return checkResults(state.lookup(node), node);
2533
3062
  }
2534
3063
  break;
2535
- case "MemberExpression":
2536
- if (!node.computed && node.property.type === "Identifier") {
2537
- if (!name || node.property.name === name) {
3064
+ case "MemberExpression": {
3065
+ const property = (0,external_api_cjs_namespaceObject.isLookupCandidate)(node);
3066
+ if (property) {
3067
+ if (!name || property.name === name) {
2538
3068
  return checkResults(state.lookup(node), node) || ["object"];
2539
3069
  }
2540
3070
  return ["object"];
2541
3071
  }
2542
3072
  break;
3073
+ }
2543
3074
  case "MethodDefinition": {
2544
3075
  if (!state.inType) {
2545
3076
  throw new Error("Method definition outside of type!");
@@ -2548,7 +3079,6 @@ function visitor_visitReferences(state, ast, name, defn, callback) {
2548
3079
  node.params.forEach((param) => {
2549
3080
  if (param.type == "BinaryExpression") {
2550
3081
  state.traverse(param.right);
2551
- state.inType = true;
2552
3082
  }
2553
3083
  });
2554
3084
  }
@@ -2571,6 +3101,9 @@ function visitor_visitReferences(state, ast, name, defn, callback) {
2571
3101
 
2572
3102
 
2573
3103
 
3104
+
3105
+
3106
+
2574
3107
  function collectClassInfo(state) {
2575
3108
  const toybox = state.stack[0].decls["Toybox"][0];
2576
3109
  const lang = toybox.decls["Lang"][0];
@@ -2630,8 +3163,7 @@ function collectClassInfo(state) {
2630
3163
  c.decls &&
2631
3164
  Object.values(c.decls).forEach((funcs) => {
2632
3165
  funcs.forEach((f) => {
2633
- if (isStateNode(f) &&
2634
- f.type === "FunctionDeclaration" &&
3166
+ if (f.type === "FunctionDeclaration" &&
2635
3167
  hasProperty(cls.decls, f.name)) {
2636
3168
  f.node.hasOverride = true;
2637
3169
  }
@@ -2644,6 +3176,15 @@ function collectClassInfo(state) {
2644
3176
  state.allClasses.forEach((elm) => {
2645
3177
  if (elm.superClass)
2646
3178
  markOverrides(elm, elm.superClass);
3179
+ if (elm.hasInvoke && elm.decls) {
3180
+ Object.values(elm.decls).forEach((funcs) => {
3181
+ funcs.forEach((f) => {
3182
+ if (f.type === "FunctionDeclaration" && !f.isStatic) {
3183
+ markInvokeClassMethod(f);
3184
+ }
3185
+ });
3186
+ });
3187
+ }
2647
3188
  });
2648
3189
  }
2649
3190
  function getFileSources(fnMap) {
@@ -2683,7 +3224,7 @@ async function analyze(fnMap, barrelList, config) {
2683
3224
  const preState = {
2684
3225
  fnMap,
2685
3226
  config,
2686
- allFunctions: [],
3227
+ allFunctions: {},
2687
3228
  allClasses: [],
2688
3229
  shouldExclude(node) {
2689
3230
  if ("attrs" in node &&
@@ -2713,11 +3254,6 @@ async function analyze(fnMap, barrelList, config) {
2713
3254
  pre(node, state) {
2714
3255
  switch (node.type) {
2715
3256
  case "FunctionDeclaration":
2716
- if (markApi) {
2717
- node.body = null;
2718
- break;
2719
- }
2720
- // falls through
2721
3257
  case "ModuleDeclaration":
2722
3258
  case "ClassDeclaration": {
2723
3259
  const [scope] = state.stack.slice(-1);
@@ -2728,7 +3264,18 @@ async function analyze(fnMap, barrelList, config) {
2728
3264
  (scope.node.attrs &&
2729
3265
  scope.node.attrs.access &&
2730
3266
  scope.node.attrs.access.includes("static"));
2731
- state.allFunctions.push(scope);
3267
+ if (markApi) {
3268
+ node.body = null;
3269
+ scope.info = getApiFunctionInfo(scope);
3270
+ delete scope.stack;
3271
+ }
3272
+ const allFuncs = state.allFunctions;
3273
+ if (!hasProperty(allFuncs, scope.name)) {
3274
+ allFuncs[scope.name] = [scope];
3275
+ }
3276
+ else {
3277
+ allFuncs[scope.name].push(scope);
3278
+ }
2732
3279
  }
2733
3280
  else if (scope.type === "ClassDeclaration") {
2734
3281
  state.allClasses.push(scope);
@@ -2753,7 +3300,7 @@ async function analyze(fnMap, barrelList, config) {
2753
3300
  value.hasTests = hasTests;
2754
3301
  });
2755
3302
  delete state.shouldExclude;
2756
- delete state.post;
3303
+ delete state.pre;
2757
3304
  collectClassInfo(state);
2758
3305
  const diagnosticType = config?.checkInvalidSymbols !== "OFF"
2759
3306
  ? config?.checkInvalidSymbols || "WARNING"
@@ -2776,6 +3323,8 @@ async function analyze(fnMap, barrelList, config) {
2776
3323
  });
2777
3324
  });
2778
3325
  }
3326
+ state.exposed = state.nextExposed;
3327
+ state.nextExposed = {};
2779
3328
  return state;
2780
3329
  }
2781
3330
  function compareLiteralLike(a, b) {
@@ -2821,15 +3370,12 @@ function getLiteralNode(node) {
2821
3370
  if (node.argument.type != "Literal")
2822
3371
  return null;
2823
3372
  switch (node.operator) {
2824
- case "-":
2825
- if (typeof node.argument.value == "number") {
2826
- return {
2827
- ...node.argument,
2828
- value: -node.argument.value,
2829
- raw: "-" + node.argument.value,
2830
- enumType: node.enumType,
2831
- };
3373
+ case "-": {
3374
+ const [arg, type] = getNodeValue(node.argument);
3375
+ if (type === "Number" || type === "Long") {
3376
+ return replacementLiteral(arg, -arg.value, type);
2832
3377
  }
3378
+ }
2833
3379
  }
2834
3380
  }
2835
3381
  return null;
@@ -2848,29 +3394,29 @@ function getNodeValue(node) {
2848
3394
  if (node.type != "Literal") {
2849
3395
  return [null, null];
2850
3396
  }
2851
- let type = node.value === null ? "Null" : typeof node.value;
3397
+ if (node.value === null) {
3398
+ return [node, "Null"];
3399
+ }
3400
+ const type = typeof node.value;
2852
3401
  if (type === "number") {
2853
- const match = node.raw && LiteralIntegerRe.exec(node.raw);
3402
+ const match = prettier_plugin_monkeyc_namespaceObject.LiteralIntegerRe.exec(node.raw);
2854
3403
  if (match) {
2855
- type = match[2] == "l" ? "Long" : "Number";
2856
- }
2857
- else if (node.raw && node.raw.endsWith("d")) {
2858
- type = "Double";
2859
- }
2860
- else {
2861
- type = "Float";
3404
+ return match[2] === "l" || match[2] === "L"
3405
+ ? [node, "Long"]
3406
+ : [node, "Number"];
2862
3407
  }
3408
+ return [node, node.raw.endsWith("d") ? "Double" : "Float"];
2863
3409
  }
2864
- else if (type === "string") {
2865
- type = "String";
3410
+ if (type === "bigint") {
3411
+ return [node, "Long"];
2866
3412
  }
2867
- else if (type === "boolean") {
2868
- type = "Boolean";
3413
+ if (type === "string") {
3414
+ return [node, "String"];
2869
3415
  }
2870
- else {
2871
- type = "Unknown";
3416
+ if (type === "boolean") {
3417
+ return [node, "Boolean"];
2872
3418
  }
2873
- return [node, type];
3419
+ throw new Error(`Literal has unknown type '${type}'`);
2874
3420
  }
2875
3421
  function fullTypeName(state, tsp) {
2876
3422
  if (typeof tsp.name === "string") {
@@ -2915,6 +3461,46 @@ function isBooleanExpression(state, node) {
2915
3461
  }
2916
3462
  return false;
2917
3463
  }
3464
+ function replacementLiteral(arg, value, type) {
3465
+ if (typeof value === "boolean") {
3466
+ type = "Boolean";
3467
+ }
3468
+ else if (type === "Number") {
3469
+ value = Number(BigInt.asIntN(32, BigInt(value)));
3470
+ }
3471
+ else if (type === "Long") {
3472
+ value = BigInt.asIntN(64, BigInt(value));
3473
+ }
3474
+ return {
3475
+ ...arg,
3476
+ value,
3477
+ raw: value.toString() + (type === "Long" ? "l" : ""),
3478
+ };
3479
+ }
3480
+ const operators = {
3481
+ "+": (left, right) => left + right,
3482
+ "-": (left, right) => left - right,
3483
+ "*": (left, right) => left * right,
3484
+ "/": (left, right) => left / right,
3485
+ "%": (left, right) => left % right,
3486
+ "&": (left, right) => left & right,
3487
+ "|": (left, right) => left | right,
3488
+ "^": (left, right) => left ^ right,
3489
+ "<<": (left, right) => left << (right & 127n),
3490
+ ">>": (left, right) => left >> (right & 127n),
3491
+ "==": (left, right) =>
3492
+ // two string literals will compare unequal, becuase string
3493
+ // equality is object equality.
3494
+ typeof left === "string" ? false : left === right,
3495
+ "!=": (left, right) => typeof left === "string" ? true : left !== right,
3496
+ "<=": (left, right) => left <= right,
3497
+ ">=": (left, right) => left >= right,
3498
+ "<": (left, right) => left < right,
3499
+ ">": (left, right) => left > right,
3500
+ as: null,
3501
+ instanceof: null,
3502
+ has: null,
3503
+ };
2918
3504
  function optimizeNode(state, node) {
2919
3505
  switch (node.type) {
2920
3506
  case "UnaryExpression": {
@@ -2929,29 +3515,17 @@ function optimizeNode(state, node) {
2929
3515
  break;
2930
3516
  case "-":
2931
3517
  if (type === "Number" || type === "Long") {
2932
- return {
2933
- ...arg,
2934
- value: -arg.value,
2935
- raw: (-arg.value).toString() + (type === "Long" ? "l" : ""),
2936
- };
3518
+ return replacementLiteral(arg, -arg.value, type);
2937
3519
  }
2938
3520
  break;
2939
3521
  case "!":
2940
3522
  case "~":
2941
3523
  {
2942
- let value;
2943
3524
  if (type === "Number" || type === "Long") {
2944
- value = -arg.value - 1;
3525
+ return replacementLiteral(arg, ~BigInt(arg.value), type);
2945
3526
  }
2946
- else if (type === "Boolean" && node.operator == "!") {
2947
- value = !arg.value;
2948
- }
2949
- if (value !== undefined) {
2950
- return {
2951
- ...arg,
2952
- value,
2953
- raw: value.toString() + (type === "Long" ? "l" : ""),
2954
- };
3527
+ if (type === "Boolean" && node.operator == "!") {
3528
+ return replacementLiteral(arg, !arg.value, type);
2955
3529
  }
2956
3530
  }
2957
3531
  break;
@@ -2959,27 +3533,6 @@ function optimizeNode(state, node) {
2959
3533
  break;
2960
3534
  }
2961
3535
  case "BinaryExpression": {
2962
- const operators = {
2963
- "+": (left, right) => left + right,
2964
- "-": (left, right) => left - right,
2965
- "*": (left, right) => left * right,
2966
- "/": (left, right) => Math.trunc(left / right),
2967
- "%": (left, right) => left % right,
2968
- "&": (left, right, type) => type === "Number" ? left & right : null,
2969
- "|": (left, right, type) => type === "Number" ? left | right : null,
2970
- "^": (left, right, type) => type === "Number" ? left ^ right : null,
2971
- "<<": (left, right, type) => type === "Number" ? left << right : null,
2972
- ">>": (left, right, type) => type === "Number" ? left >> right : null,
2973
- "==": (left, right) => left == right,
2974
- "!=": (left, right) => left != right,
2975
- "<=": (left, right) => left <= right,
2976
- ">=": (left, right) => left >= right,
2977
- "<": (left, right) => left < right,
2978
- ">": (left, right) => left > right,
2979
- as: null,
2980
- instanceof: null,
2981
- has: null,
2982
- };
2983
3536
  const op = operators[node.operator];
2984
3537
  if (op) {
2985
3538
  const [left, left_type] = getNodeValue(node.left);
@@ -2987,23 +3540,22 @@ function optimizeNode(state, node) {
2987
3540
  if (!left || !right)
2988
3541
  break;
2989
3542
  let value = null;
2990
- if (left_type != right_type ||
2991
- (left_type != "Number" && left_type != "Long")) {
3543
+ let type;
3544
+ if ((left_type != "Number" && left_type != "Long") ||
3545
+ left_type != right_type) {
2992
3546
  if (node.operator !== "==" && node.operator !== "!=") {
2993
3547
  break;
2994
3548
  }
2995
3549
  value = operators[node.operator](left.value, right.value);
3550
+ type = "Boolean";
2996
3551
  }
2997
3552
  else {
2998
- value = op(left.value, right.value, left_type);
3553
+ type = left_type;
3554
+ value = op(BigInt(left.value), BigInt(right.value));
2999
3555
  }
3000
3556
  if (value === null)
3001
3557
  break;
3002
- return {
3003
- ...left,
3004
- value,
3005
- raw: value.toString() + (left_type === "Long" ? "l" : ""),
3006
- };
3558
+ return replacementLiteral(left, value, type);
3007
3559
  }
3008
3560
  break;
3009
3561
  }
@@ -3013,7 +3565,8 @@ function optimizeNode(state, node) {
3013
3565
  break;
3014
3566
  const falsy = left.value === false ||
3015
3567
  left.value === null ||
3016
- (left.value === 0 && (left_type === "Number" || left_type === "Long"));
3568
+ ((left_type === "Number" || left_type === "Long") &&
3569
+ (left.value === 0 || left.value === 0n));
3017
3570
  if (falsy === (node.operator === "&&")) {
3018
3571
  return left;
3019
3572
  }
@@ -3054,9 +3607,7 @@ function evaluateFunction(state, func, args) {
3054
3607
  const paramValues = args &&
3055
3608
  Object.fromEntries(func.params.map((p, i) => [variableDeclarationName(p), args[i]]));
3056
3609
  let ret = null;
3057
- const body = args
3058
- ? JSON.parse(JSON.stringify(func.body))
3059
- : func.body;
3610
+ const body = args ? cloneDeep(func.body) : func.body;
3060
3611
  try {
3061
3612
  traverseAst(body, (node) => {
3062
3613
  switch (node.type) {
@@ -3107,12 +3658,10 @@ function markFunctionCalled(state, func) {
3107
3658
  pushUnique(state.calledFunctions[func.id.name], func);
3108
3659
  }
3109
3660
  async function optimizeMonkeyC(fnMap, barrelList, config) {
3110
- const state = {
3111
- ...(await analyze(fnMap, barrelList, config)),
3112
- localsStack: [{}],
3113
- exposed: {},
3114
- calledFunctions: {},
3115
- };
3661
+ const state = (await analyze(fnMap, barrelList, config));
3662
+ state.localsStack = [{}];
3663
+ state.calledFunctions = {};
3664
+ state.usedByName = {};
3116
3665
  const replace = (node, old) => {
3117
3666
  if (node === false || node === null)
3118
3667
  return node;
@@ -3201,6 +3750,58 @@ async function optimizeMonkeyC(fnMap, barrelList, config) {
3201
3750
  f.type == "FunctionDeclaration" &&
3202
3751
  maybeCalled(f.node))) ||
3203
3752
  (sc.superClass && checkInherited(sc, name))));
3753
+ const renamer = (idnode) => {
3754
+ const ident = idnode.type === "Identifier" ? idnode : idnode.left;
3755
+ const locals = topLocals();
3756
+ const { map } = locals;
3757
+ if (map) {
3758
+ const declName = ident.name;
3759
+ const name = renameVariable(state, locals, declName);
3760
+ if (name) {
3761
+ const [, results] = state.lookupValue(ident);
3762
+ if (!results) {
3763
+ throw new Error(`Didn't find local ${declName} which needed renaming`);
3764
+ }
3765
+ if (results.length !== 1) {
3766
+ throw new Error(`Lookup of local ${declName} found more than one result`);
3767
+ }
3768
+ const parent = results[0].parent;
3769
+ if (!parent) {
3770
+ throw new Error(`No parent in lookup of local ${declName}`);
3771
+ }
3772
+ const decls = parent.decls;
3773
+ if (!decls || !hasProperty(decls, declName)) {
3774
+ throw new Error(`Missing decls in lookup of local ${declName}`);
3775
+ }
3776
+ if (hasProperty(decls, name)) {
3777
+ throw new Error(`While renaming ${declName} to ${name}, there was already a variable ${name}`);
3778
+ }
3779
+ if (decls[declName].length === 1) {
3780
+ decls[name] = decls[declName];
3781
+ delete decls[declName];
3782
+ }
3783
+ else {
3784
+ let i = decls[declName].length;
3785
+ while (i--) {
3786
+ const decl = decls[declName][i];
3787
+ if (decl === idnode ||
3788
+ (decl.type === "VariableDeclarator" && decl.node.id === idnode)) {
3789
+ decls[declName].splice(i, 1);
3790
+ decls[name] = [decl];
3791
+ break;
3792
+ }
3793
+ }
3794
+ if (i < 0) {
3795
+ throw new Error(`While renaming ${declName} to ${name}: Didn't find original declaration`);
3796
+ }
3797
+ }
3798
+ ident.name = name;
3799
+ }
3800
+ else {
3801
+ map[declName] = true;
3802
+ }
3803
+ }
3804
+ };
3204
3805
  state.pre = (node) => {
3205
3806
  switch (node.type) {
3206
3807
  case "ConditionalExpression":
@@ -3221,7 +3822,11 @@ async function optimizeMonkeyC(fnMap, barrelList, config) {
3221
3822
  result = !!value.value;
3222
3823
  }
3223
3824
  if (result !== null) {
3224
- node.test = { type: "Literal", value: result };
3825
+ node.test = {
3826
+ type: "Literal",
3827
+ value: result,
3828
+ raw: result.toString(),
3829
+ };
3225
3830
  if (node.type === "IfStatement" ||
3226
3831
  node.type === "ConditionalExpression") {
3227
3832
  return [result ? "consequent" : "alternate"];
@@ -3240,7 +3845,7 @@ async function optimizeMonkeyC(fnMap, barrelList, config) {
3240
3845
  return null;
3241
3846
  }
3242
3847
  case "EnumDeclaration":
3243
- return false;
3848
+ return [];
3244
3849
  case "ForStatement": {
3245
3850
  const map = topLocals().map;
3246
3851
  if (map) {
@@ -3249,43 +3854,13 @@ async function optimizeMonkeyC(fnMap, barrelList, config) {
3249
3854
  break;
3250
3855
  }
3251
3856
  case "VariableDeclarator": {
3252
- const locals = topLocals();
3253
- const { map } = locals;
3254
- if (map) {
3255
- const declName = variableDeclarationName(node.id);
3256
- const name = renameVariable(state, locals, declName);
3257
- if (name) {
3258
- if (node.id.type === "Identifier") {
3259
- node.id.name = name;
3260
- }
3261
- else {
3262
- node.id.left.name = name;
3263
- }
3264
- }
3265
- else {
3266
- map[declName] = true;
3267
- }
3268
- }
3857
+ renamer(node.id);
3269
3858
  return ["init"];
3270
3859
  }
3271
3860
  case "CatchClause":
3272
3861
  if (node.param) {
3273
3862
  state.localsStack.push({ node, map: { ...(topLocals().map || {}) } });
3274
- const locals = topLocals();
3275
- const map = locals.map;
3276
- const declName = variableDeclarationName(node.param);
3277
- const name = renameVariable(state, locals, declName);
3278
- if (name) {
3279
- if (node.param.type === "Identifier") {
3280
- node.param.name = name;
3281
- }
3282
- else {
3283
- node.param.left.name = name;
3284
- }
3285
- }
3286
- else {
3287
- map[declName] = true;
3288
- }
3863
+ renamer(node.param);
3289
3864
  return ["body"];
3290
3865
  }
3291
3866
  break;
@@ -3301,14 +3876,8 @@ async function optimizeMonkeyC(fnMap, barrelList, config) {
3301
3876
  break;
3302
3877
  case "UnaryExpression":
3303
3878
  if (node.operator == ":") {
3304
- // If we produce a Symbol, for a given name,
3305
- // its possible that someone uses that symbol
3306
- // indirectly, so we can't remove any enums or
3307
- // constants with that name (we can still replace
3308
- // uses of those constants though).
3309
- state.exposed[node.argument.name] = true;
3310
- // In any case, we can't replace *this* use of the
3311
- // symbol with its value...
3879
+ // node.argument is not a normal identifier.
3880
+ // don't visit it.
3312
3881
  return [];
3313
3882
  }
3314
3883
  break;
@@ -3320,29 +3889,73 @@ async function optimizeMonkeyC(fnMap, barrelList, config) {
3320
3889
  if (typeof name === "string") {
3321
3890
  node.name = name;
3322
3891
  }
3892
+ const [, results] = state.lookupValue(node);
3893
+ if (results) {
3894
+ if (results.length !== 1 || results[0].results.length !== 1) {
3895
+ throw new Error(`Local ${node.name} had multiple lookup results`);
3896
+ }
3897
+ const parent = results[0].parent;
3898
+ if (!parent) {
3899
+ throw new Error(`Local ${node.name} had no parent`);
3900
+ }
3901
+ const decl = results[0].results[0];
3902
+ if (parent.type === "FunctionDeclaration" ||
3903
+ decl.type !== "VariableDeclarator") {
3904
+ // we can't optimize away function or catch parameters
3905
+ return [];
3906
+ }
3907
+ if (parent.type !== "BlockStatement") {
3908
+ throw new Error(`Local ${node.name} was not declared at block scope(??)`);
3909
+ }
3910
+ decl.used = true;
3911
+ }
3323
3912
  }
3324
3913
  }
3325
3914
  if (hasProperty(state.index, node.name)) {
3326
3915
  if (!lookupAndReplace(node)) {
3327
- state.exposed[node.name] = true;
3916
+ state.usedByName[node.name] = true;
3328
3917
  }
3329
3918
  }
3330
3919
  return [];
3331
3920
  }
3332
- case "MemberExpression":
3333
- if (node.property.type === "Identifier" && !node.computed) {
3334
- if (hasProperty(state.index, node.property.name)) {
3921
+ case "MemberExpression": {
3922
+ const property = isLookupCandidate(node);
3923
+ if (property) {
3924
+ if (hasProperty(state.index, property.name)) {
3335
3925
  if (lookupAndReplace(node)) {
3336
3926
  return false;
3337
3927
  }
3338
3928
  else {
3339
- state.exposed[node.property.name] = true;
3929
+ state.usedByName[property.name] = true;
3340
3930
  }
3341
3931
  }
3342
3932
  // Don't optimize the property.
3343
3933
  return ["object"];
3344
3934
  }
3345
3935
  break;
3936
+ }
3937
+ case "AssignmentExpression":
3938
+ case "UpdateExpression": {
3939
+ const lhs = node.type === "AssignmentExpression" ? node.left : node.argument;
3940
+ if (lhs.type === "Identifier") {
3941
+ const map = topLocals().map;
3942
+ if (map) {
3943
+ if (hasProperty(map, lhs.name)) {
3944
+ const name = map[lhs.name];
3945
+ if (typeof name === "string") {
3946
+ lhs.name = name;
3947
+ }
3948
+ }
3949
+ }
3950
+ }
3951
+ else if (lhs.type === "MemberExpression") {
3952
+ state.traverse(lhs.object);
3953
+ if (lhs.computed) {
3954
+ state.traverse(lhs.property);
3955
+ }
3956
+ }
3957
+ return node.type === "AssignmentExpression" ? ["right"] : [];
3958
+ }
3346
3959
  case "BlockStatement": {
3347
3960
  const map = topLocals().map;
3348
3961
  if (map) {
@@ -3358,7 +3971,11 @@ async function optimizeMonkeyC(fnMap, barrelList, config) {
3358
3971
  node.params &&
3359
3972
  node.params.forEach((p) => (map[variableDeclarationName(p)] = true));
3360
3973
  state.localsStack.push({ node, map });
3361
- const [parent] = state.stack.slice(-2);
3974
+ const [parent, self] = state.stack.slice(-2);
3975
+ if (state.currentFunction) {
3976
+ throw new Error(`Nested functions: ${self.fullName} was activated during processing of ${state.currentFunction.fullName}`);
3977
+ }
3978
+ state.currentFunction = self;
3362
3979
  if (parent.type == "ClassDeclaration" && !maybeCalled(node)) {
3363
3980
  let used = false;
3364
3981
  if (node.id.name == "initialize") {
@@ -3385,10 +4002,23 @@ async function optimizeMonkeyC(fnMap, barrelList, config) {
3385
4002
  return replace(opt, node);
3386
4003
  }
3387
4004
  switch (node.type) {
4005
+ case "FunctionDeclaration":
4006
+ if (!state.currentFunction) {
4007
+ throw new Error(`Finished function ${state.stack.slice(-1)[0].fullName}, but it was not marked current`);
4008
+ }
4009
+ state.currentFunction.info = state.currentFunction.next_info;
4010
+ delete state.currentFunction.next_info;
4011
+ delete state.currentFunction;
4012
+ break;
3388
4013
  case "BlockStatement":
3389
4014
  if (node.body.length === 1 && node.body[0].type === "BlockStatement") {
3390
4015
  node.body.splice(0, 1, ...node.body[0].body);
3391
4016
  }
4017
+ // fall through
4018
+ case "ForStatement":
4019
+ if (locals.map) {
4020
+ cleanupUnusedVars(state, node);
4021
+ }
3392
4022
  break;
3393
4023
  case "ConditionalExpression":
3394
4024
  case "IfStatement":
@@ -3421,17 +4051,20 @@ async function optimizeMonkeyC(fnMap, barrelList, config) {
3421
4051
  return replace(optimizeCall(state, node.argument, node), node.argument);
3422
4052
  }
3423
4053
  break;
4054
+ case "NewExpression":
4055
+ if (state.currentFunction) {
4056
+ const [, results] = state.lookup(node.callee);
4057
+ if (results) {
4058
+ recordCalledFuncs(state.currentFunction, findCalleesForNew(results));
4059
+ }
4060
+ else {
4061
+ recordModifiedUnknown(state.currentFunction);
4062
+ }
4063
+ }
4064
+ break;
3424
4065
  case "CallExpression": {
3425
4066
  return replace(optimizeCall(state, node, null), node);
3426
4067
  }
3427
- case "AssignmentExpression":
3428
- if (node.operator === "=" &&
3429
- node.left.type === "Identifier" &&
3430
- node.right.type === "Identifier" &&
3431
- node.left.name === node.right.name) {
3432
- return { type: "Literal", value: null, raw: "null" };
3433
- }
3434
- break;
3435
4068
  case "VariableDeclaration": {
3436
4069
  const locals = topLocals();
3437
4070
  if (locals.map &&
@@ -3444,10 +4077,10 @@ async function optimizeMonkeyC(fnMap, barrelList, config) {
3444
4077
  while (i < node.declarations.length) {
3445
4078
  const decl = declarations[i++];
3446
4079
  if (decl.init && decl.init.type === "CallExpression") {
3447
- const inlined = optimizeCall(state, decl.init, decl);
4080
+ const inlined = replace(optimizeCall(state, decl.init, decl), decl.init);
3448
4081
  if (!inlined)
3449
4082
  continue;
3450
- if (inlined.type != "BlockStatement") {
4083
+ if (Array.isArray(inlined) || inlined.type != "BlockStatement") {
3451
4084
  throw new Error("Unexpected inlined result");
3452
4085
  }
3453
4086
  if (!results) {
@@ -3505,6 +4138,32 @@ async function optimizeMonkeyC(fnMap, barrelList, config) {
3505
4138
  }
3506
4139
  }
3507
4140
  break;
4141
+ case "AssignmentExpression":
4142
+ if (node.operator === "=" &&
4143
+ node.left.type === "Identifier" &&
4144
+ node.right.type === "Identifier" &&
4145
+ node.left.name === node.right.name) {
4146
+ return { type: "Literal", value: null, raw: "null" };
4147
+ }
4148
+ // fall through;
4149
+ case "UpdateExpression":
4150
+ if (state.currentFunction) {
4151
+ const lhs = node.type === "AssignmentExpression" ? node.left : node.argument;
4152
+ const [, results] = state.lookup(lhs);
4153
+ if (results) {
4154
+ recordModifiedDecls(state.currentFunction, results);
4155
+ }
4156
+ else {
4157
+ const id = lhs.type === "Identifier" ? lhs : isLookupCandidate(lhs);
4158
+ if (id) {
4159
+ recordModifiedName(state.currentFunction, id.name);
4160
+ }
4161
+ else {
4162
+ recordModifiedUnknown(state.currentFunction);
4163
+ }
4164
+ }
4165
+ }
4166
+ break;
3508
4167
  }
3509
4168
  return null;
3510
4169
  };
@@ -3512,13 +4171,16 @@ async function optimizeMonkeyC(fnMap, barrelList, config) {
3512
4171
  collectNamespaces(f.ast, state);
3513
4172
  });
3514
4173
  state.calledFunctions = {};
3515
- state.exposed = {};
4174
+ state.exposed = state.nextExposed;
4175
+ state.nextExposed = {};
3516
4176
  Object.values(fnMap).forEach((f) => {
3517
4177
  collectNamespaces(f.ast, state);
3518
4178
  });
4179
+ state.exposed = state.nextExposed;
4180
+ state.nextExposed = {};
3519
4181
  delete state.pre;
3520
4182
  delete state.post;
3521
- state.allFunctions.forEach((fn) => sizeBasedPRE(state, fn));
4183
+ Object.values(state.allFunctions).forEach((fns) => fns.forEach((fn) => sizeBasedPRE(state, fn)));
3522
4184
  const cleanup = (node) => {
3523
4185
  switch (node.type) {
3524
4186
  case "ThisExpression":
@@ -3528,7 +4190,8 @@ async function optimizeMonkeyC(fnMap, barrelList, config) {
3528
4190
  if (node.members.every((m) => {
3529
4191
  const name = "name" in m ? m.name : m.id.name;
3530
4192
  return (hasProperty(state.index, name) &&
3531
- !hasProperty(state.exposed, name));
4193
+ !hasProperty(state.exposed, name) &&
4194
+ !hasProperty(state.usedByName, name));
3532
4195
  })) {
3533
4196
  node.enumType = [
3534
4197
  ...new Set(node.members.map((m) => {
@@ -3571,7 +4234,9 @@ async function optimizeMonkeyC(fnMap, barrelList, config) {
3571
4234
  case "VariableDeclaration": {
3572
4235
  node.declarations = node.declarations.filter((d) => {
3573
4236
  const name = variableDeclarationName(d.id);
3574
- return (!hasProperty(state.index, name) || hasProperty(state.exposed, name));
4237
+ return (!hasProperty(state.index, name) ||
4238
+ hasProperty(state.exposed, name) ||
4239
+ hasProperty(state.usedByName, name));
3575
4240
  });
3576
4241
  if (!node.declarations.length) {
3577
4242
  return false;
@@ -3604,7 +4269,7 @@ async function optimizeMonkeyC(fnMap, barrelList, config) {
3604
4269
  }
3605
4270
  return null;
3606
4271
  };
3607
- Object.values(fnMap).forEach((f) => {
4272
+ Object.entries(fnMap).forEach(([name, f]) => {
3608
4273
  traverseAst(f.ast, undefined, (node) => {
3609
4274
  const ret = cleanup(node);
3610
4275
  if (ret === false) {
@@ -3612,16 +4277,15 @@ async function optimizeMonkeyC(fnMap, barrelList, config) {
3612
4277
  }
3613
4278
  return ret;
3614
4279
  });
4280
+ if (state.config && state.config.checkBuildPragmas) {
4281
+ pragmaChecker(state, f.ast, state.diagnostics?.[name]);
4282
+ }
3615
4283
  });
3616
4284
  return state.diagnostics;
3617
4285
  }
3618
4286
  function optimizeCall(state, node, context) {
3619
4287
  const [name, results] = state.lookupNonlocal(node.callee);
3620
- const callees = results &&
3621
- results
3622
- .map((r) => r.results)
3623
- .flat()
3624
- .filter((c) => c.type === "FunctionDeclaration");
4288
+ const callees = results ? findCallees(results) : null;
3625
4289
  if (!callees || !callees.length) {
3626
4290
  const n = name ||
3627
4291
  ("name" in node.callee && node.callee.name) ||
@@ -3630,14 +4294,24 @@ function optimizeCall(state, node, context) {
3630
4294
  "name" in node.callee.property &&
3631
4295
  node.callee.property.name);
3632
4296
  if (n) {
3633
- state.exposed[n] = true;
4297
+ if (hasProperty(state.allFunctions, n)) {
4298
+ if (state.currentFunction) {
4299
+ recordCalledFuncs(state.currentFunction, state.allFunctions[n]);
4300
+ }
4301
+ state.allFunctions[n].forEach((fn) => markFunctionCalled(state, fn.node));
4302
+ }
3634
4303
  }
3635
- else {
3636
- // There are unnamed CallExpressions, such as new [size]
3637
- // So there's nothing to do here.
4304
+ else if (state.currentFunction) {
4305
+ // I don't think this can happen: foo[x](args)
4306
+ // doesn't parse, so you can't even do things like
4307
+ // $.Toybox.Lang[:format]("fmt", [])
4308
+ recordModifiedUnknown(state.currentFunction);
3638
4309
  }
3639
4310
  return null;
3640
4311
  }
4312
+ if (state.currentFunction) {
4313
+ recordCalledFuncs(state.currentFunction, callees);
4314
+ }
3641
4315
  if (callees.length == 1 && callees[0].type === "FunctionDeclaration") {
3642
4316
  const callee = callees[0].node;
3643
4317
  if (!context &&
@@ -3848,20 +4522,18 @@ function checkOne(state, ns, decls, node, isStatic) {
3848
4522
  return next ? (result ? result.concat(next) : next) : result;
3849
4523
  }, null);
3850
4524
  };
3851
- if (api_isStateNode(ns)) {
3852
- const ndecls = ns[decls];
3853
- if (ast_hasProperty(ndecls, node.name)) {
3854
- return ndecls[node.name];
3855
- }
3856
- switch (ns.type) {
3857
- case "ClassDeclaration":
3858
- if (!isStatic) {
3859
- return superChain(ns) || superChainScopes(ns) || false;
3860
- }
3861
- // fall through
3862
- case "ModuleDeclaration":
3863
- return lookupInContext(ns) || false;
3864
- }
4525
+ const ndecls = ns[decls];
4526
+ if (ast_hasProperty(ndecls, node.name)) {
4527
+ return ndecls[node.name];
4528
+ }
4529
+ switch (ns.type) {
4530
+ case "ClassDeclaration":
4531
+ if (!isStatic) {
4532
+ return superChain(ns) || superChainScopes(ns) || false;
4533
+ }
4534
+ // fall through
4535
+ case "ModuleDeclaration":
4536
+ return lookupInContext(ns) || false;
3865
4537
  }
3866
4538
  return null;
3867
4539
  }
@@ -3884,6 +4556,13 @@ function sameLookupDefinition(a, b) {
3884
4556
  function api_sameLookupResult(a, b) {
3885
4557
  return (0,external_util_cjs_namespaceObject.sameArrays)(a, b, sameLookupDefinition);
3886
4558
  }
4559
+ function api_isLookupCandidate(node) {
4560
+ return node.computed
4561
+ ? node.property.type === "UnaryExpression" &&
4562
+ node.property.operator === ":" &&
4563
+ node.property.argument
4564
+ : node.property.type === "Identifier" && node.property;
4565
+ }
3887
4566
  /**
3888
4567
  *
3889
4568
  * @param state - The ProgramState
@@ -3904,9 +4583,9 @@ function lookup(state, decls, node, name, maybeStack, nonlocal) {
3904
4583
  const stack = maybeStack || state.stack;
3905
4584
  switch (node.type) {
3906
4585
  case "MemberExpression": {
3907
- if (node.property.type != "Identifier" || node.computed)
4586
+ const property = api_isLookupCandidate(node);
4587
+ if (!property)
3908
4588
  break;
3909
- const property = node.property;
3910
4589
  let result;
3911
4590
  if (node.object.type === "ThisExpression") {
3912
4591
  [, result] = lookup(state, decls, node.property, name, stack, true);
@@ -3920,6 +4599,9 @@ function lookup(state, decls, node, name, maybeStack, nonlocal) {
3920
4599
  result = results.reduce((current, lookupDef) => {
3921
4600
  const items = lookupDef.results
3922
4601
  .map((module) => {
4602
+ if (!api_isStateNode(module)) {
4603
+ return null;
4604
+ }
3923
4605
  const res = checkOne(state, module, decls, property, false);
3924
4606
  return res ? { parent: module, results: res } : null;
3925
4607
  })
@@ -4004,6 +4686,8 @@ function lookup(state, decls, node, name, maybeStack, nonlocal) {
4004
4686
  }
4005
4687
  function api_collectNamespaces(ast, stateIn) {
4006
4688
  const state = (stateIn || {});
4689
+ if (!state.nextExposed)
4690
+ state.nextExposed = {};
4007
4691
  if (!state.index)
4008
4692
  state.index = {};
4009
4693
  if (!state.stack) {
@@ -4039,7 +4723,7 @@ function api_collectNamespaces(ast, stateIn) {
4039
4723
  state.stackClone = () => state.stack.map((elm) => elm.type === "ModuleDeclaration" || elm.type === "Program"
4040
4724
  ? { ...elm }
4041
4725
  : elm);
4042
- state.inType = false;
4726
+ state.inType = 0;
4043
4727
  state.traverse = (root) => ast_traverseAst(root, (node) => {
4044
4728
  try {
4045
4729
  if (state.shouldExclude && state.shouldExclude(node)) {
@@ -4047,6 +4731,13 @@ function api_collectNamespaces(ast, stateIn) {
4047
4731
  return [];
4048
4732
  }
4049
4733
  switch (node.type) {
4734
+ case "UnaryExpression":
4735
+ if (node.operator === ":" && !state.inType) {
4736
+ state.nextExposed[node.argument.name] = true;
4737
+ }
4738
+ break;
4739
+ case "AttributeList":
4740
+ return [];
4050
4741
  case "Program":
4051
4742
  if (state.stack.length != 1) {
4052
4743
  throw new Error("Unexpected stack length for Program node");
@@ -4055,7 +4746,7 @@ function api_collectNamespaces(ast, stateIn) {
4055
4746
  break;
4056
4747
  case "TypeSpecList":
4057
4748
  case "TypeSpecPart":
4058
- state.inType = true;
4749
+ state.inType++;
4059
4750
  break;
4060
4751
  case "ImportModule":
4061
4752
  case "Using": {
@@ -4101,6 +4792,16 @@ function api_collectNamespaces(ast, stateIn) {
4101
4792
  });
4102
4793
  }
4103
4794
  break;
4795
+ case "ForStatement":
4796
+ if (node.init && node.init.type === "VariableDeclaration") {
4797
+ state.stack.push({
4798
+ type: "BlockStatement",
4799
+ fullName: undefined,
4800
+ name: undefined,
4801
+ node: node,
4802
+ });
4803
+ }
4804
+ break;
4104
4805
  case "BlockStatement": {
4105
4806
  const [parent] = state.stack.slice(-1);
4106
4807
  if (parent.node === node ||
@@ -4167,15 +4868,17 @@ function api_collectNamespaces(ast, stateIn) {
4167
4868
  // an EnumDeclaration doesn't create a scope, but
4168
4869
  // it does create a type (if it has a name)
4169
4870
  case "EnumDeclaration": {
4170
- if (!node.id)
4871
+ if (!node.id) {
4872
+ state.inType++;
4171
4873
  break;
4874
+ }
4172
4875
  const [parent] = state.stack.slice(-1);
4173
4876
  const name = (parent.fullName + "." + node.id.name).replace(/^\$\./, "");
4174
4877
  node.body.members.forEach((m) => (("init" in m ? m.init : m).enumType = name));
4175
4878
  }
4176
4879
  // fall through
4177
4880
  case "TypedefDeclaration": {
4178
- state.inType = true;
4881
+ state.inType++;
4179
4882
  const name = node.id.name;
4180
4883
  const [parent] = state.stack.slice(-1);
4181
4884
  if (!parent.type_decls)
@@ -4228,13 +4931,21 @@ function api_collectNamespaces(ast, stateIn) {
4228
4931
  break;
4229
4932
  }
4230
4933
  case "EnumStringBody": {
4231
- state.inType = false;
4934
+ if (state.inType !== 1) {
4935
+ throw new Error(`Expected inType to be 1 at EnumStringBody. Got ${state.inType}.`);
4936
+ }
4937
+ state.inType--;
4232
4938
  const [parent] = state.stack.slice(-1);
4233
4939
  const values = parent.decls || (parent.decls = {});
4234
4940
  let prev = -1;
4235
4941
  node.members.forEach((m, i) => {
4236
4942
  if (m.type == "Identifier") {
4237
- prev += 1;
4943
+ if (typeof prev === "bigint") {
4944
+ prev += 1n;
4945
+ }
4946
+ else {
4947
+ prev += 1;
4948
+ }
4238
4949
  m = node.members[i] = {
4239
4950
  type: "EnumStringMember",
4240
4951
  loc: m.loc,
@@ -4244,7 +4955,7 @@ function api_collectNamespaces(ast, stateIn) {
4244
4955
  init: {
4245
4956
  type: "Literal",
4246
4957
  value: prev,
4247
- raw: prev.toString(),
4958
+ raw: prev.toString() + (typeof prev === "bigint" ? "l" : ""),
4248
4959
  enumType: m.enumType,
4249
4960
  loc: m.loc,
4250
4961
  start: m.start,
@@ -4296,22 +5007,22 @@ function api_collectNamespaces(ast, stateIn) {
4296
5007
  if (state.post)
4297
5008
  ret = state.post(node, state);
4298
5009
  switch (type) {
4299
- // Don't clear inType for TypeSpecPart, since they
4300
- // generally occur in TypeSpecLists. But do clear it for
4301
- // SizedArrayExpression, since thats the only place they
4302
- // happen on their own.
4303
- case "SizedArrayExpression":
5010
+ case "TypeSpecPart":
4304
5011
  case "TypeSpecList":
4305
5012
  case "TypedefDeclaration":
4306
5013
  case "EnumDeclaration":
4307
- state.inType = false;
5014
+ state.inType--;
4308
5015
  break;
4309
5016
  case "EnumStringBody":
4310
- state.inType = true;
5017
+ state.inType++;
4311
5018
  break;
4312
5019
  }
4313
5020
  const [parent] = state.stack.slice(-1);
4314
5021
  if (parent.node === node ||
5022
+ // The pre function might cause node.body to be skipped,
5023
+ // so we need to check here, just in case.
5024
+ // (this actually happens with prettier-extenison-monkeyc's
5025
+ // findItemsByRange)
4315
5026
  (node.type === "CatchClause" && parent.node === node.body)) {
4316
5027
  delete parent.usings;
4317
5028
  delete parent.imports;
@@ -4330,6 +5041,9 @@ function api_collectNamespaces(ast, stateIn) {
4330
5041
  }
4331
5042
  });
4332
5043
  state.traverse(ast);
5044
+ if (state.inType) {
5045
+ throw new Error(`inType was non-zero on exit: ${state.inType}`);
5046
+ }
4333
5047
  if (state.stack.length != 1) {
4334
5048
  throw new Error("Invalid AST!");
4335
5049
  }
@@ -4364,7 +5078,7 @@ function api_formatAst(node, monkeyCSource = null) {
4364
5078
  // json. The parser knows to just treat the last line of the input
4365
5079
  // as the ast itself, and the printers will find what they're
4366
5080
  // looking for in the source.
4367
- const source = (monkeyCSource || "") + "\n" + JSON.stringify(node);
5081
+ const source = (monkeyCSource || "") + "\n" + (0,prettier_plugin_monkeyc_namespaceObject.serializeMonkeyC)(node);
4368
5082
  return external_prettier_namespaceObject.format(source, {
4369
5083
  parser: "monkeyc-json",
4370
5084
  plugins: [(prettier_plugin_monkeyc_default())],
@@ -4451,6 +5165,36 @@ function findUsingForNode(state, stack, i, node, isType) {
4451
5165
  }
4452
5166
  return null;
4453
5167
  }
5168
+ const invokeInfo = {};
5169
+ const toyboxFnInfo = {};
5170
+ function api_getApiFunctionInfo(func) {
5171
+ if (func.fullName === "$.Toybox.Lang.Method.invoke" ||
5172
+ (func.node.params &&
5173
+ func.node.params.some((param) => param.type === "BinaryExpression" &&
5174
+ param.right.ts.some((tsp) => tsp.type === "TypeSpecPart" && tsp.callspec)))) {
5175
+ if (!invokeInfo.calledFuncs) {
5176
+ invokeInfo.modifiedDecls = new Set();
5177
+ invokeInfo.calledFuncs = new Set();
5178
+ invokeInfo.callsExposed = true;
5179
+ }
5180
+ if (func.name === "initialize") {
5181
+ const top = func.stack[func.stack.length - 1];
5182
+ if (top.type === "ClassDeclaration") {
5183
+ top.hasInvoke = true;
5184
+ }
5185
+ }
5186
+ return invokeInfo;
5187
+ }
5188
+ if (!toyboxFnInfo.calledFuncs) {
5189
+ toyboxFnInfo.modifiedDecls = new Set();
5190
+ toyboxFnInfo.calledFuncs = new Set();
5191
+ toyboxFnInfo.resolvedDecls = new Set();
5192
+ }
5193
+ return toyboxFnInfo;
5194
+ }
5195
+ function api_markInvokeClassMethod(func) {
5196
+ func.info = invokeInfo;
5197
+ }
4454
5198
 
4455
5199
  })();
4456
5200