← board

N: sys.version_info = 3.9, sys.implementation = nilpy, from one constant

Decided in [[decide-nilpy-what-version-does-sys-version-info-claim]] (owner, 2026-08-30). The ruling and the measurement behind it are there; this is the work.

What to add

sys.version_info was never the identity field — PEP 421 put identity in sys.implementation. Answering both is what lets NilPy report a usable language level without claiming to be CPython. Both are absent today; both raise AttributeError.

sys.version_info  = (3, 9, 0, 'final', 0)
sys.version       = '3.9.0 (NilPy <ver>)'
sys.hexversion    = 0x030900F0
sys.implementation.name    = 'nilpy'
sys.implementation.version = NilPy's own version tuple

One constant, four readers. The ticket that raised this said so and it is the part most likely to be skipped: version, hexversion and version_info disagreeing is a worse failure than any of them being wrong, because code that cross-checks them is code that was already being careful.

Where

Also land, and they need no decision

sys.maxsize (2**63 - 1 on the 64-bit targets — derive it, do not hardcode across targets) and sys.byteorder ('little'). Flagged as independent by the deciding ticket: they are plain facts about the target with no product claim attached.

The probe suite is part of this ticket, not a follow-up

The number is a judgment call, not a derivation — the measurement showed the implemented subset is not an interval (gaps at 3.3 and 3.5 alongside features at 3.9), so nothing in the tree can compute it. That is exactly why it needs a test underneath it: an asserted number rots silently, a tested one does not.

Ship the probes that produced the ruling as a NilPy test. Each is a few lines:

must COMPILE and run must be REFUSED (and the refusal is the assertion)
f-strings; 1_000_000 yield from
f-string = specifier async def
@dataclass walrus :=
dict | merge; str.removeprefix positional-only /
* / ** unpacking match; except*; PEP 695 def f[T]
tuple comparison (v >= (3, 7))

The right column is the load-bearing half and it is not a claim that those features should stay unimplemented — it is a tripwire on the version claim. When one starts compiling, the test fails, and whoever landed it has to decide whether 3.9 is still the right number. That is the mechanism that stops the claim drifting away from reality; without it the constant is someone's opinion from 2026-08-30. See [[bug-n-yield-from-is-not-implemented]] and [[bug-n-async-def-and-await-are-not-implemented]] — both are filed to be FIXED, and fixing either should trip this test by design.

Include the tuple-comparison probe even though it passes today: v >= (3, 7) is the operation every real version test performs, and the whole feature is worthless if it regresses.

Docs

devdocs/dev/nilpy-semantics-divergences.md gets the gap list, and the claim is stated as a compatibility affordance, not conformance — the discipline CLAUDE.md already applies to the two different "byte-identical" claims:

NilPy reports 3.9 so that version-gated code selects a branch NilPy can compile. It does not implement all of 3.9; the gaps are listed here.

Gate

make test-nilpy green + self-host byte-identical + cross (Track N's gate). Plus the functional check: a program doing `import sys; print(sys.version_info

= (3, 7))prints True,sys.implementation.nameprintsnilpy, and version/hexversion/version_info` agree.