From 433544bf6f8b886f5f364e3a8de4cc1c0eb43da9 Mon Sep 17 00:00:00 2001 From: Spencer Brown Date: Tue, 29 Mar 2022 12:10:13 +1000 Subject: [PATCH] Recognise both `attrs` and `attr` package names in the plugin (#12469) Recognise new 'attrs' package name in the attrs plugin --- mypy/plugins/attrs.py | 8 +- test-data/unit/check-attr.test | 36 ++++++ test-data/unit/lib-stub/attrs/__init__.pyi | 128 +++++++++++++++++++ test-data/unit/lib-stub/attrs/converters.pyi | 12 ++ 4 files changed, 181 insertions(+), 3 deletions(-) create mode 100644 test-data/unit/lib-stub/attrs/__init__.pyi create mode 100644 test-data/unit/lib-stub/attrs/converters.pyi diff --git a/mypy/plugins/attrs.py b/mypy/plugins/attrs.py index 9e4b80c905cf..0f6431d4700a 100644 --- a/mypy/plugins/attrs.py +++ b/mypy/plugins/attrs.py @@ -41,14 +41,16 @@ attr_dataclass_makers: Final = { 'attr.dataclass', } -attr_frozen_makers: Final = {"attr.frozen"} -attr_define_makers: Final = {"attr.define", "attr.mutable"} +attr_frozen_makers: Final = {"attr.frozen", "attrs.frozen"} +attr_define_makers: Final = {"attr.define", "attr.mutable", "attrs.define", "attrs.mutable"} attr_attrib_makers: Final = { 'attr.ib', 'attr.attrib', 'attr.attr', 'attr.field', + 'attrs.field', } +attr_optional_converters: Final = {'attr.converters.optional', 'attrs.converters.optional'} SELF_TVAR_NAME: Final = "_AT" MAGIC_ATTR_NAME: Final = "__attrs_attrs__" @@ -609,7 +611,7 @@ def _parse_converter(ctx: 'mypy.plugin.ClassDefContext', if (isinstance(converter, CallExpr) and isinstance(converter.callee, RefExpr) - and converter.callee.fullname == "attr.converters.optional" + and converter.callee.fullname in attr_optional_converters and converter.args and converter.args[0]): # Special handling for attr.converters.optional(type) diff --git a/test-data/unit/check-attr.test b/test-data/unit/check-attr.test index 688366e61617..a69bd473624d 100644 --- a/test-data/unit/check-attr.test +++ b/test-data/unit/check-attr.test @@ -400,7 +400,22 @@ reveal_type(D) # N: Revealed type is "def (b: Any) -> __main__.D" [builtins fixtures/bool.pyi] +[case testAttrsNewPackage] +import attrs +@attrs.define +class A: + a: int = attrs.field() + b: bool + +@attrs.frozen +class B: + a: bool + b: int +reveal_type(A) # N: Revealed type is "def (a: builtins.int, b: builtins.bool) -> __main__.A" +reveal_type(B) # N: Revealed type is "def (a: builtins.bool, b: builtins.int) -> __main__.B" + +[builtins fixtures/bool.pyi] [case testAttrsDataClass] import attr @@ -1133,6 +1148,27 @@ A(None, None) [builtins fixtures/attr.pyi] +[case testAttrsOptionalConverterNewPackage] +# flags: --strict-optional +import attrs +from attrs.converters import optional +from typing import Optional + +def converter(s:int) -> str: + return 'hello' + + +@attrs.define +class A: + y: Optional[int] = attrs.field(converter=optional(int)) + z: Optional[str] = attrs.field(converter=optional(converter)) + + +A(None, None) + +[builtins fixtures/attr.pyi] + + [case testAttrsTypeVarNoCollision] from typing import TypeVar, Generic import attr diff --git a/test-data/unit/lib-stub/attrs/__init__.pyi b/test-data/unit/lib-stub/attrs/__init__.pyi new file mode 100644 index 000000000000..d25774045132 --- /dev/null +++ b/test-data/unit/lib-stub/attrs/__init__.pyi @@ -0,0 +1,128 @@ +from typing import TypeVar, overload, Callable, Any, Optional, Union, Sequence, Mapping + +_T = TypeVar('_T') +_C = TypeVar('_C', bound=type) + +_ValidatorType = Callable[[Any, Any, _T], Any] +_ConverterType = Callable[[Any], _T] +_ValidatorArgType = Union[_ValidatorType[_T], Sequence[_ValidatorType[_T]]] + +@overload +def define( + maybe_cls: _C, + *, + these: Optional[Mapping[str, Any]] = ..., + repr: bool = ..., + hash: Optional[bool] = ..., + init: bool = ..., + slots: bool = ..., + frozen: bool = ..., + weakref_slot: bool = ..., + str: bool = ..., + auto_attribs: bool = ..., + kw_only: bool = ..., + cache_hash: bool = ..., + auto_exc: bool = ..., + eq: Optional[bool] = ..., + order: Optional[bool] = ..., + auto_detect: bool = ..., + getstate_setstate: Optional[bool] = ..., + on_setattr: Optional[object] = ..., +) -> _C: ... +@overload +def define( + maybe_cls: None = ..., + *, + these: Optional[Mapping[str, Any]] = ..., + repr: bool = ..., + hash: Optional[bool] = ..., + init: bool = ..., + slots: bool = ..., + frozen: bool = ..., + weakref_slot: bool = ..., + str: bool = ..., + auto_attribs: bool = ..., + kw_only: bool = ..., + cache_hash: bool = ..., + auto_exc: bool = ..., + eq: Optional[bool] = ..., + order: Optional[bool] = ..., + auto_detect: bool = ..., + getstate_setstate: Optional[bool] = ..., + on_setattr: Optional[object] = ..., +) -> Callable[[_C], _C]: ... + +mutable = define +frozen = define # they differ only in their defaults + +@overload +def field( + *, + default: None = ..., + validator: None = ..., + repr: object = ..., + hash: Optional[bool] = ..., + init: bool = ..., + metadata: Optional[Mapping[Any, Any]] = ..., + converter: None = ..., + factory: None = ..., + kw_only: bool = ..., + eq: Optional[bool] = ..., + order: Optional[bool] = ..., + on_setattr: Optional[_OnSetAttrArgType] = ..., +) -> Any: ... + +# This form catches an explicit None or no default and infers the type from the +# other arguments. +@overload +def field( + *, + default: None = ..., + validator: Optional[_ValidatorArgType[_T]] = ..., + repr: object = ..., + hash: Optional[bool] = ..., + init: bool = ..., + metadata: Optional[Mapping[Any, Any]] = ..., + converter: Optional[_ConverterType] = ..., + factory: Optional[Callable[[], _T]] = ..., + kw_only: bool = ..., + eq: Optional[bool] = ..., + order: Optional[bool] = ..., + on_setattr: Optional[object] = ..., +) -> _T: ... + +# This form catches an explicit default argument. +@overload +def field( + *, + default: _T, + validator: Optional[_ValidatorArgType[_T]] = ..., + repr: object = ..., + hash: Optional[bool] = ..., + init: bool = ..., + metadata: Optional[Mapping[Any, Any]] = ..., + converter: Optional[_ConverterType] = ..., + factory: Optional[Callable[[], _T]] = ..., + kw_only: bool = ..., + eq: Optional[bool] = ..., + order: Optional[bool] = ..., + on_setattr: Optional[object] = ..., +) -> _T: ... + +# This form covers type=non-Type: e.g. forward references (str), Any +@overload +def field( + *, + default: Optional[_T] = ..., + validator: Optional[_ValidatorArgType[_T]] = ..., + repr: object = ..., + hash: Optional[bool] = ..., + init: bool = ..., + metadata: Optional[Mapping[Any, Any]] = ..., + converter: Optional[_ConverterType] = ..., + factory: Optional[Callable[[], _T]] = ..., + kw_only: bool = ..., + eq: Optional[bool] = ..., + order: Optional[bool] = ..., + on_setattr: Optional[object] = ..., +) -> Any: ... diff --git a/test-data/unit/lib-stub/attrs/converters.pyi b/test-data/unit/lib-stub/attrs/converters.pyi new file mode 100644 index 000000000000..33800490894d --- /dev/null +++ b/test-data/unit/lib-stub/attrs/converters.pyi @@ -0,0 +1,12 @@ +from typing import TypeVar, Optional, Callable, overload +from attr import _ConverterType + +_T = TypeVar("_T") + +def optional( + converter: _ConverterType[_T] +) -> _ConverterType[Optional[_T]]: ... +@overload +def default_if_none(default: _T) -> _ConverterType[_T]: ... +@overload +def default_if_none(*, factory: Callable[[], _T]) -> _ConverterType[_T]: ...