← board

Meta: pxx dialect extensions ⟷ FPC compatibility (two aims, switch-guarded)

The two aims (deliberately distinct)

pxx pursues two goals at once, and they pull in opposite directions:

  1. pxx's own dialect — lax, ergonomic, boilerplate-eliminating. Type inference (var x := expr, the auto type), Delphi-10.3-Rio inline vars (for var i := ...), optional sloppy locals, forward-visible decl order, etc. This is our language; it may extend beyond standard Pascal where the design is good (Delphi set real precedents worth stealing).
  2. FPC compatibility — on request. When asked (--strict / --mimic-fpc), pxx must compile standard FPC/Delphi-classic source and reject the pxx-only extensions, so FPC-targeted code stays portable and the cold FPC seed builds.

These are not in conflict because they are guarded by switches. Neither aim is sacrificed: lenient is the productive default; strict is one flag away.

The contract (rule for every dialect extension)

Any feature that diverges from standard FPC/Delphi-classic MUST:

  1. Be available by default (lenient) or behind an explicit opt-in switch — never silently mandatory.
  2. Be disabled / rejected under the strict family (--strict / --mimic-fpc / the relevant {$...} strict directive), so a strict compile is FPC-faithful. A dialect-only construct under strict must error with a clear "not valid in strict/FPC mode" message.
  3. Keep the self-build honest. The compiler's own source must compile under the chosen default profile, and the strict path must stay reachable (so strict can become a profile without breaking bootstrap). Self-host byte-identical.
  4. Have a test on both sides — the extension works in lax; the same source errors under strict.

A new dialect ticket should state, up front, which switch guards it and what strict does.

Index — dialect extensions (the lax aim)

Strict-flag family additions (2026-07-14/15 night):

Shipped:

Planned:

Index — FPC-strict / compatibility (the compat aim)

Open governance question (for the user)

Should --strict be a single master switch that turns all dialect extensions off at once (simplest mental model), or per-feature directives that --strict merely defaults on? Recommendation: a master --strict / --mimic-fpc that sets the strict default for every guarded extension, with per-feature {$...} overrides for fine control. Decide before the second dialect feature lands so the switch wiring is uniform.

2026-08-07 — AnsiString + UCS4Char: an EXTENSION, not an ambiguity (low prio)

Logged so it is on the record, not because anything needs doing. Reached via NilPy ([[feature-nilpy-text-string-kind]]); the Pascal side is fine.

Measured against FPC 3.2.2:

expression FPC
AnsiString + AnsiChar OK
UnicodeString + WideChar OK
AnsiString + WideChar OK
UCS4String + UCS4Char rejected
UnicodeString + UCS4Char rejected

UCS4String exists (array of UCS4Char) but no FPC string type concatenates with a UCS4Char, not even its own — in FPC the type is inert for string building and you go through the conversion functions. pxx converts it to the code point's UTF-8 encoding (feat(A) b0cbeba60).

Why it does NOT go behind --strict-fpc

The classification is the point, and it is the general rule this instance illustrates:

Ambiguity — the same source compiles under both and means something different. Dangerous, silent, and exactly what the strict flags exist for.

Extension — pxx accepts source FPC rejects. Harmless to forward compatibility: no FPC program can contain it, so no FPC program changes meaning. Nothing to disambiguate, so nothing for a strict flag to do.

AnsiString + UCS4Char is squarely the second. FPC rejects it outright, so there is no FPC program it can affect. Gating it under --strict-fpc would be category error — and would make the type nearly unusable in strict mode, since FPC offers no concat path at all, so strict code would need an explicit UCS4ToUTF8(c) spelling first.

(A previous draft of this reasoning proposed a rule that every new laxness gets a strict gate. That is wrong for the same reason: only laxness that creates divergent meaning needs one.)

The one thing genuinely open

We do not know what a newer FPC does here. 3.2.2 is what was measured; a nightly or the next stable may grow a UCS4Char concat path, and if it does, this stops being an extension and becomes a place where the two disagree — which would be strict-flag territory. Worth re-measuring when a new FPC stable lands (or refreshing a nightly if one is already pulled). No action before then.

Idea (NOT a ticket): a future --ultra-strict-fpc — the portability guarantee

Recorded as a design note only, at the user's request. No work item.

The strict family so far is about meaning. A third, different guarantee is about acceptance:

--ultra-strict-fpc: if this is set and pxx compiles it, FPC compiles it.

That is not another parity switch, it is a closed-world property — "there exists no construct pxx accepts here that FPC rejects" — which is why it is a separate mode rather than another --strict-* flag.

The two guarantees compose, and both are needed for the use case:

flag guarantees prevents
--strict-fpc same source, same meaning ambiguity (silent divergence)
--ultra-strict-fpc FPC also accepts it extensions (pxx-only syntax)

Either alone is insufficient for the real target: a program that compiles under FPC but behaves differently is worse than one FPC refuses outright. So ultra-strict would imply strict, not replace it.

Why it has real-world value (the user's point, and it is the strongest argument for eventually building it): a library or application meant to build under both compilers. Develop against pxx — fast, self-hosting, better diagnostics — and retain the guarantee that FPC users can still build the result. That is a concrete audience, unlike parity-for-its-own-sake.

It can only be verified, never asserted. A closed-world claim needs a differential: compile the corpus with the flag, feed the same sources to FPC, and require FPC to accept everything pxx accepted. The machinery largely exists (tools/fpc_diff_probe.sh, the FPC seed canary in gate.sh) — Track T-flavoured when it happens. Anything short of that is a promise nobody checked.

Cost to note before starting: every existing pxx extension needs a decision (gate it, or declare the mode incompatible with it), which is an audit of the whole dialect surface — not a flag one afternoon.