← board

A base class named by a Pascal reserved word is not mapped

Repro (measured 2026-09-09, compiler 418064fca1d3)

import array
class C(array.array):      # was: Nil Python: unknown base class array
    pass
class D(array.array_):     # compiled
    pass

The five sites, so the next one is not missed either

PyMapReservedMember is applied at:

site position
pyparser.inc value/call, via ConsumeUnitQualifier's NilPy caller had it
pasparser_expr.inc expression had it
PyClassCreate constructor name added 2026-09-09 (was SILENT)
the gate routing a qualified construction there added 2026-09-09
base class added 2026-09-09 — this ticket

Why a grep could not find this class of defect

frankH's framing, kept because it generalises: the constructor gap was not two copies that DRIFTED — it was a site where the check had never been written. Nothing was wrong anywhere; something was absent, and absence collides with nothing. A grep for PyMapReservedMember returns the sites that are already right and says nothing whatever about a missing one, so the usual find-the-copies-and-compare sweep is blind to it.

What worked was inverting the search: enumerate the positions the rule should cover — every site that consumes a unit qualifier — and subtract the ones that have it. That is the reusable half.

And the first attempt at the fix silently did nothing, which is the second lesson

The obvious edit is to map the name right after ConsumeUnitQualifier returns:

baseQName := CurTok.SVal;
baseQUnit := ConsumeUnitQualifier(baseQName);
if baseQUnit >= 0 then baseQName := PyMapReservedMember(baseQName);   { does NOTHING }

It compiles, it is in the right place, and it changes no behaviour: the base lookups below read CurTok.SVal, not baseQName. The variable the qualifier consumer writes and the variable the resolver reads are different, and the repro failed identically before and after. Caught only because the repro was re-run rather than assumed to be fixed — a make and a rebuild are not evidence that an edit did anything.

The fix is in the qualified arm of the lookup itself, after the plain spelling:

baseCi := FindUClassInUnit(CurTok.SVal, baseQUnit);
if baseCi < 0 then
  baseCi := FindUClassInUnit(PyMapReservedMember(CurTok.SVal), baseQUnit);

Plain-spelling-first means a unit that really exports the bare name keeps it, so this can only add a resolution and never redirect an existing one.

Log