diff --git a/python/src/com/jetbrains/python/inspections/PyDataclassInspection.kt b/python/src/com/jetbrains/python/inspections/PyDataclassInspection.kt
index 1562bddaa9cf..5cc044a8cfb8 100644
--- a/python/src/com/jetbrains/python/inspections/PyDataclassInspection.kt
+++ b/python/src/com/jetbrains/python/inspections/PyDataclassInspection.kt
@@ -90,6 +90,7 @@ class PyDataclassInspection : PyInspection() {
?.also { processAttrsPostInitDefinition(it, dataclassParameters) }
processAttrsDefaultThroughDecorator(node)
+ processAttrsInitializersAndValidators(node)
}
PyNamedTupleInspection.inspectFieldsOrder(
@@ -361,6 +362,41 @@ class PyDataclassInspection : PyInspection() {
}
}
+ private fun processAttrsInitializersAndValidators(cls: PyClass) {
+ cls.visitMethods(
+ { method ->
+ val decorators = method.decoratorList?.decorators
+
+ if (decorators != null) {
+ decorators
+ .asSequence()
+ .mapNotNull { it.qualifiedName }
+ .filter { it.componentCount == 2 }
+ .mapNotNull { it.lastComponent }
+ .forEach {
+ val expectedParameters = when (it) {
+ "default" -> 1
+ "validator" -> 3
+ else -> return@forEach
+ }
+
+ val actualParameters = method.parameterList
+ if (actualParameters.parameters.size != expectedParameters) {
+ val message = "'${method.name}' should take only $expectedParameters parameter" +
+ if (expectedParameters > 1) "s" else ""
+
+ registerProblem(actualParameters, message, ProblemHighlightType.GENERIC_ERROR)
+ }
+ }
+ }
+
+ true
+ },
+ false,
+ myTypeEvalContext
+ )
+ }
+
private fun processAsInitVar(field: PyTargetExpression, postInit: PyFunction?): PyTargetExpression? {
if (isInitVar(field)) {
if (postInit == null) {
diff --git a/python/testData/inspections/PyDataclassInspection/attrsInitializersAndValidators.py b/python/testData/inspections/PyDataclassInspection/attrsInitializersAndValidators.py
new file mode 100644
index 000000000000..6920342366a8
--- /dev/null
+++ b/python/testData/inspections/PyDataclassInspection/attrsInitializersAndValidators.py
@@ -0,0 +1,27 @@
+import attr
+
+
+@attr.s
+class A:
+ x = attr.ib()
+
+ @x.default
+ def init_x1(self):
+ return 10
+
+ @x.validator
+ def check_x1(self, attribute, value):
+ pass
+
+
+@attr.s
+class A:
+ x = attr.ib()
+
+ @x.default
+ def init_x2(self, attribute, value):
+ return 10
+
+ @x.validator
+ def check_x2(self):
+ pass
\ No newline at end of file
diff --git a/python/testSrc/com/jetbrains/python/inspections/PyDataclassInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyDataclassInspectionTest.java
index f40b2ad47fe5..fa97b88cd5e2 100644
--- a/python/testSrc/com/jetbrains/python/inspections/PyDataclassInspectionTest.java
+++ b/python/testSrc/com/jetbrains/python/inspections/PyDataclassInspectionTest.java
@@ -161,6 +161,11 @@ public class PyDataclassInspectionTest extends PyInspectionTestCase {
doTest();
}
+ // PY-26354
+ public void testAttrsInitializersAndValidators() {
+ doTest();
+ }
+
@Override
protected void doTest() {
runWithLanguageLevel(