diff --git a/python/src/com/jetbrains/python/inspections/PyDataclassInspection.kt b/python/src/com/jetbrains/python/inspections/PyDataclassInspection.kt index 2593a76fa56e..8ded8ec1a096 100644 --- a/python/src/com/jetbrains/python/inspections/PyDataclassInspection.kt +++ b/python/src/com/jetbrains/python/inspections/PyDataclassInspection.kt @@ -7,6 +7,7 @@ import com.intellij.codeInspection.LocalInspectionToolSession 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.parseDataclassParameters import com.jetbrains.python.codeInsight.typing.PyTypingTypeProvider import com.jetbrains.python.psi.* @@ -126,6 +127,32 @@ class PyDataclassInspection : PyInspection() { } } + override fun visitPyReferenceExpression(node: PyReferenceExpression?) { + super.visitPyReferenceExpression(node) + + if (node != null && node.isQualified) { + val cls = getInstancePyClass(node.qualifier) ?: return + + if (parseDataclassParameters(cls, myTypeEvalContext) != null) { + cls.processClassLevelDeclarations { element, _ -> + if (element is PyTargetExpression && element.name == node.name) { + val type = myTypeEvalContext.getType(element) + + if (type is PyClassType && type.classQName == DATACLASSES_INITVAR_TYPE) { + registerProblem(node.lastChild, + "'${cls.name}' object could have no attribute '${element.name}' because it is declared as init-only", + ProblemHighlightType.GENERIC_ERROR_OR_WARNING) + + return@processClassLevelDeclarations false + } + } + + true + } + } + } + } + private fun getInstancePyClass(element: PyTypedElement?): PyClass? { val type = element?.let { myTypeEvalContext.getType(it) } as? PyClassType return if (type != null && !type.isDefinition) type.pyClass else null diff --git a/python/testData/inspections/PyDataclassInspection/accessToInitVar.py b/python/testData/inspections/PyDataclassInspection/accessToInitVar.py new file mode 100644 index 000000000000..4ce2905254d5 --- /dev/null +++ b/python/testData/inspections/PyDataclassInspection/accessToInitVar.py @@ -0,0 +1,14 @@ +import dataclasses + +@dataclasses.dataclass +class A: + a: int + b: dataclasses.InitVar[str] + + def __post_init__(self, b: str): + pass + + +a = A(1, "a") +print(a.a) +print(a.b) diff --git a/python/testData/inspections/PyDataclassInspection/dataclasses.py b/python/testData/inspections/PyDataclassInspection/dataclasses.py index 1ebf34462de8..9b82cefbfe53 100644 --- a/python/testData/inspections/PyDataclassInspection/dataclasses.py +++ b/python/testData/inspections/PyDataclassInspection/dataclasses.py @@ -1,3 +1,11 @@ +class _InitVarMeta(type): + def __getitem__(self, params): + return self + +class InitVar(metaclass=_InitVarMeta): + pass + + def dataclass(_cls=None, *, init=True, repr=True, eq=True, order=False, hash=None, frozen=False): pass diff --git a/python/testSrc/com/jetbrains/python/inspections/PyDataclassInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyDataclassInspectionTest.java index 0a08843c5c46..84c8e02eea7b 100644 --- a/python/testSrc/com/jetbrains/python/inspections/PyDataclassInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/PyDataclassInspectionTest.java @@ -51,6 +51,11 @@ public class PyDataclassInspectionTest extends PyInspectionTestCase { doTest(); } + // PY-27398 + public void testAccessToInitVar() { + doTest(); + } + @Override protected void doTest() { runWithLanguageLevel(