← board

Calling an instance whose NAME matches a class runs the CONSTRUCTOR

class Parser:
    def __init__(self, n):
        self.n = n
    def __call__(self, a):
        return self.n + a

parser = Parser(1)
print(parser.run())      # fine — method calls are unaffected
print(parser(2))         # CPython 3     pxx 131405738672336

The result is the instance POINTER printed as a number: parser(2) built a new Parser instead of invoking __call__ on the existing one.

Why this is prio 55

parser = Parser(...), widget = Widget(), p = P(3) is how Python code is normally written — the instance takes the class's name in lower case. Any such object that is CALLABLE silently returns a fresh instance's pointer instead of its __call__ result. Method calls on the same object are fine, so a file can be almost entirely correct and wrong on one line.

It also constructs an object nobody asked for, so a class counting its instances (or doing anything in __init__) is silently off — that is how this surfaced: a P.count class attribute read 3 where CPython said 2.

Cause — one condition, precisely located

compiler/parser.inc ~9060:

  if PyExprMode and (CurTok.Kind = tkIdent) and
     (FindUClassNonRecord(CurTok.SVal) >= 0) and
     (TokPos < TokCount) and (Tokens[TokPos].Kind = tkLParen) then
  begin
    CurASTNode := PyClassCreateExpr;

FindUClassNonRecord is case-insensitive, so the identifier parser matches the class Parser and any <ident>( becomes a construction. Nothing checks whether the name is already bound as a VALUE.

Same family as the recorded [[bug-nilpy-a-local-named-like-a-class-is-typed-as-that-class]], which was fixed for the VALUE position by making that lookup case-sensitive. The call position is a separate site and did not get the same treatment — worth grepping for other FindUClass* uses in decision positions while fixing this, since a third site is likely.

Shape of a fix

Stand the intercept down when the name is bound as a value in scope — Python scoping says a local/parameter/global shadows a class of the same name, which is the same rule the value-position fix restored. FindSym(CurTok.SVal) >= 0 is the direct test; an exact-case class match is the weaker alternative and still leaves P = P(3)-style exact collisions wrong.

Careful with the legitimate case the site exists for: a genuine Word("x") / tk.Canvas(root) construction where the name is NOT bound as a value must keep working, and the comment above it records that reading it as a record typecast was a previous bug. So the guard is "bound as a value wins", not "never construct".

Gate

.npy diffed against CPython: an instance named like its class in lower case, in the SAME case, and one named differently (control); __call__ and a method call on each; a class-attribute instance counter proving no extra construction happens; and a genuine construction of an unbound class name still working.

2026-08-09 — FIXED (sole-A confirmed by the user)

Name( is a constructor only on an EXACT-case class match now, which is Python's own rule. The intercept tested FindUClassNonRecord, which matches case-INSENSITIVELY, so a lowercase instance name found its CapWords class.

A case-insensitive match with a different spelling can only arise between names that differ solely in case, which no NilPy source and no shim does — so requiring exactness costs nothing and removes the whole class of collision.

This is the CALL half of the family whose TYPING half PyIsClassTypeExact fixed earlier (a local named like a class was TYPED as that class); the new predicate is deliberately written in the same shape, next to it.

The control that always worked — thing = Other() then thing(3) — is what identified the NAME as the variable rather than __call__ dispatch being broken in general, and it is kept in the test for that reason. The rest of the test is the regression surface for narrowing an intercept: ordinary construction inline, in a comprehension, nested in another constructor's arguments, for a @dataclass, and for a builtin exception.

Verified against CPython; gate.sh quick GREEN.

Follow-up the same day: a class ALIAS is an exact name too

The first cut compared only against the CLASS name, which rejected every aliased constructor: from m import Box as BoxAlias registers BoxAlias against Box's row, so the class name never equals the spelling used and BoxAlias(9) stopped compiling. The predicate now also accepts an exact-case match against the alias TEXT — an alias is a real Python name and Python is case-sensitive about it too.

Found by the whole-suite HEAD-vs-pinned sweep, not by the fix's own test. Nothing in test_nilpy_instance_named_like_its_class.npy exercises an alias; test_nilpy_from_import_as_alias did, and the sweep is what compared it against pinned and flagged it as the one HEAD-only failure. That is the argument for running the sweep after a narrowing change: narrowing an intercept cannot be regression-tested by the test that motivated it.