Inspect access to dataclasses' init only fields (PY-27398)

This commit is contained in:
Semyon Proshev
2018-01-22 22:52:04 +03:00
parent ee2feb681e
commit ce63287531
4 changed files with 54 additions and 0 deletions
@@ -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
@@ -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.<warning descr="'A' object could have no attribute 'b' because it is declared as init-only">b</warning>)
@@ -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
@@ -51,6 +51,11 @@ public class PyDataclassInspectionTest extends PyInspectionTestCase {
doTest();
}
// PY-27398
public void testAccessToInitVar() {
doTest();
}
@Override
protected void doTest() {
runWithLanguageLevel(