mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Raise an error about invalid order in inherited fields (PY-28506, PY-31762, PY-33165)
This commit is contained in:
@@ -110,6 +110,7 @@ class PyDataclassInspection : PyInspection() {
|
||||
PyNamedTupleInspection.inspectFieldsOrder(
|
||||
node,
|
||||
{ parseDataclassParameters(it, myTypeEvalContext) != null },
|
||||
dataclassParameters.type == PyDataclassParameters.Type.STD,
|
||||
myTypeEvalContext,
|
||||
this::registerProblem,
|
||||
{
|
||||
|
||||
@@ -20,21 +20,48 @@ class PyNamedTupleInspection : PyInspection() {
|
||||
companion object {
|
||||
fun inspectFieldsOrder(cls: PyClass,
|
||||
ancestorsFilter: (PyClass) -> Boolean,
|
||||
checkInheritedOrder: Boolean,
|
||||
context: TypeEvalContext,
|
||||
callback: (PsiElement, String, ProblemHighlightType) -> Unit,
|
||||
fieldsFilter: (PyTargetExpression) -> Boolean = { true },
|
||||
hasAssignedValue: (PyTargetExpression) -> Boolean = PyTargetExpression::hasAssignedValue) {
|
||||
val fieldsProcessor = processFields(cls, fieldsFilter, hasAssignedValue)
|
||||
|
||||
val ancestors = cls.getAncestorClasses(context)
|
||||
val ancestorsFields = ancestors.map {
|
||||
when {
|
||||
!ancestorsFilter(it) -> Ancestor.FILTERED
|
||||
processFields(it, fieldsFilter, hasAssignedValue).fieldsWithDefaultValue.isNotEmpty() -> Ancestor.HAS_FIELD_WITH_DEFAULT_VALUE
|
||||
else -> Ancestor.HAS_NOT_FIELD_WITH_DEFAULT_VALUE
|
||||
}
|
||||
}
|
||||
|
||||
if (checkInheritedOrder) {
|
||||
var seenAncestorHavingFieldWithDefaultValue: PyClass? = null
|
||||
for (ancestorAndFields in ancestors.zip(ancestorsFields).asReversed()) {
|
||||
if (ancestorAndFields.second == Ancestor.HAS_FIELD_WITH_DEFAULT_VALUE) seenAncestorHavingFieldWithDefaultValue = ancestorAndFields.first
|
||||
else if (ancestorAndFields.second == Ancestor.HAS_NOT_FIELD_WITH_DEFAULT_VALUE && seenAncestorHavingFieldWithDefaultValue != null) {
|
||||
callback(
|
||||
cls.superClassExpressionList!!,
|
||||
"Inherited non-default argument(s) defined in ${ancestorAndFields.first.name} follows " +
|
||||
"inherited default argument defined in ${seenAncestorHavingFieldWithDefaultValue.name}",
|
||||
ProblemHighlightType.GENERIC_ERROR
|
||||
)
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val lastFieldWithoutDefaultValue = fieldsProcessor.lastFieldWithoutDefaultValue
|
||||
if (lastFieldWithoutDefaultValue != null) {
|
||||
val ancestorsHavingFieldsWithDefaultValue = cls.getAncestorClasses(context).filter {
|
||||
ancestorsFilter(it) && processFields(it, fieldsFilter, hasAssignedValue).fieldsWithDefaultValue.isNotEmpty()
|
||||
}
|
||||
|
||||
if (ancestorsHavingFieldsWithDefaultValue.isNotEmpty()) {
|
||||
if (ancestorsFields.contains(Ancestor.HAS_FIELD_WITH_DEFAULT_VALUE)) {
|
||||
cls.nameIdentifier?.let { name ->
|
||||
val ancestorsNames = ancestorsHavingFieldsWithDefaultValue.joinToString { "'${it.name}'" }
|
||||
val ancestorsNames = ancestors
|
||||
.asSequence()
|
||||
.zip(ancestorsFields.asSequence())
|
||||
.filter { it.second == Ancestor.HAS_FIELD_WITH_DEFAULT_VALUE }
|
||||
.joinToString { "'${it.first.name}'" }
|
||||
|
||||
callback(name,
|
||||
"Non-default argument(s) follows default argument(s) defined in $ancestorsNames",
|
||||
@@ -58,6 +85,10 @@ class PyNamedTupleInspection : PyInspection() {
|
||||
|
||||
return fieldsProcessor
|
||||
}
|
||||
|
||||
private enum class Ancestor {
|
||||
FILTERED, HAS_FIELD_WITH_DEFAULT_VALUE, HAS_NOT_FIELD_WITH_DEFAULT_VALUE
|
||||
}
|
||||
}
|
||||
|
||||
override fun buildVisitor(holder: ProblemsHolder,
|
||||
@@ -72,7 +103,7 @@ class PyNamedTupleInspection : PyInspection() {
|
||||
if (node != null &&
|
||||
LanguageLevel.forElement(node).isAtLeast(LanguageLevel.PYTHON36) &&
|
||||
PyNamedTupleTypeProvider.isTypingNamedTupleDirectInheritor(node, myTypeEvalContext)) {
|
||||
inspectFieldsOrder(node, { false }, myTypeEvalContext, this::registerProblem)
|
||||
inspectFieldsOrder(node, { false }, false, myTypeEvalContext, this::registerProblem)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,3 +28,28 @@ class A3:
|
||||
@dataclasses.dataclass
|
||||
class B3(A3):
|
||||
y1: str
|
||||
|
||||
@dataclasses.dataclass
|
||||
class A41:
|
||||
field1: int
|
||||
|
||||
@dataclasses.dataclass
|
||||
class A42:
|
||||
field2: str = "1"
|
||||
|
||||
@dataclasses.dataclass
|
||||
class B4<error descr="Inherited non-default argument(s) defined in A41 follows inherited default argument defined in A42">(A41, A42)</error>:
|
||||
pass
|
||||
|
||||
@attr.dataclass
|
||||
class A51:
|
||||
field1: int
|
||||
|
||||
@attr.dataclass
|
||||
class A52:
|
||||
field2: str = "1"
|
||||
|
||||
@attr.dataclass
|
||||
class B5(A51, A52):
|
||||
pass
|
||||
|
||||
|
||||
Reference in New Issue
Block a user