All files / transpiler/output/codegen/helpers TransitiveModificationPropagator.ts

100% Statements 23/23
92.85% Branches 13/14
100% Functions 4/4
100% Lines 23/23

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                                                                              877x 877x 889x 889x 866x 65x           65x 15x                                 65x     65x 65x 2x     63x     63x             11x       52x                             63x 63x                     52x 52x 37x   15x 15x          
/**
 * TransitiveModificationPropagator
 *
 * Performs fixed-point iteration to propagate parameter modifications
 * transitively through a function call graph. If a parameter is passed to
 * a function that modifies its corresponding parameter, then the caller's
 * parameter is also considered modified.
 *
 * Issue #269: Extracted from CodeGenerator for improved testability.
 */
 
/**
 * Call info entry in the function call graph.
 * Represents a call from one function to another, tracking which parameter
 * of the caller was passed to which parameter position of the callee.
 */
interface ICallInfo {
  callee: string;
  paramIndex: number;
  argParamName: string;
}
 
class TransitiveModificationPropagator {
  /**
   * Propagate transitive parameter modifications through the call graph.
   *
   * Uses fixed-point iteration: if a parameter is passed to a function that
   * modifies its corresponding param, then the caller's parameter is also
   * considered modified. This continues until no more changes occur.
   *
   * @param functionCallGraph - Map of function name to call info array
   * @param functionParamLists - Map of function name to parameter name list
   * @param modifiedParameters - Map of function name to modified parameter set (mutated in place)
   */
  static propagate(
    functionCallGraph: ReadonlyMap<string, readonly ICallInfo[]>,
    functionParamLists: ReadonlyMap<string, string[]>,
    modifiedParameters: Map<string, Set<string>>,
  ): void {
    let changed = true;
    while (changed) {
      changed = false;
      for (const [funcName, calls] of functionCallGraph) {
        for (const call of calls) {
          const didPropagate = TransitiveModificationPropagator.propagateCall(
            funcName,
            call,
            functionParamLists,
            modifiedParameters,
          );
          if (didPropagate) {
            changed = true;
          }
        }
      }
    }
  }
 
  /**
   * Check if a single call propagates a modification from callee to caller.
   * Returns true if a new modification was added.
   */
  private static propagateCall(
    callerName: string,
    call: ICallInfo,
    functionParamLists: ReadonlyMap<string, string[]>,
    modifiedParameters: Map<string, Set<string>>,
  ): boolean {
    const { callee, paramIndex, argParamName } = call;
 
    // Get the callee's parameter list
    const calleeParams = functionParamLists.get(callee);
    if (!calleeParams || paramIndex >= calleeParams.length) {
      return false;
    }
 
    const calleeParamName = calleeParams[paramIndex];
 
    // Check if callee modifies this parameter
    if (
      !TransitiveModificationPropagator.isParamModified(
        callee,
        calleeParamName,
        modifiedParameters,
      )
    ) {
      return false;
    }
 
    // Mark caller's parameter as modified if not already
    return TransitiveModificationPropagator.markParamModified(
      callerName,
      argParamName,
      modifiedParameters,
    );
  }
 
  /**
   * Check if a function's parameter is in the modified set.
   */
  private static isParamModified(
    funcName: string,
    paramName: string,
    modifiedParameters: ReadonlyMap<string, Set<string>>,
  ): boolean {
    const modified = modifiedParameters.get(funcName);
    return modified?.has(paramName) ?? false;
  }
 
  /**
   * Mark a function's parameter as modified. Returns true if newly added.
   */
  private static markParamModified(
    funcName: string,
    paramName: string,
    modifiedParameters: Map<string, Set<string>>,
  ): boolean {
    const modified = modifiedParameters.get(funcName);
    if (!modified || modified.has(paramName)) {
      return false;
    }
    modified.add(paramName);
    return true;
  }
}
 
export default TransitiveModificationPropagator;