@markw65/monkeyc-optimizer 1.0.30 → 1.0.33

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),
@@ -278,6 +281,7 @@ const promises_namespaceObject = require("fs/promises");
278
281
  ;// CONCATENATED MODULE: external "prettier"
279
282
  const external_prettier_namespaceObject = require("prettier");
280
283
  ;// CONCATENATED MODULE: ./src/ast.ts
284
+
281
285
  /*
282
286
  * This ensures that mctreeTypeInfo has every key of MCTreeTypeInfo,
283
287
  * and that the corresponding arrays contain every element of the
@@ -464,21 +468,188 @@ function ast_withLoc(node, start, end) {
464
468
  }
465
469
  return node;
466
470
  }
467
- function ast_withLocDeep(node, start, end) {
468
- node = ast_withLoc({ ...node }, start, end);
471
+ function ast_withLocDeep(node, start, end, inplace) {
472
+ node = ast_withLoc(inplace ? node : { ...node }, start, end);
469
473
  for (const key of mctreeTypeInfo[node.type].keys) {
470
474
  const value = node[key];
471
475
  if (!value)
472
476
  continue;
473
- const fix = (v) => isMCTreeNode(v) ? ast_withLocDeep(v, start, end) : v;
477
+ const fix = (v) => isMCTreeNode(v) ? ast_withLocDeep(v, start, end, inplace) : v;
474
478
  const repl = Array.isArray(value) ? value.map(fix) : fix(value);
475
- node[key] = repl;
479
+ inplace || (node[key] = repl);
476
480
  }
477
481
  return node;
478
482
  }
483
+ function ast_cloneDeep(node) {
484
+ return ast_withLocDeep(node, null);
485
+ }
486
+ function ast_getNodeValue(node) {
487
+ if (node.type == "BinaryExpression" &&
488
+ node.operator == "as" &&
489
+ node.right.type == "TypeSpecList" &&
490
+ node.right.ts.length == 1 &&
491
+ typeof node.right.ts[0] == "string") {
492
+ // this is a cast we inserted to retain the type of an enum
493
+ // any arithmetic on it will revert to "Number", or "Long",
494
+ // so just ignore it.
495
+ return ast_getNodeValue(node.left);
496
+ }
497
+ if (node.type != "Literal") {
498
+ return [null, null];
499
+ }
500
+ if (node.value === null) {
501
+ return [node, "Null"];
502
+ }
503
+ const type = typeof node.value;
504
+ if (type === "number") {
505
+ const match = prettier_plugin_monkeyc_namespaceObject.LiteralIntegerRe.exec(node.raw);
506
+ if (match) {
507
+ return match[2] === "l" || match[2] === "L"
508
+ ? [node, "Long"]
509
+ : [node, "Number"];
510
+ }
511
+ return [node, node.raw.endsWith("d") ? "Double" : "Float"];
512
+ }
513
+ if (type === "bigint") {
514
+ return [node, "Long"];
515
+ }
516
+ if (type === "string") {
517
+ return [node, "String"];
518
+ }
519
+ if (type === "boolean") {
520
+ return [node, "Boolean"];
521
+ }
522
+ throw new Error(`Literal has unknown type '${type}'`);
523
+ }
479
524
 
480
525
  ;// CONCATENATED MODULE: external "./api.cjs"
481
526
  const external_api_cjs_namespaceObject = require("./api.cjs");
527
+ ;// CONCATENATED MODULE: ./src/function-info.ts
528
+
529
+ function function_info_cloneSet(ae) {
530
+ return new Set(ae);
531
+ }
532
+ function function_info_mergeSet(a, b) {
533
+ b.forEach((event) => a.add(event));
534
+ }
535
+ function recordModifiedDecl(func, decl) {
536
+ if (!func.next_info) {
537
+ func.next_info = { modifiedDecls: new Set(), calledFuncs: new Set() };
538
+ }
539
+ func.next_info.modifiedDecls.add(decl);
540
+ return null;
541
+ }
542
+ function function_info_recordModifiedDecls(func, lookupDefs) {
543
+ lookupDefs.forEach((lookupDef) => lookupDef.results.forEach((result) => {
544
+ if (result.type == "VariableDeclarator" && result.node.kind === "var") {
545
+ recordModifiedDecl(func, result);
546
+ }
547
+ }));
548
+ }
549
+ function function_info_recordModifiedName(func, name) {
550
+ if (!func.next_info) {
551
+ func.next_info = { modifiedDecls: new Set(), calledFuncs: new Set() };
552
+ }
553
+ if (!func.next_info.modifiedNames) {
554
+ func.next_info.modifiedNames = new Set();
555
+ }
556
+ func.next_info.modifiedNames.add(name);
557
+ }
558
+ function function_info_recordModifiedUnknown(func) {
559
+ if (!func.next_info) {
560
+ func.next_info = { modifiedDecls: new Set(), calledFuncs: new Set() };
561
+ }
562
+ func.next_info.modifiedUnknown = true;
563
+ }
564
+ function recordCalledFunc(func, callee) {
565
+ if (!func.next_info) {
566
+ func.next_info = { modifiedDecls: new Set(), calledFuncs: new Set() };
567
+ }
568
+ func.next_info.calledFuncs.add(callee);
569
+ return null;
570
+ }
571
+ function function_info_recordCalledFuncs(func, callees) {
572
+ callees.forEach((callee) => {
573
+ recordCalledFunc(func, callee);
574
+ });
575
+ }
576
+ function function_info_functionMayModify(state, func, decl) {
577
+ const info = func.info;
578
+ if (!info || info.modifiedUnknown)
579
+ return true;
580
+ if (info.resolvedDecls) {
581
+ return info.resolvedDecls.has(decl);
582
+ }
583
+ if (info.modifiedNames?.has(decl.name))
584
+ return true;
585
+ if (info.modifiedDecls.has(decl))
586
+ return true;
587
+ const visited = new Set();
588
+ const resolved = new Set();
589
+ const resolveDecls = (f) => {
590
+ if (visited.has(f))
591
+ return true;
592
+ if (!f.info)
593
+ return false;
594
+ if (f.info.modifiedUnknown) {
595
+ info.modifiedUnknown = true;
596
+ return false;
597
+ }
598
+ if (f.info.modifiedNames) {
599
+ if (info.modifiedNames) {
600
+ function_info_mergeSet(info.modifiedNames, f.info.modifiedNames);
601
+ }
602
+ else {
603
+ info.modifiedNames = function_info_cloneSet(f.info.modifiedNames);
604
+ }
605
+ }
606
+ function_info_mergeSet(resolved, f.info.modifiedDecls);
607
+ visited.add(f);
608
+ const q = true;
609
+ if (q &&
610
+ f.info.callsExposed &&
611
+ state.exposed &&
612
+ !Object.keys(state.exposed).every((key) => !state.allFunctions[key] ||
613
+ state.allFunctions[key].every(resolveDecls))) {
614
+ return false;
615
+ }
616
+ return Array.from(f.info.calledFuncs).every(resolveDecls);
617
+ };
618
+ if (resolveDecls(func)) {
619
+ info.resolvedDecls = resolved;
620
+ return resolved.has(decl);
621
+ }
622
+ return true;
623
+ }
624
+ function function_info_findCallees(lookupDefs) {
625
+ const decls = lookupDefs.reduce((decls, r) => (decls ? decls.concat(r.results) : r.results), null);
626
+ return (decls &&
627
+ decls.filter((decl) => decl ? decl.type === "FunctionDeclaration" : false));
628
+ }
629
+ function function_info_findCalleesForNew(lookupDefs) {
630
+ const initializer = (decl) => {
631
+ if (hasProperty(decl.decls, "initialize")) {
632
+ return decl.decls["initialize"];
633
+ }
634
+ if (decl.superClass && decl.superClass !== true) {
635
+ return decl.superClass.reduce((cur, cls) => {
636
+ const init = initializer(cls);
637
+ if (init) {
638
+ if (!cur)
639
+ return init;
640
+ return cur.concat(init);
641
+ }
642
+ return cur;
643
+ }, null);
644
+ }
645
+ return null;
646
+ };
647
+ return lookupDefs.flatMap((r) => r.results
648
+ .filter((decl) => decl.type === "ClassDeclaration")
649
+ .flatMap(initializer)
650
+ .filter((decl) => decl ? decl.type === "FunctionDeclaration" : false));
651
+ }
652
+
482
653
  ;// CONCATENATED MODULE: ./src/variable-renamer.ts
483
654
 
484
655
 
@@ -539,6 +710,7 @@ function variable_renamer_renameVariable(state, locals, declName) {
539
710
 
540
711
 
541
712
 
713
+
542
714
  function getArgSafety(state, func, args, requireAll) {
543
715
  // determine whether decl might be changed by a function call
544
716
  // or assignment during the evaluation of FunctionStateNode.
@@ -571,8 +743,9 @@ function getArgSafety(state, func, args, requireAll) {
571
743
  }
572
744
  };
573
745
  const safeArgs = [];
746
+ const argDecls = [];
574
747
  let allSafe = true;
575
- if (!args.every((arg) => {
748
+ if (!args.every((arg, i) => {
576
749
  switch (arg.type) {
577
750
  case "Literal":
578
751
  safeArgs.push(true);
@@ -586,13 +759,17 @@ function getArgSafety(state, func, args, requireAll) {
586
759
  safeArgs.push(null);
587
760
  return !requireAll;
588
761
  }
589
- const safety = getSafety(results[0].results[0]);
762
+ const decl = results[0].results[0];
763
+ const safety = getSafety(decl);
590
764
  safeArgs.push(safety);
591
765
  if (!safety) {
592
766
  allSafe = false;
593
767
  if (safety === null) {
594
768
  return !requireAll;
595
769
  }
770
+ else if (decl.type === "VariableDeclarator") {
771
+ argDecls[i] = decl;
772
+ }
596
773
  }
597
774
  return true;
598
775
  }
@@ -605,34 +782,91 @@ function getArgSafety(state, func, args, requireAll) {
605
782
  }
606
783
  if (allSafe && requireAll)
607
784
  return true;
608
- let callSeen = false;
785
+ const callsSeen = new Set();
786
+ const modifiedDecls = new Set();
787
+ let modifiedUnknown = false;
609
788
  const params = Object.fromEntries(func.node.params.map((param, i) => [variableDeclarationName(param), i]));
610
789
  // look for uses of "unsafe" args that occur after a call.
611
790
  // use post to do the checking, because arguments are evaluated
612
791
  // prior to the call, so eg "return f(x.y);" is fine, but
613
792
  // "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;
793
+ const { pre, post, stack } = state;
794
+ try {
795
+ delete state.pre;
796
+ state.post = (node) => {
797
+ switch (node.type) {
798
+ case "AssignmentExpression":
799
+ case "UpdateExpression": {
800
+ const v = node.type == "UpdateExpression" ? node.argument : node.left;
801
+ if (v.type === "Identifier" && hasProperty(params, v.name)) {
802
+ // If a parameter is modified, we can't just substitute the
803
+ // argument wherever the parameter is used.
804
+ safeArgs[params[v.name]] = null;
805
+ break;
806
+ }
807
+ if (modifiedUnknown)
808
+ break;
809
+ const [, results] = state.lookup(v);
810
+ if (results) {
811
+ results.forEach((r) => r.results.forEach((decl) => decl.type === "VariableDeclarator" && modifiedDecls.add(decl)));
812
+ }
813
+ else {
814
+ modifiedUnknown = true;
815
+ }
816
+ break;
621
817
  }
818
+ case "CallExpression":
819
+ case "NewExpression":
820
+ if (!modifiedUnknown) {
821
+ const [, results] = state.lookup(node.callee, null,
822
+ // calls are looked up as non-locals, but new is not
823
+ node.type === "CallExpression" ? func.stack : state.stack);
824
+ if (!results) {
825
+ const callee_name = node.callee.type === "Identifier"
826
+ ? node.callee
827
+ : node.callee.type === "MemberExpression"
828
+ ? isLookupCandidate(node.callee)
829
+ : null;
830
+ if (callee_name) {
831
+ const callees = state.allFunctions[callee_name.name];
832
+ if (callees) {
833
+ callees.forEach((callee) => callsSeen.add(callee));
834
+ }
835
+ }
836
+ else {
837
+ modifiedUnknown = true;
838
+ }
839
+ }
840
+ else {
841
+ const callees = node.type === "CallExpression"
842
+ ? findCallees(results)
843
+ : findCalleesForNew(results);
844
+ if (callees) {
845
+ callees.forEach((callee) => callsSeen.add(callee));
846
+ }
847
+ }
848
+ }
849
+ break;
850
+ case "Identifier":
851
+ if (hasProperty(params, node.name) &&
852
+ !safeArgs[params[node.name]] &&
853
+ (modifiedUnknown ||
854
+ !argDecls[params[node.name]] ||
855
+ modifiedDecls.has(argDecls[params[node.name]]) ||
856
+ Array.from(callsSeen).some((callee) => functionMayModify(state, callee, argDecls[params[node.name]])))) {
857
+ safeArgs[params[node.name]] = null;
858
+ }
622
859
  }
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
- });
860
+ return null;
861
+ };
862
+ state.stack = func.stack;
863
+ state.traverse(func.node.body);
864
+ }
865
+ finally {
866
+ state.pre = pre;
867
+ state.post = post;
868
+ state.stack = stack;
869
+ }
636
870
  return safeArgs;
637
871
  }
638
872
  function canInline(state, func, args) {
@@ -737,9 +971,6 @@ function processInlineBody(state, func, call, root, params) {
737
971
  state.pre = (node) => {
738
972
  if (failed)
739
973
  return [];
740
- node.start = call.start;
741
- node.end = call.end;
742
- node.loc = call.loc;
743
974
  if (replacements.has(node))
744
975
  return false;
745
976
  const result = pre(node, state);
@@ -754,6 +985,7 @@ function processInlineBody(state, func, call, root, params) {
754
985
  if (params[paramName] >= 0)
755
986
  return null;
756
987
  const name = renameVariable(state, locals, paramName) || paramName;
988
+ locals.map[name] = true;
757
989
  return {
758
990
  type: "VariableDeclarator",
759
991
  id: { type: "Identifier", name },
@@ -772,31 +1004,49 @@ function processInlineBody(state, func, call, root, params) {
772
1004
  }
773
1005
  return result;
774
1006
  };
1007
+ const fixId = (node) => {
1008
+ if (state.inType)
1009
+ return null;
1010
+ if (hasProperty(params, node.name)) {
1011
+ const ix = params[node.name];
1012
+ if (ix >= 0) {
1013
+ const replacement = { ...call.arguments[ix] };
1014
+ replacements.add(replacement);
1015
+ return replacement;
1016
+ }
1017
+ return null;
1018
+ }
1019
+ const replacement = fixNodeScope(state, node, func.stack);
1020
+ if (!replacement) {
1021
+ failed = true;
1022
+ inlineDiagnostic(state, func, call, `Failed to resolve '${node.name}'`);
1023
+ }
1024
+ return replacement;
1025
+ };
775
1026
  state.post = (node) => {
776
1027
  if (failed)
777
1028
  return post(node, state);
778
1029
  let replacement = null;
779
1030
  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;
1031
+ case "AssignmentExpression":
1032
+ if (node.left.type === "Identifier") {
1033
+ const rep = fixId(node.left);
1034
+ if (rep) {
1035
+ node.left = rep;
789
1036
  }
790
- break;
791
1037
  }
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);
1038
+ break;
1039
+ case "UpdateExpression":
1040
+ if (node.argument.type === "Identifier") {
1041
+ const rep = fixId(node.argument);
1042
+ if (rep) {
1043
+ node.argument = rep;
1044
+ }
797
1045
  }
798
1046
  break;
799
- }
1047
+ case "Identifier":
1048
+ replacement = fixId(node);
1049
+ break;
800
1050
  }
801
1051
  const ret = post(replacement || node, state);
802
1052
  return ret === false || ret ? ret : replacement;
@@ -965,7 +1215,7 @@ function inlineWithArgs(state, func, call, context) {
965
1215
  }
966
1216
  }
967
1217
  }
968
- const body = JSON.parse(JSON.stringify(func.node.body));
1218
+ const body = cloneDeep(func.node.body);
969
1219
  const safeArgs = getArgSafety(state, func, call.arguments, false);
970
1220
  const params = Object.fromEntries(func.node.params.map((param, i) => {
971
1221
  const argnum = safeArgs === true || (safeArgs !== false && safeArgs[i] !== null)
@@ -1014,18 +1264,19 @@ function inlineWithArgs(state, func, call, context) {
1014
1264
  --block.body.length;
1015
1265
  }
1016
1266
  }
1267
+ withLocDeep(body, context, context, true);
1017
1268
  return body;
1018
1269
  }
1019
1270
  function inliner_inlineFunction(state, func, call, context) {
1020
1271
  if (context) {
1021
1272
  return inlineWithArgs(state, func, call, context);
1022
1273
  }
1023
- const retArg = JSON.parse(JSON.stringify(func.node.body.body[0].argument));
1274
+ const retArg = cloneDeep(func.node.body.body[0].argument);
1024
1275
  const params = Object.fromEntries(func.node.params.map((param, i) => [variableDeclarationName(param), i]));
1025
1276
  const map = fixupLocalsMap(state);
1026
1277
  const ret = processInlineBody(state, func, call, retArg, params);
1027
1278
  state.localsStack[state.localsStack.length - 1].map = map;
1028
- return ret;
1279
+ return ret && withLocDeep(ret, call, call, true);
1029
1280
  }
1030
1281
  function applyTypeIfNeeded(node) {
1031
1282
  if ("enumType" in node && node.enumType) {
@@ -1130,6 +1381,121 @@ function fixNodeScope(state, lookupNode, nodeStack) {
1130
1381
  return null;
1131
1382
  }
1132
1383
 
1384
+ ;// CONCATENATED MODULE: ./src/pragma-checker.ts
1385
+
1386
+
1387
+
1388
+ function pragma_checker_pragmaChecker(state, ast, diagnostics) {
1389
+ const comments = ast.comments;
1390
+ if (!comments)
1391
+ return;
1392
+ diagnostics = diagnostics
1393
+ ?.slice()
1394
+ .sort((d1, d2) => d1.loc.start < d2.loc.start ? -1 : d1.loc.start == d2.loc.start ? 0 : 1);
1395
+ let diagIndex = 0;
1396
+ let index = -1;
1397
+ let comment;
1398
+ let matchers;
1399
+ const next = () => {
1400
+ while (++index < comments.length) {
1401
+ comment = comments[index];
1402
+ let match = comment.value.match(/^\s*@(match|expect)\s+(.+)/);
1403
+ if (!match)
1404
+ continue;
1405
+ const kind = match[1];
1406
+ let str = match[2];
1407
+ matchers = [];
1408
+ while ((match = str.match(/^([/%&#@"])(.+?(?<!\\)(?:\\{2})*)\1(\s+|$)/))) {
1409
+ matchers.push({ kind, quote: match[1], needle: match[2] });
1410
+ str = str.substring(match[0].length);
1411
+ if (!str.length)
1412
+ break;
1413
+ }
1414
+ if (!str.length)
1415
+ break;
1416
+ if (!matchers.length) {
1417
+ match = str.match(/^(\S+)\s+$/);
1418
+ if (match) {
1419
+ matchers.push({ kind, quote: '"', needle: match[1] });
1420
+ break;
1421
+ }
1422
+ }
1423
+ diagnostic(state, comment.loc, `Build pragma '${comment.value}' is invalid`, "ERROR");
1424
+ }
1425
+ };
1426
+ const matcher = (quote, needle, haystack) => {
1427
+ if (quote == '"') {
1428
+ return haystack.includes(needle);
1429
+ }
1430
+ const re = new RegExp(needle.replace(/@(\d+)/g, "(pre_)?$1(_\\d+)?"));
1431
+ return re.test(haystack);
1432
+ };
1433
+ next();
1434
+ traverseAst(ast, (node) => {
1435
+ if (index >= comments.length)
1436
+ return false;
1437
+ if (node.start && node.start >= (comment.end || Infinity)) {
1438
+ const { kind, quote, needle } = matchers.shift();
1439
+ if (kind === "match") {
1440
+ const haystack = formatAst(node).replace(/([\r\n]|\s)+/g, " ");
1441
+ if (!matcher(quote, needle, haystack)) {
1442
+ matcher(quote, needle, haystack);
1443
+ diagnostic(state, comment.loc, `Didn't find '${needle}' in '${haystack}'`, "ERROR");
1444
+ }
1445
+ }
1446
+ else if (kind === "expect") {
1447
+ const locCmp = (a, b) => {
1448
+ if (!b)
1449
+ return -1;
1450
+ if (a.start.line < b.start.line)
1451
+ return -1;
1452
+ if (a.start.line === b.start.line &&
1453
+ a.start.column < b.start.column) {
1454
+ return -1;
1455
+ }
1456
+ if (a.end.line > b.end.line)
1457
+ return 1;
1458
+ if (a.end.line === b.end.line && a.end.column >= b.end.column) {
1459
+ return 1;
1460
+ }
1461
+ return 0;
1462
+ };
1463
+ let found = false;
1464
+ if (diagnostics) {
1465
+ while (true) {
1466
+ if (diagIndex >= diagnostics.length) {
1467
+ diagnostics = null;
1468
+ break;
1469
+ }
1470
+ const diag = diagnostics[diagIndex];
1471
+ const cmp = locCmp(diag.loc, node.loc);
1472
+ if (cmp > 0) {
1473
+ break;
1474
+ }
1475
+ diagIndex++;
1476
+ if (cmp < 0)
1477
+ continue;
1478
+ if (matcher(quote, needle, diag.message)) {
1479
+ found = true;
1480
+ diag.type = "INFO";
1481
+ }
1482
+ }
1483
+ }
1484
+ if (!found) {
1485
+ diagnostic(state, comment.loc, `Missing error message '${needle}`, "ERROR");
1486
+ }
1487
+ }
1488
+ if (matchers.length) {
1489
+ // if we're checking a series of nodes, we need
1490
+ // to skip over this one.
1491
+ return false;
1492
+ }
1493
+ next();
1494
+ }
1495
+ return null;
1496
+ });
1497
+ }
1498
+
1133
1499
  ;// CONCATENATED MODULE: external "./util.cjs"
1134
1500
  const external_util_cjs_namespaceObject = require("./util.cjs");
1135
1501
  ;// CONCATENATED MODULE: ./src/control-flow.ts
@@ -1652,6 +2018,7 @@ var priorityqueuejs = __webpack_require__(2789);
1652
2018
 
1653
2019
 
1654
2020
 
2021
+
1655
2022
  /**
1656
2023
  * This implements a pseudo Partial Redundancy Elimination
1657
2024
  * pass. It isn't quite like traditional PRE because we're
@@ -1721,7 +2088,7 @@ function pre_sizeBasedPRE(state, func) {
1721
2088
  return;
1722
2089
  }
1723
2090
  const { graph: head, identifiers } = buildPREGraph(state, func);
1724
- const candidates = computeAttributes(head);
2091
+ const candidates = computeAttributes(state, head);
1725
2092
  if (candidates) {
1726
2093
  if (logging) {
1727
2094
  console.log(`Found ${candidates.size} candidates in ${func.fullName}`);
@@ -1741,8 +2108,10 @@ function pre_sizeBasedPRE(state, func) {
1741
2108
  let i = 0;
1742
2109
  do {
1743
2110
  name = `pre_${declName(decl)}${i ? "_" + i : ""}`;
1744
- if (!identifiers.has(name))
2111
+ if (!identifiers.has(name)) {
2112
+ identifiers.add(name);
1745
2113
  break;
2114
+ }
1746
2115
  i++;
1747
2116
  } while (true);
1748
2117
  declMap.set(decl, name);
@@ -1833,11 +2202,16 @@ function buildPREGraph(state, func) {
1833
2202
  case "ParenthesizedExpression":
1834
2203
  break;
1835
2204
  case "Literal":
1836
- if (!node.value && refCost(node) > LocalRefCost) {
1837
- let decl = literals.get(node.value);
2205
+ if (refCost(node) > LocalRefCost) {
2206
+ const result = getNodeValue(node);
2207
+ const key = result[1] +
2208
+ (result[0].value === null
2209
+ ? ""
2210
+ : "-" + result[0].value.toString());
2211
+ let decl = literals.get(key);
1838
2212
  if (!decl) {
1839
2213
  decl = node;
1840
- literals.set(node.value, decl);
2214
+ literals.set(key, decl);
1841
2215
  }
1842
2216
  return {
1843
2217
  type: "ref",
@@ -1917,10 +2291,18 @@ function buildPREGraph(state, func) {
1917
2291
  }
1918
2292
  break;
1919
2293
  }
1920
- case "NewExpression":
1921
- case "CallExpression":
2294
+ case "NewExpression": {
2295
+ const [, results] = state.lookup(node.callee);
2296
+ const callees = results ? findCalleesForNew(results) : null;
1922
2297
  liveDef(null, stmt);
1923
- return { type: "mod", node, mayThrow };
2298
+ return { type: "mod", node, mayThrow, callees };
2299
+ }
2300
+ case "CallExpression": {
2301
+ liveDef(null, stmt);
2302
+ const [, results] = state.lookup(node.callee);
2303
+ const callees = results ? findCallees(results) : null;
2304
+ return { type: "mod", node, mayThrow, callees };
2305
+ }
1924
2306
  default:
1925
2307
  if (!isExpression(node))
1926
2308
  break;
@@ -1936,12 +2318,6 @@ function buildPREGraph(state, func) {
1936
2318
  function anticipatedDecls() {
1937
2319
  return new Map();
1938
2320
  }
1939
- function cloneSet(ae) {
1940
- return new Set(ae);
1941
- }
1942
- function mergeSet(a, b) {
1943
- b.forEach((event) => a.add(event));
1944
- }
1945
2321
  function equalSet(a, b) {
1946
2322
  if (a.size != b.size)
1947
2323
  return false;
@@ -2020,6 +2396,7 @@ function refCost(node) {
2020
2396
  switch (typeof node.value) {
2021
2397
  case "string":
2022
2398
  return 5;
2399
+ case "bigint":
2023
2400
  case "number":
2024
2401
  return 5;
2025
2402
  case "boolean":
@@ -2081,7 +2458,7 @@ function candidateCost(candState) {
2081
2458
  cost += defCost(candState.node) * boundarySize;
2082
2459
  return cost;
2083
2460
  }
2084
- function computeAttributes(head) {
2461
+ function computeAttributes(state, head) {
2085
2462
  const order = getPostOrder(head);
2086
2463
  order.forEach((block, i) => {
2087
2464
  block.order = i;
@@ -2190,7 +2567,9 @@ function computeAttributes(head) {
2190
2567
  curState.forEach((candidates, decl) => {
2191
2568
  if (decl.type === "VariableDeclarator" &&
2192
2569
  decl.node.kind === "var" &&
2193
- candidates.live) {
2570
+ candidates.live &&
2571
+ (!event.callees ||
2572
+ event.callees.some((callee) => functionMayModify(state, callee, decl)))) {
2194
2573
  candidates.ant.add(getMod(event, decl, candidates.node));
2195
2574
  candidates.live = false;
2196
2575
  }
@@ -2379,20 +2758,26 @@ function applyReplacements(func, nodeMap, declMap) {
2379
2758
  stmtStack.pop();
2380
2759
  const events = nodeMap.get(node);
2381
2760
  if (events) {
2382
- if (events.length === 1) {
2383
- if (events[0].type === "ref") {
2761
+ const ret = events.reduce((ret, event) => {
2762
+ if (event.type === "ref") {
2763
+ if (ret) {
2764
+ throw new Error(`ref found when there was already a replacement for this node`);
2765
+ }
2384
2766
  if (node.type !== "Identifier" &&
2385
2767
  node.type !== "MemberExpression" &&
2386
2768
  node.type !== "Literal") {
2387
2769
  throw new Error(`Ref found, but wrong type of node: ${node.type}`);
2388
2770
  }
2389
- const name = declMap.get(events[0].decl);
2771
+ const name = declMap.get(event.decl);
2390
2772
  if (!name) {
2391
2773
  throw new Error(`No replacement found for "${formatAst(node)}"`);
2392
2774
  }
2393
2775
  return ident(name, node);
2394
2776
  }
2395
- else if (events[0].type === "def") {
2777
+ if (event.type === "def") {
2778
+ if (ret) {
2779
+ throw new Error(`def found when there was already a replacement for this node`);
2780
+ }
2396
2781
  if (node.type !== "AssignmentExpression" &&
2397
2782
  node.type !== "UpdateExpression") {
2398
2783
  throw new Error(`Def found, but wrong type of node: ${node.type}`);
@@ -2400,7 +2785,7 @@ function applyReplacements(func, nodeMap, declMap) {
2400
2785
  const target = node.type === "AssignmentExpression"
2401
2786
  ? node.left
2402
2787
  : node.argument;
2403
- const name = declMap.get(events[0].decl);
2788
+ const name = declMap.get(event.decl);
2404
2789
  if (!name) {
2405
2790
  throw new Error(`No replacement found for "${formatAst(target)}"`);
2406
2791
  }
@@ -2419,20 +2804,24 @@ function applyReplacements(func, nodeMap, declMap) {
2419
2804
  }
2420
2805
  return withLoc({ type: "SequenceExpression", expressions: [node, assign] }, node);
2421
2806
  }
2422
- }
2423
- events.forEach((event) => {
2424
- if (event.type !== "mod") {
2425
- throw new Error(`Unexpected ${event.type} found amongst multiple events`);
2426
- }
2427
- if (!event.decl) {
2428
- throw new Error(`Unexpected null decl on mod event`);
2807
+ if (event.type === "mod") {
2808
+ if (!event.decl) {
2809
+ throw new Error(`Unexpected null decl on mod event`);
2810
+ }
2811
+ let pending = pendingMap.get(stmt);
2812
+ if (!pending) {
2813
+ pendingMap.set(stmt, (pending = new Set()));
2814
+ }
2815
+ pending.add(event);
2429
2816
  }
2430
- let pending = pendingMap.get(stmt);
2431
- if (!pending) {
2432
- pendingMap.set(stmt, (pending = new Set()));
2817
+ else {
2818
+ throw new Error(`Unexpected ${event.type} found`);
2433
2819
  }
2434
- pending.add(event);
2435
- });
2820
+ return ret;
2821
+ }, null);
2822
+ if (ret) {
2823
+ return ret;
2824
+ }
2436
2825
  }
2437
2826
  const pending = pendingMap.get(node);
2438
2827
  if (node.type === "SequenceExpression") {
@@ -2506,6 +2895,148 @@ function applyReplacements(func, nodeMap, declMap) {
2506
2895
  });
2507
2896
  }
2508
2897
 
2898
+ ;// CONCATENATED MODULE: ./src/unused-exprs.ts
2899
+
2900
+
2901
+
2902
+ function unused_exprs_cleanupUnusedVars(state, node) {
2903
+ const [parent] = state.stack.slice(-1);
2904
+ if (parent.node !== node) {
2905
+ return;
2906
+ }
2907
+ if (parent.type != "BlockStatement") {
2908
+ throw new Error(`Unexpected parent type '${parent.type}' for local declaration`);
2909
+ }
2910
+ if (parent.decls) {
2911
+ let toRemove = null;
2912
+ Object.values(parent.decls).forEach((decls) => {
2913
+ if (decls.length === 1 &&
2914
+ decls[0].type === "VariableDeclarator" &&
2915
+ !decls[0].used) {
2916
+ if (!toRemove)
2917
+ toRemove = {};
2918
+ toRemove[decls[0].name] = decls[0];
2919
+ }
2920
+ });
2921
+ if (toRemove) {
2922
+ const varDeclarations = new Map();
2923
+ traverseAst(node, null, (node) => {
2924
+ switch (node.type) {
2925
+ case "VariableDeclaration": {
2926
+ node.declarations.forEach((decl, i) => {
2927
+ const name = variableDeclarationName(decl.id);
2928
+ if (hasProperty(toRemove, name)) {
2929
+ const indices = varDeclarations.get(node);
2930
+ if (indices) {
2931
+ indices.push(i);
2932
+ }
2933
+ else {
2934
+ varDeclarations.set(node, [i]);
2935
+ }
2936
+ }
2937
+ });
2938
+ break;
2939
+ }
2940
+ case "ExpressionStatement":
2941
+ if (node.expression.type === "AssignmentExpression") {
2942
+ if (node.expression.left.type === "Identifier" &&
2943
+ hasProperty(toRemove, node.expression.left.name)) {
2944
+ return unused(node.expression.right);
2945
+ }
2946
+ }
2947
+ else if (node.expression.type === "UpdateExpression" &&
2948
+ node.expression.argument.type === "Identifier" &&
2949
+ hasProperty(toRemove, node.expression.argument.name)) {
2950
+ return false;
2951
+ }
2952
+ break;
2953
+ case "SequenceExpression": {
2954
+ for (let i = node.expressions.length; i--;) {
2955
+ const expr = node.expressions[i];
2956
+ if (expr.type === "AssignmentExpression") {
2957
+ if (expr.left.type === "Identifier" &&
2958
+ hasProperty(toRemove, expr.left.name)) {
2959
+ const rep = unused(expr.right);
2960
+ if (!rep.length) {
2961
+ node.expressions.splice(i, 1);
2962
+ }
2963
+ else {
2964
+ // Sequence expressions can only be assignments
2965
+ // or update expressions. Even calls aren't allowed
2966
+ toRemove[expr.left.name] = null;
2967
+ expr.operator = "=";
2968
+ }
2969
+ }
2970
+ }
2971
+ else if (expr.type === "UpdateExpression" &&
2972
+ expr.argument.type === "Identifier" &&
2973
+ hasProperty(toRemove, expr.argument.name)) {
2974
+ node.expressions.splice(i, 1);
2975
+ }
2976
+ }
2977
+ break;
2978
+ }
2979
+ }
2980
+ return null;
2981
+ });
2982
+ varDeclarations.forEach((indices, decl) => {
2983
+ let index = -1;
2984
+ for (let ii = indices.length, j = decl.declarations.length; ii--;) {
2985
+ const i = indices[ii];
2986
+ const vdecl = decl.declarations[i];
2987
+ const name = variableDeclarationName(vdecl.id);
2988
+ if (hasProperty(toRemove, name)) {
2989
+ const rep = vdecl.init ? unused(vdecl.init) : [];
2990
+ if (rep.length) {
2991
+ if (parent.node.type === "ForStatement") {
2992
+ // declarations whose inits have side effects
2993
+ // can't be deleted from for statements.
2994
+ continue;
2995
+ }
2996
+ if (index < 0) {
2997
+ index = parent.node.body.findIndex((s) => s === decl);
2998
+ if (index < 0) {
2999
+ throw new Error(`Failed to find variable declaration for ${variableDeclarationName(vdecl.id)}`);
3000
+ }
3001
+ }
3002
+ if (j > i + 1) {
3003
+ const tail = {
3004
+ ...decl,
3005
+ declarations: decl.declarations.slice(i + 1, j),
3006
+ };
3007
+ if (decl.loc && vdecl.loc) {
3008
+ tail.loc = { ...decl.loc, start: vdecl.loc.end };
3009
+ tail.start = vdecl.end;
3010
+ }
3011
+ rep.push(tail);
3012
+ }
3013
+ if (decl.loc && vdecl.loc) {
3014
+ decl.loc = { ...decl.loc, end: vdecl.loc.start };
3015
+ decl.end = vdecl.start;
3016
+ }
3017
+ decl.declarations.splice(i);
3018
+ parent.node.body.splice(index + 1, 0, ...rep);
3019
+ j = i;
3020
+ continue;
3021
+ }
3022
+ if (toRemove[name]) {
3023
+ j--;
3024
+ decl.declarations.splice(i, 1);
3025
+ if (i === j && decl.loc && vdecl.loc) {
3026
+ decl.loc = { ...decl.loc, end: vdecl.loc.start };
3027
+ decl.end = vdecl.start;
3028
+ }
3029
+ }
3030
+ else {
3031
+ delete vdecl.init;
3032
+ }
3033
+ }
3034
+ }
3035
+ });
3036
+ }
3037
+ }
3038
+ }
3039
+
2509
3040
  ;// CONCATENATED MODULE: ./src/visitor.ts
2510
3041
 
2511
3042
  function visitor_visitReferences(state, ast, name, defn, callback) {
@@ -2570,14 +3101,16 @@ function visitor_visitReferences(state, ast, name, defn, callback) {
2570
3101
  return checkResults(state.lookup(node), node);
2571
3102
  }
2572
3103
  break;
2573
- case "MemberExpression":
2574
- if (!node.computed && node.property.type === "Identifier") {
2575
- if (!name || node.property.name === name) {
3104
+ case "MemberExpression": {
3105
+ const property = (0,external_api_cjs_namespaceObject.isLookupCandidate)(node);
3106
+ if (property) {
3107
+ if (!name || property.name === name) {
2576
3108
  return checkResults(state.lookup(node), node) || ["object"];
2577
3109
  }
2578
3110
  return ["object"];
2579
3111
  }
2580
3112
  break;
3113
+ }
2581
3114
  case "MethodDefinition": {
2582
3115
  if (!state.inType) {
2583
3116
  throw new Error("Method definition outside of type!");
@@ -2586,7 +3119,6 @@ function visitor_visitReferences(state, ast, name, defn, callback) {
2586
3119
  node.params.forEach((param) => {
2587
3120
  if (param.type == "BinaryExpression") {
2588
3121
  state.traverse(param.right);
2589
- state.inType = true;
2590
3122
  }
2591
3123
  });
2592
3124
  }
@@ -2609,6 +3141,9 @@ function visitor_visitReferences(state, ast, name, defn, callback) {
2609
3141
 
2610
3142
 
2611
3143
 
3144
+
3145
+
3146
+
2612
3147
  function collectClassInfo(state) {
2613
3148
  const toybox = state.stack[0].decls["Toybox"][0];
2614
3149
  const lang = toybox.decls["Lang"][0];
@@ -2668,8 +3203,7 @@ function collectClassInfo(state) {
2668
3203
  c.decls &&
2669
3204
  Object.values(c.decls).forEach((funcs) => {
2670
3205
  funcs.forEach((f) => {
2671
- if (isStateNode(f) &&
2672
- f.type === "FunctionDeclaration" &&
3206
+ if (f.type === "FunctionDeclaration" &&
2673
3207
  hasProperty(cls.decls, f.name)) {
2674
3208
  f.node.hasOverride = true;
2675
3209
  }
@@ -2682,6 +3216,15 @@ function collectClassInfo(state) {
2682
3216
  state.allClasses.forEach((elm) => {
2683
3217
  if (elm.superClass)
2684
3218
  markOverrides(elm, elm.superClass);
3219
+ if (elm.hasInvoke && elm.decls) {
3220
+ Object.values(elm.decls).forEach((funcs) => {
3221
+ funcs.forEach((f) => {
3222
+ if (f.type === "FunctionDeclaration" && !f.isStatic) {
3223
+ markInvokeClassMethod(f);
3224
+ }
3225
+ });
3226
+ });
3227
+ }
2685
3228
  });
2686
3229
  }
2687
3230
  function getFileSources(fnMap) {
@@ -2721,7 +3264,7 @@ async function analyze(fnMap, barrelList, config) {
2721
3264
  const preState = {
2722
3265
  fnMap,
2723
3266
  config,
2724
- allFunctions: [],
3267
+ allFunctions: {},
2725
3268
  allClasses: [],
2726
3269
  shouldExclude(node) {
2727
3270
  if ("attrs" in node &&
@@ -2751,11 +3294,6 @@ async function analyze(fnMap, barrelList, config) {
2751
3294
  pre(node, state) {
2752
3295
  switch (node.type) {
2753
3296
  case "FunctionDeclaration":
2754
- if (markApi) {
2755
- node.body = null;
2756
- break;
2757
- }
2758
- // falls through
2759
3297
  case "ModuleDeclaration":
2760
3298
  case "ClassDeclaration": {
2761
3299
  const [scope] = state.stack.slice(-1);
@@ -2766,7 +3304,18 @@ async function analyze(fnMap, barrelList, config) {
2766
3304
  (scope.node.attrs &&
2767
3305
  scope.node.attrs.access &&
2768
3306
  scope.node.attrs.access.includes("static"));
2769
- state.allFunctions.push(scope);
3307
+ if (markApi) {
3308
+ node.body = null;
3309
+ scope.info = getApiFunctionInfo(scope);
3310
+ delete scope.stack;
3311
+ }
3312
+ const allFuncs = state.allFunctions;
3313
+ if (!hasProperty(allFuncs, scope.name)) {
3314
+ allFuncs[scope.name] = [scope];
3315
+ }
3316
+ else {
3317
+ allFuncs[scope.name].push(scope);
3318
+ }
2770
3319
  }
2771
3320
  else if (scope.type === "ClassDeclaration") {
2772
3321
  state.allClasses.push(scope);
@@ -2791,7 +3340,7 @@ async function analyze(fnMap, barrelList, config) {
2791
3340
  value.hasTests = hasTests;
2792
3341
  });
2793
3342
  delete state.shouldExclude;
2794
- delete state.post;
3343
+ delete state.pre;
2795
3344
  collectClassInfo(state);
2796
3345
  const diagnosticType = config?.checkInvalidSymbols !== "OFF"
2797
3346
  ? config?.checkInvalidSymbols || "WARNING"
@@ -2814,6 +3363,8 @@ async function analyze(fnMap, barrelList, config) {
2814
3363
  });
2815
3364
  });
2816
3365
  }
3366
+ state.exposed = state.nextExposed;
3367
+ state.nextExposed = {};
2817
3368
  return state;
2818
3369
  }
2819
3370
  function compareLiteralLike(a, b) {
@@ -2859,57 +3410,16 @@ function getLiteralNode(node) {
2859
3410
  if (node.argument.type != "Literal")
2860
3411
  return null;
2861
3412
  switch (node.operator) {
2862
- case "-":
2863
- if (typeof node.argument.value == "number") {
2864
- return {
2865
- ...node.argument,
2866
- value: -node.argument.value,
2867
- raw: "-" + node.argument.value,
2868
- enumType: node.enumType,
2869
- };
3413
+ case "-": {
3414
+ const [arg, type] = ast_getNodeValue(node.argument);
3415
+ if (type === "Number" || type === "Long") {
3416
+ return replacementLiteral(arg, -arg.value, type);
2870
3417
  }
3418
+ }
2871
3419
  }
2872
3420
  }
2873
3421
  return null;
2874
3422
  }
2875
- function getNodeValue(node) {
2876
- if (node.type == "BinaryExpression" &&
2877
- node.operator == "as" &&
2878
- node.right.type == "TypeSpecList" &&
2879
- node.right.ts.length == 1 &&
2880
- typeof node.right.ts[0] == "string") {
2881
- // this is a cast we inserted to retain the type of an enum
2882
- // any arithmetic on it will revert to "Number", or "Long",
2883
- // so just ignore it.
2884
- return getNodeValue(node.left);
2885
- }
2886
- if (node.type != "Literal") {
2887
- return [null, null];
2888
- }
2889
- let type = node.value === null ? "Null" : typeof node.value;
2890
- if (type === "number") {
2891
- const match = node.raw && LiteralIntegerRe.exec(node.raw);
2892
- if (match) {
2893
- type = match[2] == "l" ? "Long" : "Number";
2894
- }
2895
- else if (node.raw && node.raw.endsWith("d")) {
2896
- type = "Double";
2897
- }
2898
- else {
2899
- type = "Float";
2900
- }
2901
- }
2902
- else if (type === "string") {
2903
- type = "String";
2904
- }
2905
- else if (type === "boolean") {
2906
- type = "Boolean";
2907
- }
2908
- else {
2909
- type = "Unknown";
2910
- }
2911
- return [node, type];
2912
- }
2913
3423
  function fullTypeName(state, tsp) {
2914
3424
  if (typeof tsp.name === "string") {
2915
3425
  return tsp.name;
@@ -2953,6 +3463,46 @@ function isBooleanExpression(state, node) {
2953
3463
  }
2954
3464
  return false;
2955
3465
  }
3466
+ function replacementLiteral(arg, value, type) {
3467
+ if (typeof value === "boolean") {
3468
+ type = "Boolean";
3469
+ }
3470
+ else if (type === "Number") {
3471
+ value = Number(BigInt.asIntN(32, BigInt(value)));
3472
+ }
3473
+ else if (type === "Long") {
3474
+ value = BigInt.asIntN(64, BigInt(value));
3475
+ }
3476
+ return {
3477
+ ...arg,
3478
+ value,
3479
+ raw: value.toString() + (type === "Long" ? "l" : ""),
3480
+ };
3481
+ }
3482
+ const operators = {
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,
3490
+ "^": (left, right) => left ^ right,
3491
+ "<<": (left, right) => left << (right & 127n),
3492
+ ">>": (left, right) => left >> (right & 127n),
3493
+ "==": (left, right) =>
3494
+ // two string literals will compare unequal, becuase string
3495
+ // equality is object equality.
3496
+ typeof left === "string" ? false : left === right,
3497
+ "!=": (left, right) => typeof left === "string" ? true : left !== right,
3498
+ "<=": (left, right) => left <= right,
3499
+ ">=": (left, right) => left >= right,
3500
+ "<": (left, right) => left < right,
3501
+ ">": (left, right) => left > right,
3502
+ as: null,
3503
+ instanceof: null,
3504
+ has: null,
3505
+ };
2956
3506
  function optimizeNode(state, node) {
2957
3507
  switch (node.type) {
2958
3508
  case "UnaryExpression": {
@@ -2967,29 +3517,17 @@ function optimizeNode(state, node) {
2967
3517
  break;
2968
3518
  case "-":
2969
3519
  if (type === "Number" || type === "Long") {
2970
- return {
2971
- ...arg,
2972
- value: -arg.value,
2973
- raw: (-arg.value).toString() + (type === "Long" ? "l" : ""),
2974
- };
3520
+ return replacementLiteral(arg, -arg.value, type);
2975
3521
  }
2976
3522
  break;
2977
3523
  case "!":
2978
3524
  case "~":
2979
3525
  {
2980
- let value;
2981
3526
  if (type === "Number" || type === "Long") {
2982
- value = -arg.value - 1;
2983
- }
2984
- else if (type === "Boolean" && node.operator == "!") {
2985
- value = !arg.value;
3527
+ return replacementLiteral(arg, ~BigInt(arg.value), type);
2986
3528
  }
2987
- if (value !== undefined) {
2988
- return {
2989
- ...arg,
2990
- value,
2991
- raw: value.toString() + (type === "Long" ? "l" : ""),
2992
- };
3529
+ if (type === "Boolean" && node.operator == "!") {
3530
+ return replacementLiteral(arg, !arg.value, type);
2993
3531
  }
2994
3532
  }
2995
3533
  break;
@@ -2997,27 +3535,6 @@ function optimizeNode(state, node) {
2997
3535
  break;
2998
3536
  }
2999
3537
  case "BinaryExpression": {
3000
- const operators = {
3001
- "+": (left, right) => left + right,
3002
- "-": (left, right) => left - right,
3003
- "*": (left, right) => left * right,
3004
- "/": (left, right) => Math.trunc(left / right),
3005
- "%": (left, right) => left % right,
3006
- "&": (left, right, type) => type === "Number" ? left & right : null,
3007
- "|": (left, right, type) => type === "Number" ? left | right : null,
3008
- "^": (left, right, type) => type === "Number" ? left ^ right : null,
3009
- "<<": (left, right, type) => type === "Number" ? left << right : null,
3010
- ">>": (left, right, type) => type === "Number" ? left >> right : null,
3011
- "==": (left, right) => left == right,
3012
- "!=": (left, right) => left != right,
3013
- "<=": (left, right) => left <= right,
3014
- ">=": (left, right) => left >= right,
3015
- "<": (left, right) => left < right,
3016
- ">": (left, right) => left > right,
3017
- as: null,
3018
- instanceof: null,
3019
- has: null,
3020
- };
3021
3538
  const op = operators[node.operator];
3022
3539
  if (op) {
3023
3540
  const [left, left_type] = getNodeValue(node.left);
@@ -3025,23 +3542,22 @@ function optimizeNode(state, node) {
3025
3542
  if (!left || !right)
3026
3543
  break;
3027
3544
  let value = null;
3028
- if (left_type != right_type ||
3029
- (left_type != "Number" && left_type != "Long")) {
3545
+ let type;
3546
+ if ((left_type != "Number" && left_type != "Long") ||
3547
+ left_type != right_type) {
3030
3548
  if (node.operator !== "==" && node.operator !== "!=") {
3031
3549
  break;
3032
3550
  }
3033
3551
  value = operators[node.operator](left.value, right.value);
3552
+ type = "Boolean";
3034
3553
  }
3035
3554
  else {
3036
- value = op(left.value, right.value, left_type);
3555
+ type = left_type;
3556
+ value = op(BigInt(left.value), BigInt(right.value));
3037
3557
  }
3038
3558
  if (value === null)
3039
3559
  break;
3040
- return {
3041
- ...left,
3042
- value,
3043
- raw: value.toString() + (left_type === "Long" ? "l" : ""),
3044
- };
3560
+ return replacementLiteral(left, value, type);
3045
3561
  }
3046
3562
  break;
3047
3563
  }
@@ -3051,7 +3567,8 @@ function optimizeNode(state, node) {
3051
3567
  break;
3052
3568
  const falsy = left.value === false ||
3053
3569
  left.value === null ||
3054
- (left.value === 0 && (left_type === "Number" || left_type === "Long"));
3570
+ ((left_type === "Number" || left_type === "Long") &&
3571
+ (left.value === 0 || left.value === 0n));
3055
3572
  if (falsy === (node.operator === "&&")) {
3056
3573
  return left;
3057
3574
  }
@@ -3092,9 +3609,7 @@ function evaluateFunction(state, func, args) {
3092
3609
  const paramValues = args &&
3093
3610
  Object.fromEntries(func.params.map((p, i) => [variableDeclarationName(p), args[i]]));
3094
3611
  let ret = null;
3095
- const body = args
3096
- ? JSON.parse(JSON.stringify(func.body))
3097
- : func.body;
3612
+ const body = args ? cloneDeep(func.body) : func.body;
3098
3613
  try {
3099
3614
  traverseAst(body, (node) => {
3100
3615
  switch (node.type) {
@@ -3145,12 +3660,10 @@ function markFunctionCalled(state, func) {
3145
3660
  pushUnique(state.calledFunctions[func.id.name], func);
3146
3661
  }
3147
3662
  async function optimizeMonkeyC(fnMap, barrelList, config) {
3148
- const state = {
3149
- ...(await analyze(fnMap, barrelList, config)),
3150
- localsStack: [{}],
3151
- exposed: {},
3152
- calledFunctions: {},
3153
- };
3663
+ const state = (await analyze(fnMap, barrelList, config));
3664
+ state.localsStack = [{}];
3665
+ state.calledFunctions = {};
3666
+ state.usedByName = {};
3154
3667
  const replace = (node, old) => {
3155
3668
  if (node === false || node === null)
3156
3669
  return node;
@@ -3239,6 +3752,58 @@ async function optimizeMonkeyC(fnMap, barrelList, config) {
3239
3752
  f.type == "FunctionDeclaration" &&
3240
3753
  maybeCalled(f.node))) ||
3241
3754
  (sc.superClass && checkInherited(sc, name))));
3755
+ const renamer = (idnode) => {
3756
+ const ident = idnode.type === "Identifier" ? idnode : idnode.left;
3757
+ const locals = topLocals();
3758
+ const { map } = locals;
3759
+ if (map) {
3760
+ const declName = ident.name;
3761
+ const name = renameVariable(state, locals, declName);
3762
+ if (name) {
3763
+ const [, results] = state.lookupValue(ident);
3764
+ if (!results) {
3765
+ throw new Error(`Didn't find local ${declName} which needed renaming`);
3766
+ }
3767
+ if (results.length !== 1) {
3768
+ throw new Error(`Lookup of local ${declName} found more than one result`);
3769
+ }
3770
+ const parent = results[0].parent;
3771
+ if (!parent) {
3772
+ throw new Error(`No parent in lookup of local ${declName}`);
3773
+ }
3774
+ const decls = parent.decls;
3775
+ if (!decls || !hasProperty(decls, declName)) {
3776
+ throw new Error(`Missing decls in lookup of local ${declName}`);
3777
+ }
3778
+ if (hasProperty(decls, name)) {
3779
+ throw new Error(`While renaming ${declName} to ${name}, there was already a variable ${name}`);
3780
+ }
3781
+ if (decls[declName].length === 1) {
3782
+ decls[name] = decls[declName];
3783
+ delete decls[declName];
3784
+ }
3785
+ else {
3786
+ let i = decls[declName].length;
3787
+ while (i--) {
3788
+ const decl = decls[declName][i];
3789
+ if (decl === idnode ||
3790
+ (decl.type === "VariableDeclarator" && decl.node.id === idnode)) {
3791
+ decls[declName].splice(i, 1);
3792
+ decls[name] = [decl];
3793
+ break;
3794
+ }
3795
+ }
3796
+ if (i < 0) {
3797
+ throw new Error(`While renaming ${declName} to ${name}: Didn't find original declaration`);
3798
+ }
3799
+ }
3800
+ ident.name = name;
3801
+ }
3802
+ else {
3803
+ map[declName] = true;
3804
+ }
3805
+ }
3806
+ };
3242
3807
  state.pre = (node) => {
3243
3808
  switch (node.type) {
3244
3809
  case "ConditionalExpression":
@@ -3259,7 +3824,11 @@ async function optimizeMonkeyC(fnMap, barrelList, config) {
3259
3824
  result = !!value.value;
3260
3825
  }
3261
3826
  if (result !== null) {
3262
- node.test = { type: "Literal", value: result };
3827
+ node.test = {
3828
+ type: "Literal",
3829
+ value: result,
3830
+ raw: result.toString(),
3831
+ };
3263
3832
  if (node.type === "IfStatement" ||
3264
3833
  node.type === "ConditionalExpression") {
3265
3834
  return [result ? "consequent" : "alternate"];
@@ -3278,7 +3847,7 @@ async function optimizeMonkeyC(fnMap, barrelList, config) {
3278
3847
  return null;
3279
3848
  }
3280
3849
  case "EnumDeclaration":
3281
- return false;
3850
+ return [];
3282
3851
  case "ForStatement": {
3283
3852
  const map = topLocals().map;
3284
3853
  if (map) {
@@ -3287,43 +3856,13 @@ async function optimizeMonkeyC(fnMap, barrelList, config) {
3287
3856
  break;
3288
3857
  }
3289
3858
  case "VariableDeclarator": {
3290
- const locals = topLocals();
3291
- const { map } = locals;
3292
- if (map) {
3293
- const declName = variableDeclarationName(node.id);
3294
- const name = renameVariable(state, locals, declName);
3295
- if (name) {
3296
- if (node.id.type === "Identifier") {
3297
- node.id.name = name;
3298
- }
3299
- else {
3300
- node.id.left.name = name;
3301
- }
3302
- }
3303
- else {
3304
- map[declName] = true;
3305
- }
3306
- }
3859
+ renamer(node.id);
3307
3860
  return ["init"];
3308
3861
  }
3309
3862
  case "CatchClause":
3310
3863
  if (node.param) {
3311
3864
  state.localsStack.push({ node, map: { ...(topLocals().map || {}) } });
3312
- const locals = topLocals();
3313
- const map = locals.map;
3314
- const declName = variableDeclarationName(node.param);
3315
- const name = renameVariable(state, locals, declName);
3316
- if (name) {
3317
- if (node.param.type === "Identifier") {
3318
- node.param.name = name;
3319
- }
3320
- else {
3321
- node.param.left.name = name;
3322
- }
3323
- }
3324
- else {
3325
- map[declName] = true;
3326
- }
3865
+ renamer(node.param);
3327
3866
  return ["body"];
3328
3867
  }
3329
3868
  break;
@@ -3339,14 +3878,8 @@ async function optimizeMonkeyC(fnMap, barrelList, config) {
3339
3878
  break;
3340
3879
  case "UnaryExpression":
3341
3880
  if (node.operator == ":") {
3342
- // If we produce a Symbol, for a given name,
3343
- // its possible that someone uses that symbol
3344
- // indirectly, so we can't remove any enums or
3345
- // constants with that name (we can still replace
3346
- // uses of those constants though).
3347
- state.exposed[node.argument.name] = true;
3348
- // In any case, we can't replace *this* use of the
3349
- // symbol with its value...
3881
+ // node.argument is not a normal identifier.
3882
+ // don't visit it.
3350
3883
  return [];
3351
3884
  }
3352
3885
  break;
@@ -3358,29 +3891,73 @@ async function optimizeMonkeyC(fnMap, barrelList, config) {
3358
3891
  if (typeof name === "string") {
3359
3892
  node.name = name;
3360
3893
  }
3894
+ const [, results] = state.lookupValue(node);
3895
+ if (results) {
3896
+ if (results.length !== 1 || results[0].results.length !== 1) {
3897
+ throw new Error(`Local ${node.name} had multiple lookup results`);
3898
+ }
3899
+ const parent = results[0].parent;
3900
+ if (!parent) {
3901
+ throw new Error(`Local ${node.name} had no parent`);
3902
+ }
3903
+ const decl = results[0].results[0];
3904
+ if (parent.type === "FunctionDeclaration" ||
3905
+ decl.type !== "VariableDeclarator") {
3906
+ // we can't optimize away function or catch parameters
3907
+ return [];
3908
+ }
3909
+ if (parent.type !== "BlockStatement") {
3910
+ throw new Error(`Local ${node.name} was not declared at block scope(??)`);
3911
+ }
3912
+ decl.used = true;
3913
+ }
3361
3914
  }
3362
3915
  }
3363
3916
  if (hasProperty(state.index, node.name)) {
3364
3917
  if (!lookupAndReplace(node)) {
3365
- state.exposed[node.name] = true;
3918
+ state.usedByName[node.name] = true;
3366
3919
  }
3367
3920
  }
3368
3921
  return [];
3369
3922
  }
3370
- case "MemberExpression":
3371
- if (node.property.type === "Identifier" && !node.computed) {
3372
- if (hasProperty(state.index, node.property.name)) {
3923
+ case "MemberExpression": {
3924
+ const property = isLookupCandidate(node);
3925
+ if (property) {
3926
+ if (hasProperty(state.index, property.name)) {
3373
3927
  if (lookupAndReplace(node)) {
3374
3928
  return false;
3375
3929
  }
3376
3930
  else {
3377
- state.exposed[node.property.name] = true;
3931
+ state.usedByName[property.name] = true;
3378
3932
  }
3379
3933
  }
3380
3934
  // Don't optimize the property.
3381
3935
  return ["object"];
3382
3936
  }
3383
3937
  break;
3938
+ }
3939
+ case "AssignmentExpression":
3940
+ case "UpdateExpression": {
3941
+ const lhs = node.type === "AssignmentExpression" ? node.left : node.argument;
3942
+ if (lhs.type === "Identifier") {
3943
+ const map = topLocals().map;
3944
+ if (map) {
3945
+ if (hasProperty(map, lhs.name)) {
3946
+ const name = map[lhs.name];
3947
+ if (typeof name === "string") {
3948
+ lhs.name = name;
3949
+ }
3950
+ }
3951
+ }
3952
+ }
3953
+ else if (lhs.type === "MemberExpression") {
3954
+ state.traverse(lhs.object);
3955
+ if (lhs.computed) {
3956
+ state.traverse(lhs.property);
3957
+ }
3958
+ }
3959
+ return node.type === "AssignmentExpression" ? ["right"] : [];
3960
+ }
3384
3961
  case "BlockStatement": {
3385
3962
  const map = topLocals().map;
3386
3963
  if (map) {
@@ -3396,7 +3973,11 @@ async function optimizeMonkeyC(fnMap, barrelList, config) {
3396
3973
  node.params &&
3397
3974
  node.params.forEach((p) => (map[variableDeclarationName(p)] = true));
3398
3975
  state.localsStack.push({ node, map });
3399
- const [parent] = state.stack.slice(-2);
3976
+ const [parent, self] = state.stack.slice(-2);
3977
+ if (state.currentFunction) {
3978
+ throw new Error(`Nested functions: ${self.fullName} was activated during processing of ${state.currentFunction.fullName}`);
3979
+ }
3980
+ state.currentFunction = self;
3400
3981
  if (parent.type == "ClassDeclaration" && !maybeCalled(node)) {
3401
3982
  let used = false;
3402
3983
  if (node.id.name == "initialize") {
@@ -3423,10 +4004,23 @@ async function optimizeMonkeyC(fnMap, barrelList, config) {
3423
4004
  return replace(opt, node);
3424
4005
  }
3425
4006
  switch (node.type) {
4007
+ case "FunctionDeclaration":
4008
+ if (!state.currentFunction) {
4009
+ throw new Error(`Finished function ${state.stack.slice(-1)[0].fullName}, but it was not marked current`);
4010
+ }
4011
+ state.currentFunction.info = state.currentFunction.next_info;
4012
+ delete state.currentFunction.next_info;
4013
+ delete state.currentFunction;
4014
+ break;
3426
4015
  case "BlockStatement":
3427
4016
  if (node.body.length === 1 && node.body[0].type === "BlockStatement") {
3428
4017
  node.body.splice(0, 1, ...node.body[0].body);
3429
4018
  }
4019
+ // fall through
4020
+ case "ForStatement":
4021
+ if (locals.map) {
4022
+ cleanupUnusedVars(state, node);
4023
+ }
3430
4024
  break;
3431
4025
  case "ConditionalExpression":
3432
4026
  case "IfStatement":
@@ -3459,17 +4053,20 @@ async function optimizeMonkeyC(fnMap, barrelList, config) {
3459
4053
  return replace(optimizeCall(state, node.argument, node), node.argument);
3460
4054
  }
3461
4055
  break;
4056
+ case "NewExpression":
4057
+ if (state.currentFunction) {
4058
+ const [, results] = state.lookup(node.callee);
4059
+ if (results) {
4060
+ recordCalledFuncs(state.currentFunction, findCalleesForNew(results));
4061
+ }
4062
+ else {
4063
+ recordModifiedUnknown(state.currentFunction);
4064
+ }
4065
+ }
4066
+ break;
3462
4067
  case "CallExpression": {
3463
4068
  return replace(optimizeCall(state, node, null), node);
3464
4069
  }
3465
- case "AssignmentExpression":
3466
- if (node.operator === "=" &&
3467
- node.left.type === "Identifier" &&
3468
- node.right.type === "Identifier" &&
3469
- node.left.name === node.right.name) {
3470
- return { type: "Literal", value: null, raw: "null" };
3471
- }
3472
- break;
3473
4070
  case "VariableDeclaration": {
3474
4071
  const locals = topLocals();
3475
4072
  if (locals.map &&
@@ -3543,6 +4140,32 @@ async function optimizeMonkeyC(fnMap, barrelList, config) {
3543
4140
  }
3544
4141
  }
3545
4142
  break;
4143
+ case "AssignmentExpression":
4144
+ if (node.operator === "=" &&
4145
+ node.left.type === "Identifier" &&
4146
+ node.right.type === "Identifier" &&
4147
+ node.left.name === node.right.name) {
4148
+ return { type: "Literal", value: null, raw: "null" };
4149
+ }
4150
+ // fall through;
4151
+ case "UpdateExpression":
4152
+ if (state.currentFunction) {
4153
+ const lhs = node.type === "AssignmentExpression" ? node.left : node.argument;
4154
+ const [, results] = state.lookup(lhs);
4155
+ if (results) {
4156
+ recordModifiedDecls(state.currentFunction, results);
4157
+ }
4158
+ else {
4159
+ const id = lhs.type === "Identifier" ? lhs : isLookupCandidate(lhs);
4160
+ if (id) {
4161
+ recordModifiedName(state.currentFunction, id.name);
4162
+ }
4163
+ else {
4164
+ recordModifiedUnknown(state.currentFunction);
4165
+ }
4166
+ }
4167
+ }
4168
+ break;
3546
4169
  }
3547
4170
  return null;
3548
4171
  };
@@ -3550,13 +4173,16 @@ async function optimizeMonkeyC(fnMap, barrelList, config) {
3550
4173
  collectNamespaces(f.ast, state);
3551
4174
  });
3552
4175
  state.calledFunctions = {};
3553
- state.exposed = {};
4176
+ state.exposed = state.nextExposed;
4177
+ state.nextExposed = {};
3554
4178
  Object.values(fnMap).forEach((f) => {
3555
4179
  collectNamespaces(f.ast, state);
3556
4180
  });
4181
+ state.exposed = state.nextExposed;
4182
+ state.nextExposed = {};
3557
4183
  delete state.pre;
3558
4184
  delete state.post;
3559
- state.allFunctions.forEach((fn) => sizeBasedPRE(state, fn));
4185
+ Object.values(state.allFunctions).forEach((fns) => fns.forEach((fn) => sizeBasedPRE(state, fn)));
3560
4186
  const cleanup = (node) => {
3561
4187
  switch (node.type) {
3562
4188
  case "ThisExpression":
@@ -3566,7 +4192,8 @@ async function optimizeMonkeyC(fnMap, barrelList, config) {
3566
4192
  if (node.members.every((m) => {
3567
4193
  const name = "name" in m ? m.name : m.id.name;
3568
4194
  return (hasProperty(state.index, name) &&
3569
- !hasProperty(state.exposed, name));
4195
+ !hasProperty(state.exposed, name) &&
4196
+ !hasProperty(state.usedByName, name));
3570
4197
  })) {
3571
4198
  node.enumType = [
3572
4199
  ...new Set(node.members.map((m) => {
@@ -3609,7 +4236,9 @@ async function optimizeMonkeyC(fnMap, barrelList, config) {
3609
4236
  case "VariableDeclaration": {
3610
4237
  node.declarations = node.declarations.filter((d) => {
3611
4238
  const name = variableDeclarationName(d.id);
3612
- return (!hasProperty(state.index, name) || hasProperty(state.exposed, name));
4239
+ return (!hasProperty(state.index, name) ||
4240
+ hasProperty(state.exposed, name) ||
4241
+ hasProperty(state.usedByName, name));
3613
4242
  });
3614
4243
  if (!node.declarations.length) {
3615
4244
  return false;
@@ -3623,6 +4252,11 @@ async function optimizeMonkeyC(fnMap, barrelList, config) {
3623
4252
  break;
3624
4253
  case "FunctionDeclaration":
3625
4254
  if (!maybeCalled(node)) {
4255
+ if (node.attrs &&
4256
+ node.attrs.attributes &&
4257
+ node.attrs.attributes.elements.some((attr) => attr.type === "UnaryExpression" && attr.argument.name === "keep")) {
4258
+ break;
4259
+ }
3626
4260
  return false;
3627
4261
  }
3628
4262
  break;
@@ -3642,7 +4276,7 @@ async function optimizeMonkeyC(fnMap, barrelList, config) {
3642
4276
  }
3643
4277
  return null;
3644
4278
  };
3645
- Object.values(fnMap).forEach((f) => {
4279
+ Object.entries(fnMap).forEach(([name, f]) => {
3646
4280
  traverseAst(f.ast, undefined, (node) => {
3647
4281
  const ret = cleanup(node);
3648
4282
  if (ret === false) {
@@ -3650,16 +4284,15 @@ async function optimizeMonkeyC(fnMap, barrelList, config) {
3650
4284
  }
3651
4285
  return ret;
3652
4286
  });
4287
+ if (state.config && state.config.checkBuildPragmas) {
4288
+ pragmaChecker(state, f.ast, state.diagnostics?.[name]);
4289
+ }
3653
4290
  });
3654
4291
  return state.diagnostics;
3655
4292
  }
3656
4293
  function optimizeCall(state, node, context) {
3657
4294
  const [name, results] = state.lookupNonlocal(node.callee);
3658
- const callees = results &&
3659
- results
3660
- .map((r) => r.results)
3661
- .flat()
3662
- .filter((c) => c.type === "FunctionDeclaration");
4295
+ const callees = results ? findCallees(results) : null;
3663
4296
  if (!callees || !callees.length) {
3664
4297
  const n = name ||
3665
4298
  ("name" in node.callee && node.callee.name) ||
@@ -3668,14 +4301,24 @@ function optimizeCall(state, node, context) {
3668
4301
  "name" in node.callee.property &&
3669
4302
  node.callee.property.name);
3670
4303
  if (n) {
3671
- state.exposed[n] = true;
4304
+ if (hasProperty(state.allFunctions, n)) {
4305
+ if (state.currentFunction) {
4306
+ recordCalledFuncs(state.currentFunction, state.allFunctions[n]);
4307
+ }
4308
+ state.allFunctions[n].forEach((fn) => markFunctionCalled(state, fn.node));
4309
+ }
3672
4310
  }
3673
- else {
3674
- // There are unnamed CallExpressions, such as new [size]
3675
- // So there's nothing to do here.
4311
+ else if (state.currentFunction) {
4312
+ // I don't think this can happen: foo[x](args)
4313
+ // doesn't parse, so you can't even do things like
4314
+ // $.Toybox.Lang[:format]("fmt", [])
4315
+ recordModifiedUnknown(state.currentFunction);
3676
4316
  }
3677
4317
  return null;
3678
4318
  }
4319
+ if (state.currentFunction) {
4320
+ recordCalledFuncs(state.currentFunction, callees);
4321
+ }
3679
4322
  if (callees.length == 1 && callees[0].type === "FunctionDeclaration") {
3680
4323
  const callee = callees[0].node;
3681
4324
  if (!context &&
@@ -3886,20 +4529,18 @@ function checkOne(state, ns, decls, node, isStatic) {
3886
4529
  return next ? (result ? result.concat(next) : next) : result;
3887
4530
  }, null);
3888
4531
  };
3889
- if (api_isStateNode(ns)) {
3890
- const ndecls = ns[decls];
3891
- if (ast_hasProperty(ndecls, node.name)) {
3892
- return ndecls[node.name];
3893
- }
3894
- switch (ns.type) {
3895
- case "ClassDeclaration":
3896
- if (!isStatic) {
3897
- return superChain(ns) || superChainScopes(ns) || false;
3898
- }
3899
- // fall through
3900
- case "ModuleDeclaration":
3901
- return lookupInContext(ns) || false;
3902
- }
4532
+ const ndecls = ns[decls];
4533
+ if (ast_hasProperty(ndecls, node.name)) {
4534
+ return ndecls[node.name];
4535
+ }
4536
+ switch (ns.type) {
4537
+ case "ClassDeclaration":
4538
+ if (!isStatic) {
4539
+ return superChain(ns) || superChainScopes(ns) || false;
4540
+ }
4541
+ // fall through
4542
+ case "ModuleDeclaration":
4543
+ return lookupInContext(ns) || false;
3903
4544
  }
3904
4545
  return null;
3905
4546
  }
@@ -3922,6 +4563,13 @@ function sameLookupDefinition(a, b) {
3922
4563
  function api_sameLookupResult(a, b) {
3923
4564
  return (0,external_util_cjs_namespaceObject.sameArrays)(a, b, sameLookupDefinition);
3924
4565
  }
4566
+ function api_isLookupCandidate(node) {
4567
+ return node.computed
4568
+ ? node.property.type === "UnaryExpression" &&
4569
+ node.property.operator === ":" &&
4570
+ node.property.argument
4571
+ : node.property.type === "Identifier" && node.property;
4572
+ }
3925
4573
  /**
3926
4574
  *
3927
4575
  * @param state - The ProgramState
@@ -3942,9 +4590,9 @@ function lookup(state, decls, node, name, maybeStack, nonlocal) {
3942
4590
  const stack = maybeStack || state.stack;
3943
4591
  switch (node.type) {
3944
4592
  case "MemberExpression": {
3945
- if (node.property.type != "Identifier" || node.computed)
4593
+ const property = api_isLookupCandidate(node);
4594
+ if (!property)
3946
4595
  break;
3947
- const property = node.property;
3948
4596
  let result;
3949
4597
  if (node.object.type === "ThisExpression") {
3950
4598
  [, result] = lookup(state, decls, node.property, name, stack, true);
@@ -3958,6 +4606,9 @@ function lookup(state, decls, node, name, maybeStack, nonlocal) {
3958
4606
  result = results.reduce((current, lookupDef) => {
3959
4607
  const items = lookupDef.results
3960
4608
  .map((module) => {
4609
+ if (!api_isStateNode(module)) {
4610
+ return null;
4611
+ }
3961
4612
  const res = checkOne(state, module, decls, property, false);
3962
4613
  return res ? { parent: module, results: res } : null;
3963
4614
  })
@@ -4042,6 +4693,8 @@ function lookup(state, decls, node, name, maybeStack, nonlocal) {
4042
4693
  }
4043
4694
  function api_collectNamespaces(ast, stateIn) {
4044
4695
  const state = (stateIn || {});
4696
+ if (!state.nextExposed)
4697
+ state.nextExposed = {};
4045
4698
  if (!state.index)
4046
4699
  state.index = {};
4047
4700
  if (!state.stack) {
@@ -4077,7 +4730,7 @@ function api_collectNamespaces(ast, stateIn) {
4077
4730
  state.stackClone = () => state.stack.map((elm) => elm.type === "ModuleDeclaration" || elm.type === "Program"
4078
4731
  ? { ...elm }
4079
4732
  : elm);
4080
- state.inType = false;
4733
+ state.inType = 0;
4081
4734
  state.traverse = (root) => ast_traverseAst(root, (node) => {
4082
4735
  try {
4083
4736
  if (state.shouldExclude && state.shouldExclude(node)) {
@@ -4085,6 +4738,13 @@ function api_collectNamespaces(ast, stateIn) {
4085
4738
  return [];
4086
4739
  }
4087
4740
  switch (node.type) {
4741
+ case "UnaryExpression":
4742
+ if (node.operator === ":" && !state.inType) {
4743
+ state.nextExposed[node.argument.name] = true;
4744
+ }
4745
+ break;
4746
+ case "AttributeList":
4747
+ return [];
4088
4748
  case "Program":
4089
4749
  if (state.stack.length != 1) {
4090
4750
  throw new Error("Unexpected stack length for Program node");
@@ -4093,7 +4753,7 @@ function api_collectNamespaces(ast, stateIn) {
4093
4753
  break;
4094
4754
  case "TypeSpecList":
4095
4755
  case "TypeSpecPart":
4096
- state.inType = true;
4756
+ state.inType++;
4097
4757
  break;
4098
4758
  case "ImportModule":
4099
4759
  case "Using": {
@@ -4139,6 +4799,16 @@ function api_collectNamespaces(ast, stateIn) {
4139
4799
  });
4140
4800
  }
4141
4801
  break;
4802
+ case "ForStatement":
4803
+ if (node.init && node.init.type === "VariableDeclaration") {
4804
+ state.stack.push({
4805
+ type: "BlockStatement",
4806
+ fullName: undefined,
4807
+ name: undefined,
4808
+ node: node,
4809
+ });
4810
+ }
4811
+ break;
4142
4812
  case "BlockStatement": {
4143
4813
  const [parent] = state.stack.slice(-1);
4144
4814
  if (parent.node === node ||
@@ -4205,15 +4875,17 @@ function api_collectNamespaces(ast, stateIn) {
4205
4875
  // an EnumDeclaration doesn't create a scope, but
4206
4876
  // it does create a type (if it has a name)
4207
4877
  case "EnumDeclaration": {
4208
- if (!node.id)
4878
+ if (!node.id) {
4879
+ state.inType++;
4209
4880
  break;
4881
+ }
4210
4882
  const [parent] = state.stack.slice(-1);
4211
4883
  const name = (parent.fullName + "." + node.id.name).replace(/^\$\./, "");
4212
4884
  node.body.members.forEach((m) => (("init" in m ? m.init : m).enumType = name));
4213
4885
  }
4214
4886
  // fall through
4215
4887
  case "TypedefDeclaration": {
4216
- state.inType = true;
4888
+ state.inType++;
4217
4889
  const name = node.id.name;
4218
4890
  const [parent] = state.stack.slice(-1);
4219
4891
  if (!parent.type_decls)
@@ -4266,13 +4938,21 @@ function api_collectNamespaces(ast, stateIn) {
4266
4938
  break;
4267
4939
  }
4268
4940
  case "EnumStringBody": {
4269
- state.inType = false;
4941
+ if (state.inType !== 1) {
4942
+ throw new Error(`Expected inType to be 1 at EnumStringBody. Got ${state.inType}.`);
4943
+ }
4944
+ state.inType--;
4270
4945
  const [parent] = state.stack.slice(-1);
4271
4946
  const values = parent.decls || (parent.decls = {});
4272
4947
  let prev = -1;
4273
4948
  node.members.forEach((m, i) => {
4274
4949
  if (m.type == "Identifier") {
4275
- prev += 1;
4950
+ if (typeof prev === "bigint") {
4951
+ prev += 1n;
4952
+ }
4953
+ else {
4954
+ prev += 1;
4955
+ }
4276
4956
  m = node.members[i] = {
4277
4957
  type: "EnumStringMember",
4278
4958
  loc: m.loc,
@@ -4282,7 +4962,7 @@ function api_collectNamespaces(ast, stateIn) {
4282
4962
  init: {
4283
4963
  type: "Literal",
4284
4964
  value: prev,
4285
- raw: prev.toString(),
4965
+ raw: prev.toString() + (typeof prev === "bigint" ? "l" : ""),
4286
4966
  enumType: m.enumType,
4287
4967
  loc: m.loc,
4288
4968
  start: m.start,
@@ -4334,22 +5014,22 @@ function api_collectNamespaces(ast, stateIn) {
4334
5014
  if (state.post)
4335
5015
  ret = state.post(node, state);
4336
5016
  switch (type) {
4337
- // Don't clear inType for TypeSpecPart, since they
4338
- // generally occur in TypeSpecLists. But do clear it for
4339
- // SizedArrayExpression, since thats the only place they
4340
- // happen on their own.
4341
- case "SizedArrayExpression":
5017
+ case "TypeSpecPart":
4342
5018
  case "TypeSpecList":
4343
5019
  case "TypedefDeclaration":
4344
5020
  case "EnumDeclaration":
4345
- state.inType = false;
5021
+ state.inType--;
4346
5022
  break;
4347
5023
  case "EnumStringBody":
4348
- state.inType = true;
5024
+ state.inType++;
4349
5025
  break;
4350
5026
  }
4351
5027
  const [parent] = state.stack.slice(-1);
4352
5028
  if (parent.node === node ||
5029
+ // The pre function might cause node.body to be skipped,
5030
+ // so we need to check here, just in case.
5031
+ // (this actually happens with prettier-extenison-monkeyc's
5032
+ // findItemsByRange)
4353
5033
  (node.type === "CatchClause" && parent.node === node.body)) {
4354
5034
  delete parent.usings;
4355
5035
  delete parent.imports;
@@ -4368,6 +5048,9 @@ function api_collectNamespaces(ast, stateIn) {
4368
5048
  }
4369
5049
  });
4370
5050
  state.traverse(ast);
5051
+ if (state.inType) {
5052
+ throw new Error(`inType was non-zero on exit: ${state.inType}`);
5053
+ }
4371
5054
  if (state.stack.length != 1) {
4372
5055
  throw new Error("Invalid AST!");
4373
5056
  }
@@ -4402,7 +5085,7 @@ function api_formatAst(node, monkeyCSource = null) {
4402
5085
  // json. The parser knows to just treat the last line of the input
4403
5086
  // as the ast itself, and the printers will find what they're
4404
5087
  // looking for in the source.
4405
- const source = (monkeyCSource || "") + "\n" + JSON.stringify(node);
5088
+ const source = (monkeyCSource || "") + "\n" + (0,prettier_plugin_monkeyc_namespaceObject.serializeMonkeyC)(node);
4406
5089
  return external_prettier_namespaceObject.format(source, {
4407
5090
  parser: "monkeyc-json",
4408
5091
  plugins: [(prettier_plugin_monkeyc_default())],
@@ -4489,6 +5172,36 @@ function findUsingForNode(state, stack, i, node, isType) {
4489
5172
  }
4490
5173
  return null;
4491
5174
  }
5175
+ const invokeInfo = {};
5176
+ const toyboxFnInfo = {};
5177
+ function api_getApiFunctionInfo(func) {
5178
+ if (func.fullName === "$.Toybox.Lang.Method.invoke" ||
5179
+ (func.node.params &&
5180
+ func.node.params.some((param) => param.type === "BinaryExpression" &&
5181
+ param.right.ts.some((tsp) => tsp.type === "TypeSpecPart" && tsp.callspec)))) {
5182
+ if (!invokeInfo.calledFuncs) {
5183
+ invokeInfo.modifiedDecls = new Set();
5184
+ invokeInfo.calledFuncs = new Set();
5185
+ invokeInfo.callsExposed = true;
5186
+ }
5187
+ if (func.name === "initialize") {
5188
+ const top = func.stack[func.stack.length - 1];
5189
+ if (top.type === "ClassDeclaration") {
5190
+ top.hasInvoke = true;
5191
+ }
5192
+ }
5193
+ return invokeInfo;
5194
+ }
5195
+ if (!toyboxFnInfo.calledFuncs) {
5196
+ toyboxFnInfo.modifiedDecls = new Set();
5197
+ toyboxFnInfo.calledFuncs = new Set();
5198
+ toyboxFnInfo.resolvedDecls = new Set();
5199
+ }
5200
+ return toyboxFnInfo;
5201
+ }
5202
+ function api_markInvokeClassMethod(func) {
5203
+ func.info = invokeInfo;
5204
+ }
4492
5205
 
4493
5206
  })();
4494
5207