Consider fields defined via field function while inspecting their order (PY-27398)

This commit is contained in:
Semyon Proshev
2018-01-22 22:52:09 +03:00
parent 315b33c41f
commit f8a6644f03
3 changed files with 43 additions and 14 deletions
@@ -68,7 +68,20 @@ class PyDataclassInspection : PyInspection() {
processPostInitDefinition(postInit, dataclassParameters, initVars)
}
PyNamedTupleInspection.inspectFieldsOrder(node, myTypeEvalContext, this::registerProblem)
PyNamedTupleInspection.inspectFieldsOrder(
node,
this::registerProblem,
{ !PyTypingTypeProvider.isClassVar(it, myTypeEvalContext) },
{
val fieldStub = PyDataclassFieldStubImpl.create(it)
if (fieldStub != null) {
fieldStub.hasDefault() || fieldStub.hasDefaultFactory()
}
else {
it.hasAssignedValue()
}
}
)
}
}
}
@@ -9,18 +9,19 @@ import com.intellij.psi.PsiElementVisitor
import com.intellij.psi.ResolveState
import com.intellij.psi.scope.PsiScopeProcessor
import com.jetbrains.python.codeInsight.stdlib.PyStdlibTypeProvider
import com.jetbrains.python.codeInsight.typing.PyTypingTypeProvider
import com.jetbrains.python.psi.LanguageLevel
import com.jetbrains.python.psi.PyClass
import com.jetbrains.python.psi.PyTargetExpression
import com.jetbrains.python.psi.types.TypeEvalContext
import java.util.*
class PyNamedTupleInspection : PyInspection() {
companion object {
fun inspectFieldsOrder(cls: PyClass, context: TypeEvalContext, callback: (PsiElement, String, ProblemHighlightType) -> Unit) {
val fieldsProcessor = FieldsProcessor(context)
fun inspectFieldsOrder(cls: PyClass,
callback: (PsiElement, String, ProblemHighlightType) -> Unit,
filter: (PyTargetExpression) -> Boolean = { true },
hasAssignedValue: (PyTargetExpression) -> Boolean = PyTargetExpression::hasAssignedValue) {
val fieldsProcessor = FieldsProcessor(filter, hasAssignedValue)
cls.processClassLevelDeclarations(fieldsProcessor)
@@ -54,12 +55,13 @@ class PyNamedTupleInspection : PyInspection() {
if (node != null &&
LanguageLevel.forElement(node).isAtLeast(LanguageLevel.PYTHON36) &&
PyStdlibTypeProvider.isTypingNamedTupleDirectInheritor(node, myTypeEvalContext)) {
inspectFieldsOrder(node, myTypeEvalContext, this::registerProblem)
inspectFieldsOrder(node, this::registerProblem)
}
}
}
private class FieldsProcessor(private val context: TypeEvalContext) : PsiScopeProcessor {
private class FieldsProcessor(private val filter: (PyTargetExpression) -> Boolean,
private val hasAssignedValue: (PyTargetExpression) -> Boolean) : PsiScopeProcessor {
val lastFieldWithoutDefaultValue: PyTargetExpression?
get() = lastFieldWithoutDefaultValueBox.result
@@ -74,13 +76,9 @@ class PyNamedTupleInspection : PyInspection() {
}
override fun execute(element: PsiElement, state: ResolveState): Boolean {
if (element is PyTargetExpression) {
if (PyTypingTypeProvider.isClassVar(element, context)) {
return true
}
if (element is PyTargetExpression && filter(element)) {
when {
element.findAssignedValue() != null -> fieldsWithDefaultValue.add(element)
hasAssignedValue(element) -> fieldsWithDefaultValue.add(element)
else -> lastFieldWithoutDefaultValueBox.apply(element)
}
}
@@ -36,4 +36,22 @@ class A4:
foo1: int
bar2: ClassVar = 2
baz2: int
foo2: int = 3
foo2: int = 3
@dataclasses.dataclass
class B1:
a: int = dataclasses.field()
b: int
@dataclasses.dataclass
class B2:
<error descr="Fields with a default value must come after any fields without a default.">a</error>: int = dataclasses.field(default=1)
b: int = dataclasses.field()
@dataclasses.dataclass
class B3:
<error descr="Fields with a default value must come after any fields without a default.">a</error>: int = dataclasses.field(default_factory=int)
b: int = dataclasses.field()