From e47698cc4110aac41a610160bd9b7d8af8f7f7b9 Mon Sep 17 00:00:00 2001 From: Ilya Konstantinov Date: Wed, 25 Jan 2023 23:07:09 -0500 Subject: [PATCH] wip --- mypy/plugins/attrs.py | 16 +++++++++++++++ mypy/plugins/default.py | 12 ++++++++++- test-data/unit/check-attr.test | 24 +++++++++++++++++++++- test-data/unit/lib-stub/attr/__init__.pyi | 2 ++ test-data/unit/lib-stub/attrs/__init__.pyi | 1 + 5 files changed, 53 insertions(+), 2 deletions(-) diff --git a/mypy/plugins/attrs.py b/mypy/plugins/attrs.py index 16e8891e5f575..1ae737eeec16d 100644 --- a/mypy/plugins/attrs.py +++ b/mypy/plugins/attrs.py @@ -883,3 +883,19 @@ def add_method( """ self_type = self_type if self_type is not None else self.self_type add_method(self.ctx, method_name, args, ret_type, self_type, tvd) + + +def evolve_callback(ctx: mypy.plugin.FunctionSigContext) -> FunctionLike: + """Callback to provide an accurate signature for attrs.evolve.""" + md = ctx.args[0][0].node.type.type.metadata + + args = { + md_attribute['name']: ctx.api.named_generic_type(md_attribute['init_type'], args=[]) + for md_attribute in md.get('attrs', {}).get('attributes', []) + } + + return ctx.default_signature.copy_modified( + arg_kinds=ctx.default_signature.arg_kinds[:1] + [ARG_NAMED_OPT] * len(args), + arg_names=ctx.default_signature.arg_names[:1] + list(args.keys()), + arg_types=ctx.default_signature.arg_types[:1] + list(args.values()), + ) diff --git a/mypy/plugins/default.py b/mypy/plugins/default.py index 04971868e8f41..26ecbd8e39fb8 100644 --- a/mypy/plugins/default.py +++ b/mypy/plugins/default.py @@ -13,6 +13,8 @@ MethodSigContext, Plugin, ) +from mypy.plugin import FunctionSigContext +from mypy.plugins import attrs from mypy.plugins.common import try_getting_str_literals from mypy.subtypes import is_subtype from mypy.typeops import is_literal_type_like, make_simplified_union @@ -45,10 +47,18 @@ def get_function_hook(self, fullname: str) -> Callable[[FunctionContext], Type] return singledispatch.create_singledispatch_function_callback return None + def get_function_signature_hook( + self, fullname: str + ) -> Callable[[FunctionSigContext], FunctionLike] | None: + if fullname == "attr.evolve": + return attrs.evolve_callback + return None + + def get_method_signature_hook( self, fullname: str ) -> Callable[[MethodSigContext], FunctionLike] | None: - from mypy.plugins import ctypes, singledispatch + from mypy.plugins import attrs, ctypes, singledispatch if fullname == "typing.Mapping.get": return typed_dict_get_signature_callback diff --git a/test-data/unit/check-attr.test b/test-data/unit/check-attr.test index f555f2ea70111..7fd0b15aac18d 100644 --- a/test-data/unit/check-attr.test +++ b/test-data/unit/check-attr.test @@ -1866,4 +1866,26 @@ reveal_type(D) # N: Revealed type is "def (a: builtins.int, b: builtins.str) -> D(1, "").a = 2 # E: Cannot assign to final attribute "a" D(1, "").b = "2" # E: Cannot assign to final attribute "b" -[builtins fixtures/property.pyi] \ No newline at end of file +[builtins fixtures/property.pyi] + +[case testEvolve] +import attr + +@attr.s(auto_attribs=True) +class C: + name: str + +c = C(name='foo') +attr.evolve(c) +attr.evolve(c, name='bar') +attr.evolve( + c, + name=42, # type: ignore[arg-type] +) +attr.evolve( + c, + age=42, # type: ignore[call-arg] +) + +[builtins fixtures/dict.pyi] +[typing fixtures/typing-medium.pyi] diff --git a/test-data/unit/lib-stub/attr/__init__.pyi b/test-data/unit/lib-stub/attr/__init__.pyi index 795e5d3f4f697..06b321244f506 100644 --- a/test-data/unit/lib-stub/attr/__init__.pyi +++ b/test-data/unit/lib-stub/attr/__init__.pyi @@ -244,3 +244,5 @@ def field( order: Optional[bool] = ..., on_setattr: Optional[object] = ..., ) -> Any: ... + +def evolve(inst: _T, **changes: Any) -> _T: ... diff --git a/test-data/unit/lib-stub/attrs/__init__.pyi b/test-data/unit/lib-stub/attrs/__init__.pyi index d257740451324..cdd23dc1d747d 100644 --- a/test-data/unit/lib-stub/attrs/__init__.pyi +++ b/test-data/unit/lib-stub/attrs/__init__.pyi @@ -126,3 +126,4 @@ def field( order: Optional[bool] = ..., on_setattr: Optional[object] = ..., ) -> Any: ... +