All files / utils EnumMemberValues.ts

95.83% Statements 23/24
90% Branches 18/20
100% Functions 6/6
100% Lines 19/19

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                                      104x                                     374x 374x 836x                     374x                             8x 8x   4x 8x 8x 6x 4x 4x                     759x 421x 421x           836x 825x 825x              
/**
 * One enum's member values, in declaration order (#1669, ADR-017 "Member
 * Values"). The one place the auto-increment, the `i32` range and the
 * "members above it only" rule are applied, so 1.4 Resolve settling the
 * values and 2.1 Analyze reporting them cannot disagree.
 *
 * Pure: what a NAME is worth is the caller's environment's to answer, as for
 * every constant expression. `ownMember` is how that environment answers a
 * member of the enum being computed.
 */
import ConstantEvaluator from "./ConstantEvaluator";
import TypeCheckUtils from "./TypeCheckUtils";
import type IConstantEnvironment from "./types/IConstantEnvironment";
import type ISourcePosition from "./types/ISourcePosition";
import type TConstExpr from "../types/TConstExpr";
import type TConstResult from "../types/TConstResult";
import type TEnumMemberValue from "../types/TEnumMemberValue";
 
/** ADR-017: a member's value is an i32 */
const MEMBER_TYPE = "i32";
 
interface IEnumMemberInput {
  readonly name: string;
  readonly valueExpr: TConstExpr | null;
}
 
class EnumMemberValues {
  /**
   * Every member's value. `envAt(index, settled)` is the environment member
   * `index`'s value is written in, with the members above it already settled.
   */
  static compute(
    members: ReadonlyArray<IEnumMemberInput>,
    envAt: (
      index: number,
      settled: ReadonlyArray<TEnumMemberValue>,
    ) => IConstantEnvironment,
  ): TEnumMemberValue[] {
    const settled: TEnumMemberValue[] = [];
    members.forEach((member, index) => {
      settled.push(
        EnumMemberValues.ranged(
          member.valueExpr === null
            ? EnumMemberValues.continued(members, settled, index)
            : ConstantEvaluator.evaluate(
                member.valueExpr,
                envAt(index, settled),
              ),
        ),
      );
    });
    return settled;
  }
 
  /**
   * What `member`, a member of the enum being computed, is worth to member
   * `index`'s value: only a member declared above it has a value yet.
   */
  static ownMember(
    names: ReadonlyArray<string>,
    index: number,
    settled: ReadonlyArray<TEnumMemberValue>,
    member: string,
    spelling: string,
    at: ISourcePosition,
  ): TConstResult {
    const position = names.indexOf(member);
    const without = (
      reason: "undeclaredMember" | "selfMember" | "laterMember" | "unfolded",
    ) => ({ kind: "notConstant", reason, spelling, at }) as const;
    Iif (position < 0) return without("undeclaredMember");
    if (position === index) return without("selfMember");
    if (position > index) return without("laterMember");
    const above = settled[position];
    return above.kind === "value"
      ? { kind: "value", value: above.value, typeName: null }
      : without("unfolded");
  }
 
  /** ADR-017: a member written without a value is the one above it, plus one */
  private static continued(
    members: ReadonlyArray<IEnumMemberInput>,
    settled: ReadonlyArray<TEnumMemberValue>,
    index: number,
  ): TEnumMemberValue {
    if (index === 0) return { kind: "value", value: 0n, typeName: null };
    const above = settled[index - 1];
    return above.kind === "value"
      ? { kind: "value", value: above.value + 1n, typeName: null }
      : { kind: "follows", member: members[index - 1].name };
  }
 
  private static ranged(result: TEnumMemberValue): TEnumMemberValue {
    if (result.kind !== "value") return result;
    const range = TypeCheckUtils.integerRange(MEMBER_TYPE)!;
    return result.value < range[0] || result.value > range[1]
      ? { kind: "outOfRange", value: result.value }
      : result;
  }
}
 
export default EnumMemberValues;