diff --git a/mypy/plugins/attrs.py b/mypy/plugins/attrs.py index afd9423d6820..e956eda1ee9a 100644 --- a/mypy/plugins/attrs.py +++ b/mypy/plugins/attrs.py @@ -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, @@ -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 @@ -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) diff --git a/mypy/plugins/dataclasses.py b/mypy/plugins/dataclasses.py index 2fd903f2f8b9..60188d0b6d6d 100644 --- a/mypy/plugins/dataclasses.py +++ b/mypy/plugins/dataclasses.py @@ -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 @@ -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. diff --git a/test-data/unit/check-plugin-attrs.test b/test-data/unit/check-plugin-attrs.test index 9aa31c1ed10b..47b4437d7b64 100644 --- a/test-data/unit/check-plugin-attrs.test +++ b/test-data/unit/check-plugin-attrs.test @@ -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