Warn about non-outer most Final (PEP 591) (PY-34945)

GitOrigin-RevId: 57be34d3f7d6574ade00be2cbef34228cdf3a882
This commit is contained in:
Semyon Proshev
2019-07-02 06:52:16 +03:00
committed by intellij-monorepo-bot
parent 67a0f2f9c0
commit 9709256ca6
2 changed files with 46 additions and 0 deletions
@@ -5,8 +5,10 @@ import com.intellij.codeInspection.LocalInspectionToolSession
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.psi.PsiElementVisitor
import com.intellij.psi.impl.source.resolve.FileContextUtil
import com.jetbrains.python.codeInsight.functionTypeComments.psi.PyParameterTypeList
import com.jetbrains.python.codeInsight.typing.PyTypingTypeProvider
import com.jetbrains.python.codeInsight.typing.PyTypingTypeProvider.getFunctionTypeAnnotation
import com.jetbrains.python.documentation.doctest.PyDocstringFile
import com.jetbrains.python.psi.*
import com.jetbrains.python.psi.PyKnownDecoratorUtil.KnownDecorator.TYPING_FINAL
import com.jetbrains.python.psi.PyKnownDecoratorUtil.KnownDecorator.TYPING_FINAL_EXT
@@ -93,6 +95,23 @@ class PyFinalInspection : PyInspection() {
}
}
override fun visitPyReferenceExpression(node: PyReferenceExpression) {
super.visitPyReferenceExpression(node)
checkFinalIsOuterMost(node)
}
private fun checkFinalIsOuterMost(node: PyReferenceExpression) {
if (isTopLevelInAnnotationOrTypeComment(node)) return
(node.parent as? PySubscriptionExpression)?.let {
if (it.operand == node && isTopLevelInAnnotationOrTypeComment(it)) return
}
if (PyTypingTypeProvider.isInAnnotationOrTypeComment(node) && resolvesToFinal(node)) {
registerProblem(node, "'Final' could only be used as the outermost type")
}
}
private fun isFinal(decoratable: PyDecoratable): Boolean {
return PyKnownDecoratorUtil.getKnownDecorators(decoratable, myTypeEvalContext).any { it == TYPING_FINAL || it == TYPING_FINAL_EXT }
}
@@ -120,5 +139,12 @@ class PyFinalInspection : PyInspection() {
val file = FileContextUtil.getContextFile(node) ?: return null
return PyUtil.createExpressionFromFragment(typeComment, file)
}
private fun isTopLevelInAnnotationOrTypeComment(node: PyExpression): Boolean {
if (node.parent is PyAnnotation) return true
if (node.parent is PyExpressionStatement && node.parent.parent is PyDocstringFile) return true
if (node.parent is PyParameterTypeList) return true
return false
}
}
}
@@ -142,6 +142,26 @@ public class PyFinalInspectionTest extends PyInspectionTestCase {
);
}
// PY-34945
public void testOuterMostFinal() {
runWithLanguageLevel(
LanguageLevel.PYTHON36,
() -> doTestByText("from typing_extensions import Final\n" +
"\n" +
"a1: Final[int] = 10\n" +
"b1: List[<warning descr=\"'Final' could only be used as the outermost type\">Final</warning>[int]] = []\n" +
"\n" +
"a2 = 10 # type: Final[int]\n" +
"b2 = [] # type: List[<warning descr=\"'Final' could only be used as the outermost type\">Final</warning>[int]]\n" +
"\n" +
"a3: Final = 10\n" +
"b3: List[<warning descr=\"'Final' could only be used as the outermost type\">Final</warning>] = []\n" +
"\n" +
"a4 = 10 # type: Final\n" +
"b4 = [] # type: List[<warning descr=\"'Final' could only be used as the outermost type\">Final</warning>]")
);
}
@NotNull
@Override
protected Class<? extends PyInspection> getInspectionClass() {