← board

uses <unit> in '<file>' — the standard Delphi/FPC spelling

Filed 2026-08-19 at the user's direction, explicitly LOWER priority than the NilPy import work it was found beside. "We totally overlooked the uses .. in syntax, even when we were talking about uses .. as. That's also worth doing, but lower prio."

Measured, on pin v361

uses mymod in 'mymod.pas';   ->  parse error at `in`
uses './mymod.pas' as m;     ->  works  (m.Twice(21) = 42)
uses './lib2.c'    as c;     ->  works  (cross-language)

So pxx has a way to name a unit's file and it is not the standard one. in is what FPC and Delphi write, and it is what appears in every real .dpr / project source — so a corpus program using it is refused at its uses clause, before any semantics are reached.

Why it is a compat item and not just a nicety

This is squarely reference-compatibility: the default is to behave like FPC, and in is not an obscure corner — it is how Delphi projects have named unit files for thirty years. It is also the one place the two spellings differ in meaning, which is the interesting part:

spelling binds the name reaches foreign symbols
uses mymod in 'mymod.pas'; (FPC) mymod, unqualified — the unit's own name n/a, same language
uses './mymod.pas' as m; (pxx) m, an alias yes — the alias maps to the REAL unit's Strs[] index

So in should bind the unit under its declared name, not an alias, and the existing alias machinery is not automatically the right implementation. See decided/decide-cross-language-qualifier-syntax for why the alias form resolves foreign symbols the way it does — that reasoning is about the alias, and in is not one.

Scope note

in is a within-language file selector: it says where a Pascal unit lives, not what language it is. It is therefore not an alternative to [[feature-a-a-bare-nilpy-import-means-python-and-another-language-needs-its-extension]] and does not satisfy that ticket's rule 2. Two different jobs that happen to look alike.

Gate

Track P's: make compiler/pascal26 (fixedpoint) + tools/gate.sh quick. The Pascal frontend lives in the shared lexer.inc/parser.inc, so it obeys A's no-concurrent-edit rule.

Log

Triage 2026-08-19 (Track D re-triage pass, pin v364 — after the import/uses refactor landed)

Genuine feature, still wanted — and specifically NOT settled by the refactor it sits next to. Re-measured against v364 rather than the v361 the ticket quotes, because that work changed how units are named:

uses mymod in 'mymod.pas';   ->  pascal26:2: error: unexpected token  (near `in`)
uses './mymod.pas' as m;     ->  works, m.Twice(21) = 42

Unchanged on both rows. The quoted-path form still works and the standard Delphi/FPC spelling still does not parse, so the compat gap the ticket describes survived the refactor intact. Its central design point stands too: in must bind the unit under its declared name, not as an alias, so the existing alias machinery is not automatically the implementation.

Resolution 2026-09-04 (frankD, Track P)

ParseUsesEntry grows an in arm beside the existing as arm. The file is loaded through the quoted-path form that already worked, and then the declared name is wired to whatever the loader registered — so there is no second file-finding path to keep in step with the first, which is the failure mode normalise-dont-special-case.md describes.

uses mymod in 'mymod.pas'    binds `mymod`, the unit's own declared name
uses './mymod.pas' as m      binds `m`, an alias

When the file's basename already equals the declared name the two intern to the same slot and the alias row is skipped as a no-op. That is not an optimisation: it is the only shape FPC permits at all, so it is the shape all real input takes.

The bare file name is the whole feature, and it is one line

uses Unit1 in 'Unit1.pas' — no separator — is what every generated .dpr writes, and it is the form the ticket's measured symptom was really about. FPC resolves it against the directory of the file holding the clause; pxx's loader already means exactly that by a leading ./, because relative paths there are taken against CurUnitDir (SourceFileDir for the program, the unit's own directory for a nested one). Without the prefix the loader reads the string as a unit NAME containing a dot and reports unit source not found: mymod.pas.

Only when there is no separator: an author who wrote in 'sub/other.pas' already has a path, and prefixing an absolute one would be wrong. A backslash is deliberately not treated as a separator — Delphi sources write in '..\shared\Foo.pas' and pxx's existing quoted-path form does not accept that either, so it is one question about Windows path spelling across both spellings rather than something to answer twice, differently, here.

What is asserted against FPC, and what is deliberately not

test/test_uses_in_explicit_file.pas (+ three helper units) runs three entries in one clause — bare name, path with a separator, and a qualified use of the declared name — and prints the same two lines under pxx and FPC 3.2.2. The qualified row is there because a flat lookup answers the first two either way and so cannot show which name in bound.

A declared name that differs from the unit's own is NOT asserted. pxx accepts it; FPC's behaviour there was measured directly rather than assumed, on one unchanged file:

clause name FPC 3.2.2
A, Ab, Abc, SomeName (≤ 8 chars) accepted
SomeNameXX, othername_i, DeclaredElsewhere Illegal unit name
othername_in (the unit's own name) accepted

That is a DOS 8.3 short-name tolerance, not a rule — and it sits on top of FPC's more basic requirement that a unit's name match its file's name at all, in or no in. No valid FPC source contains the shape. Us accepting what FPC rejects is not a defect, so the behaviour is left free rather than frozen against a length artifact.

An earlier reading of this was wrong and is recorded because it nearly went into the test: a same-directory probe with the name SomeName matched FPC, which I first explained as a stale .ppu being reused. It was not — SomeName is exactly eight characters, and the probe was measuring the tolerance. The correction came from re-running the same shape at several name LENGTHS, which is the variation the original probe did not make.

Tooling: the new spelling was invisible to the wiring gate

tools/check_test_wiring.py follows uses references to decide whether a helper in test/ is reached from a wired test, and it split a uses clause on commas and took each entry as a bare name. mymod_in in 'mymod_in.pas' is not a bare name, so all three helpers of the new test landed in THIS PUSH ADDS 3 TEST FILE(S) THAT NOTHING RUNS. Exempting them in UNWIRED.txt would have been the wrong fix — they ARE wired, the tool could not see it, and every future in test would have hit the same wall.

The checker now takes both halves of every entry: each bare identifier and the basename of each quoted path. That covers in '<file>', pxx's '<path>' as <alias>, and the cross-language './lib2.c' as c in one pass. Positive-controlled: a planted unwired test is still reported, the scanned population is unchanged at 3411, the parked and advisory rows are identical, and both of the checker's own devtests pass.