All files / transpiler/logic/analysis ModificationAnalyzer.ts

100% Statements 18/18
100% Branches 4/4
100% Functions 7/7
100% Lines 18/18

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 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138                                                                          370x             370x                     607x 753x 753x   366x 29x       387x                             603x 748x   382x                       319x 319x                 619x                 616x                   3x               305x 305x          
/**
 * Modification Analyzer
 * Issue #593: Dedicated analyzer for cross-file parameter modification
 * tracking. Issue #1171: used by both C and C++ mode -- the analysis is
 * language-neutral, and gating it on C++ gave C mode a different answer to
 * "does this callee modify its parameter?".
 *
 * Accumulates function parameter modifications across multiple files for
 * cross-file const inference in C++ mode. This centralizes the accumulation
 * logic that was previously duplicated in Transpiler.
 *
 * Usage:
 * - Call accumulateResults() after analyzing each file
 * - Call getModifications()/getParamLists() to get accumulated state
 * - Call clear() between transpilation runs
 */
 
/**
 * Result type from CodeGenerator.analyzeModificationsOnly()
 */
interface IAnalysisResult {
  modifications: Map<string, Set<string>>;
  paramLists: Map<string, string[]>;
}
 
/**
 * Analyzer that tracks parameter modifications across files for C++ const inference.
 *
 * In C++ mode, we need to know which function parameters are modified so we can
 * generate correct const qualifiers. This analyzer accumulates that information
 * as files are processed, enabling cross-file transitive propagation.
 */
class ModificationAnalyzer {
  /**
   * Accumulated parameter modifications across all processed files.
   * Maps function name -> set of modified parameter names.
   */
  private readonly modifications: Map<string, Set<string>> = new Map();
 
  /**
   * Accumulated function parameter lists across all processed files.
   * Maps function name -> ordered list of parameter names.
   * Used for transitive propagation (mapping call argument indices to param names).
   */
  private readonly paramLists: Map<string, string[]> = new Map();
 
  /**
   * Accumulate modifications from analysis results.
   * Merges new modifications with existing ones (union of param sets).
   *
   * @param newModifications - Map of function name to modified param names
   */
  accumulateModifications(
    newModifications: ReadonlyMap<string, ReadonlySet<string>>,
  ): void {
    for (const [funcName, params] of newModifications) {
      const existing = this.modifications.get(funcName);
      if (existing) {
        // Merge: add all new params to existing set
        for (const param of params) {
          existing.add(param);
        }
      } else {
        // New function: create new set with copy of params
        this.modifications.set(funcName, new Set(params));
      }
    }
  }
 
  /**
   * Accumulate param lists from analysis results.
   * Uses first-wins semantics: once a function's param list is recorded,
   * subsequent attempts to set it are ignored.
   *
   * @param newParamLists - Map of function name to parameter name array
   */
  accumulateParamLists(
    newParamLists: ReadonlyMap<string, readonly string[]>,
  ): void {
    for (const [funcName, params] of newParamLists) {
      if (!this.paramLists.has(funcName)) {
        // Only set if not already present (first wins)
        this.paramLists.set(funcName, [...params]);
      }
    }
  }
 
  /**
   * Convenience method to accumulate both modifications and param lists.
   * This is the typical pattern when processing analysis results.
   *
   * @param results - Analysis results containing both modifications and param lists
   */
  accumulateResults(results: IAnalysisResult): void {
    this.accumulateModifications(results.modifications);
    this.accumulateParamLists(results.paramLists);
  }
 
  /**
   * Get readonly view of accumulated modifications.
   *
   * @returns Readonly map of function name to readonly set of modified params
   */
  getModifications(): ReadonlyMap<string, ReadonlySet<string>> {
    return this.modifications;
  }
 
  /**
   * Get readonly view of accumulated param lists.
   *
   * @returns Readonly map of function name to readonly param name array
   */
  getParamLists(): ReadonlyMap<string, readonly string[]> {
    return this.paramLists;
  }
 
  /**
   * Check if any modifications have been accumulated.
   * Useful for conditional logic in code generation.
   *
   * @returns true if there are any accumulated modifications
   */
  hasModifications(): boolean {
    return this.modifications.size > 0;
  }
 
  /**
   * Clear all accumulated state.
   * Call this between transpilation runs to reset the analyzer.
   */
  clear(): void {
    this.modifications.clear();
    this.paramLists.clear();
  }
}
 
export default ModificationAnalyzer;