Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
ikonst committed Jan 26, 2023
1 parent 0665ce9 commit cd832cc
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 1 deletion.
19 changes: 19 additions & 0 deletions mypy/plugins/attrs.py
Original file line number Diff line number Diff line change
Expand Up @@ -883,3 +883,22 @@ 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."""
if len(ctx.args[0]) < 1:
return ctx.default_signature

metadata = 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 metadata.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()),
)
10 changes: 10 additions & 0 deletions mypy/plugins/default.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
AttributeContext,
ClassDefContext,
FunctionContext,
FunctionSigContext,
MethodContext,
MethodSigContext,
Plugin,
Expand Down Expand Up @@ -45,6 +46,15 @@ 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:
from mypy.plugins import attrs

if fullname in ("attr.evolve", "attr.assoc"):
return attrs.evolve_callback
return None

def get_method_signature_hook(
self, fullname: str
) -> Callable[[MethodSigContext], FunctionLike] | None:
Expand Down
36 changes: 35 additions & 1 deletion test-data/unit/check-attr.test
Original file line number Diff line number Diff line change
Expand Up @@ -1866,4 +1866,38 @@ 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]
[builtins fixtures/property.pyi]

[case testEvolve]
import attr

@attr.s(auto_attribs=True)
class C:
name: str

c = C(name='foo')
attr.evolve() # E: Missing positional argument "inst" in call to "evolve"
attr.evolve(c)
attr.evolve(c, name='bar')
attr.evolve(
c,
name=42, # E: Argument "name" to "evolve" has incompatible type "int"; expected "str"
)
attr.evolve(
c,
age=42, # type: ignore[call-arg]
)

# 'assoc' is the deprecated one:

attr.assoc(
c,
name=42, # E: Argument "name" to "assoc" has incompatible type "int"; expected "str"
)
attr.assoc(
c,
age=42, # type: ignore[call-arg]
)

[builtins fixtures/dict.pyi]
[typing fixtures/typing-medium.pyi]
3 changes: 3 additions & 0 deletions test-data/unit/lib-stub/attr/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -244,3 +244,6 @@ def field(
order: Optional[bool] = ...,
on_setattr: Optional[object] = ...,
) -> Any: ...

def evolve(inst: _T, **changes: Any) -> _T: ...
def assoc(inst: _T, **changes: Any) -> _T: ...

0 comments on commit cd832cc

Please sign in to comment.