Skip to content

Commit cd832cc

Browse files
committed
wip
1 parent 0665ce9 commit cd832cc

File tree

4 files changed

+67
-1
lines changed

4 files changed

+67
-1
lines changed

mypy/plugins/attrs.py

+19
Original file line numberDiff line numberDiff line change
@@ -883,3 +883,22 @@ def add_method(
883883
"""
884884
self_type = self_type if self_type is not None else self.self_type
885885
add_method(self.ctx, method_name, args, ret_type, self_type, tvd)
886+
887+
888+
def evolve_callback(ctx: mypy.plugin.FunctionSigContext) -> FunctionLike:
889+
"""Callback to provide an accurate signature for attrs.evolve."""
890+
if len(ctx.args[0]) < 1:
891+
return ctx.default_signature
892+
893+
metadata = ctx.args[0][0].node.type.type.metadata
894+
895+
args = {
896+
md_attribute['name']: ctx.api.named_generic_type(md_attribute['init_type'], args=[])
897+
for md_attribute in metadata.get('attrs', {}).get('attributes', [])
898+
}
899+
900+
return ctx.default_signature.copy_modified(
901+
arg_kinds=ctx.default_signature.arg_kinds[:1] + [ARG_NAMED_OPT] * len(args),
902+
arg_names=ctx.default_signature.arg_names[:1] + list(args.keys()),
903+
arg_types=ctx.default_signature.arg_types[:1] + list(args.values()),
904+
)

mypy/plugins/default.py

+10
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
AttributeContext,
1010
ClassDefContext,
1111
FunctionContext,
12+
FunctionSigContext,
1213
MethodContext,
1314
MethodSigContext,
1415
Plugin,
@@ -45,6 +46,15 @@ def get_function_hook(self, fullname: str) -> Callable[[FunctionContext], Type]
4546
return singledispatch.create_singledispatch_function_callback
4647
return None
4748

49+
def get_function_signature_hook(
50+
self, fullname: str
51+
) -> Callable[[FunctionSigContext], FunctionLike] | None:
52+
from mypy.plugins import attrs
53+
54+
if fullname in ("attr.evolve", "attr.assoc"):
55+
return attrs.evolve_callback
56+
return None
57+
4858
def get_method_signature_hook(
4959
self, fullname: str
5060
) -> Callable[[MethodSigContext], FunctionLike] | None:

test-data/unit/check-attr.test

+35-1
Original file line numberDiff line numberDiff line change
@@ -1866,4 +1866,38 @@ reveal_type(D) # N: Revealed type is "def (a: builtins.int, b: builtins.str) ->
18661866
D(1, "").a = 2 # E: Cannot assign to final attribute "a"
18671867
D(1, "").b = "2" # E: Cannot assign to final attribute "b"
18681868

1869-
[builtins fixtures/property.pyi]
1869+
[builtins fixtures/property.pyi]
1870+
1871+
[case testEvolve]
1872+
import attr
1873+
1874+
@attr.s(auto_attribs=True)
1875+
class C:
1876+
name: str
1877+
1878+
c = C(name='foo')
1879+
attr.evolve() # E: Missing positional argument "inst" in call to "evolve"
1880+
attr.evolve(c)
1881+
attr.evolve(c, name='bar')
1882+
attr.evolve(
1883+
c,
1884+
name=42, # E: Argument "name" to "evolve" has incompatible type "int"; expected "str"
1885+
)
1886+
attr.evolve(
1887+
c,
1888+
age=42, # type: ignore[call-arg]
1889+
)
1890+
1891+
# 'assoc' is the deprecated one:
1892+
1893+
attr.assoc(
1894+
c,
1895+
name=42, # E: Argument "name" to "assoc" has incompatible type "int"; expected "str"
1896+
)
1897+
attr.assoc(
1898+
c,
1899+
age=42, # type: ignore[call-arg]
1900+
)
1901+
1902+
[builtins fixtures/dict.pyi]
1903+
[typing fixtures/typing-medium.pyi]

test-data/unit/lib-stub/attr/__init__.pyi

+3
Original file line numberDiff line numberDiff line change
@@ -244,3 +244,6 @@ def field(
244244
order: Optional[bool] = ...,
245245
on_setattr: Optional[object] = ...,
246246
) -> Any: ...
247+
248+
def evolve(inst: _T, **changes: Any) -> _T: ...
249+
def assoc(inst: _T, **changes: Any) -> _T: ...

0 commit comments

Comments
 (0)