@soundscript/soundscript 0.1.16 → 0.1.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/json.js +1 -1
  2. package/numerics.js +4 -4
  3. package/package.json +6 -6
  4. package/project-transform/src/checker/rules/flow.js +16 -3
  5. package/project-transform/src/checker/rules/flow.ts +20 -3
  6. package/project-transform/src/checker/rules/unsound_syntax.js +0 -3
  7. package/project-transform/src/checker/rules/unsound_syntax.ts +0 -4
  8. package/project-transform/src/cli.js +1 -1
  9. package/project-transform/src/cli.ts +1 -1
  10. package/project-transform/src/compiler/compile_project.js +75 -9
  11. package/project-transform/src/compiler/compile_project.ts +121 -7
  12. package/project-transform/src/compiler/ir.ts +19 -1
  13. package/project-transform/src/compiler/lower.js +10335 -1477
  14. package/project-transform/src/compiler/lower.ts +16826 -4074
  15. package/project-transform/src/compiler/toolchain.js +36 -4
  16. package/project-transform/src/compiler/toolchain.ts +36 -4
  17. package/project-transform/src/compiler/wasm_js_host_runtime.js +134 -0
  18. package/project-transform/src/compiler/wasm_js_host_runtime.ts +146 -0
  19. package/project-transform/src/compiler/wat_arrays.js +4 -1
  20. package/project-transform/src/compiler/wat_arrays.ts +5 -1
  21. package/project-transform/src/compiler/wat_emitter.js +1497 -311
  22. package/project-transform/src/compiler/wat_emitter.ts +2971 -1017
  23. package/project-transform/src/compiler/wat_tagged.js +5 -0
  24. package/project-transform/src/compiler/wat_tagged.ts +5 -0
  25. package/project-transform/src/compiler_generator_runner.js +2139 -19
  26. package/project-transform/src/compiler_generator_runner.ts +2143 -20
  27. package/project-transform/src/compiler_promise_runner.js +4615 -636
  28. package/project-transform/src/compiler_promise_runner.ts +4703 -659
  29. package/project-transform/src/compiler_test_helpers.js +0 -579
  30. package/project-transform/src/compiler_test_helpers.ts +0 -648
  31. package/project-transform/src/frontend/macro_expander.js +4 -6
  32. package/project-transform/src/frontend/macro_expander.ts +4 -6
  33. package/project-transform/src/frontend/macro_operand_semantics.js +124 -1
  34. package/project-transform/src/frontend/macro_operand_semantics.ts +230 -6
  35. package/project-transform/src/frontend/macro_resolver.js +2 -2
  36. package/project-transform/src/frontend/macro_resolver.ts +2 -1
  37. package/project-transform/src/frontend/project_macro_support.js +29 -5
  38. package/project-transform/src/frontend/project_macro_support.ts +46 -10
@@ -1572,15 +1572,13 @@ export function expandPreparedProgramWithFileRegistries(
1572
1572
  >,
1573
1573
  preserveMissingExpanders = false,
1574
1574
  annotateExpansions = false,
1575
+ sourceFiles = preparedProgram.program.getSourceFiles(),
1575
1576
  ): ReadonlyMap<string, ts.SourceFile> {
1576
- const collected = collectResolvedMacroPlaceholders(preparedProgram);
1577
+ const nonDeclarationSourceFiles = sourceFiles.filter((sourceFile) => !sourceFile.isDeclarationFile);
1578
+ const collected = collectResolvedMacroPlaceholders(preparedProgram, nonDeclarationSourceFiles);
1577
1579
  const expandedFiles = new Map<string, ts.SourceFile>();
1578
1580
 
1579
- for (const sourceFile of preparedProgram.program.getSourceFiles()) {
1580
- if (sourceFile.isDeclarationFile) {
1581
- continue;
1582
- }
1583
-
1581
+ for (const sourceFile of nonDeclarationSourceFiles) {
1584
1582
  const fileRegistries = registriesByFile.get(sourceFile.fileName);
1585
1583
  const advancedRegistry = fileRegistries?.advancedRegistry ?? new Map();
1586
1584
  const registry = fileRegistries?.registry ?? new Map();
@@ -4,6 +4,7 @@ import { createPreparedProgram, mapProgramPositionToSource, mapSourcePositionToP
4
4
  import { rewriteMacroSource } from './macro_rewrite.js';
5
5
  import { createMacroSemantics } from './macro_semantics.js';
6
6
  const COMPLETION_PLACEHOLDER_IDENTIFIER = '__sts_completion_target';
7
+ const sharedExprOperandFileSourceCache = new WeakMap();
7
8
  function isNestedMacroHoverTarget(value) {
8
9
  return 'kind' in value;
9
10
  }
@@ -22,6 +23,18 @@ function getExprArgumentSpan(invocation, index) {
22
23
  const argument = invocation.argumentSpans[index];
23
24
  return argument?.kind === 'ExprArg' ? argument.span : undefined;
24
25
  }
26
+ function exprArgumentContainsNestedMacroInvocation(fileName, originalText, exprSpan, nestedRegistries) {
27
+ const sourceFile = ts.createSourceFile(fileName, originalText, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX);
28
+ const importPrelude = sourceFile.statements
29
+ .filter(ts.isImportDeclaration)
30
+ .map((statement) => statement.getText(sourceFile))
31
+ .join('\n');
32
+ const operandText = originalText.slice(exprSpan.start, exprSpan.end);
33
+ const probeSourceText = importPrelude.length > 0
34
+ ? `${importPrelude}\nconst __sts_nested_probe = ${operandText};\n`
35
+ : `const __sts_nested_probe = ${operandText};\n`;
36
+ return rewriteMacroSource(fileName, probeSourceText, nestedRegistries.siteKindsBySpecifier ?? new Map(), getAlwaysAvailableBuiltinMacroSiteKinds()).replacements.length > 0;
37
+ }
25
38
  function _getExpressionArgumentSpan(invocation, expressionArgumentIndex) {
26
39
  const expressionArguments = invocation.argumentSpans.filter((argument) => argument.kind === 'ExprArg');
27
40
  return expressionArguments[expressionArgumentIndex]?.span;
@@ -287,6 +300,7 @@ function expandNestedOperandSource(preparedProgram, resolved, patchedText, neste
287
300
  };
288
301
  const finalProgram = ts.createProgram({
289
302
  host: overrideHost,
303
+ oldProgram: nestedPreparedProgram.program,
290
304
  options: nestedPreparedProgram.options,
291
305
  rootNames: nestedPreparedProgram.rootNames.map((fileName) => nestedPreparedProgram.toProgramFileName(fileName)),
292
306
  });
@@ -407,6 +421,95 @@ function findExpressionAtSpan(sourceFile, start, end) {
407
421
  visit(sourceFile);
408
422
  return found;
409
423
  }
424
+ function sharedExprOperandFileSourceCacheKey(rewrittenProgramFileName, index) {
425
+ return `${rewrittenProgramFileName}\0${index}`;
426
+ }
427
+ function buildSharedExprOperandFileSource(preparedProgram, preparedFile, sourceFileName, index, nestedRegistries) {
428
+ const rewrittenProgramFileName = preparedProgram.toProgramFileName(sourceFileName);
429
+ const replacements = [];
430
+ for (const replacement of preparedFile.rewriteResult.replacements) {
431
+ const invocation = preparedFile.rewriteResult.macrosById.get(replacement.id);
432
+ if (!invocation) {
433
+ continue;
434
+ }
435
+ const exprSpan = getExprArgumentSpan(invocation, index);
436
+ if (!exprSpan) {
437
+ continue;
438
+ }
439
+ if (exprArgumentContainsNestedMacroInvocation(sourceFileName, preparedFile.originalText, exprSpan, nestedRegistries)) {
440
+ continue;
441
+ }
442
+ const operandText = preparedFile.originalText.slice(exprSpan.start, exprSpan.end);
443
+ replacements.push({
444
+ id: replacement.id,
445
+ end: mapStageOnePositionToProgram(preparedFile, replacement.rewrittenSpan.end),
446
+ operandText,
447
+ start: mapStageOnePositionToProgram(preparedFile, replacement.rewrittenSpan.start),
448
+ });
449
+ }
450
+ replacements.sort((left, right) => left.start - right.start);
451
+ if (replacements.length === 0) {
452
+ return null;
453
+ }
454
+ let patchedText = '';
455
+ let cursor = 0;
456
+ const spansByPlaceholderId = new Map();
457
+ for (const replacement of replacements) {
458
+ patchedText += preparedFile.rewrittenText.slice(cursor, replacement.start);
459
+ const patchedStart = patchedText.length;
460
+ patchedText += replacement.operandText;
461
+ spansByPlaceholderId.set(replacement.id, {
462
+ end: patchedStart + replacement.operandText.length,
463
+ fileName: sourceFileName,
464
+ start: patchedStart,
465
+ });
466
+ cursor = replacement.end;
467
+ }
468
+ patchedText += preparedFile.rewrittenText.slice(cursor);
469
+ const overrideHost = {
470
+ ...preparedProgram.preparedHost.host,
471
+ getSourceFile(fileName, languageVersion, onError, shouldCreateNewSourceFile) {
472
+ if (fileName === rewrittenProgramFileName) {
473
+ return ts.createSourceFile(fileName, patchedText, languageVersion, true);
474
+ }
475
+ return preparedProgram.preparedHost.host.getSourceFile(fileName, languageVersion, onError, shouldCreateNewSourceFile);
476
+ },
477
+ readFile(fileName) {
478
+ if (fileName === rewrittenProgramFileName) {
479
+ return patchedText;
480
+ }
481
+ return preparedProgram.preparedHost.host.readFile(fileName);
482
+ },
483
+ };
484
+ const patchedProgram = ts.createProgram({
485
+ host: overrideHost,
486
+ oldProgram: preparedProgram.program,
487
+ options: preparedProgram.options,
488
+ rootNames: preparedProgram.rootNames.map((fileName) => preparedProgram.toProgramFileName(fileName)),
489
+ });
490
+ const patchedSourceFile = patchedProgram.getSourceFile(rewrittenProgramFileName);
491
+ if (!patchedSourceFile) {
492
+ return null;
493
+ }
494
+ return {
495
+ semantics: createMacroSemantics(patchedProgram),
496
+ sourceFile: patchedSourceFile,
497
+ spansByPlaceholderId,
498
+ };
499
+ }
500
+ function getSharedExprOperandFileSource(preparedProgram, preparedFile, sourceFileName, index, nestedRegistries) {
501
+ let programCache = sharedExprOperandFileSourceCache.get(preparedProgram);
502
+ if (!programCache) {
503
+ programCache = new Map();
504
+ sharedExprOperandFileSourceCache.set(preparedProgram, programCache);
505
+ }
506
+ const rewrittenProgramFileName = preparedProgram.toProgramFileName(sourceFileName);
507
+ const cacheKey = sharedExprOperandFileSourceCacheKey(rewrittenProgramFileName, index);
508
+ if (!programCache.has(cacheKey)) {
509
+ programCache.set(cacheKey, buildSharedExprOperandFileSource(preparedProgram, preparedFile, sourceFileName, index, nestedRegistries));
510
+ }
511
+ return programCache.get(cacheKey) ?? null;
512
+ }
410
513
  function findDeepestNodeContainingPosition(node, position) {
411
514
  if (position < node.getFullStart() || position >= node.getEnd()) {
412
515
  return undefined;
@@ -438,6 +541,7 @@ function createPatchedMacroSource(preparedProgram, resolved, replacementText) {
438
541
  };
439
542
  const patchedProgram = ts.createProgram({
440
543
  host: overrideHost,
544
+ oldProgram: preparedProgram.program,
441
545
  options: preparedProgram.options,
442
546
  rootNames: preparedProgram.rootNames.map((fileName) => preparedProgram.toProgramFileName(fileName)),
443
547
  });
@@ -747,11 +851,29 @@ export function resolveExprArgumentOperand(preparedProgram, resolved, index, nes
747
851
  const replacement = resolved.placeholder.replacement;
748
852
  const preparedFile = resolved.placeholder.preparedFile;
749
853
  const operandText = preparedFile.originalText.slice(exprSpan.start, exprSpan.end);
750
- const nestedExpansion = materializeNestedOperandExpansion(preparedProgram, resolved, operandText, nestedRegistries) ?? { expressionText: operandText, preludeTexts: [] };
854
+ const nestedExpansion = exprArgumentContainsNestedMacroInvocation(resolved.placeholder.invocation.fileName, preparedFile.originalText, exprSpan, nestedRegistries)
855
+ ? materializeNestedOperandExpansion(preparedProgram, resolved, operandText, nestedRegistries) ?? { expressionText: operandText, preludeTexts: [] }
856
+ : { expressionText: operandText, preludeTexts: [] };
751
857
  if (!nestedExpansion) {
752
858
  return null;
753
859
  }
754
860
  const expandedText = nestedExpansion.expressionText;
861
+ if (nestedExpansion.preludeTexts.length === 0 && expandedText === operandText) {
862
+ const sharedSource = getSharedExprOperandFileSource(preparedProgram, preparedFile, resolved.placeholder.invocation.fileName, index, nestedRegistries);
863
+ const sharedSpan = sharedSource?.spansByPlaceholderId.get(resolved.placeholder.id);
864
+ if (sharedSource && sharedSpan) {
865
+ const patchedExpression = findExpressionAtSpan(sharedSource.sourceFile, sharedSpan.start, sharedSpan.end);
866
+ if (patchedExpression) {
867
+ return {
868
+ expandedText,
869
+ preludeTexts: nestedExpansion.preludeTexts,
870
+ node: patchedExpression,
871
+ semantics: sharedSource.semantics,
872
+ sourceFile: sharedSource.sourceFile,
873
+ };
874
+ }
875
+ }
876
+ }
755
877
  const containingStatement = findContainingStatement(resolved.callExpression);
756
878
  const programReplacementStart = mapStageOnePositionToProgram(preparedFile, replacement.rewrittenSpan.start);
757
879
  const programReplacementEnd = mapStageOnePositionToProgram(preparedFile, replacement.rewrittenSpan.end);
@@ -787,6 +909,7 @@ export function resolveExprArgumentOperand(preparedProgram, resolved, index, nes
787
909
  };
788
910
  const patchedProgram = ts.createProgram({
789
911
  host: overrideHost,
912
+ oldProgram: preparedProgram.program,
790
913
  options: preparedProgram.options,
791
914
  rootNames: preparedProgram.rootNames.map((fileName) => preparedProgram.toProgramFileName(fileName)),
792
915
  });
@@ -30,6 +30,12 @@ export interface ResolvedExprArgumentOperand {
30
30
 
31
31
  export interface ResolvedPrimaryExprOperand extends ResolvedExprArgumentOperand {}
32
32
 
33
+ interface SharedExprOperandFileSource {
34
+ semantics: ReturnType<typeof createMacroSemantics>;
35
+ sourceFile: ts.SourceFile;
36
+ spansByPlaceholderId: ReadonlyMap<number, SourceSpan>;
37
+ }
38
+
33
39
  export interface MaterializedMacroMappingSegment {
34
40
  generatedEnd: number;
35
41
  generatedStart: number;
@@ -73,6 +79,10 @@ export interface NestedMacroHoverTarget {
73
79
  }
74
80
 
75
81
  const COMPLETION_PLACEHOLDER_IDENTIFIER = '__sts_completion_target';
82
+ const sharedExprOperandFileSourceCache = new WeakMap<
83
+ PreparedProgram,
84
+ Map<string, SharedExprOperandFileSource | null>
85
+ >();
76
86
 
77
87
  function isNestedMacroHoverTarget(
78
88
  value: MaterializedMacroHoverRegion | NestedMacroHoverTarget,
@@ -106,6 +116,36 @@ function getExprArgumentSpan(
106
116
  return argument?.kind === 'ExprArg' ? argument.span : undefined;
107
117
  }
108
118
 
119
+ function exprArgumentContainsNestedMacroInvocation(
120
+ fileName: string,
121
+ originalText: string,
122
+ exprSpan: SourceSpan,
123
+ nestedRegistries: NestedMacroRegistries,
124
+ ): boolean {
125
+ const sourceFile = ts.createSourceFile(
126
+ fileName,
127
+ originalText,
128
+ ts.ScriptTarget.Latest,
129
+ true,
130
+ ts.ScriptKind.TSX,
131
+ );
132
+ const importPrelude = sourceFile.statements
133
+ .filter(ts.isImportDeclaration)
134
+ .map((statement) => statement.getText(sourceFile))
135
+ .join('\n');
136
+ const operandText = originalText.slice(exprSpan.start, exprSpan.end);
137
+ const probeSourceText = importPrelude.length > 0
138
+ ? `${importPrelude}\nconst __sts_nested_probe = ${operandText};\n`
139
+ : `const __sts_nested_probe = ${operandText};\n`;
140
+
141
+ return rewriteMacroSource(
142
+ fileName,
143
+ probeSourceText,
144
+ nestedRegistries.siteKindsBySpecifier ?? new Map(),
145
+ getAlwaysAvailableBuiltinMacroSiteKinds(),
146
+ ).replacements.length > 0;
147
+ }
148
+
109
149
  function _getExpressionArgumentSpan(
110
150
  invocation: ParsedMacroInvocation,
111
151
  expressionArgumentIndex: number,
@@ -507,6 +547,7 @@ function expandNestedOperandSource(
507
547
  };
508
548
  const finalProgram = ts.createProgram({
509
549
  host: overrideHost,
550
+ oldProgram: nestedPreparedProgram.program,
510
551
  options: nestedPreparedProgram.options,
511
552
  rootNames: nestedPreparedProgram.rootNames.map((fileName) =>
512
553
  nestedPreparedProgram.toProgramFileName(fileName)
@@ -733,6 +774,153 @@ function findExpressionAtSpan(
733
774
  return found;
734
775
  }
735
776
 
777
+ function sharedExprOperandFileSourceCacheKey(
778
+ rewrittenProgramFileName: string,
779
+ index: number,
780
+ ): string {
781
+ return `${rewrittenProgramFileName}\0${index}`;
782
+ }
783
+
784
+ function buildSharedExprOperandFileSource(
785
+ preparedProgram: PreparedProgram,
786
+ preparedFile: PreparedSourceFile,
787
+ sourceFileName: string,
788
+ index: number,
789
+ nestedRegistries: NestedMacroRegistries,
790
+ ): SharedExprOperandFileSource | null {
791
+ const rewrittenProgramFileName = preparedProgram.toProgramFileName(sourceFileName);
792
+ const replacements: Array<{ end: number; id: number; operandText: string; start: number }> = [];
793
+ for (const replacement of preparedFile.rewriteResult.replacements) {
794
+ const invocation = preparedFile.rewriteResult.macrosById.get(replacement.id);
795
+ if (!invocation) {
796
+ continue;
797
+ }
798
+
799
+ const exprSpan = getExprArgumentSpan(invocation, index);
800
+ if (!exprSpan) {
801
+ continue;
802
+ }
803
+
804
+ if (
805
+ exprArgumentContainsNestedMacroInvocation(
806
+ sourceFileName,
807
+ preparedFile.originalText,
808
+ exprSpan,
809
+ nestedRegistries,
810
+ )
811
+ ) {
812
+ continue;
813
+ }
814
+
815
+ const operandText = preparedFile.originalText.slice(exprSpan.start, exprSpan.end);
816
+ replacements.push({
817
+ id: replacement.id,
818
+ end: mapStageOnePositionToProgram(preparedFile, replacement.rewrittenSpan.end),
819
+ operandText,
820
+ start: mapStageOnePositionToProgram(preparedFile, replacement.rewrittenSpan.start),
821
+ });
822
+ }
823
+ replacements.sort((left, right) => left.start - right.start);
824
+
825
+ if (replacements.length === 0) {
826
+ return null;
827
+ }
828
+
829
+ let patchedText = '';
830
+ let cursor = 0;
831
+ const spansByPlaceholderId = new Map<number, SourceSpan>();
832
+ for (const replacement of replacements) {
833
+ patchedText += preparedFile.rewrittenText.slice(cursor, replacement.start);
834
+ const patchedStart = patchedText.length;
835
+ patchedText += replacement.operandText;
836
+ spansByPlaceholderId.set(replacement.id, {
837
+ end: patchedStart + replacement.operandText.length,
838
+ fileName: sourceFileName,
839
+ start: patchedStart,
840
+ });
841
+ cursor = replacement.end;
842
+ }
843
+ patchedText += preparedFile.rewrittenText.slice(cursor);
844
+
845
+ const overrideHost: ts.CompilerHost = {
846
+ ...preparedProgram.preparedHost.host,
847
+ getSourceFile(
848
+ fileName: string,
849
+ languageVersion: ts.ScriptTarget | ts.CreateSourceFileOptions,
850
+ onError?: (message: string) => void,
851
+ shouldCreateNewSourceFile?: boolean,
852
+ ): ts.SourceFile | undefined {
853
+ if (fileName === rewrittenProgramFileName) {
854
+ return ts.createSourceFile(fileName, patchedText, languageVersion, true);
855
+ }
856
+
857
+ return preparedProgram.preparedHost.host.getSourceFile(
858
+ fileName,
859
+ languageVersion,
860
+ onError,
861
+ shouldCreateNewSourceFile,
862
+ );
863
+ },
864
+ readFile(fileName: string): string | undefined {
865
+ if (fileName === rewrittenProgramFileName) {
866
+ return patchedText;
867
+ }
868
+
869
+ return preparedProgram.preparedHost.host.readFile(fileName);
870
+ },
871
+ };
872
+
873
+ const patchedProgram = ts.createProgram({
874
+ host: overrideHost,
875
+ oldProgram: preparedProgram.program,
876
+ options: preparedProgram.options,
877
+ rootNames: preparedProgram.rootNames.map((fileName) =>
878
+ preparedProgram.toProgramFileName(fileName)
879
+ ),
880
+ });
881
+ const patchedSourceFile = patchedProgram.getSourceFile(rewrittenProgramFileName);
882
+ if (!patchedSourceFile) {
883
+ return null;
884
+ }
885
+
886
+ return {
887
+ semantics: createMacroSemantics(patchedProgram),
888
+ sourceFile: patchedSourceFile,
889
+ spansByPlaceholderId,
890
+ };
891
+ }
892
+
893
+ function getSharedExprOperandFileSource(
894
+ preparedProgram: PreparedProgram,
895
+ preparedFile: PreparedSourceFile,
896
+ sourceFileName: string,
897
+ index: number,
898
+ nestedRegistries: NestedMacroRegistries,
899
+ ): SharedExprOperandFileSource | null {
900
+ let programCache = sharedExprOperandFileSourceCache.get(preparedProgram);
901
+ if (!programCache) {
902
+ programCache = new Map();
903
+ sharedExprOperandFileSourceCache.set(preparedProgram, programCache);
904
+ }
905
+
906
+ const rewrittenProgramFileName = preparedProgram.toProgramFileName(sourceFileName);
907
+ const cacheKey = sharedExprOperandFileSourceCacheKey(rewrittenProgramFileName, index);
908
+ if (!programCache.has(cacheKey)) {
909
+ programCache.set(
910
+ cacheKey,
911
+ buildSharedExprOperandFileSource(
912
+ preparedProgram,
913
+ preparedFile,
914
+ sourceFileName,
915
+ index,
916
+ nestedRegistries,
917
+ ),
918
+ );
919
+ }
920
+
921
+ return programCache.get(cacheKey) ?? null;
922
+ }
923
+
736
924
  function findDeepestNodeContainingPosition(node: ts.Node, position: number): ts.Node | undefined {
737
925
  if (position < node.getFullStart() || position >= node.getEnd()) {
738
926
  return undefined;
@@ -805,6 +993,7 @@ function createPatchedMacroSource(
805
993
 
806
994
  const patchedProgram = ts.createProgram({
807
995
  host: overrideHost,
996
+ oldProgram: preparedProgram.program,
808
997
  options: preparedProgram.options,
809
998
  rootNames: preparedProgram.rootNames.map((fileName) =>
810
999
  preparedProgram.toProgramFileName(fileName)
@@ -1325,16 +1514,50 @@ export function resolveExprArgumentOperand(
1325
1514
  const replacement = resolved.placeholder.replacement;
1326
1515
  const preparedFile = resolved.placeholder.preparedFile;
1327
1516
  const operandText = preparedFile.originalText.slice(exprSpan.start, exprSpan.end);
1328
- const nestedExpansion = materializeNestedOperandExpansion(
1329
- preparedProgram,
1330
- resolved,
1331
- operandText,
1332
- nestedRegistries,
1333
- ) ?? { expressionText: operandText, preludeTexts: [] as readonly string[] };
1517
+ const nestedExpansion = exprArgumentContainsNestedMacroInvocation(
1518
+ resolved.placeholder.invocation.fileName,
1519
+ preparedFile.originalText,
1520
+ exprSpan,
1521
+ nestedRegistries,
1522
+ )
1523
+ ? materializeNestedOperandExpansion(
1524
+ preparedProgram,
1525
+ resolved,
1526
+ operandText,
1527
+ nestedRegistries,
1528
+ ) ?? { expressionText: operandText, preludeTexts: [] as readonly string[] }
1529
+ : { expressionText: operandText, preludeTexts: [] as readonly string[] };
1334
1530
  if (!nestedExpansion) {
1335
1531
  return null;
1336
1532
  }
1337
1533
  const expandedText = nestedExpansion.expressionText;
1534
+ if (nestedExpansion.preludeTexts.length === 0 && expandedText === operandText) {
1535
+ const sharedSource = getSharedExprOperandFileSource(
1536
+ preparedProgram,
1537
+ preparedFile,
1538
+ resolved.placeholder.invocation.fileName,
1539
+ index,
1540
+ nestedRegistries,
1541
+ );
1542
+ const sharedSpan = sharedSource?.spansByPlaceholderId.get(resolved.placeholder.id);
1543
+ if (sharedSource && sharedSpan) {
1544
+ const patchedExpression = findExpressionAtSpan(
1545
+ sharedSource.sourceFile,
1546
+ sharedSpan.start,
1547
+ sharedSpan.end,
1548
+ );
1549
+ if (patchedExpression) {
1550
+ return {
1551
+ expandedText,
1552
+ preludeTexts: nestedExpansion.preludeTexts,
1553
+ node: patchedExpression,
1554
+ semantics: sharedSource.semantics,
1555
+ sourceFile: sharedSource.sourceFile,
1556
+ };
1557
+ }
1558
+ }
1559
+ }
1560
+
1338
1561
  const containingStatement = findContainingStatement(resolved.callExpression);
1339
1562
  const programReplacementStart = mapStageOnePositionToProgram(
1340
1563
  preparedFile,
@@ -1397,6 +1620,7 @@ export function resolveExprArgumentOperand(
1397
1620
 
1398
1621
  const patchedProgram = ts.createProgram({
1399
1622
  host: overrideHost,
1623
+ oldProgram: preparedProgram.program,
1400
1624
  options: preparedProgram.options,
1401
1625
  rootNames: preparedProgram.rootNames.map((fileName) =>
1402
1626
  preparedProgram.toProgramFileName(fileName)
@@ -41,9 +41,9 @@ export function resolveMacroPlaceholdersInSourceFile(sourceFile, placeholderInde
41
41
  visit(sourceFile);
42
42
  return resolved;
43
43
  }
44
- export function collectResolvedMacroPlaceholders(preparedProgram) {
44
+ export function collectResolvedMacroPlaceholders(preparedProgram, sourceFiles = preparedProgram.program.getSourceFiles()) {
45
45
  const placeholderIndex = preparedProgram.placeholderIndex();
46
- const collected = preparedProgram.program.getSourceFiles()
46
+ const collected = sourceFiles
47
47
  .filter((sourceFile) => !sourceFile.isDeclarationFile)
48
48
  .flatMap((sourceFile) => {
49
49
  const sourceFileName = preparedProgram.toSourceFileName(sourceFile.fileName);
@@ -90,9 +90,10 @@ export function resolveMacroPlaceholdersInSourceFile(
90
90
 
91
91
  export function collectResolvedMacroPlaceholders(
92
92
  preparedProgram: MacroResolverPreparedProgram,
93
+ sourceFiles = preparedProgram.program.getSourceFiles(),
93
94
  ): CollectedResolvedMacroPlaceholder[] {
94
95
  const placeholderIndex = preparedProgram.placeholderIndex();
95
- const collected = preparedProgram.program.getSourceFiles()
96
+ const collected = sourceFiles
96
97
  .filter((sourceFile) => !sourceFile.isDeclarationFile)
97
98
  .flatMap((sourceFile) => {
98
99
  const sourceFileName = preparedProgram.toSourceFileName(sourceFile.fileName);
@@ -1233,6 +1233,17 @@ export function createProjectMacroEnvironment(preparedProgram, builtinDefinition
1233
1233
  return cached;
1234
1234
  }
1235
1235
  const macroNames = macroNamesForFile(sourceFile);
1236
+ if (macroNames.size === 0) {
1237
+ const emptyBindings = {
1238
+ advancedRegistry: new Map(),
1239
+ definitions: new Map(),
1240
+ importedBindingUsage: new Map(),
1241
+ registry: new Map(),
1242
+ siteKindsBySpecifier: new Map(),
1243
+ };
1244
+ bindingsByFile.set(sourceFile.fileName, emptyBindings);
1245
+ return emptyBindings;
1246
+ }
1236
1247
  const definitions = new Map();
1237
1248
  const registry = new Map();
1238
1249
  const advancedRegistry = new Map();
@@ -1372,7 +1383,18 @@ export function createProjectMacroEnvironment(preparedProgram, builtinDefinition
1372
1383
  const registriesByFile = new Map();
1373
1384
  const bindingUsageByFile = new Map();
1374
1385
  const hasBindingsByFile = new Map();
1386
+ const expandedFiles = new Map();
1387
+ const macroSourceFiles = [];
1375
1388
  for (const sourceFile of sourceFiles) {
1389
+ const macroNames = macroNamesForFile(sourceFile);
1390
+ if (macroNames.size === 0) {
1391
+ const sourceFileName = preparedProgram.toSourceFileName(sourceFile.fileName);
1392
+ const preparedSource = preparedProgram.preparedHost.getPreparedSourceFile(sourceFileName);
1393
+ expandedFiles.set(sourceFile.fileName, preparedSource
1394
+ ? ts.createSourceFile(sourceFile.fileName, preparedSource.originalText, preparedProgram.options.target ?? ts.ScriptTarget.Latest, true, scriptKindForHostFile(sourceFile.fileName))
1395
+ : sourceFile);
1396
+ continue;
1397
+ }
1376
1398
  const bindings = bindingsForSourceFile(sourceFile);
1377
1399
  registriesByFile.set(sourceFile.fileName, {
1378
1400
  registry: bindings.registry,
@@ -1381,21 +1403,23 @@ export function createProjectMacroEnvironment(preparedProgram, builtinDefinition
1381
1403
  });
1382
1404
  bindingUsageByFile.set(sourceFile.fileName, bindings.importedBindingUsage);
1383
1405
  hasBindingsByFile.set(sourceFile.fileName, hasResolvedMacroBindings(bindings));
1406
+ macroSourceFiles.push(sourceFile);
1384
1407
  }
1385
- const expanded = expandPreparedProgramWithFileRegistries(preparedProgram, registriesByFile, preserveMissingExpanders, annotateExpansions);
1386
- const stripped = new Map();
1408
+ const expanded = macroSourceFiles.length > 0
1409
+ ? expandPreparedProgramWithFileRegistries(preparedProgram, registriesByFile, preserveMissingExpanders, annotateExpansions, macroSourceFiles)
1410
+ : new Map();
1387
1411
  for (const [fileName, sourceFile] of expanded.entries()) {
1388
1412
  if (!hasBindingsByFile.get(fileName)) {
1389
1413
  const sourceFileName = preparedProgram.toSourceFileName(fileName);
1390
1414
  const preparedSource = preparedProgram.preparedHost.getPreparedSourceFile(sourceFileName);
1391
- stripped.set(fileName, preparedSource
1415
+ expandedFiles.set(fileName, preparedSource
1392
1416
  ? ts.createSourceFile(fileName, preparedSource.originalText, preparedProgram.options.target ?? ts.ScriptTarget.Latest, true, scriptKindForHostFile(fileName))
1393
1417
  : sourceFile);
1394
1418
  continue;
1395
1419
  }
1396
- stripped.set(fileName, stripCompileTimeOnlyImportedBindings(sourceFile, bindingUsageByFile.get(fileName) ?? new Map(), preserveRemovedImportStatements));
1420
+ expandedFiles.set(fileName, stripCompileTimeOnlyImportedBindings(sourceFile, bindingUsageByFile.get(fileName) ?? new Map(), preserveRemovedImportStatements));
1397
1421
  }
1398
- return stripped;
1422
+ return expandedFiles;
1399
1423
  },
1400
1424
  };
1401
1425
  }
@@ -1895,6 +1895,18 @@ export function createProjectMacroEnvironment(
1895
1895
  }
1896
1896
 
1897
1897
  const macroNames = macroNamesForFile(sourceFile);
1898
+ if (macroNames.size === 0) {
1899
+ const emptyBindings = {
1900
+ advancedRegistry: new Map<string, AdvancedMacroExpander>(),
1901
+ definitions: new Map<string, MacroDefinition>(),
1902
+ importedBindingUsage: new Map<string, ImportedBindingUsage>(),
1903
+ registry: new Map<string, RewriteMacroExpander>(),
1904
+ siteKindsBySpecifier: new Map<string, Map<string, ImportedMacroSiteKind>>(),
1905
+ };
1906
+ bindingsByFile.set(sourceFile.fileName, emptyBindings);
1907
+ return emptyBindings;
1908
+ }
1909
+
1898
1910
  const definitions = new Map<string, MacroDefinition>();
1899
1911
  const registry = new Map<string, RewriteMacroExpander>();
1900
1912
  const advancedRegistry = new Map<string, AdvancedMacroExpander>();
@@ -2084,8 +2096,29 @@ export function createProjectMacroEnvironment(
2084
2096
  >();
2085
2097
  const bindingUsageByFile = new Map<string, ReadonlyMap<string, ImportedBindingUsage>>();
2086
2098
  const hasBindingsByFile = new Map<string, boolean>();
2099
+ const expandedFiles = new Map<string, ts.SourceFile>();
2100
+ const macroSourceFiles: ts.SourceFile[] = [];
2087
2101
 
2088
2102
  for (const sourceFile of sourceFiles) {
2103
+ const macroNames = macroNamesForFile(sourceFile);
2104
+ if (macroNames.size === 0) {
2105
+ const sourceFileName = preparedProgram.toSourceFileName(sourceFile.fileName);
2106
+ const preparedSource = preparedProgram.preparedHost.getPreparedSourceFile(sourceFileName);
2107
+ expandedFiles.set(
2108
+ sourceFile.fileName,
2109
+ preparedSource
2110
+ ? ts.createSourceFile(
2111
+ sourceFile.fileName,
2112
+ preparedSource.originalText,
2113
+ preparedProgram.options.target ?? ts.ScriptTarget.Latest,
2114
+ true,
2115
+ scriptKindForHostFile(sourceFile.fileName),
2116
+ )
2117
+ : sourceFile,
2118
+ );
2119
+ continue;
2120
+ }
2121
+
2089
2122
  const bindings = bindingsForSourceFile(sourceFile);
2090
2123
  registriesByFile.set(sourceFile.fileName, {
2091
2124
  registry: bindings.registry,
@@ -2094,20 +2127,23 @@ export function createProjectMacroEnvironment(
2094
2127
  });
2095
2128
  bindingUsageByFile.set(sourceFile.fileName, bindings.importedBindingUsage);
2096
2129
  hasBindingsByFile.set(sourceFile.fileName, hasResolvedMacroBindings(bindings));
2130
+ macroSourceFiles.push(sourceFile);
2097
2131
  }
2098
2132
 
2099
- const expanded = expandPreparedProgramWithFileRegistries(
2100
- preparedProgram,
2101
- registriesByFile,
2102
- preserveMissingExpanders,
2103
- annotateExpansions,
2104
- );
2105
- const stripped = new Map<string, ts.SourceFile>();
2133
+ const expanded = macroSourceFiles.length > 0
2134
+ ? expandPreparedProgramWithFileRegistries(
2135
+ preparedProgram,
2136
+ registriesByFile,
2137
+ preserveMissingExpanders,
2138
+ annotateExpansions,
2139
+ macroSourceFiles,
2140
+ )
2141
+ : new Map<string, ts.SourceFile>();
2106
2142
  for (const [fileName, sourceFile] of expanded.entries()) {
2107
2143
  if (!hasBindingsByFile.get(fileName)) {
2108
2144
  const sourceFileName = preparedProgram.toSourceFileName(fileName);
2109
2145
  const preparedSource = preparedProgram.preparedHost.getPreparedSourceFile(sourceFileName);
2110
- stripped.set(
2146
+ expandedFiles.set(
2111
2147
  fileName,
2112
2148
  preparedSource
2113
2149
  ? ts.createSourceFile(
@@ -2122,7 +2158,7 @@ export function createProjectMacroEnvironment(
2122
2158
  continue;
2123
2159
  }
2124
2160
 
2125
- stripped.set(
2161
+ expandedFiles.set(
2126
2162
  fileName,
2127
2163
  stripCompileTimeOnlyImportedBindings(
2128
2164
  sourceFile,
@@ -2131,7 +2167,7 @@ export function createProjectMacroEnvironment(
2131
2167
  ),
2132
2168
  );
2133
2169
  }
2134
- return stripped;
2170
+ return expandedFiles;
2135
2171
  },
2136
2172
  };
2137
2173
  }