All files / TRANSPILE/1-Analyze/helpers ConstantDiagnostics.ts

85.71% Statements 12/14
78.57% Branches 11/14
100% Functions 2/2
84.61% Lines 11/13

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                              48x                                                                   22x 3x                 19x 17x 22x 1x   16x             1x 1x                   48x        
/**
 * What a value fixed at compile time is told when it has none (#1175, #1669):
 * the wording for E0909 and E0910, once, for an enum member's value and an
 * array dimension alike.
 */
import type TConstResult from "../../../types/TConstResult";
 
type TWithoutValue = Extract<
  TConstResult,
  { kind: "notConstant" } | { kind: "foreign" }
>;
 
/** Each reason's sentence, after the offending part's spelling */
const REASON: Readonly<
  Record<Extract<TConstResult, { kind: "notConstant" }>["reason"], string>
> = {
  variable: "is a variable",
  parameter: "is a parameter",
  function: "is a function",
  call: "is a function call",
  scope: "is a scope",
  unfolded: "has no value known at compile time",
  laterMember: "is declared below it",
  selfMember: "is the member itself",
  unknown: "is not declared",
  subscript: "is an array element",
  float: "is a floating-point value",
  string: "is a string",
  character: "is a character",
  initializer: "is an initializer",
  address: "is an address",
  member: "is a member access",
  undeclaredMember: "is not declared",
  leadingZero:
    "is a leading-zero literal, which has no value: C-Next has no octal literal (E0912)",
  divisionByZero: "divides by zero",
  sizeofExpression:
    "is the size of an expression, which C-Next does not write for C: use sizeof of its type",
  negativeShift:
    "shifts by a negative amount, or shifts a negative value right",
};
 
class ConstantDiagnostics {
  /**
   * Why `result` has no value, as E0909 says it; null when another code owns
   * the reason -- a division by zero is E0800's, an undeclared name E0427's --
   * so a program is told once.
   */
  static why(result: TWithoutValue): string | null {
    if (result.kind === "foreign") {
      return {
        header: `'${result.spelling}' is defined by a C or C++ header`,
        maybeHeader: `'${result.spelling}' is not declared in C-Next, so only an included C or C++ header can define it`,
        targetSize: `'${result.spelling}' is decided by the target`,
      }[result.why];
    }
    // A bare name nothing declares is E0427's. A member a scope or an enum does
    // not have, and a divisor that is zero only once computed, are reported by
    // nothing else (#1863 review), so E0909 says them
    if (result.reason === "unknown") return null;
    const subject = result.spelling === "" ? "it" : `'${result.spelling}'`;
    if (result.reason === "unfolded" && result.because) {
      return `${subject} has no value known at compile time: ${ConstantDiagnostics.cause(result.because)}`;
    }
    return `${subject} ${REASON[result.reason]}`;
  }
 
  /** Why a const's own initializer has no value, said at a use of the const */
  private static cause(
    because: Exclude<TConstResult, { readonly kind: "value" }>,
  ): string {
    Eif (because.kind === "overflow") {
      return `its initializer overflows ${because.typeName} (ADR-044)`;
    }
    const inner = ConstantDiagnostics.why(because);
    return inner === null
      ? "its initializer names something not declared"
      : `in its initializer, ${inner}`;
  }
 
  /** E0910's help: the fix ADR-044 offers */
  static readonly OVERFLOW_HELP =
    "Do the arithmetic at a width that holds the result, for example by casting the operands to a wider type";
}
 
export default ConstantDiagnostics;