← board

What happens now

printf 'import "/usr/include/strings.h"\nprint(ffs(8))\n' > /tmp/pc.npy
./compiler/pascal26 /tmp/pc.npy /tmp/pc
pascal26:1: error: this build would die at exec: `ffs` is imported from
libstrings.so, which no library on this machine answers to. ...

ffs is in libc.so.6. strings.h sits directly in /usr/include, so CSynthDirName is include, LdCacheDirCandidate finds no libinclude.so, state goes to 2, and CSynthDirAnswersFor exits before its libc/libm arms — the arms that would have answered correctly.

Under SDL2/SDL_version.h the same symbols DO resolve, because that directory names an installed library and the state-1 gate opens. So the outcome depends on a property of the header's directory that has nothing to do with where the symbol lives.

The scope is deliberate, and its argument is in the code

pasparser_proc.inc, CSynthDirAnswersFor:

SCOPED to a header whose directory DID name a real library (state 1). Without that, a header naming no library at all keeps today's error, which is the right answer for it — this is not a general licence to satisfy any unresolved symbol out of libc.

That is a real hazard and not a hypothetical: a header declaring its own open or index wrapper would bind to libc's under the wider rule, silently, where today it errors loudly.

The fork, stated as what we want

Do we want an import whose library we cannot name to fail loudly, or to succeed whenever the symbol demonstrably comes from libc?

The evidence standard is the same either way — libc's own .dynsym is read, not guessed. What changes is which mistake we prefer to make: refusing a build that would have worked, or linking a same-named-but-different function.

Note the wider rule is not "satisfy anything out of libc": a header synthesising libfoo.so and declaring foo_init still errors, because libc does not export it. Only names libc actually defines are absorbed.

Not urgent — but the green here has NO OWNER

The one fixture that hit this (test_nilpy_qualified_name_error_names_the_ receiver) was moved off strings in 0997c6088 for an unrelated and correct reason, so no row is red on it.

Read that as luck, not as evidence. Nothing in the tree asserts the current behaviour. The row that would have caught a change to it left for reasons that had nothing to do with this question, so if the fork is decided the other way, or if someone moves a fixture back onto a top-level header, there is no instrument standing here to notice. The silence is an absence of coverage wearing the shape of a passing suite.

Deliberately NOT fixed by writing a test now: the behaviour is the thing under question, and pinning it would pin one arm of the fork before it is decided — the assertion would then have to be deleted by whoever answers, which makes it an obstacle rather than a guard. Whoever settles this should land the test in the same commit, in whichever direction they choose.

(frankB's observation, on reading the ticket. Recorded here rather than in a message because a message is not where the next reader looks.) Found while landing bug-c-an-unresolvable-synthesised-soname-still-reaches-dt-needed, whose fix does not touch this scope.

WIDENED 2026-09-10 — the libc case is one instance, and the fork is bigger

frankuser measured a two-line repro that needs no SDL2 and no window:

import zlib
x = zlib.crc32
-> this build would die at exec: `crc32` is imported from libzlib.so,
   which no library on this machine answers to

zlib.h synthesises libzlib.so; the library is libz.so.1. And the sibling shows why this survived: sqlite3.h synthesises libsqlite3.so, which EXISTS, so import sqlite3 binds and runs. One wrong rule, and the first case anyone tried was the one where it happens to be right.

Three shapes, all measured at 39441c6fe:

header derived real library outcome before
sqlite3.h libsqlite3.so libsqlite3.so.0 binds — stem IS the library
zlib.h libzlib.so libz.so.1 REFUSED — stem is not
SDL2/SDL_version.h libsdl_version.so libSDL2-2.0.so.0 binds via the DIRECTORY rule

The libc/libm arms this ticket was filed about are the same question with the answer already known. The real question is one notch out.

The measurement that makes it decidable

Asking which library on this box exports the symbol is not a guess: it is the same .dynsym evidence standard the directory rule already uses. Scanned all 1439 native entries in ld.so.cache:

crc32            1 library   libz
compress         1 library   libz
deflate          1 library   libz
inflate          1 library   libz
sqlite3_open     1 library   libsqlite3
png_read_png     1 library   libpng16
curl_easy_init   2           libcurl, libcurl-gnutls
SDL_Init         3           libSDL-1.2, libSDL2-2.0, libSDL3

Ambiguity is real but structured. Six of eight are unique. The two that are not are variants of one library — two TLS backends, three major versions — never two unrelated libraries competing for a name. That is a far narrower hazard than "satisfy any unresolved symbol from anywhere", which is what the existing scope comment was written against, and it means a search that ERRORS when more than one family answers loses almost nothing.

Cost is bounded and lands only on a path that today is a hard error: one pass over the cache, and 35.7s here through 1439 readelf invocations — the compiler reads ELF directly and would be far cheaper.

The fork, restated

Does an import name a LIBRARY, or a place to look for one?

If it names a library, a derived soname that answers to nothing is an error and the user should say which library they meant. If it names a place to look, the compiler should find the library that actually exports what was asked for.

Point fix landed meanwhile, and it is recorded AS a point fix

zlib.h -> libz.so.1 is now a row in CSonameForStem (import zlib builds, links libz.so.1 and runs; test_nilpy_import_zlib.npy asserts the value, the ABSENCE of libzlib.so and the PRESENCE of libz.so.1). That unblocks feature-n-mimic-zlib-..., and it is the THIRD row of a table whose premise is that the header stem is the library name. It does not close this ticket and should not be read as evidence the class is handled.