All files / TRANSPILE/2-Plan CastRequirement.ts

100% Statements 13/13
100% Branches 14/14
100% Functions 2/2
100% Lines 12/12

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                                                            58x                           59x 6x       53x 12x     41x 41x           41x 1x       40x                                 33x   31x                
import TYPE_WIDTH from "../../types/TYPE_WIDTH";
import INTEGER_TYPES from "../../types/INTEGER_TYPES";
import FLOAT_TYPES from "../../types/FLOAT_TYPES";
 
/**
 * Does a conversion need an explicit cast? (MISRA C:2012 Rule 10.3)
 *
 * 2.2 Plan decides; 2.3 Render formats. This is the decision half of what
 * `NarrowingCastHelper` used to hold whole: whether a cast appears at all is a
 * choice about WHAT C exists, while wrapping the expression in one -- picking
 * `(uint8_t)` over `static_cast<uint8_t>`, or a `!= 0U` comparison for a bool
 * target -- is a choice about how that reads. The second needs `CppModeHelper`
 * and `TYPE_MAP`, both render-side; this needs neither, which is the test that
 * it belongs here (#1450 box 4).
 *
 * It was worth separating for a reason beyond tidiness. `output/` carried
 * THREE answers to "is this conversion narrowing?": this one, and two dead
 * copies on `CastValidator` and `TypeResolver` that disagreed with it and with
 * each other on an unknown width -- deleted in `01d00cbe`. A decision with one
 * live implementation and two dead ones is one edit away from the wrong one
 * being revived, and the surviving answer is the one the corpus exercises.
 */
class CastRequirement {
  /**
   * Type widths including C's promoted `int`.
   *
   * `TYPE_WIDTH` is the shared table and holds C-Next types only; `int` is
   * what C's integer promotion produces, so a comparison against a promoted
   * operand needs it and nothing else does.
   */
  private static readonly WIDTH: Record<string, number> = {
    ...TYPE_WIDTH,
    int: 32,
  };
 
  /**
   * True when converting `sourceType` to `targetType` must be written as an
   * explicit cast.
   *
   * @param sourceType - the expression's type, or `"int"` once promoted
   * @param targetType - the target's C-Next type
   */
  static forConversion(sourceType: string, targetType: string): boolean {
    // Same type never needs a cast.
    if (sourceType === targetType) {
      return false;
    }
 
    // A bool target from a non-bool source always needs conversion.
    if (targetType === "bool" && sourceType !== "bool") {
      return true;
    }
 
    const sourceWidth = CastRequirement.WIDTH[sourceType];
    const targetWidth = CastRequirement.WIDTH[targetType];
 
    // An unknown width is not evidence of narrowing. Being conservative here
    // is deliberate and is the point the two deleted copies disagreed on: one
    // of them reported narrowing whenever the TARGET was unknown, which adds a
    // cast to a conversion nobody can size.
    if (sourceWidth === undefined || targetWidth === undefined) {
      return false;
    }
 
    // Narrowing: the source is wider than the target.
    return sourceWidth > targetWidth;
  }
 
  /**
   * True when the conversion must be CLAMPED, not merely cast.
   *
   * Issue #632: a float whose value falls outside the target integer's range
   * is undefined behavior in C, so float-to-integer needs explicit bounds
   * checking rather than a cast. Folded in beside `forConversion` because they
   * are two answers to one question -- "what does this conversion need?" --
   * and they were two modules: `CastValidator` held this one alone after
   * `01d00cbe` deleted the six dead predicates around it.
   */
  static requiresClamping(
    sourceType: string | null,
    targetType: string,
  ): boolean {
    if (!sourceType) return false;
 
    return (
      (INTEGER_TYPES as readonly string[]).includes(targetType) &&
      (FLOAT_TYPES as readonly string[]).includes(sourceType)
    );
  }
}
 
export default CastRequirement;