← 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.