Default standard units: System + textfile
- Type: feature (compiler / RTL loading)
- Status: done
- Track: A
- Owner: —
- Opened: 2026-06-21
- Relation: follows
feature-textfile-keyword-io-dispatchandbug-implicit-textfile-unit-method-local; needed byexamples/adventure. Also sets the expected home forSystemhelpers such asInc.
Problem
The current implicit textfile import is a token-scan special case. It works for a
simple program with a program-level var f: Text, but it misses method-local
Text declarations inside units:
procedure TGame.SaveTo(const path: AnsiString);
var f: Text;
begin
Assign(f, path); Rewrite(f);
pascal26:563: error: undefined variable (Assign)
This is the wrong shape long-term. Classic Pascal expects a standard surface:
System is always in scope, and text-file I/O is close enough to that surface
that making users write uses textfile everywhere is awkward. At the same time,
the compiler must still be buildable by FPC, and the self-host compiler should
not be forced through unstable RTL while that surface is still moving.
Direction
- Introduce a default standard unit set, initially
Systemandtextfile. - Load default standard units uniformly for programs and units before user declarations are resolved.
- Keep implementations in RTL units. Do not move
Text/Assign/Incinto compiler builtins just to make them visible. - Preserve zero-cost unused code: a trivial program that does not use text-file I/O must not emit textfile routines or pull platform file I/O into the binary.
- Keep
textfileparse-safe on all targets: platform/PAL calls must stay inside referenced routines, with no mandatory unit initialization. - Add an escape hatch for bootstrap and constrained targets while the default
RTL surface stabilizes. Prefer a command-line switch for build scripts, such
as
--no-default-rtlor--no-implicit-system; a source directive such as{$PXXDEFAULTUNITS OFF}can be added if source-level control is useful. - When the escape hatch is active, code must explicitly import the units it needs, or rely only on true compiler builtins.
Acceptance
- A minimal program can use
IncfromSystemwithout an explicituses. - A unit method with local
var f: Textcan callAssign/Rewrite/WriteLn(f, ...)without explicituses textfile. examples/adventuregets pastengine.pas:563without adding an explicituses textfileworkaround.make bootstrap/ compiler self-build has a documented opt-out path untilSystemandtextfileare stable enough to compile the compiler itself under default units.test/hello.pasremains the same size as the current baseline, 29,086 bytes, or any size increase is explained and tracked as a dead-code/lazy-emission follow-up before this broadens further.- Existing explicit
uses textfiletests continue to pass.
Log
- 2026-06-21 - Opened after confirming the implicit textfile scanner catches a
simple program but misses
Textin a unit method-local var section. Design preference: default-loadSystemandtextfile, with an opt-out for compiler self-build while RTL stability catches up. - 2026-06-21 - Baseline guard:
test/hello.pascompiles to 29,086 bytes with both pinned v26 and rebuilt livecompiler/pascal26; use this as the first check that unused default units stay free. - 2026-06-21 - DONE (commit d5c7498, Track A).
textfile(+ itsbuiltinnumeric-format backing) is now loaded by default on every non-ESP target, uniformly for programs and units, replacing the: Texttoken scan. The posix PAL backend dir is auto-added to the unit search path (ExeDir-anchored + CWD-relative) sopxx foo.pasresolvesplatform_backendwith no-Fu. Opt-out:--no-default-rtlflag or{$define PXX_NODEFAULTRTL}in source; the compiler defines the latter so every self-build path (bootstrap, cross-bootstrap, stabilize) opts out with no per-site Makefile flag, and the self-host fixedpoint stays byte-identical. Acceptance: unit method-localvar f: Textresolves Assign/Rewrite/WriteLn with nouses/-Fu;examples/adventureclears the engine.pas:563 blocker (now stops at :604, the separate nested-proc bug).Inc/Systempart:Incwas already a true compiler builtin (works with nouses), so noSystemRTL unit was needed for the acceptance — left as a no-op; revisit if/when realSystemhelpers land. Two latent compiler bugs surfaced and fixed in the same commit (would crash any program that pulled a >8-byte managed record): (1) whole array-of-record idents mis-routed through the record-by-value temp path in IRLowerCallArg → corrupted SetLength target; (2) managed value-records leaked into the__rttiregclass registry via EmitLayoutRTTI's UClsRTTIOff. Size guard NOT met / accepted tradeoff:test/hello.pasgrows 29,086 → ~42,661 bytes (textfile+builtin in every program, no DCE). User chose the simpler "always include" over chasing the token scan. Tracked asfeature-lazy-standard-unit-emission(dead-code / routine-level lazy emission) before the default surface broadens further.