From 2f5a4946e0569e9bc69729fc59794a1ea4347b91 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Thu, 25 Jul 2024 16:01:00 -0400 Subject: [PATCH] lookup manager type via mro (#2276) there's a lot going on in the test but this specifically breaks lookup through `TypeVar` in mypy 1.11 (mypy 1.10 also failed in this way as well -- but was fixed in python/mypy#17381) test originally failing with: ``` /tmp/django-stubs/tests/typecheck/models/test_inheritance.yml:114: E pytest_mypy_plugins.utils.TypecheckAssertionError: Invalid output: E Actual: E myapp/models:23: error: Incompatible return value type (got "Bound", expected "T") [return-value] (diff) E Expected: E (empty) ``` --- mypy_django_plugin/transformers/models.py | 8 ++-- tests/typecheck/managers/test_managers.yml | 1 - tests/typecheck/models/test_inheritance.yml | 41 +++++++++++++++++++++ 3 files changed, 46 insertions(+), 4 deletions(-) diff --git a/mypy_django_plugin/transformers/models.py b/mypy_django_plugin/transformers/models.py index 0ade78630..2bb17ed08 100644 --- a/mypy_django_plugin/transformers/models.py +++ b/mypy_django_plugin/transformers/models.py @@ -326,7 +326,7 @@ def run_with_model_cls(self, model_cls: Type[Model]) -> None: incomplete_manager_defs = set() for manager_name, manager in model_cls._meta.managers_map.items(): - manager_node = self.model_classdef.info.names.get(manager_name, None) + manager_node = self.model_classdef.info.get(manager_name) manager_fullname = helpers.get_class_fullname(manager.__class__) manager_info = self.lookup_manager(manager_fullname, manager) @@ -343,7 +343,8 @@ def run_with_model_cls(self, model_cls: Type[Model]) -> None: incomplete_manager_defs.add(manager_name) continue - manager_type = Instance(manager_info, [Instance(self.model_classdef.info, [])]) + assert self.model_classdef.info.self_type is not None + manager_type = Instance(manager_info, [self.model_classdef.info.self_type]) self.add_new_node_to_model_class(manager_name, manager_type, is_classvar=True) if incomplete_manager_defs: @@ -359,9 +360,10 @@ def run_with_model_cls(self, model_cls: Type[Model]) -> None: # setting _some_ type fallback_manager_info = self.get_or_create_manager_with_any_fallback() if fallback_manager_info is not None: + assert self.model_classdef.info.self_type is not None self.add_new_node_to_model_class( manager_name, - Instance(fallback_manager_info, [Instance(self.model_classdef.info, [])]), + Instance(fallback_manager_info, [self.model_classdef.info.self_type]), is_classvar=True, ) diff --git a/tests/typecheck/managers/test_managers.yml b/tests/typecheck/managers/test_managers.yml index 1769c0cdf..d8c2f3e38 100644 --- a/tests/typecheck/managers/test_managers.yml +++ b/tests/typecheck/managers/test_managers.yml @@ -543,7 +543,6 @@ myapp/models:23: error: Could not resolve manager type for "myapp.models.TwoUnresolvable.objects" [django-manager-missing] myapp/models:24: error: Could not resolve manager type for "myapp.models.TwoUnresolvable.second_objects" [django-manager-missing] myapp/models:27: error: Could not resolve manager type for "myapp.models.AbstractUnresolvable.objects" [django-manager-missing] - myapp/models:32: error: Could not resolve manager type for "myapp.models.InvisibleUnresolvable.objects" [django-manager-missing] myapp/models:36: note: Revealed type is "django.db.models.manager.Manager[myapp.models.User]" myapp/models:37: note: Revealed type is "django.db.models.manager.Manager[myapp.models.User]" myapp/models:39: note: Revealed type is "myapp.models.UnknownManager[myapp.models.Booking]" diff --git a/tests/typecheck/models/test_inheritance.yml b/tests/typecheck/models/test_inheritance.yml index 552a5b64b..436497fc4 100644 --- a/tests/typecheck/models/test_inheritance.yml +++ b/tests/typecheck/models/test_inheritance.yml @@ -109,3 +109,44 @@ abstract = True class User(Mixin1, Mixin2): name = models.TextField() + +- case: test_manager_typevar_through_bounds + main: | + from myapp.models import ResultProcessorConcrete + reveal_type(ResultProcessorConcrete().f()) # N: Revealed type is "myapp.models.Concrete" + installed_apps: + - myapp + files: + - path: myapp/__init__.py + - path: myapp/models.py + content: | + from __future__ import annotations + + from django.db.models import Model + from django.db.models.manager import Manager + from typing import TypeVar, Generic, ClassVar + from typing_extensions import Self + + M = TypeVar("M", bound=Model, covariant=True) + + class BaseManager(Manager[M]): ... + + class Base(Model): + custom_objects: ClassVar[BaseManager[Self]] = BaseManager() + + class Bound(Base): pass + + T = TypeVar("T", bound=Bound) + + class ResultProcessorBase(Generic[T]): + @property + def model_cls(self) -> type[T]: + raise NotImplementedError + + def f(self) -> T: + return self.model_cls.custom_objects.get() + + class Concrete(Bound): pass + + class ResultProcessorConcrete(ResultProcessorBase[Concrete]): + pass