← board

An aliased from-import binds a same-named Pascal routine the plain spelling refuses

Found 2026-09-21 while closing the case-fold class, by asking what ELSE reaches the stdlib tables. It is a different mechanism that happens to share a name with the bug that exposed it, which is why it needed its own ticket rather than a line in that one.

Reproduced at HEAD and on the pin

from random import Random as R
print(R())              # CPython: <random.Random object ...>   pxx: 0
print(R(10) < 10)       # pxx: True

R(10) < 10 is what identifies it: that is Pascal's Random(n), which returns 0..n-1. The name is bound to the routine in the backing unit lib/rtl/random.pas (and/or System.Random), not to anything Python means by random.Random.

THE CONDITION THAT SPRINGS IT IS THE ALIAS, AND THAT IS THE WHOLE FINDING

from random import Random           -> REFUSED: no overload of Random matches these arguments
from random import Random as R      -> COMPILES, returns 0

One import, two spellings, two different answers, and the LOUD one is the spelling a CPython programmer is less likely to write. Random as R is ordinary Python style. So the arrangement that is silent is not the exotic one.

This is the tree's own rule arriving again: the sibling is usually a SPELLING, not a shape, which is why grepping for the construct misses it. Grepping for Random finds both arms; only running both separates them.

What it is NOT

Why 60

A program asking for a seeded RNG gets an integer and can go on computing with it. There is no diagnostic, no crash, and the value is plausible — the tree's expensive shape. Against that: random.Random is the one name in the whole admitted key space where this can happen (see below), so the blast radius is one name today, and it is bounded by a Pascal routine existing under the same spelling in a backing unit.

It is above the floor because the mechanism is general and the population grows with every mimic_ unit that gains a Pascal-named routine, and because feature-n-random-random-has-no-per-instance-rng-class-... will be implemented on exactly this name — whoever does that must know the alias arm exists, or the class will be shadowed by the routine for the aliased spelling only.

THE KEY-SPACE CENSUS THAT BOUNDS IT, and that closes the case-fold class

Method is the one from pxx@3d3d90a2d: reason over the key space rather than sample spellings, so the answer covers names nobody has written yet.

Question: within a module the dispatcher admits, do two REAL CPython attributes differ only by case? If none do, a case-fold can only ever accept a name CPython REJECTS — which is upward-compatible and a feature under the N rule, not a defect.

Population: every attribute of the eight importable admitted bases, against CPython 3.14.4.

os 427 · sys 115 · textwrap 17 · select 38 · math 67 · time 44 · collections 38
random: COLLISION -> ['Random', 'random']

816 attributes examined, exactly 1 case-collision.

So random.Random is the only name in the admitted key space where a case-fold can return the wrong KIND of thing. Everywhere else — sys.ARGV, os.SEP, both verified to compile — the fold accepts what CPython rejects, and that is the one direction NilPy is allowed to differ in.

That is the case-fold class closed by argument rather than by spot-checks, and it is a NULL result: nothing else folds harmfully, and the one name that could is the one already fixed.

THE FORK IS NARROWER THAN THIS TICKET FIRST SAID (frankuser, 2026-09-21)

Filed saying the correct behaviour was undecided because "make the two spellings agree" does not name a target. That is too weak, and the project's own rule closes the half that matters without settling anything about an RNG class. Real code compiling or running wrong is a bug, and from random import Random as R is not an exotic input — it is ordinary Python that someone MEANT to write, the spelling a style guide pushes you toward. So one answer is ruled OUT today: a silent wrong value.

The floor is that both spellings behave the same, and a LOUD REFUSAL satisfies it. That is decidable now, costs nothing, pre-empts nobody, and it makes the shadowing hazard below impossible to hit silently — which was the actual danger. Reproduced independently under the pin before this was written.

MECHANISM CORRECTED: it is the UNIT arm and Pascal case-insensitivity

This ticket first implied the stdlib alias table. It is not that. Measured 2026-09-21 at HEAD:

from math import Sqrt as S ; print(S(9))   ->  3.0     (Pascal `Sqrt`)
from math import sqrt as s ; print(s(9))   ->  3.0
from random import Random as R ; print(R())  ->  0
from random import Random as R ; print(R(0)) ->  0     (identical)

PyStdProvidesMember answers FALSE for both math.Sqrt and random.Random once the member is matched as spelled, so neither reaches PyStdAliasRecord at all. Both are bound by the BACKING-UNIT arm, where Pascal's case-insensitive routine lookup matches Sqrt to sqrt and Random to Random. So the case-insensitivity here is Pascal's, leaking into Python name resolution — not the fold that pxx@3d3d90a2d removed.

R() and R(0) give the same answer, so the no-argument call is reaching Pascal's Random(n) with a zero, not Pascal's argumentless Random: Real.

AND THIS CONFIRMS THE KEY-SPACE CENSUS BY A ROUTE THAT WAS NOT TESTED WHEN IT WAS WRITTEN. The census says math has no case-collision, so a fold there can only accept what CPython rejects — and Sqrt as S is exactly that: accepted by us, rejected by CPython, harmless. The argument predicted the behaviour of a name nobody had tried, which is the property an observational census does not have.

What a fix MUST NOT break

from math import sqrt as s and from random import randint as ri both work and are genuine Python. Any rule that refuses Random as R by requiring the member to be a Python-facing name has to keep those. Sqrt as S may be refused — CPython rejects it too — but that is a widening of the blast radius beyond the floor above, so it should be a stated decision rather than a side effect.

Not attempted here. The binding site is shared by every from <unit> import <name>, so the change is a name-resolution rule and not a local repair; landing it needs a full tier rather than the quick one. Banked rather than microfixed.

What would retire THIS ticket

from random import Random as R either refusing (agreeing with the plain spelling) or binding the real RNG class once that feature lands. Both spellings must be in the fixture — the whole defect is that they disagree, so a row covering only one certifies the other.

Positive control: the fixture must fail on a build where the alias arm binds the Pascal routine. Pin v414 is that build, so it needs no manufacturing — run the row against stable_linux_amd64/default/pinned and it must return 0.