A unit's private external hides a C header function of the same name
Repro (riscv32 ESP object; probably every target)
ch.h:
unsigned long largest(unsigned int caps);
cu.pas:
unit cu;
interface
function UnitLargest: LongWord;
implementation
function largest(caps: LongWord): NativeUInt; external;
function UnitLargest: LongWord;
begin
Result := largest(4);
end;
end.
cp.pas:
program cp;
uses cu, ch;
function Get: LongWord; cdecl;
begin
Result := largest(4) + UnitLargest;
end;
begin
end.
pascal26 --target=riscv32 --platform=esp --no-signals --emit-obj -I. -Fu. cp.pas cp.o
pascal26:5: error: undefined variable (largest)
With uses ch, cu; instead it compiles.
Expected
An implementation-section declaration is private to its unit, so it should not affect name lookup in units that use it. Even if it did leak, it names the same C symbol with a compatible signature, so it should not end up as "undefined variable".
Real-world hit
lib/rtl/scheduler.pas:628 (930d67d54a, TrySpawnSized) declares
heap_caps_get_largest_free_block privately. museum_landkaart/async/src/lkapp.pas
has uses platform, scheduler, ...; and then uses idf;, and calls
heap_caps_get_largest_free_block from the header, which now fails. The app works
around it by putting uses idf; first. (espsys.pas:47 has the same private
declaration and would cause the same failure if an app uses espsys.)
Log
- 2026-10-08: filed from museum_landkaart/async.
- 2026-10-08: fixed. The header prototype bound to the private row through the
forceSystemExternal exact-name walk in ParseCSubroutine (cparser.inc), which
scanned every proc with no visibility test (FindProc already answered -1).
The walk now skips a row private to another unit. Makefile row
impl_external_vs_header (red on the pinned compiler). Both orders compile;
the object carries two
U largestentries (one per row), which ld accepts.