Raise a warning on Final parameters (PEP 591) (PY-34945)

GitOrigin-RevId: 42cc2e46ade7564581ab7bde30dd45bdaea84983
This commit is contained in:
Semyon Proshev
2019-07-02 06:52:16 +03:00
committed by intellij-monorepo-bot
parent b5fe834814
commit 67a0f2f9c0
2 changed files with 58 additions and 10 deletions
@@ -4,7 +4,9 @@ package com.jetbrains.python.inspections
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.typing.PyTypingTypeProvider
import com.jetbrains.python.codeInsight.typing.PyTypingTypeProvider.getFunctionTypeAnnotation
import com.jetbrains.python.psi.*
import com.jetbrains.python.psi.PyKnownDecoratorUtil.KnownDecorator.TYPING_FINAL
import com.jetbrains.python.psi.PyKnownDecoratorUtil.KnownDecorator.TYPING_FINAL_EXT
@@ -64,26 +66,33 @@ class PyFinalInspection : PyInspection() {
else if (isFinal(node)) {
registerProblem(node.nameIdentifier, "Non-method function could not be marked as '@final'")
}
getFunctionTypeAnnotation(node)?.let { comment ->
if (comment.parameterTypeList.parameterTypes.any { resolvesToFinal(if (it is PySubscriptionExpression) it.operand else it) }) {
registerProblem(node.typeComment, "'Final' could not be used in annotations for function parameters")
}
}
}
override fun visitPyTargetExpression(node: PyTargetExpression) {
super.visitPyTargetExpression(node)
if (!node.hasAssignedValue()) {
val value = node.annotation?.value
if (value is PyReferenceExpression) {
value
.multiFollowAssignmentsChain(resolveContext) { !isFinal(it.qualifiedName) }
.asSequence()
.mapNotNull { it.element }
.any { it is PyTargetExpression && isFinal(it.qualifiedName) }
.let {
if (it) registerProblem(value, "If assigned value is omitted, there should be an explicit type argument to 'Final'")
}
node.annotation?.value.takeIf(this::resolvesToFinal).let {
registerProblem(it, "If assigned value is omitted, there should be an explicit type argument to 'Final'")
}
}
}
override fun visitPyNamedParameter(node: PyNamedParameter) {
super.visitPyNamedParameter(node)
val typeHint = typeHintAsExpression(node)
if (resolvesToFinal(if (typeHint is PySubscriptionExpression) typeHint.operand else typeHint)) {
registerProblem(node.annotation?.value ?: node.typeComment, "'Final' could not be used in annotations for function parameters")
}
}
private fun isFinal(decoratable: PyDecoratable): Boolean {
return PyKnownDecoratorUtil.getKnownDecorators(decoratable, myTypeEvalContext).any { it == TYPING_FINAL || it == TYPING_FINAL_EXT }
}
@@ -91,5 +100,25 @@ class PyFinalInspection : PyInspection() {
private fun isFinal(qualifiedName: String?): Boolean {
return qualifiedName == PyTypingTypeProvider.FINAL || qualifiedName == PyTypingTypeProvider.FINAL_EXT
}
private fun resolvesToFinal(expression: PyExpression?): Boolean {
return expression is PyReferenceExpression &&
expression
.multiFollowAssignmentsChain(resolveContext) { !isFinal(it.qualifiedName) }
.asSequence()
.mapNotNull { it.element }
.any { it is PyTargetExpression && isFinal(it.qualifiedName) }
}
private fun <T> typeHintAsExpression(node: T): PyExpression? where T : PyAnnotationOwner, T : PyTypeCommentOwner {
val annotation = node.annotation?.value
if (annotation != null) return annotation
val typeComment = node.typeCommentAnnotation
if (typeComment == null) return null
val file = FileContextUtil.getContextFile(node) ?: return null
return PyUtil.createExpressionFromFragment(typeComment, file)
}
}
}
@@ -123,6 +123,25 @@ public class PyFinalInspectionTest extends PyInspectionTestCase {
assertSdkRootsNotParsed(currentFile);
}
// PY-34945
public void testFinalParameter() {
runWithLanguageLevel(
LanguageLevel.PYTHON35,
() -> doTestByText("from typing_extensions import Final\n" +
"\n" +
"def foo(a: <warning descr=\"'Final' could not be used in annotations for function parameters\">Final</warning>) -> None:\n" +
" pass\n" +
"\n" +
"def bar(a, <warning descr=\"'Final' could not be used in annotations for function parameters\"># type: Final[str]</warning>\n" +
" ):\n" +
" pass\n" +
"\n" +
"def baz(a):\n" +
" <warning descr=\"'Final' could not be used in annotations for function parameters\"># type: (Final[int]) -> None</warning>\n" +
" pass")
);
}
@NotNull
@Override
protected Class<? extends PyInspection> getInspectionClass() {