← board

from <package> import <submodule> binds the parent package instead of the submodule

Why this is a bug and not a compat item

CLAUDE.md's compat table sends "CPython accepts a form we reject" to a compat ticket at whatever prio real usage justifies. This is the row below it: the form compiles and then resolves to the wrong object. For any name that exists in both the package and the submodule, we return the package's silently — no diagnostic, no crash, a plausible wrong value. That is the silent-wrong-behaviour escape, so it is filed as a bug-.

Repro

from <pkg> import <submodule> is the canonical spelling for minidom — it is what the CPython stdlib documentation uses — so real code writes it constantly.

from xml.dom import minidom
print(minidom.getDOMImplementation())
error: no member getDOMImplementation came of the qualifier minidom
       — check what minidom resolves to; an import that bound nothing
         gives exactly this (minidom.getDOMImplementation)

The diagnostic's own guess is wrong in an instructive way: the import did not bind nothing, it bound the parent package. That is why the suggestion sends you looking for a missing module rather than a mis-resolved one.

The boundary — measured, one factor at a time

Against a stub mimic_xml_dom_minidom.py exposing getDOMImplementation():

spelling compiles runs verdict
import xml.dom.minidom then xml.dom.minidom.getDOMImplementation() yes 42 correct
from xml.dom.minidom import getDOMImplementation then getDOMImplementation() yes 42 correct
from xml.dom import minidom then minidom.getDOMImplementation() no — BUG

So dotted imports are not broken in general. Two of the three spellings resolve the submodule correctly; only submodule-as-imported-name does not.

What minidom is actually bound to

Not nothing — the parent package. EMPTY_NAMESPACE and XHTML_NAMESPACE are top-level names in xml.dom (i.e. lib/rtl/mimic_xml_dom.py) and are not minidom's:

from xml.dom import minidom
print(minidom.EMPTY_NAMESPACE)   # -> None                                (xml.dom's)
print(minidom.XHTML_NAMESPACE)   # -> http://www.w3.org/1999/xhtml        (xml.dom's)

Both compile and both run. Compare the control import xml.dom; print(xml.dom.EMPTY_NAMESPACE) -> None — identical. minidom and xml.dom are the same object here.

This is the part that makes it a bug rather than a missing feature. Had the import bound nothing, every use would fail loudly and the ticket would be a compat item. Because it binds the parent, a program that touches only names the two modules share gets a wrong answer with no diagnostic anywhere.

Controls, so the claim is not broader than the evidence

from xml.dom import Node          COMPILES, runs   <- real member of the package, correct
from xml.dom import nosuchname    REJECTED: undefined variable (nosuchname)  <- correct

A genuine member still binds, and a genuine typo is still caught. The defect is narrowly the case where the imported name is a submodule.

CPython oracle

minidom.getDOMImplementation()   -> DOMImplementation instance
minidom.__name__                 -> 'xml.dom.minidom'
from xml.dom import nosuchname   -> ImportError

CPython binds the submodule and agrees with us on the other two rows.

Suggested shape of the fix

When from P import N cannot resolve N as a member of P, try P.N as a module before falling back — and if it resolves neither, reject. The present behaviour looks like the fallback is "bind P itself", which is the one outcome that can be silently wrong. Failing loudly here would already be an improvement over today even without submodule support.

Gate

Track N's: make test-nilpy green + self-host byte-identical. Plus the three spellings in the table above all compiling and running, and both controls keeping their current behaviour.

Worth noting for whoever takes it

This is why lib/rtl/mimic_xml_dom.py may be seen carrying a _MinidomNamespace shim object binding minidom by hand. That is a workaround for this bug, it is tracked as one, and it should be deleted when this closes rather than left to rot — see devdocs/dev/track-b-workarounds.md for that lifecycle.

Re-measured 2026-09-10, frankB, compiler ca814b0aabcc — STILL LIVE, and the

boundary is narrower than the title

Reproduces exactly, and the compiler says so itself in a note nobody was reading as a diagnosis:

$ pascal26 xmldom.npy
note: xml_dom -> mimic_xml_dom (shim, subset)
ok: ...

from xml.dom import minidom bound xml.dom. The proof that it is the parent and not a coincidence is a value only the parent has:

pxx CPython
minidom.EMPTY_NAMESPACE None None
minidom.XHTML_NAMESPACE http://www.w3.org/1999/xhtml AttributeError

CPython: "module 'xml.dom.minidom' has no attribute 'XHTML_NAMESPACE'". We answer the PARENT's attribute through the CHILD's name, silently. Still a silent wrong value; the severity claim on this one has NOT decayed.

lib/rtl/mimic_xml_dom_minidom.py EXISTS. The correct target is sitting in the tree unused, so this is a resolution bug and not a missing shim.

What a real filesystem package does — the control that nearly closed this

ticket wrongly

A package with a parent and child that BOTH define WHO with different values, so a parent-binding cannot hide:

spelling pxx CPython
from parpkg import child then child.WHO child child
import parpkg.child then parpkg.child.WHO child child
import parpkg.child as c then c.WHO child child

All three correct. So the defect is NOT in from <package> import <submodule> generally — it is in the DOTTED SHIM path, where xml.dom is flattened to mimic_xml_dom and the trailing name is dropped rather than carried into mimic_xml_dom_minidom.

That distinction is the reason this ticket is still open. A green probe on the wrong path is indistinguishable, afterwards, from a green probe on the right one: three correct spellings against a discriminator built so a wrong binding could not hide is more evidence than most closes get, and it was evidence about a different mechanism. The title should say shim where it says package, and whoever takes it should re-derive that boundary rather than trust this table.