Skip to content

Commit 2f6b11e

Browse files
Fix crash with dataclasses that have __init_subclass__ modifying __init__
Co-authored-by: Pierre-Sassoulas <[email protected]>
1 parent 7904a25 commit 2f6b11e

File tree

3 files changed

+55
-2
lines changed

3 files changed

+55
-2
lines changed

pylint/checkers/dataclass_checker.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,52 @@
1414
from pylint.checkers import BaseChecker, utils
1515
from pylint.interfaces import INFERENCE
1616

17+
18+
# Monkey patch to fix astroid issue with __init_subclass__ modifying __init__
19+
# See: https://github.com/pylint-dev/pylint/issues/10519
20+
def _patched_find_arguments_from_base_classes(node):
21+
"""Patched version that handles AssignAttr nodes in __init__."""
22+
import astroid.brain.brain_dataclasses as brain_dataclasses_module
23+
24+
# Get the original function implementation
25+
orig_func = brain_dataclasses_module._find_arguments_from_base_classes.__wrapped__ if hasattr(brain_dataclasses_module._find_arguments_from_base_classes, '__wrapped__') else brain_dataclasses_module._find_arguments_from_base_classes
26+
27+
pos_only_store = {}
28+
kw_only_store = {}
29+
30+
for base in reversed(node.mro()):
31+
if not base.is_dataclass:
32+
continue
33+
try:
34+
base_init = base.locals["__init__"][0]
35+
except KeyError:
36+
continue
37+
38+
# Check if base_init is actually a FunctionDef node
39+
# When __init_subclass__ modifies __init__ by assignment, it becomes an AssignAttr node
40+
if not isinstance(base_init, nodes.FunctionDef):
41+
continue
42+
43+
pos_only, kw_only = base_init.args._get_arguments_data()
44+
for posarg, data in pos_only.items():
45+
pos_only_store[posarg] = data
46+
47+
for kwarg, data in kw_only.items():
48+
kw_only_store[kwarg] = data
49+
50+
return pos_only_store, kw_only_store
51+
52+
53+
# Apply the monkey patch
54+
try:
55+
import astroid.brain.brain_dataclasses as brain_dataclasses_module
56+
if not hasattr(brain_dataclasses_module._find_arguments_from_base_classes, '__wrapped__'):
57+
brain_dataclasses_module._find_arguments_from_base_classes.__wrapped__ = brain_dataclasses_module._find_arguments_from_base_classes
58+
brain_dataclasses_module._find_arguments_from_base_classes = _patched_find_arguments_from_base_classes
59+
except ImportError:
60+
# astroid.brain.brain_dataclasses might not be available in all versions
61+
pass
62+
1763
if TYPE_CHECKING:
1864
from pylint.lint import PyLinter
1965

test_issue.py renamed to tests/functional/d/dataclass/dataclass_generic_init_subclass.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""Playground file. Do not commit"""
1+
"""Test for issue #10519: Crash with generic dataclass that has __init_subclass__"""
22

33
from abc import ABC
44
from collections.abc import Callable
@@ -10,6 +10,7 @@
1010

1111
@dataclass
1212
class Foo[T](ABC):
13+
"""A generic dataclass with __init_subclass__ that modifies __init__"""
1314

1415
_foo: T | None = field(init=False)
1516
_bar: dict[str, str] = field(init=False)
@@ -30,4 +31,10 @@ def _w(*args: _P.args, **kwds: _P.kwargs) -> None:
3031

3132

3233
@dataclass
33-
class Bar(Foo): ...
34+
class Bar(Foo):
35+
"""A subclass of the generic dataclass without type parameter"""
36+
37+
38+
@dataclass
39+
class Baz(Foo[str]):
40+
"""A subclass of the generic dataclass with type parameter"""

tests/functional/d/dataclass/dataclass_generic_init_subclass.txt

Whitespace-only changes.

0 commit comments

Comments
 (0)