diff --git a/tests/assert_type/db/models/test_manager_overrides/__init__.py b/tests/assert_type/db/models/test_manager_overrides/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/assert_type/db/models/test_manager_overrides/models.py b/tests/assert_type/db/models/test_manager_overrides/models.py new file mode 100644 index 000000000..75b53f1a3 --- /dev/null +++ b/tests/assert_type/db/models/test_manager_overrides/models.py @@ -0,0 +1,75 @@ +"""Overriding `Manager.create()` with a narrowed return type. + +Regression test for https://github.com/typeddjango/django-stubs/issues/578. +""" + +from __future__ import annotations + +from typing import Any + +from django.db import models +from typing_extensions import assert_type, override + +# --------------------------------------------------------------------------- +# Unparametrized manager: the plugin re-parametrizes it with the model it's on +# --------------------------------------------------------------------------- + + +class ArticleManager(models.Manager): # pyright: ignore[reportMissingTypeArgument] + # Without a type parameter, the inherited `create()` returns the unsolved `_T`, so narrowing it is an error. + # Only mypy checks the override against `_T`, the other type checkers treat it as unknown. + @override + def create(self, **kwargs: Any) -> Article: # type: ignore[override] + return super().create(**kwargs) # type: ignore[return-value] # pyright: ignore[reportUnknownVariableType] + + +class Article(models.Model): + objects = ArticleManager() + + +def override_manager_create_no_type_param() -> None: + assert_type(Article.objects.create(), Article) + + +# --------------------------------------------------------------------------- +# Manager parametrized with the model it's assigned to +# --------------------------------------------------------------------------- + + +class BookManager(models.Manager["Book"]): + @override + def create(self, **kwargs: Any) -> Book: + return super().create(**kwargs) + + +class Book(models.Model): + # pyrefly doesn't solve `Self` in the `objects: ClassVar[Manager[Self]]` declaration + objects = BookManager() # pyrefly: ignore[bad-assignment] + + +def override_manager_create_with_type_param() -> None: + assert_type(Book.objects.create(), Book) + + +# --------------------------------------------------------------------------- +# Manager parametrized with another model than the one it's assigned to +# --------------------------------------------------------------------------- + + +class ReviewManager(models.Manager["Author"]): + # The type parameter, not the model the manager ends up on, drives what the inherited `create()` returns. + @override + def create(self, **kwargs: Any) -> Review: # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride] # pyrefly: ignore[bad-override] # ty: ignore[invalid-method-override] + return super().create(**kwargs) # type: ignore[return-value] # pyright: ignore[reportReturnType] # pyrefly: ignore[bad-return] # ty: ignore[invalid-return-type] + + +class Review(models.Model): + objects = ReviewManager() # pyright: ignore[reportGeneralTypeIssues] # pyrefly: ignore[bad-assignment] + + +class Author(models.Model): + pass + + +def override_manager_create_with_incoherent_type_param() -> None: + assert_type(Review.objects.create(), Review) diff --git a/tests/typecheck/managers/test_managers.yml b/tests/typecheck/managers/test_managers.yml index c6bb1e8bf..8cbbf77b3 100644 --- a/tests/typecheck/managers/test_managers.yml +++ b/tests/typecheck/managers/test_managers.yml @@ -401,54 +401,6 @@ movie = models.TextField() -- case: override_manager_create1 - main: | - from myapp.models import MyModel - installed_apps: - - myapp - out: | - myapp/models:7: error: Return type "MyModel" of "create" incompatible with return type "_T" in supertype "django.db.models.manager.Manager" [override] - myapp/models:8: error: Incompatible return value type (got "_T", expected "MyModel") [return-value] - files: - - path: myapp/__init__.py - - path: myapp/models.py - content: | - from typing import Any - from django.db import models - from typing_extensions import override - - class MyModelManager(models.Manager): - @override - def create(self, **kwargs: Any) -> 'MyModel': - return super().create(**kwargs) - - - class MyModel(models.Model): - objects = MyModelManager() - - -- case: override_manager_create2 - main: | - from myapp.models import MyModel - MyModel.objects.create() - installed_apps: - - myapp - files: - - path: myapp/__init__.py - - path: myapp/models.py - content: | - from typing import Any - from django.db import models - from typing_extensions import override - - class MyModelManager(models.Manager['MyModel']): - @override - def create(self, **kwargs: Any) -> 'MyModel': - return super().create(**kwargs) - - class MyModel(models.Model): - objects = MyModelManager() - - case: regression_manager_scope_foreign main: | from typing_extensions import reveal_type