Repository navigation
Adding __init__ breaks type inference from __new__ #12045
Description
Hello, i'm trying to fix a bug in django type stubs, and i believe i've stubled upon bug in mypy.
Django has following syntax for defining foreign keys:
class Book(models.Model):
author = models.ForeignKey(Author, null=True)This means author should be Author | None.
Right now django-types are using @override for __init__ with differently typed self parameter:
@overload
def __init__(
self: ForeignKey[_M],
...
null: Literal[False],
...
@overload
def __init__(
self: ForeignKey[Optional[_M]],
...
null: Literal[True],
...
And it works for mypy, but doesn't work for pyright. In mypy it works becouse it uses undocumented feature of typing self, as i discovered here:
Proper, and most concise way of doing this, would be overloading __new__ with args/kwargs and bool: Literal[...], and leaving full annotation with all arguments typed in __init__ (which i attempted here sbdchd/django-types#86).
But, when we try to do this: mypy would ignore overrides in __new__ and will always use non-optional case.
To Reproduce
To reproduce this without much clutter, we can use following code:
# related_field_minimal.pyi
from typing import Generic, Literal, Optional, Type, TypeVar, overload
class Model: ...
_M = TypeVar("_M", bound=Optional[Model])
class ForeignKey(Generic[_M]):
@overload
def __new__(
cls,
to: Type[_M],
null: Literal[False] = ...,
) -> ForeignKey[_M]: ...
@overload
def __new__(
cls,
to: Type[_M],
null: Literal[True],
) -> ForeignKey[Optional[_M]]: ...
def __init__(self, to: Type[_M], null: bool) -> None: ...# models.py
from typing import Optional
from related_field_minimal import ForeignKey, Model
class Test(Model):
pass
class BookModel(Model):
required_field = ForeignKey(to=Test, null=False)
optional_f1 = ForeignKey(to=Test, null=True)
optional_f2 = ForeignKey[Test](to=Test, null=True)
optional_f3 = ForeignKey[Optional[Test]](to=Test, null=True)
reveal_type(BookModel.required_field)
reveal_type(BookModel.optional_f1)
reveal_type(BookModel.optional_f2)
reveal_type(BookModel.optional_f3)When there is no __init__ in ForeignKey, inferred types are correct, both in mypy and pyright.
models.py:15: note: Revealed type is "related_field_minimal.ForeignKey[models.Test]"
models.py:16: note: Revealed type is "related_field_minimal.ForeignKey[Union[models.Test, None]]"
models.py:17: note: Revealed type is "related_field_minimal.ForeignKey[Union[models.Test, None]]"
models.py:18: note: Revealed type is "related_field_minimal.ForeignKey[Union[models.Test, None]]"
As soon as we add __init__ to our type definition in related_field_minimal.pyi, it becomes incorrect in mypy:
models.py:15: note: Revealed type is "related_field_minimal.ForeignKey[models.Test]"
models.py:16: note: Revealed type is "related_field_minimal.ForeignKey[models.Test]"
models.py:17: note: Revealed type is "related_field_minimal.ForeignKey[models.Test]"
models.py:18: note: Revealed type is "related_field_minimal.ForeignKey[Union[models.Test, None]]"
(pyright still handles this case correctly).
Your Environment
- Mypy version used: 0.931
- Mypy command-line flags: None
- Mypy configuration options from
mypy.ini(and other config files): None - Python version used: 3.10
Activity
stereobutter commentedon Apr 8, 2023
I've also encountered this issue (using mypy 1.2.0).
I've found no way to make mypy happy when
__init__was defined. This makes it impossible to use inline type annotations (instead of.pyistubs) because at runtime__init__is obviously needed.ikonst commentedon Apr 8, 2023
The same is normally possible by wrapping in
if not TYPE_CHECKING:.Mr-Pepe commentedon Dec 1, 2023
However, this breaks autocomplete and linters because attributes set inside
__init__are not visible anymore.