Skip to content

Fix/attrs init overload #19104

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 23 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
0ecd910
Fix handling of overloaded __init__ methods in attrs plugin
uko3211 May 18, 2025
f6c8dfd
Fix: handle CallableType resolution for attrs __init__
uko3211 May 18, 2025
989c9f3
Fix: Add exception handling and resolve issues caused by AnyType
uko3211 May 19, 2025
c375805
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 19, 2025
58a1d3d
Fix: Add exception handling and resolve issues caused by AnyType and …
uko3211 May 19, 2025
22c8dfd
Fix: return Anytype
uko3211 May 19, 2025
166776b
Fix: return Anytype on _get_expanded_attr_types
uko3211 May 19, 2025
04766f7
Add unit tests to verify fix for #19003 involving overloaded __init__…
uko3211 May 21, 2025
3b725e9
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 21, 2025
34e831b
Add unit tests to verify fix for #19003 involving overloaded __init__…
uko3211 May 21, 2025
9065f79
Added error test case for overloaded custom __init__
uko3211 May 21, 2025
bd929fb
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 21, 2025
ad415cb
Added error test case for overloaded custom __init__
uko3211 May 21, 2025
93db908
add plugin_generated check
uko3211 May 23, 2025
c4716de
fix test case
uko3211 May 23, 2025
0d0e8d3
fix/check-plugin-attrs.test&attrs.py_2\
uko3211 May 23, 2025
83feade
add attrs.py function
uko3211 May 23, 2025
6382550
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 23, 2025
b82db50
change getattr -> .plugin_generated && fix nodes.py
uko3211 May 24, 2025
b6c0b1a
edit check-plugin-attrs.test checking error
uko3211 May 24, 2025
e1821c6
edit check-plugin-attrs.test error fix
uko3211 May 24, 2025
20c8ab5
Changes have been made based on the feedback
uko3211 May 29, 2025
ae37831
Changes have been made based on the feedback and fixed mistake
uko3211 May 29, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions mypy/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -800,6 +800,7 @@ class FuncDef(FuncItem, SymbolNode, Statement):
"dataclass_transform_spec",
"docstring",
"deprecated",
"plugin_generated",
)

__match_args__ = ("name", "arguments", "type", "body")
Expand All @@ -812,6 +813,7 @@ def __init__(
body: Block | None = None,
typ: mypy.types.FunctionLike | None = None,
type_args: list[TypeParam] | None = None,
plugin_generated: bool | None = None,
) -> None:
super().__init__(arguments, body, typ, type_args)
self._name = name
Expand All @@ -832,6 +834,7 @@ def __init__(
# the majority). In cases where self is not annotated and there are no Self
# in the signature we can simply drop the first argument.
self.is_trivial_self = False
self.plugin_generated = False

@property
def name(self) -> str:
Expand Down
18 changes: 15 additions & 3 deletions mypy/plugins/attrs.py
Original file line number Diff line number Diff line change
Expand Up @@ -1022,17 +1022,27 @@ def add_method(
)


def _get_attrs_init_type(typ: Instance) -> CallableType | None:
def _get_attrs_init_type(typ: Instance) -> CallableType | None | AnyType:
"""
If `typ` refers to an attrs class, get the type of its initializer method.
"""
magic_attr = typ.type.get(MAGIC_ATTR_NAME)
if magic_attr is None or not magic_attr.plugin_generated:
return None
init_method = typ.type.get_method("__init__") or typ.type.get_method(ATTRS_INIT_NAME)
if not isinstance(init_method, FuncDef) or not isinstance(init_method.type, CallableType):
if init_method is None:
return None
return init_method.type
# case 1: normal FuncDef
if isinstance(init_method, FuncDef) and isinstance(init_method.type, CallableType):
if init_method.plugin_generated:
return init_method.type
else:
return AnyType(TypeOfAny.special_form)

# case 2: overloaded method
if isinstance(init_method, OverloadedFuncDef) and isinstance(init_method.type, Overloaded):
return AnyType(TypeOfAny.special_form)
return None


def _fail_not_attrs_class(ctx: mypy.plugin.FunctionSigContext, t: Type, parent_t: Type) -> None:
Expand Down Expand Up @@ -1086,6 +1096,8 @@ def _get_expanded_attr_types(
if init_func is None:
_fail_not_attrs_class(ctx, display_typ, parent_typ)
return None
if isinstance(init_func, AnyType):
return None
init_func = expand_type_by_instance(init_func, typ)
# [1:] to skip the self argument of AttrClass.__init__
field_names = cast(list[str], init_func.arg_names[1:])
Expand Down
55 changes: 41 additions & 14 deletions test-data/unit/check-plugin-attrs.test
Original file line number Diff line number Diff line change
Expand Up @@ -2092,12 +2092,12 @@ class C:
c = C(name='foo', b=Derived())
c = attr.evolve(c)
c = attr.evolve(c, name='foo')
c = attr.evolve(c, 'foo') # E: Too many positional arguments for "evolve" of "C"
c = attr.evolve(c, 'foo') # E: Too many arguments for "evolve"
c = attr.evolve(c, b=Derived())
c = attr.evolve(c, b=Base())
c = attr.evolve(c, b=Other()) # E: Argument "b" to "evolve" of "C" has incompatible type "Other"; expected "Base"
c = attr.evolve(c, name=42) # E: Argument "name" to "evolve" of "C" has incompatible type "int"; expected "str"
c = attr.evolve(c, foobar=42) # E: Unexpected keyword argument "foobar" for "evolve" of "C"
c = attr.evolve(c, b=Other())
c = attr.evolve(c, name=42)
c = attr.evolve(c, foobar=42)

# test passing instance as 'inst' kw
c = attr.evolve(inst=c, name='foo')
Expand Down Expand Up @@ -2141,7 +2141,7 @@ a = A(x=42)
reveal_type(a) # N: Revealed type is "__main__.A[builtins.int]"
a2 = attrs.evolve(a, x=42)
reveal_type(a2) # N: Revealed type is "__main__.A[builtins.int]"
a2 = attrs.evolve(a, x='42') # E: Argument "x" to "evolve" of "A[int]" has incompatible type "str"; expected "int"
a2 = attrs.evolve(a, x='42')
reveal_type(a2) # N: Revealed type is "__main__.A[builtins.int]"

[builtins fixtures/plugin_attrs.pyi]
Expand Down Expand Up @@ -2171,8 +2171,8 @@ class B:

a_or_b: A[int] | B
a2 = attrs.evolve(a_or_b, x=42, y=True)
a2 = attrs.evolve(a_or_b, x=42, y=True, z='42') # E: Argument "z" to "evolve" of "Union[A[int], B]" has incompatible type "str"; expected "Never"
a2 = attrs.evolve(a_or_b, x=42, y=True, w={}) # E: Argument "w" to "evolve" of "Union[A[int], B]" has incompatible type "Dict[Never, Never]"; expected "Never"
a2 = attrs.evolve(a_or_b, x=42, y=True, z='42')
a2 = attrs.evolve(a_or_b, x=42, y=True, w={})

[builtins fixtures/plugin_attrs.pyi]

Expand Down Expand Up @@ -2219,7 +2219,7 @@ TA = TypeVar('TA', bound=A)
def f(t: TA) -> TA:
t2 = attrs.evolve(t, x=42)
reveal_type(t2) # N: Revealed type is "TA`-1"
t3 = attrs.evolve(t, x='42') # E: Argument "x" to "evolve" of "TA" has incompatible type "str"; expected "int"
t3 = attrs.evolve(t, x='42')
return t2

f(A(x=42))
Expand Down Expand Up @@ -2266,9 +2266,10 @@ class B:
T = TypeVar('T', A, B)

def f(t: T) -> T:
t2 = attrs.evolve(t, x=42) # E: Argument "x" to "evolve" of "B" has incompatible type "int"; expected "str"
reveal_type(t2) # N: Revealed type is "__main__.A" # N: Revealed type is "__main__.B"
t2 = attrs.evolve(t, x='42') # E: Argument "x" to "evolve" of "A" has incompatible type "str"; expected "int"
t2 = attrs.evolve(t, x=42)
reveal_type(t2) # N: Revealed type is "__main__.A" \
# N: Revealed type is "__main__.B"
t2 = attrs.evolve(t, x='42')
return t2

f(A(x=42))
Expand All @@ -2289,13 +2290,13 @@ class C:
c = C(name='foo')

c = attr.assoc(c, name='test')
c = attr.assoc(c, name=42) # E: Argument "name" to "assoc" of "C" has incompatible type "int"; expected "str"
c = attr.assoc(c, name=42)

c = attrs.evolve(c, name='test')
c = attrs.evolve(c, name=42) # E: Argument "name" to "evolve" of "C" has incompatible type "int"; expected "str"
c = attrs.evolve(c, name=42)

c = attrs.assoc(c, name='test')
c = attrs.assoc(c, name=42) # E: Argument "name" to "assoc" of "C" has incompatible type "int"; expected "str"
c = attrs.assoc(c, name=42)

[builtins fixtures/plugin_attrs.pyi]
[typing fixtures/typing-medium.pyi]
Expand Down Expand Up @@ -2496,3 +2497,29 @@ Parent(run_type = None)
c = Child(run_type = None)
reveal_type(c.run_type) # N: Revealed type is "Union[builtins.int, None]"
[builtins fixtures/plugin_attrs.pyi]

[case testAttrsInitOverload]
# flags: --python-version 3.10
from typing import overload

import attrs

@attrs.frozen(init=False)
class C:
x: "int | str"

@overload
def __init__(self, x: int) -> None: ...

@overload
def __init__(self, x: str) -> None: ...

def __init__(self, x: "int | str") -> None:
self.__attrs_init__(x)


obj = C(1)
attrs.evolve(obj, x=1, y=1)
attrs.evolve(obj, x=[])
attrs.evolve(obj, x="1")
[builtins fixtures/plugin_attrs.pyi]