All files / transpiler/types IDeclaredFile.ts

0% Statements 0/0
0% Branches 0/0
0% Functions 0/0
0% Lines 0/0

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99                                                                                                                                                                                                     
import type { CommonTokenStream } from "antlr4ng";
 
import type { ProgramContext } from "../logic/parser/grammar/CNextParser";
import type TSymbol from "./symbols/TSymbol";
 
/**
 * One C-Next file's parse and declare, computed once and consumed twice.
 *
 * #1301: stage 3 and stage 5 each parsed and declared every `.cnx` in the run,
 * with `SymbolRegistry.reset()` running once per run rather than between them.
 * The two agreed, but only because `_sortFilesByDependency` orders files so that
 * an included file is declared before its includer -- which made the seed each
 * declare reads (`_collectExternalEnumSources`) identical by the time either ran.
 * #1167 records that a dependency cycle makes that order arbitrary, so the
 * agreement was a coincidence rather than a shared decision.
 *
 * Measured before removing it, over all 1142 `tests/**` fixtures: 1225 files were
 * resolved twice, the seed differed in 0 of them, and the resolved symbols
 * differed in 4 -- every one a spanned scope whose shared `IScopeSymbol` had
 * accumulated more `declarationSites` in between, reached by the SAME object
 * identity in both passes (175/175). So the second pass recomputed what the first
 * already knew.
 */
interface IDeclaredFile {
  /** The parse tree, walked by the analyzers and the generator. */
  readonly tree: ProgramContext;
 
  /**
   * The token stream that produced `tree`.
   *
   * Stage 3 discarded this and stage 5 obtained a fresh one from its own parse.
   * `runAnalyzers` needs the stream that belongs to the tree it walks, so the
   * two must be cached together or not at all.
   */
  readonly tokenStream: CommonTokenStream;
 
  /** Top-level declaration count, reported in parse-only and error results. */
  readonly declarationCount: number;
 
  /**
   * Symbols declared by this file (pass 1.3).
   *
   * Stage 3 and stage 5 now hand the SAME objects to the symbol table and to
   * codegen. What makes that safe is NOT that the symbols are frozen. `readonly`
   * blocks reassigning a property, not mutating what it points at, and three of
   * `IScopeSymbol`'s fields are mutated in production by design:
   * `SymbolRegistry.registerFunction` pushes onto `scope.functions`, and
   * `ScopeCollector` casts the readonly view away to add a `declarationSites`
   * entry and a `members` entry. The four spanned-scope files whose symbols
   * differed between the two passes differed for exactly that reason -- so a
   * reader who took "readonly, therefore frozen" at face value would be reasoning
   * from a claim this file's own measurements disprove.
   *
   * Two measured properties make the sharing safe instead:
   *
   * 1. `SymbolRegistry.getOrCreateScope` caches by path and `reset()` runs once
   *    per run, so BOTH passes already reached the same `IScopeSymbol` by object
   *    identity -- 175/175 over the cross-file fixtures. Caching introduces no
   *    aliasing the double pass did not already have.
   * 2. Every mutation of that shared object is additive and idempotent: `Set.add`
   *    for `declarationSites`, the `members` dedup from #1334, and
   *    `registerFunction`'s `isAlreadyRegistered` guard. A later mutation can
   *    therefore never invalidate what an earlier reader already saw.
   *
   * There is a FOURTH mutation site, and it is neither additive nor idempotent:
   * `SymbolTable.resolveVariableArrayDimensions` (reached from
   * `resolveExternalArrayDimensions()` in stage 3b) casts the readonly view away and
   * REPLACES `IVariableSymbol.arrayDimensions` wholesale. It fires BETWEEN the cache
   * write and the cache read, so stage 5 converts post-mutation symbols where it
   * previously converted freshly resolved ones -- the sharing described here is what
   * makes that reachable.
   *
   * It is benign because `TSymbolInfoAdapter.convert` never reads that field, which
   * is asserted rather than remembered: "#1301: convert() must not read
   * arrayDimensions" in `TSymbolInfoAdapter.test.ts` converts two symbols differing
   * only in `arrayDimensions` and requires identical output. Without that assertion
   * this would be exactly the "harmless today only by coincidence" shape the
   * `ScopeCollector` comments condemn.
   *
   * The element type is `readonly` because nothing downstream mutates the array --
   * `TSymbolInfoAdapter.convert` and `SymbolTable.addTSymbols` both only iterate --
   * so the guarantee is structural rather than a convention to be remembered.
   */
  readonly symbols: readonly TSymbol[];
}
 
/*
 * Deliberately NOT cached here: `externalEnumSources` (pass 1.4). The tree and the
 * declare are pure functions of one file's text, but that field is a function of
 * how much of the RUN has happened -- it reads a map stage 3 fills incrementally.
 * Under a cyclic include graph the toposort fails, files are visited in insertion
 * order, and a stage 3 answer is therefore partial where the stage 5 one is whole.
 * Caching it regressed a mutually-including pair from compiling to E0427; it is
 * recomputed in `_transpileFile` instead. See
 * tests/bugs/issue-1301-cyclic-include-enum-sources/.
 */
 
export default IDeclaredFile;