All files / utils SubscriptClassifier.ts

100% Statements 12/12
100% Branches 16/16
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 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                                                                                                                              967x     967x 36x       931x   931x   523x       408x                                           937x     9x       928x 518x       410x          
/**
 * Issue #579 / Issue #1100: Shared subscript classifier for array vs bit access
 *
 * Its one caller is the operand typer (`OperandTyper.subscriptOf`), which
 * classifies every subscript it walks, for an assignment target and an
 * expression alike; the assignment classifier and the postfix render read
 * the kind off the typer's step (#1668, C12 and review).
 *
 * The classification rule is:
 * 1. If isArray or isString -> array access
 * 2. Otherwise -> bit manipulation
 *
 * This rule is identical for parameters and local variables. ADR-006 requires
 * array parameters to use explicit array syntax (`u8[8] buf`), which sets
 * isArray on the parameter's type info exactly like a local array declaration
 * does. A scalar-typed parameter (no array brackets) is therefore classified
 * the same way a scalar local variable is: bit manipulation (ADR-007 — "Any
 * integer type can be indexed to access individual bits", with no carve-out
 * for parameters).
 *
 * Issue #1100: A prior version of this classifier treated ANY function
 * parameter without explicit array syntax as array access (`isParameter &&
 * !isArray -> array`), added in #579 to support buffer-style parameters
 * declared without brackets (e.g. `void fillBuffer(u8 buf)`). That blanket
 * rule was a divergent decision path from local-variable classification: it
 * silently broke ADR-007 bit-indexing for scalar parameters (`u32 v; ...
 * v[4]` inside a function became `v[4]` array-subscript C code on a pointer
 * instead of a shift-and-mask bit read), producing invalid/incorrect C
 * for the extremely common embedded pattern of reading a bit out of a
 * hardware-register-shaped scalar parameter. Buffer-style parameters must now
 * use explicit array syntax (`u8[N] buf`), which was already the ADR-006
 * documented and supported spelling — see tests/params/param-array-indexing.test.cnx.
 */
import TSubscriptKind from "../types/TSubscriptKind";
import TTypeInfo from "../types/TTypeInfo";
 
/**
 * Context needed for subscript classification
 */
interface ISubscriptContext {
  /** Type information for the variable being subscripted, null if unknown */
  typeInfo: Pick<TTypeInfo, "isArray" | "isString"> | null;
  /** Number of subscript expressions (1 for single index, 2 for range/slice) */
  subscriptCount: number;
  /** Whether this is a register access (registers always use bit manipulation) */
  isRegisterAccess?: boolean;
}
 
/**
 * Classifies subscript operations as array or bit access.
 *
 * This class provides a single source of truth for determining whether
 * a subscript operation like `x[i]` or `x[a, b]` should be treated as
 * array element access or bit manipulation.
 */
class SubscriptClassifier {
  /**
   * Classify a subscript operation.
   *
   * @param ctx - Classification context with type info and subscript count
   * @returns The kind of subscript operation (array or bit access)
   */
  static classify(ctx: ISubscriptContext): TSubscriptKind {
    const { typeInfo, subscriptCount, isRegisterAccess } = ctx;
 
    // Registers always use bit manipulation
    if (isRegisterAccess) {
      return subscriptCount === 2 ? "bit_range" : "bit_single";
    }
 
    // Check if this should be array access
    const isArrayAccess = SubscriptClassifier.isArrayAccess(typeInfo);
 
    if (isArrayAccess) {
      // Slice vs element access
      return subscriptCount === 2 ? "array_slice" : "array_element";
    }
 
    // Default: bit manipulation
    return subscriptCount === 2 ? "bit_range" : "bit_single";
  }
 
  /**
   * Determine if a type should use array access semantics.
   *
   * Array access is used when:
   * - Type is explicitly an array (isArray: true) — including array
   *   parameters declared with explicit syntax, e.g. `u8[8] buf` (ADR-006)
   * - Type is a string (strings are char arrays)
   *
   * Parameters are NOT special-cased here (Issue #1100): whether a subscript
   * is array access or bit access depends solely on the declared type, the
   * same rule used for local variables. A scalar parameter without array
   * syntax is bit-indexable exactly like a scalar local variable (ADR-007).
   *
   * @param typeInfo - Type information, or null if unknown
   * @returns true if subscript should be treated as array access
   */
  static isArrayAccess(
    typeInfo: Pick<TTypeInfo, "isArray" | "isString"> | null,
  ): boolean {
    if (!typeInfo) {
      // Unknown type - default to array access for safety
      // This matches the expression path's fallback behavior
      return true;
    }
 
    // Explicit array or string -> array access
    if (typeInfo.isArray || typeInfo.isString) {
      return true;
    }
 
    // Otherwise it's a scalar - use bit access
    return false;
  }
}
 
export default SubscriptClassifier;