mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
PY-76841 Conformance test failure: dataclasses_postinit.py
__post_init__ parameters have to match InitVars type not name GitOrigin-RevId: 0e1e4a9c3a50eee84cd724b5a3f8c5e894e90bdd
This commit is contained in:
committed by
intellij-monorepo-bot
parent
14a150d3f1
commit
5486d93839
@@ -969,6 +969,7 @@ INSP.dataclasses.method.should.be.called.on.dataclass.instances.or.types=''{0}''
|
||||
INSP.dataclasses.method.should.be.called.on.dataclass.instances=''{0}'' method should be called on dataclass instances
|
||||
INSP.dataclasses.method.should.be.called.on.attrs.instances=''{0}'' method should be called on attrs instances
|
||||
INSP.dataclasses.method.should.be.called.on.attrs.types=''{0}'' method should be called on attrs types
|
||||
INSP.dataclasses.expected.type.got.type.instead=Expected type ''{0}'', got ''{1}'' instead
|
||||
|
||||
# PyDeprecationInspection
|
||||
INSP.NAME.deprecated.function.class.or.module=Deprecated function, class, or module
|
||||
|
||||
+58
-33
@@ -8,13 +8,14 @@ import com.intellij.codeInspection.ProblemHighlightType
|
||||
import com.intellij.codeInspection.ProblemsHolder
|
||||
import com.intellij.psi.PsiElementVisitor
|
||||
import com.intellij.psi.PsiNameIdentifierOwner
|
||||
import com.intellij.util.containers.ContainerUtil
|
||||
import com.intellij.util.containers.tailOrEmpty
|
||||
import com.jetbrains.python.PyNames
|
||||
import com.jetbrains.python.PyPsiBundle
|
||||
import com.jetbrains.python.codeInsight.*
|
||||
import com.jetbrains.python.codeInsight.PyDataclassNames.Attrs
|
||||
import com.jetbrains.python.codeInsight.PyDataclassNames.Dataclasses
|
||||
import com.jetbrains.python.codeInsight.typing.PyTypingTypeProvider
|
||||
import com.jetbrains.python.documentation.PythonDocumentationProvider
|
||||
import com.jetbrains.python.psi.*
|
||||
import com.jetbrains.python.psi.impl.ParamHelper
|
||||
import com.jetbrains.python.psi.impl.PyCallExpressionHelper
|
||||
@@ -74,13 +75,13 @@ class PyDataclassInspection : PyInspection() {
|
||||
processDataclassParameters(node, dataclassParameters)
|
||||
|
||||
val postInit = node.findMethodByName(Dataclasses.DUNDER_POST_INIT, false, myTypeEvalContext)
|
||||
val localInitVars = mutableListOf<PyTargetExpression>()
|
||||
val localInitVars = mutableListOf<PyType?>()
|
||||
|
||||
node.processClassLevelDeclarations { element, _ ->
|
||||
if (element is PyTargetExpression) {
|
||||
if (!PyTypingTypeProvider.isClassVar(element, myTypeEvalContext)) {
|
||||
processDefaultFieldValue(element)
|
||||
processAsInitVar(element, postInit)?.let { localInitVars.add(it) }
|
||||
processAsInitVar(element, postInit)?.let { localInitVars.add(it.type) }
|
||||
}
|
||||
|
||||
processFieldFunctionCall(node, dataclassParameters, element)
|
||||
@@ -518,8 +519,8 @@ class PyDataclassInspection : PyInspection() {
|
||||
dataclassParameters.type.asPredefinedType == PyDataclassParameters.PredefinedType.DATACLASS_TRANSFORM ||
|
||||
PyEvaluator.evaluateAsBoolean(PyUtil.peelArgument(dataclassParameters.others["auto_attribs"]), false)) {
|
||||
cls.processClassLevelDeclarations { element, _ ->
|
||||
if (element is PyTargetExpression
|
||||
&& element.annotation == null
|
||||
if (element is PyTargetExpression
|
||||
&& element.annotation == null
|
||||
&& resolveDataclassFieldParameters(cls, dataclassParameters, element, myTypeEvalContext) != null) {
|
||||
registerProblem(element, PyPsiBundle.message("INSP.dataclasses.attribute.lacks.type.annotation", element.name),
|
||||
ProblemHighlightType.GENERIC_ERROR)
|
||||
@@ -561,20 +562,23 @@ class PyDataclassInspection : PyInspection() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun processAsInitVar(field: PyTargetExpression, postInit: PyFunction?): PyTargetExpression? {
|
||||
if (isInitVar(field)) {
|
||||
private fun processAsInitVar(field: PyTargetExpression, postInit: PyFunction?): InitVarField? {
|
||||
val fieldType = myTypeEvalContext.getType(field)
|
||||
if (isInitVar(fieldType)) {
|
||||
if (postInit == null) {
|
||||
registerProblem(field,
|
||||
PyPsiBundle.message("INSP.dataclasses.attribute.useless.until.post.init.declared", field.name),
|
||||
ProblemHighlightType.LIKE_UNUSED_SYMBOL)
|
||||
}
|
||||
|
||||
return field
|
||||
return InitVarField(getInitVarType(fieldType))
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private class InitVarField(val type: PyType?)
|
||||
|
||||
private fun processFieldFunctionCall(dataclass: PyClass, dataclassParameters: PyDataclassParameters, field: PyTargetExpression) {
|
||||
val fieldStub = resolveDataclassFieldParameters(dataclass, dataclassParameters, field, myTypeEvalContext) ?: return
|
||||
val call = field.findAssignedValue() as? PyCallExpression ?: return
|
||||
@@ -595,7 +599,7 @@ class PyDataclassInspection : PyInspection() {
|
||||
private fun processPostInitDefinition(cls: PyClass,
|
||||
postInit: PyFunction,
|
||||
dataclassParameters: PyDataclassParameters,
|
||||
localInitVars: List<PyTargetExpression>) {
|
||||
localInitVars: List<PyType?>) {
|
||||
if (!dataclassParameters.init) {
|
||||
registerProblem(postInit.nameIdentifier,
|
||||
PyPsiBundle.message("INSP.dataclasses.post.init.would.not.be.called.until.init.parameter.set.to.true"),
|
||||
@@ -606,13 +610,16 @@ class PyDataclassInspection : PyInspection() {
|
||||
|
||||
if (ParamHelper.isSelfArgsKwargsCallable(postInit, myTypeEvalContext)) return
|
||||
|
||||
val allInitVars = mutableListOf<PyTargetExpression>()
|
||||
val allInitVars = mutableListOf<PyType?>()
|
||||
for (ancestor in cls.getAncestorClasses(myTypeEvalContext).asReversed()) {
|
||||
if (parseStdDataclassParameters(ancestor, myTypeEvalContext) == null) continue
|
||||
|
||||
ancestor.processClassLevelDeclarations { element, _ ->
|
||||
if (element is PyTargetExpression && isInitVar(element)) {
|
||||
allInitVars.add(element)
|
||||
if (element is PyTargetExpression) {
|
||||
val fieldType = myTypeEvalContext.getType(element)
|
||||
if (isInitVar(fieldType)) {
|
||||
allInitVars.add(getInitVarType(fieldType))
|
||||
}
|
||||
}
|
||||
|
||||
return@processClassLevelDeclarations true
|
||||
@@ -620,26 +627,31 @@ class PyDataclassInspection : PyInspection() {
|
||||
}
|
||||
allInitVars.addAll(localInitVars)
|
||||
|
||||
val implicitParameters = postInit.getParameters(myTypeEvalContext)
|
||||
val parameters = if (implicitParameters.isEmpty()) emptyList<PyCallableParameter>() else ContainerUtil.subList(implicitParameters, 1)
|
||||
|
||||
val message = if (allInitVars.size != localInitVars.size) {
|
||||
PyPsiBundle.message("INSP.dataclasses.post.init.should.take.all.init.only.variables.including.inherited.in.same.order.they.defined")
|
||||
}
|
||||
else {
|
||||
PyPsiBundle.message("INSP.dataclasses.post.init.should.take.all.init.only.variables.in.same.order.they.defined")
|
||||
}
|
||||
|
||||
val parameters = postInit.getParameters(myTypeEvalContext).tailOrEmpty()
|
||||
|
||||
if (parameters.size != allInitVars.size) {
|
||||
val message = if (allInitVars.size != localInitVars.size) {
|
||||
PyPsiBundle.message("INSP.dataclasses.post.init.should.take.all.init.only.variables.including.inherited.in.same.order.they.defined")
|
||||
}
|
||||
else {
|
||||
PyPsiBundle.message("INSP.dataclasses.post.init.should.take.all.init.only.variables.in.same.order.they.defined")
|
||||
}
|
||||
registerProblem(postInit.parameterList, message, ProblemHighlightType.GENERIC_ERROR)
|
||||
}
|
||||
else {
|
||||
parameters
|
||||
.asSequence()
|
||||
.zip(allInitVars.asSequence())
|
||||
.all { it.first.name == it.second.name }
|
||||
.also { if (!it) registerProblem(postInit.parameterList, message) }
|
||||
for ((index, callableParameter) in parameters.withIndex()) {
|
||||
val parameter = callableParameter.parameter
|
||||
if (parameter !is PyNamedParameter) continue
|
||||
val annotation = PyTypingTypeProvider.getAnnotationValue(parameter, myTypeEvalContext) ?: continue
|
||||
val typeFromAnnotation = PyTypingTypeProvider.getType(annotation, myTypeEvalContext) ?: continue
|
||||
val initVarType = allInitVars[index]
|
||||
if (!PyTypeChecker.match(typeFromAnnotation.get(), initVarType, myTypeEvalContext)) {
|
||||
val initVarTypeName = PythonDocumentationProvider.getTypeName(initVarType, myTypeEvalContext)
|
||||
val parameterTypeName = PythonDocumentationProvider.getVerboseTypeName(typeFromAnnotation.get(), myTypeEvalContext)
|
||||
registerProblem(annotation,
|
||||
PyPsiBundle.message("INSP.dataclasses.expected.type.got.type.instead", initVarTypeName, parameterTypeName))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -695,14 +707,27 @@ class PyDataclassInspection : PyInspection() {
|
||||
}
|
||||
|
||||
private fun isInitVar(field: PyTargetExpression): Boolean {
|
||||
return (myTypeEvalContext.getType(field) as? PyClassType)?.classQName == Dataclasses.DATACLASSES_INITVAR
|
||||
return isInitVar(myTypeEvalContext.getType(field))
|
||||
}
|
||||
|
||||
private fun isExpectedDataclass(type: PyType?,
|
||||
dataclassType: PyDataclassParameters.PredefinedType?,
|
||||
allowDefinition: Boolean,
|
||||
allowInstance: Boolean,
|
||||
allowSubclass: Boolean): Boolean {
|
||||
private fun isInitVar(fieldType: PyType?): Boolean {
|
||||
return fieldType is PyCollectionType && fieldType.classQName == Dataclasses.DATACLASSES_INITVAR
|
||||
}
|
||||
|
||||
private fun getInitVarType(fieldType: PyType?): PyType? {
|
||||
if (fieldType !is PyCollectionType || fieldType.classQName != Dataclasses.DATACLASSES_INITVAR) {
|
||||
throw IllegalArgumentException()
|
||||
}
|
||||
return fieldType.elementTypes.singleOrNull()
|
||||
}
|
||||
|
||||
private fun isExpectedDataclass(
|
||||
type: PyType?,
|
||||
dataclassType: PyDataclassParameters.PredefinedType?,
|
||||
allowDefinition: Boolean,
|
||||
allowInstance: Boolean,
|
||||
allowSubclass: Boolean,
|
||||
): Boolean {
|
||||
if (type is PyStructuralType || PyTypeChecker.isUnknown(type, myTypeEvalContext)) return true
|
||||
if (type is PyUnionType) return type.members.any {
|
||||
isExpectedDataclass(it, dataclassType, allowDefinition, allowInstance, allowSubclass)
|
||||
|
||||
@@ -15,5 +15,5 @@ class B:
|
||||
b: dataclasses.InitVar[str]
|
||||
c: dataclasses.InitVar[bytes]
|
||||
|
||||
def __post_init__<warning descr="'__post_init__' should take all init-only variables in the same order as they are defined">(self, c, b)</warning>:
|
||||
def __post_init__(self, c: <warning descr="Expected type 'str', got 'bytes' instead">bytes</warning>, b: bytes):
|
||||
pass
|
||||
Reference in New Issue
Block a user