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

77.77% Statements 28/36
66.66% Branches 20/30
100% Functions 8/8
77.77% Lines 28/36

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 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192                                    22x                 22x         22x         22x                                                                     52x 7x       45x 7x     38x 38x     38x         38x                       37x 5x       32x 5x       27x 37x                           75x 17x   58x             963x             964x                     481x 481x 481x 481x     481x                                                            
/**
 * NarrowingCastHelper - MISRA C:2012 Rule 10.3 compliance
 *
 * Issue #845: Wraps expressions with explicit casts when assigning
 * to narrower types or different essential type categories.
 *
 * C's integer promotion rules mean bit operations on u8/u16 produce int,
 * which MISRA flags when assigned back to narrower types without explicit cast.
 */
 
import TYPE_WIDTH from "../types/TYPE_WIDTH.js";
import CppModeHelper from "./CppModeHelper.js";
import TYPE_MAP from "../types/TYPE_MAP.js";
 
/**
 * Extended type widths including C's promoted "int" type.
 * The shared TYPE_WIDTH doesn't include "int" since it's not a C-Next type.
 */
const EXTENDED_TYPE_WIDTH: Record<string, number> = {
  ...TYPE_WIDTH,
  int: 32, // C's int after promotion
};
 
/**
 * Types that get promoted to int in C's integer promotion rules.
 * In C, operations on types smaller than int get promoted to int.
 */
const PROMOTED_TO_INT = new Set(["u8", "i8", "u16", "i16", "bool"]);
 
/**
 * Float types for cross-category detection.
 */
const FLOAT_TYPES = new Set(["f32", "f64", "float", "double"]);
 
/**
 * Integer types for cross-category detection.
 */
const INTEGER_TYPES = new Set([
  "u8",
  "u16",
  "u32",
  "u64",
  "i8",
  "i16",
  "i32",
  "i64",
  "uint8_t",
  "uint16_t",
  "uint32_t",
  "uint64_t",
  "int8_t",
  "int16_t",
  "int32_t",
  "int64_t",
  "int",
]);
 
/**
 * Helper for adding MISRA 10.3 compliant casts to generated C code.
 */
class NarrowingCastHelper {
  /**
   * Check if a cast is needed for MISRA 10.3 compliance.
   * Returns true if:
   * - Source is wider than target (narrowing)
   * - Source and target are different essential type categories
   *
   * @param sourceType - Type of the expression (C-Next type or "int" for promoted)
   * @param targetType - Type of the target variable (C-Next type)
   */
  static needsCast(sourceType: string, targetType: string): boolean {
    // Same type never needs cast
    if (sourceType === targetType) {
      return false;
    }
 
    // Bool target from non-bool source always needs conversion
    if (targetType === "bool" && sourceType !== "bool") {
      return true;
    }
 
    const sourceWidth = EXTENDED_TYPE_WIDTH[sourceType];
    const targetWidth = EXTENDED_TYPE_WIDTH[targetType];
 
    // Unknown types - be conservative, no cast
    Iif (sourceWidth === undefined || targetWidth === undefined) {
      return false;
    }
 
    // Narrowing: source wider than target
    return sourceWidth > targetWidth;
  }
 
  /**
   * Wrap expression with cast if needed for MISRA 10.3 compliance.
   *
   * @param expr - The generated C expression
   * @param sourceType - Type of the expression (C-Next type or "int")
   * @param targetType - Type of the assignment target (C-Next type)
   * @returns Expression with cast wrapper if needed, or original expression
   */
  static wrap(expr: string, sourceType: string, targetType: string): string {
    if (!NarrowingCastHelper.needsCast(sourceType, targetType)) {
      return expr;
    }
 
    // Bool target: use comparison instead of cast (MISRA 10.5)
    if (targetType === "bool") {
      return `((${expr}) != 0U)`;
    }
 
    // Get C type name for the target
    const cType = TYPE_MAP[targetType] ?? targetType;
    return CppModeHelper.cast(cType, expr);
  }
 
  /**
   * Determine the result type of C integer promotion for a given type.
   *
   * In C, operations on types smaller than int are promoted:
   * - u8, i8, u16, i16, bool -> int (32-bit)
   * - u32, i32, u64, i64 -> no promotion (already >= int width)
   *
   * @param baseType - The C-Next type of the operand
   * @returns "int" for promoted types, or the original type
   */
  static getPromotedType(baseType: string): string {
    if (PROMOTED_TO_INT.has(baseType)) {
      return "int";
    }
    return baseType;
  }
 
  /**
   * Check if a type is a floating-point type.
   */
  static isFloatType(typeName: string): boolean {
    return FLOAT_TYPES.has(typeName);
  }
 
  /**
   * Check if a type is an integer type.
   */
  static isIntegerType(typeName: string): boolean {
    return INTEGER_TYPES.has(typeName);
  }
 
  /**
   * Check if conversion between source and target is a cross-type-category conversion.
   * MISRA 10.3 requires explicit casts for different essential type categories.
   */
  static isCrossTypeCategoryConversion(
    sourceType: string,
    targetType: string,
  ): boolean {
    const sourceIsFloat = NarrowingCastHelper.isFloatType(sourceType);
    const targetIsFloat = NarrowingCastHelper.isFloatType(targetType);
    const sourceIsInt = NarrowingCastHelper.isIntegerType(sourceType);
    const targetIsInt = NarrowingCastHelper.isIntegerType(targetType);
 
    // Float to integer or integer to float
    return (sourceIsFloat && targetIsInt) || (sourceIsInt && targetIsFloat);
  }
 
  /**
   * Get the appropriate C float type for a C-Next type.
   */
  static getCFloatType(typeName: string): string {
    if (typeName === "f32" || typeName === "float") {
      return "float";
    }
    if (typeName === "f64" || typeName === "double") {
      return "double";
    }
    return "double"; // Default to double
  }
 
  /**
   * Wrap int-to-float conversion with explicit cast for MISRA 10.3.
   *
   * @param expr - The integer expression
   * @param targetType - The float target type (f32/f64)
   * @returns Expression with cast
   */
  static wrapIntToFloat(expr: string, targetType: string): string {
    const floatType = NarrowingCastHelper.getCFloatType(targetType);
    return CppModeHelper.cast(floatType, expr);
  }
}
 
export default NarrowingCastHelper;