{$R+} / {$RANGECHECKS ON}: runtime range checking
- Type: feature (FPC-parity runtime checks). Track A.
- Status: done statement-anchored semantics per trange4) and v2 37475d8a (index bounds centralized in IRLowerAddress: reads+writes, static + dyn-depth-1 via PXXDynIdxChkI64) LANDED, oracle-matched. Slice 3 landed: Char dests + FIELD static-array indexes; enum CAST verified UNCHECKED in FPC. Slice 4: SUBRANGE dests DONE (AliasSub*/SymSub* retain lo..hi). Slices 3-6 landed (Char/field/subrange/open-array-param + the param-lo silent bug). Slice 7 (dyn fields) done. REMAINING (thin): subrange dests (needs subrange-bounds metadata — pxx maps 3..7 to the base int kind and drops the bounds), N-D/param-array bases, dyn-array FIELDS.
- Opened: 2026-07-15 night, straight out of the {$Q+} arc
([[feature-pascal-overflow-checks-q-plus]] — subword probing showed
truncation is {$R+} territory, then the oracle probe found pxx's gap
demonstrating itself: under {$R+},
a[4] := 1onarray[1..3]silently clobbered the NEXT VARIABLE while FPC raised ERangeError).
Subrange residual — implementation sketch (recon 2026-07-15)
Named subranges drop their bounds at parse (parser.inc ~19211, "bounds are
not retained"; RegisterGeneralAlias has no lo/hi). To check them:
AliasSubLo/AliasSubHi Int64 parallel arrays (set at the T = lo..hi decl,
default sentinel elsewhere — mind the parallel-array reset landmine),
a LastTypeSubLo/Hi channel out of ParseTypeKind's alias resolution,
SymSubLo/Hi stamped in AllocVar, and the AN_ASSIGN wrap keys on them ahead
of the width table. Var-decl slice first; fields/params later.
Oracle (FPC 3.2.2, probe kept in the ticket)
{$R+}
i := 256; b := i; { byte dest: ERangeError }
i := 4; a[i] := 1; { array[1..3]: ERangeError }
i := -1; b := i; { ERangeError }
{$R-}
i := 300; b := i; { wraps quietly: 44 }
FPC caught=3. pxx today: all three proceed; the OOB store corrupts the neighbour (caught printed 1 BECAUSE a[4] overwrote it).
Design — mirror the proven {$Q+} machinery
- Directive: the lexer ALREADY disambiguates
{$R+}/{$R-}from resource includes (lexer.inc ~1384) but discards the value — wireRChecksVal+ per-tokenTokRChecks(the TokPackRecords pattern, exactly like TokQChecks). - Narrowing assignment: parser tags AN_ASSIGN when the DEST's ordinal width < the promoted width and the token region has R+ (a parallel ASTRChk, AllocNode-reset + CloneAST-copied). IR/codegen: before the store, compare against the dest type's [lo, hi] (signed/unsigned by dest tk) and call a new PXXRangeError (builtinheap mirror of PXXOverflow: 'Runtime error 201', Halt(201); sysutils hook raises ERangeError — the hook pair pattern is already there twice).
- Array index: IR_INDEX carries lo + element count is derivable at the tag site — emit bounds compare when tagged. Static arrays first; dyn arrays have Length at [handle-8] (FPC checks those too — probe first!).
- Contract per meta-dialect ticket: default OFF (lax dialect keeps today's behaviour), lexically scoped, oracle-verify EVERY sub-behaviour against FPC before implementing (the {$Q+} arc hit two doc-vs-oracle mismatches: Abs/Sqr and subword are NOT checked by FPC).
Acceptance
- The probe matches FPC (caught=3, lax wraps).
- tclass5.pp's "range-check error 210" note and any skip-list entries keying on range checks get re-triaged after landing.
- x86-64 first; cross legs follow the {$Q+} porting pattern.
Log
- 2026-07-15 — resolved, commit 51279865.