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 | 702x 1273x 1273x 702x 551x 151x 1273x 1371x 497x 874x 1371x 244x 630x 1371x | /**
* Issue #1106: shared validation for subscript-chain depth.
*
* Per ADR-036 each subscript peels one array dimension; per ADR-007 a scalar
* integer/float may be bit-indexed once. So a base variable allows at most
* `arrayDimensions + 1` subscript operations. A deeper chain indexes a value
* that is not an array — e.g. `flags[4][3]` on a scalar `u8`, where `flags[4]`
* is already a single bit.
*
* This is the single source of truth for the "how many subscripts may this
* base take?" decision. Both the assignment (write) path
* (AssignmentClassifier) and the expression (read) path
* (PostfixExpressionGenerator) consult it, so the two paths cannot diverge.
* It owns the *counting* as well as the check (`countLeadingSubscripts`):
* sharing only the check while each path re-derived the count would leave the
* two agreeing by coincidence rather than by construction.
*/
import TTypeInfo from "../../types/TTypeInfo";
import TypeCheckUtils from "../../utils/TypeCheckUtils";
import invariant from "../../utils/invariant";
/**
* A planned postfix operation, which STATES its kind.
*
* #1445 review: there were two shapes here -- this one and an
* `IPostfixOpLike { expression(): unknown[] }` for raw nodes -- chosen between
* by a `"kind" in op` probe. In the one module whose stated purpose is that
* the read and write paths "cannot diverge on what counts as a subscript",
* that was the question being answered from two representations. Both callers
* pass planned ops now.
*
* The kind is the literal union rather than `string`, so a planner emitting a
* fourth kind is a compile error here instead of silently counting as
* not-a-subscript.
*/
interface IPlannedOpLike {
readonly kind: "member" | "subscript" | "call";
}
function isSubscript(op: IPlannedOpLike): boolean {
return op.kind === "subscript";
}
class SubscriptDepthValidator {
/**
* Count the leading run of subscript operations applied directly to a base,
* starting at `startIndex` and stopping at the first operation that changes
* the type (member access or call) — after that, subsequent subscripts apply
* to a different value, not to this base.
*
* Counts OPERATIONS, not expressions: the bit range `flags[4, 3]` is one
* operation with two expressions, whereas the chain `flags[4][3]` is two
* operations with one each. Conflating them would reject valid bit ranges.
*
* `startIndex` skips operations already consumed in identifying the base —
* on the read path `this.flags[4][3]` parses as `this` plus the ops
* `.flags`, `[4]`, `[3]`, so the member op is skipped with `startIndex: 1`.
* The write path needs no offset: `assignmentTarget` consumes the `this .
* IDENTIFIER` prefix in the grammar rule itself, so its ops are subscripts
* from index 0.
*/
static countLeadingSubscripts(
ops: readonly IPlannedOpLike[],
startIndex = 0,
): number {
let count = 0;
for (let index = startIndex; index < ops.length; index++) {
if (!isSubscript(ops[index])) {
break;
}
count++;
}
return count;
}
/**
* Validate that a leading run of `subscriptOpCount` subscript operations
* applied directly to `varName` (declared type `typeInfo`) is within range.
*
* Only integer/float bases are checked: those are the bit-indexable scalar
* element types (ADR-007). Strings are char arrays with their own semantics,
* bitmaps reject bracket indexing elsewhere, and struct/other bases are
* handled by the member-access paths — none are validated here.
*
* #1322: the `line` parameter went with the throw. It existed only to be
* spelled into the message, which is what a diagnostic's position is for.
*/
static validate(
typeInfo: TTypeInfo | undefined,
subscriptOpCount: number,
varName: string,
): void {
if (!typeInfo || typeInfo.isString || typeInfo.isBitmap) {
return;
}
const isBitIndexable =
TypeCheckUtils.isInteger(typeInfo.baseType) ||
TypeCheckUtils.isFloat(typeInfo.baseType);
if (!isBitIndexable) {
return;
}
const arrayDimensions = typeInfo.arrayDimensions?.length ?? 0;
// #1322: ADR-036/ADR-007's depth limit is E0856 in pass 2.1, decided from
// the declaration's own dimensions. The throw here built `Error at line N:`
// into its message -- a position carried as prose, which is what the
// relocation removes.
invariant(
subscriptOpCount <= arrayDimensions + 1,
`'${varName}' is subscripted no deeper than its shape allows -- E0856 rejects this in pass 2.1, before this runs`,
);
}
}
export default SubscriptDepthValidator;
|