Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix mishandling of typing.Self in attrs generated inits #14689

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
15 changes: 14 additions & 1 deletion mypy/plugins/attrs.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
deserialize_and_fixup_type,
)
from mypy.server.trigger import make_wildcard_trigger
from mypy.state import state
from mypy.typeops import get_type_vars, make_simplified_union, map_type_from_supertype
from mypy.types import (
AnyType,
Expand Down Expand Up @@ -123,10 +124,19 @@ def __init__(
self.context = context
self.init_type = init_type

def expand_type(self, typ: Type | None, ctx: mypy.plugin.ClassDefContext) -> Type | None:
if typ is not None and self.info.self_type is not None:
# In general, it is not safe to call `expand_type()` during semantic analyzis,
# however this plugin is called very late, so all types should be fully ready.
# Also, it is tricky to avoid eager expansion of Self types here (e.g. because
# we serialize attributes).
return expand_type(typ, {self.info.self_type.id: fill_typevars(ctx.cls.info)})
return typ

def argument(self, ctx: mypy.plugin.ClassDefContext) -> Argument:
"""Return this attribute as an argument to __init__."""
assert self.init
init_type: Type | None = None
init_type: Type | None
if self.converter:
if self.converter.init_type:
init_type = self.converter.init_type
Expand Down Expand Up @@ -171,6 +181,9 @@ def argument(self, ctx: mypy.plugin.ClassDefContext) -> Argument:
else:
arg_kind = ARG_OPT if self.has_default else ARG_POS

with state.strict_optional_set(ctx.api.options.strict_optional):
init_type = self.expand_type(init_type, ctx)

# Attrs removes leading underscores when creating the __init__ arguments.
return Argument(Var(self.name.lstrip("_"), init_type), init_type, None, arg_kind)

Expand Down
4 changes: 2 additions & 2 deletions mypy/plugins/dataclasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from __future__ import annotations

from typing import Iterator, Optional
from typing import Iterator
from typing_extensions import Final

from mypy import errorcodes, message_registry
Expand Down Expand Up @@ -120,7 +120,7 @@ def to_argument(self, current_info: TypeInfo) -> Argument:
kind=arg_kind,
)

def expand_type(self, current_info: TypeInfo) -> Optional[Type]:
def expand_type(self, current_info: TypeInfo) -> Type | None:
if self.type is not None and self.info.self_type is not None:
# In general, it is not safe to call `expand_type()` during semantic analyzis,
# however this plugin is called very late, so all types should be fully ready.
Expand Down
25 changes: 25 additions & 0 deletions test-data/unit/check-plugin-attrs.test
Original file line number Diff line number Diff line change
Expand Up @@ -1978,6 +1978,31 @@ D(1, "").b = "2" # E: Cannot assign to final attribute "b"

[builtins fixtures/property.pyi]

[case testSelfInClassInit]
# flags: --strict-optional
from attrs import define
from typing import Union, Self

@define
class C:
a: Union[Self, None] = None

reveal_type(C) # N: Revealed type is "def (a: Union[__main__.C, None] =) -> __main__.C"
C(C())
C(None)
reveal_type(C(C()).a) # N: Revealed type is "Union[__main__.C, None]"

@define
class S(C):
...

reveal_type(S) # N: Revealed type is "def (a: Union[__main__.S, None] =) -> __main__.S"
S(S())
S(None)
reveal_type(S(S()).a) # N: Revealed type is "Union[__main__.S, None]"

[builtins fixtures/property.pyi]

[case testEvolve]
import attr

Expand Down