mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Highlight mismatching between type comment and parameters (PY-20530)
This commit is contained in:
@@ -362,7 +362,7 @@ public class PyTypingTypeProvider extends PyTypeProviderBase {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static PyFunctionTypeAnnotation getFunctionTypeAnnotation(@NotNull PyFunction function) {
|
||||
public static PyFunctionTypeAnnotation getFunctionTypeAnnotation(@NotNull PyFunction function) {
|
||||
final String comment = function.getTypeCommentAnnotation();
|
||||
if (comment == null) {
|
||||
return null;
|
||||
|
||||
@@ -15,6 +15,7 @@ import com.jetbrains.python.codeInsight.controlflow.ReadWriteInstruction
|
||||
import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil
|
||||
import com.jetbrains.python.codeInsight.functionTypeComments.PyFunctionTypeAnnotationDialect
|
||||
import com.jetbrains.python.codeInsight.typing.PyTypingTypeProvider
|
||||
import com.jetbrains.python.documentation.PythonDocumentationProvider
|
||||
import com.jetbrains.python.documentation.doctest.PyDocstringFile
|
||||
import com.jetbrains.python.psi.*
|
||||
import com.jetbrains.python.psi.impl.PyEvaluator
|
||||
@@ -22,6 +23,7 @@ import com.jetbrains.python.psi.impl.PyPsiUtils
|
||||
import com.jetbrains.python.psi.resolve.PyResolveContext
|
||||
import com.jetbrains.python.psi.resolve.PyResolveUtil
|
||||
import com.jetbrains.python.psi.types.PyGenericType
|
||||
import com.jetbrains.python.psi.types.PyInstantiableType
|
||||
import com.jetbrains.python.psi.types.PyTypeChecker
|
||||
|
||||
class PyTypeHintsInspection : PyInspection() {
|
||||
@@ -121,6 +123,12 @@ class PyTypeHintsInspection : PyInspection() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitPyFunction(node: PyFunction) {
|
||||
super.visitPyFunction(node)
|
||||
|
||||
checkTypeCommentAndParameters(node)
|
||||
}
|
||||
|
||||
private fun checkTypeVarPlacement(call: PyCallExpression, target: PyExpression?) {
|
||||
if (target == null) {
|
||||
registerProblem(call, "A 'TypeVar()' expression must always directly be assigned to a variable")
|
||||
@@ -502,6 +510,48 @@ class PyTypeHintsInspection : PyInspection() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkTypeCommentAndParameters(node: PyFunction) {
|
||||
val functionTypeAnnotation = PyTypingTypeProvider.getFunctionTypeAnnotation(node) ?: return
|
||||
|
||||
val parameterTypes = functionTypeAnnotation.parameterTypeList.parameterTypes
|
||||
if (parameterTypes.singleOrNull().let { it is PyNoneLiteralExpression && it.isEllipsis }) return
|
||||
|
||||
val actualParametersSize = node.parameterList.parameters.size
|
||||
val commentParametersSize = parameterTypes.size
|
||||
|
||||
val cls = node.containingClass
|
||||
val modifier = node.modifier
|
||||
|
||||
val hasSelf = cls != null && modifier != PyFunction.Modifier.STATICMETHOD
|
||||
|
||||
if (commentParametersSize < actualParametersSize - if (hasSelf) 1 else 0) {
|
||||
registerProblem(node.typeComment, "Type signature has too few arguments")
|
||||
}
|
||||
else if (commentParametersSize > actualParametersSize) {
|
||||
registerProblem(node.typeComment, "Type signature has too many arguments")
|
||||
}
|
||||
else if (hasSelf && actualParametersSize == commentParametersSize) {
|
||||
val actualSelfType =
|
||||
(myTypeEvalContext.getType(cls!!) as? PyInstantiableType<*>)
|
||||
?.let { if (modifier == PyFunction.Modifier.CLASSMETHOD) it.toClass() else it.toInstance() }
|
||||
?: return
|
||||
|
||||
val commentSelfType =
|
||||
parameterTypes.firstOrNull()
|
||||
?.let { PyTypingTypeProvider.getType(it, myTypeEvalContext) }
|
||||
?.get()
|
||||
?: return
|
||||
|
||||
if (!PyTypeChecker.match(commentSelfType, actualSelfType, myTypeEvalContext)) {
|
||||
val actualSelfTypeDescription = PythonDocumentationProvider.getTypeDescription(actualSelfType, myTypeEvalContext)
|
||||
val commentSelfTypeDescription = PythonDocumentationProvider.getTypeDescription(commentSelfType, myTypeEvalContext)
|
||||
|
||||
registerProblem(node.typeComment,
|
||||
"The type of self '$commentSelfTypeDescription' is not a supertype of its class '$actualSelfTypeDescription'")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun followNotTypingOpaque(target: PyTargetExpression): Boolean {
|
||||
return !PyTypingTypeProvider.OPAQUE_NAMES.contains(target.qualifiedName)
|
||||
}
|
||||
|
||||
@@ -610,6 +610,159 @@ public class PyTypeHintsInspectionTest extends PyInspectionTestCase {
|
||||
);
|
||||
}
|
||||
|
||||
// PY-20530
|
||||
public void testValidTypeCommentAndParameters() {
|
||||
doTestByText("from typing import Type\n" +
|
||||
"\n" +
|
||||
"class A:\n" +
|
||||
" pass\n" +
|
||||
"\n" +
|
||||
"class Bar(A):\n" +
|
||||
" # self is specified\n" +
|
||||
" def spam11(self):\n" +
|
||||
" # type: (Bar) -> None\n" +
|
||||
" pass\n" +
|
||||
"\n" +
|
||||
" def egg11(self, a, b):\n" +
|
||||
" # type: (Bar, str, bool) -> None\n" +
|
||||
" pass\n" +
|
||||
" \n" +
|
||||
" # self is specified\n" +
|
||||
" def spam12(self):\n" +
|
||||
" # type: (A) -> None\n" +
|
||||
" pass\n" +
|
||||
"\n" +
|
||||
" def egg12(self, a, b):\n" +
|
||||
" # type: (A, str, bool) -> None\n" +
|
||||
" pass\n" +
|
||||
" \n" +
|
||||
" # self is not specified\n" +
|
||||
" def spam2(self):\n" +
|
||||
" # type: () -> None\n" +
|
||||
" pass\n" +
|
||||
"\n" +
|
||||
" def egg2(self, a, b):\n" +
|
||||
" # type: (str, bool) -> None\n" +
|
||||
" pass\n" +
|
||||
" \n" +
|
||||
" # cls is not specified \n" +
|
||||
" @classmethod\n" +
|
||||
" def spam3(cls):\n" +
|
||||
" # type: () -> None\n" +
|
||||
" pass\n" +
|
||||
"\n" +
|
||||
" @classmethod\n" +
|
||||
" def egg3(cls, a, b):\n" +
|
||||
" # type: (str, bool) -> None\n" +
|
||||
" pass\n" +
|
||||
" \n" +
|
||||
" # cls is specified \n" +
|
||||
" @classmethod\n" +
|
||||
" def spam41(cls):\n" +
|
||||
" # type: (Type[Bar]) -> None\n" +
|
||||
" pass\n" +
|
||||
"\n" +
|
||||
" @classmethod\n" +
|
||||
" def egg41(cls, a, b):\n" +
|
||||
" # type: (Type[Bar], str, bool) -> None\n" +
|
||||
" pass\n" +
|
||||
" \n" +
|
||||
" # cls is specified \n" +
|
||||
" @classmethod\n" +
|
||||
" def spam42(cls):\n" +
|
||||
" # type: (Type[A]) -> None\n" +
|
||||
" pass\n" +
|
||||
"\n" +
|
||||
" @classmethod\n" +
|
||||
" def egg42(cls, a, b):\n" +
|
||||
" # type: (Type[A], str, bool) -> None\n" +
|
||||
" pass\n" +
|
||||
" \n" +
|
||||
" @staticmethod\n" +
|
||||
" def spam5():\n" +
|
||||
" # type: () -> None\n" +
|
||||
" pass\n" +
|
||||
"\n" +
|
||||
" @staticmethod\n" +
|
||||
" def egg5(a, b):\n" +
|
||||
" # type: (str, bool) -> None\n" +
|
||||
" pass\n" +
|
||||
" \n" +
|
||||
" def baz(self, a, b, c, d):\n" +
|
||||
" # type: (...) -> None\n" +
|
||||
" pass");
|
||||
}
|
||||
|
||||
// PY-20530
|
||||
public void testInvalidTypeCommentAndParameters() {
|
||||
doTestByText("from typing import Type\n" +
|
||||
"\n" +
|
||||
"class Bar:\n" +
|
||||
" # self is specified\n" +
|
||||
" def spam1(self):\n" +
|
||||
" <warning descr=\"Type signature has too many arguments\"># type: (Bar, int) -> None</warning>\n" +
|
||||
" pass\n" +
|
||||
"\n" +
|
||||
" def egg11(self, a, b):\n" +
|
||||
" <warning descr=\"Type signature has too many arguments\"># type: (Bar, int, str, bool) -> None</warning>\n" +
|
||||
" pass\n" +
|
||||
"\n" +
|
||||
" def egg12(self, a, b):\n" +
|
||||
" <warning descr=\"Type signature has too few arguments\"># type: (Bar) -> None</warning>\n" +
|
||||
" pass\n" +
|
||||
" \n" +
|
||||
" # self is not specified\n" +
|
||||
" def spam2(self):\n" +
|
||||
" <warning descr=\"The type of self 'int' is not a supertype of its class 'Bar'\"># type: (int) -> None</warning>\n" +
|
||||
" pass\n" +
|
||||
"\n" +
|
||||
" def egg2(self, a, b):\n" +
|
||||
" <warning descr=\"The type of self 'int' is not a supertype of its class 'Bar'\"># type: (int, str, bool) -> None</warning>\n" +
|
||||
" pass\n" +
|
||||
" \n" +
|
||||
" # cls is not specified \n" +
|
||||
" @classmethod\n" +
|
||||
" def spam3(cls):\n" +
|
||||
" <warning descr=\"The type of self 'int' is not a supertype of its class 'Type[Bar]'\"># type: (int) -> None</warning>\n" +
|
||||
" pass\n" +
|
||||
"\n" +
|
||||
" @classmethod\n" +
|
||||
" def egg3(cls, a, b):\n" +
|
||||
" <warning descr=\"The type of self 'int' is not a supertype of its class 'Type[Bar]'\"># type: (int, str, bool) -> None</warning>\n" +
|
||||
" pass\n" +
|
||||
" \n" +
|
||||
" # cls is specified \n" +
|
||||
" @classmethod\n" +
|
||||
" def spam4(cls):\n" +
|
||||
" <warning descr=\"Type signature has too many arguments\"># type: (Type[Bar], int) -> None</warning>\n" +
|
||||
" pass\n" +
|
||||
"\n" +
|
||||
" @classmethod\n" +
|
||||
" def egg41(cls, a, b):\n" +
|
||||
" <warning descr=\"Type signature has too many arguments\"># type: (Type[Bar], int, str, bool) -> None</warning>\n" +
|
||||
" pass\n" +
|
||||
"\n" +
|
||||
" @classmethod\n" +
|
||||
" def egg42(cls, a, b):\n" +
|
||||
" <warning descr=\"Type signature has too few arguments\"># type: (Type[Bar]) -> None</warning>\n" +
|
||||
" pass\n" +
|
||||
" \n" +
|
||||
" @staticmethod\n" +
|
||||
" def spam5():\n" +
|
||||
" <warning descr=\"Type signature has too many arguments\"># type: (int) -> None</warning>\n" +
|
||||
" pass\n" +
|
||||
"\n" +
|
||||
" @staticmethod\n" +
|
||||
" def egg51(a, b):\n" +
|
||||
" <warning descr=\"Type signature has too many arguments\"># type: (int, str, bool) -> None</warning>\n" +
|
||||
" pass\n" +
|
||||
"\n" +
|
||||
" @staticmethod\n" +
|
||||
" def egg52(a, b):\n" +
|
||||
" <warning descr=\"Type signature has too few arguments\"># type: (int) -> None</warning>\n" +
|
||||
" pass");
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected Class<? extends PyInspection> getInspectionClass() {
|
||||
|
||||
Reference in New Issue
Block a user