mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Raise a warning on Final parameters (PEP 591) (PY-34945)
GitOrigin-RevId: 42cc2e46ade7564581ab7bde30dd45bdaea84983
This commit is contained in:
committed by
intellij-monorepo-bot
parent
b5fe834814
commit
67a0f2f9c0
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user