Skip to content

Imported placeholder names are lost before dynamic class hooks run #22326

Open

Description

@ayushin

An imported class that is still a PlaceholderNode loses its qualified name on the NameExpr. If it is the receiver of a dynamic-class factory, mypy can miss the factory's plugin hook during an import cycle. The resulting generated base is rejected as a variable, and methods inherited through it become Any.

This is reproducible on master at 7da2058491422897ecdbf61261d2540bb10099a0 with Python 3.14.6, without Django or any third-party plugin.

The standalone regression uses the existing dyn_class_from_method test plugin and this import graph:

models -> managers -> base_managers -> models

base_managers:
    BaseManager derives from QuerySet.from_queryset(BaseQuerySet)
managers:
    ChildQuerySet derives from BaseQuerySet
    ChildManager derives from BaseManager.from_queryset(ChildQuerySet)
models:
    Row.objects = ChildManager()

The complete regression and small test plugin are available in the proposed fix. Copying only those test changes onto the upstream revision reproduces the failure with:

python -m pytest -n0 mypy/test/testcheck.py -k testDynamicClassHookFromImportedPlaceholder

Expected results:

main:4: note: Revealed type is "builtins.int"
main:5: note: Revealed type is "builtins.str"
main:6: error: "ChildManager" has no attribute "missing"

Actual results on upstream master:

tmp/managers.py:9: error: Variable "managers._ChildManager" is not valid as a type
tmp/managers.py:9: error: Invalid base class "_ChildManager"
main:4: note: Revealed type is "Any"
main:5: note: Revealed type is "Any"

Assigning expr.fullname = sym.fullname or "" in the placeholder branch of SemanticAnalyzer.bind_name_expr is sufficient to make the regression pass. The full custom-plugin test file passes with that change (65 cases).

This report and patch were prepared with AI assistance from a failure encountered in downstream Django manager typing, reduced to the ordinary mypy test plugin above.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      ⌂ Proxy