Compiler modes and strictness
PXX has one dialect, not several semantic modes. What changes is how strict that dialect is about a handful of FPC-parity rules. This page explains the model as a whole; the individual switches are listed on the command line page and, for the in-source forms, on the directives page.
The model: lax → strict → mimic
-
Lax by default. PXX's own dialect is deliberately permissive. Declaration order, member visibility, operator and overload resolution, and
case-label checking are all relaxed unless you ask for the stricter behavior. This keeps quick programs and in-progress code compiling without ceremony. -
--strict— the umbrella. Turns on the FPC-parity strictness family together. Today that family is the routine-visibility check (--require-forward); the umbrella name is the stable entry point as more checks join it. -
Granular switches — one rule at a time. Each check is independently toggleable, so you can tighten (or loosen) exactly one rule without the whole umbrella:
Switch What it enforces Lax default --require-forwardRoutine defined/declared before its call site. Whole-source pre-scan finds it anywhere. --strict-overloadExplicit overload;on overloaded routines.Marker not required. --strict-overload-widthNarrowest integer overload that fits, as FPC picks. An exact-width match wins; otherwise the widest candidate. --strict-operatorReject =/<>on class operands.Value-equality operators allowed. --strict-caseInverted-range and duplicate/overlapping caselabels are errors.First-match, no diagnostics. --strict-visibilityprivate/protected/strictaccess is enforced.Markers parsed, access granted anywhere. --lax-decl-order(opt-out) declare-before-use for forward-visible globals. Enforced by default. Each also has an in-source directive form (
{$STRICT_OVERLOAD ON},{$STRICT_CASE ON},{$DECLORDER OFF}, …) so a source file can carry its own strictness need — see directives. -
--strict-fpc— the FPC-parity umbrella. Turns on the strict checks that match FPC's behaviour and are proven to compile the real FPC corpora at once:--strict-case,--strict-operator,--strict-visibility, and--require-forward. In-source form:{$STRICT_FPC ON}.It also changes semantics, not only diagnostics — so do not read "checks" too literally. Two rules under the umbrella change a computed value rather than accepting or rejecting a program:
- shift widths — FPC's masking of the shift count, rather than PXX's;
VarianttoChar— FPC takes character 1 of the variant's string form (65gives'6') where PXX answersChr(n)('A'). See Variants.
Both are cases where FPC's answer is the one ported code depends on, which is why they ride the parity umbrella rather than a switch of their own.
--strict-overloadis not part of the umbrella. PXX's own libraries overload freely without theoverload;marker — that is the intended lax dialect — so requiring the marker would reject the very libraries FPC-oriented code links against. It remains available as a standalone switch for marker-clean code.
--strict-overload-width — parity on request
Also not in the umbrella, and for a sharper version of the same reason:
--strict-overload changes which programs are accepted, but
--strict-overload-width changes which body a call binds to. Enrolling it
would change what the corpora --strict-fpc is proven against actually resolve
to, so that is a separate measured decision and it has not been made.
FPC picks the narrowest integer overload that fits. The default dialect keeps an
exact-width match and otherwise lands on the widest candidate. Given
F(Int64) and F(LongInt) declared in that order:
| argument | default | --strict-overload-width |
|---|---|---|
Integer, LongInt, a literal, an alias of Integer |
LongInt |
LongInt |
SmallInt, Byte |
Int64 |
LongInt |
Cardinal |
Int64 |
Int64 |
The Cardinal row is the one that carries the rule. It shows the choice is
narrowest-that-fits, not narrowest-declared: LongInt is the same width as
Cardinal but cannot hold its top half, so Int64 is the narrowest that fits
and the flag changes nothing there. The unsigned set behaves the same way — with
U(QWord), U(LongWord), U(Word), a Byte argument picks QWord by default
and Word under the flag.
The default is intended, not a bug. The widening is the dialect; the flag is
FPC parity on request. It is command-line only — there is no
{$STRICT_OVERLOAD_WIDTH} directive.
--mimic-fpc— the compatibility preset. For compiling FPC-oriented code. It is--strict-fpcplus what makes PXX present as FPC: the curated FPC define set (so identity-probing headers pick their FPC path) and IO checking ({$I+}). A preset built on top of the strict family, not a separate mode. The in-source equivalent is{$MIMIC FPC}. For the FPC-specific details of what ports and what does not, see FPC compatibility.
Runtime checks are a separate axis
Everything above decides whether a program is accepted — it is dialect
strictness. The runtime check switches are a different question: whether the
compiler emits a test that fires while the program runs. They are not part of
--strict or --mimic-fpc and are not affected by them.
| Switch | Emits | Default |
|---|---|---|
{$R+} / {$RANGECHECKS ON} |
range checks | off |
{$Q+} / {$OVERFLOWCHECKS ON} |
integer overflow checks | off |
{$I+} / {$IOCHECKS ON} |
IO-result checks | on (and --mimic-fpc keeps it on) |
{$NILCHECKS ON|OFF} / --no-nil-check |
nil-pointer checks | tri-state: calls checked, derefs not |
--no-div-check |
(opt-out) div/mod zero check | on |
{$NILCHECKS} is the one that does not have a single default — see
directives.
Why lax is the default
PXX is its own dialect first and an FPC-compatibility tool second. The lax
default serves the dialect; the strict flags and --mimic-fpc serve
compatibility when you want it. That split is deliberate: you opt into
FPC-parity strictness per rule, rather than opting out of it.
{$MODE} — which dialects PXX targets
PXX targets the FPC and Delphi family. Those modes are accepted as compatibility markers and map onto PXX's one dialect; strictness is controlled by the switches above, not by the mode marker. Dialects outside that family are not silently compiled as if they were in it.
{$MODE …} |
Result | Why |
|---|---|---|
objfpc fpc tp delphi delphiunicode pxx |
accepted | the family PXX targets |
iso extendedpascal |
warning | not implemented; compiled as the default dialect |
macpas |
error | not implemented, and compiling it as the default dialect produces a wrong binary rather than a diagnostic |
| anything else | error | not a mode FPC accepts either |
{$mode macpas} is an error rather than a warning because the failure is
specific and silent: MacPas conditional directives ({$setc}, {$ifc},
{$elsec}, {$endc}) are not recognised, so both arms of the conditional
compile. A program that announces {$MODE MACPAS} at the top is told there and
then, rather than a few hundred lines later or not at all.
Support for a dialect is deferred, not refused — it comes back when there is real code in active use that needs it.
{$mode delphi} is the one marker that changes behaviour
The others are inert. Delphi mode has exactly two deltas:
| default (objfpc-ish) | {$mode delphi} |
|
|---|---|---|
| Nested comments | on — { outer { inner } } is one comment |
off, as in Delphi |
| Assigning a routine to a procedural variable | write the address: f := @F |
f := F also binds the address |
-Mobjfpc is the only mode marker with a command-line form, and it selects the
default dialect.