From 47b5e556b879d5e9d2bed1afd74ec870d55a6a40 Mon Sep 17 00:00:00 2001 From: Semyon Proshev Date: Tue, 9 Jan 2018 19:13:36 +0300 Subject: [PATCH] Highlight init-only fields when `__post_init__` is not declared (PY-27398) --- .../inspections/PyDataclassInspection.kt | 23 ++++++++++++++----- .../PyDataclassInspection/uselessInitVar.py | 6 +++++ .../PyDataclassInspectionTest.java | 5 ++++ 3 files changed, 28 insertions(+), 6 deletions(-) create mode 100644 python/testData/inspections/PyDataclassInspection/uselessInitVar.py diff --git a/python/src/com/jetbrains/python/inspections/PyDataclassInspection.kt b/python/src/com/jetbrains/python/inspections/PyDataclassInspection.kt index 8ded8ec1a096..28becec8ef7d 100644 --- a/python/src/com/jetbrains/python/inspections/PyDataclassInspection.kt +++ b/python/src/com/jetbrains/python/inspections/PyDataclassInspection.kt @@ -8,6 +8,7 @@ import com.intellij.codeInspection.ProblemHighlightType import com.intellij.codeInspection.ProblemsHolder import com.intellij.psi.PsiElementVisitor import com.jetbrains.python.codeInsight.stdlib.DATACLASSES_INITVAR_TYPE +import com.jetbrains.python.codeInsight.stdlib.DUNDER_POST_INIT import com.jetbrains.python.codeInsight.stdlib.parseDataclassParameters import com.jetbrains.python.codeInsight.typing.PyTypingTypeProvider import com.jetbrains.python.psi.* @@ -52,25 +53,35 @@ class PyDataclassInspection : PyInspection() { } } + val hasPostInit = node.findMethodByName(DUNDER_POST_INIT, false, myTypeEvalContext) != null + node.processClassLevelDeclarations { element, _ -> if (element is PyTargetExpression && element.annotationValue != null) { val annotation = element.annotation if (annotation != null && !PyTypingTypeProvider.isClassVarAnnotation(annotation, myTypeEvalContext)) { val value = element.findAssignedValue() - val cls = getInstancePyClass(value) + val valueClass = getInstancePyClass(value) - if (cls != null) { + if (valueClass != null) { val builtinCache = PyBuiltinCache.getInstance(node) - if (cls == builtinCache.listType?.pyClass || - cls == builtinCache.setType?.pyClass || - cls == builtinCache.tupleType?.pyClass) { + if (valueClass == builtinCache.listType?.pyClass || + valueClass == builtinCache.setType?.pyClass || + valueClass == builtinCache.tupleType?.pyClass) { registerProblem(value, - "mutable default '${cls.name}' is not allowed", + "mutable default '${valueClass.name}' is not allowed", ProblemHighlightType.GENERIC_ERROR) } } + else if (!hasPostInit) { + val type = myTypeEvalContext.getType(element) + if (type is PyClassType && type.classQName == DATACLASSES_INITVAR_TYPE) { + registerProblem(element, + "attribute '${element.name}' is useless until '${DUNDER_POST_INIT}' is declared", + ProblemHighlightType.LIKE_UNUSED_SYMBOL) + } + } } } diff --git a/python/testData/inspections/PyDataclassInspection/uselessInitVar.py b/python/testData/inspections/PyDataclassInspection/uselessInitVar.py new file mode 100644 index 000000000000..7c1097b226aa --- /dev/null +++ b/python/testData/inspections/PyDataclassInspection/uselessInitVar.py @@ -0,0 +1,6 @@ +import dataclasses + +@dataclasses.dataclass +class A: + a: int + b: dataclasses.InitVar[str] diff --git a/python/testSrc/com/jetbrains/python/inspections/PyDataclassInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyDataclassInspectionTest.java index 84c8e02eea7b..b4f9bf4128ad 100644 --- a/python/testSrc/com/jetbrains/python/inspections/PyDataclassInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/PyDataclassInspectionTest.java @@ -56,6 +56,11 @@ public class PyDataclassInspectionTest extends PyInspectionTestCase { doTest(); } + // PY-27398 + public void testUselessInitVar() { + doTest(); + } + @Override protected void doTest() { runWithLanguageLevel(