Implicit (sloppy) local variables behind a switch — {$IMPLICITVARS ON} / --auto-locals
- Type: feature (language / parser) — Track A
- Status: DONE 2026-07-04
- Opened: 2026-06-30
- Origin: the original
--auto-localsidea (feature-implicit-identifier-binding-strictness-switch, "the originally-imagined feature"), correctly scoped after a design pass as an opt-in, non-standard layer. The typed/standard layer is [[feature-inline-loop-var-rio]].
Idea
Under an explicit opt-in switch, an assignment to a previously-undeclared name
creates an inferred local (no var, no type):
{$IMPLICITVARS ON} { or pxx --auto-locals }
begin
i := 0; { implicit local, inferred Integer }
s := 'abc'; { implicit local, inferred AnsiString }
for i := 0 to 9 do ...
end;
This is "sloppy mode" — Python/BASIC ergonomics in Pascal. pxx's Nil-Python and
BASIC frontends already do exactly this, via the same tyAuto (defs.inc:540)
inference the inline-var path uses, so the machinery exists.
Why / why behind a switch
- Why: eliminate boilerplate for scratch variables; ease porting Python/BASIC and quick scripts.
- Why opt-in (NEVER default): it masks typos —
cont := 0silently makes a new variable instead of erroring on the misspelling ofcount. Pascal-the-language is declare-before-use; this is a deliberate departure.
Behaviour
- Default (off): an undeclared
i := 0is an error (today's behaviour — the decl-order gating fix, feature-implicit-identifier-binding-strictness-switch, already does this correctly). Unchanged. {$IMPLICITVARS ON}/--auto-locals: assignment to an undeclared name declares atyAutoroutine-local, type inferred from the RHS (reuse the inline-var := exprresolve at parser.inc ~7550). Emit a warning (implicit variable 'i') so it is visible (suppressible).--strict/{$DECLORDER ON}strict family: force it off (hard error) even if requested, so strict builds stay declare-before-use.
Implementation notes
- The hook is the lvalue-resolution failure in the assignment-statement path:
where
FindSym(name) < 0for the LHS today raises "undefined variable", under the switch insteadAllocVar(name, tyAuto)(routine-local) and continue, then let the RHS inference fill the type — the exact path the inline-varstatement already uses (parser.inc ~7512/7550). Guard with the new switch global +EnableAutoVar. - A
tyAutolocal that is read before any assignment must still error (use of auto variable before type is inferred, parser.inc ~4223 already exists) — so a bare read of an undeclared name stays an error even in sloppy mode; only assignment creates one.
Acceptance
- Off by default: undeclared
i := 0errors (unchanged). With--auto-locals/{$IMPLICITVARS ON}: it declares + infers, runs, and warns.--strictforces the error regardless. Self-host byte-identical (switch off in the self-build). Tests: off-errors, on-infers-and-warns, strict-overrides, read-before-assign still errors.
Log
- 2026-07-04 — DONE (Track A). Opt-in switch landed exactly as specced.
ImplicitVarsglobal (defs.inc), default off; reset inPasInitDefines(runs before CLI parse).--auto-locals(compiler.pas) and{$IMPLICITVARS ON|OFF}(lexer.inc, next to{$DECLORDER}) both set it.- Assignment hook (
parser.inc, plain-lvalue path afteridx := FindSym(name)): when on + name undeclared + not unit-qualified + not a routine + the very next token is:=, create a routine-localtyAutovar (AllocVar(name, tyAuto)); the existing tyAuto-on-assignment block infers its type from the RHS + allocates the frame slot. Tight gate →obj.f :=,a[i] :=, a bare read, and calls all keep the normal path. - For-counter hook (
parser.inc,for-header else branch): an undeclared counter is auto-created — countedfor i := a to b→ Integer,for x in c→ tyAuto (element type backfilled by the for-in variant), mirroring thefor var iinline form. Loops are the primary sloppy-mode use. WarnImplicitVar(lexer.inc, writeln-based likeWarnStackFrame,-Werror-aware):implicit variable 'name'on each creation, so a typo'd name becoming a new var is visible.- Read of an undeclared name still errors (only assignment/for-counter creates one) — the ParseFactor rvalue path is untouched.
- Off by default → self-host byte-identical (build vs verify
cmpclean,procs 1530→1531from the new WarnImplicitVar routine only).make testgreen. Regressiontest/test_auto_locals.pas(int / string(managed) / counted-for / for-in-array,total ok 4 / 4) wired intotest-coreboth ways: passes with--auto-locals, and the same file WITHOUT the flag is asserted to fail withundefined variable. - Not done (out of original scope): a hard
--strictfamily override that forces the error even when requested — no--strictflag exists in the compiler today;{$IMPLICITVARS OFF}/ omitting the flag is the off path. File a follow-up if a strict-mode veto is wanted.