mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[python] PY-76868 Upgrade subscription type form checks
Merge-request: IJ-MR-190288 Merged-by: Nikita Paniukhin <nikita.paniukhin@jetbrains.com> GitOrigin-RevId: e484c7e6249797b66145149eac58b7c6945758b4
This commit is contained in:
committed by
intellij-monorepo-bot
parent
a8d61ea416
commit
9331cae59f
@@ -1263,6 +1263,10 @@ INSP.type.hints.at.most.one.unpacked.tuple=Type argument list can have at most o
|
||||
INSP.type.hints.cannot.use.covariant.in.function.param=Covariant type variable cannot be used in parameter type
|
||||
INSP.type.hints.cannot.use.contravariant.in.return.type=Contravariant type variable cannot be used in function return type
|
||||
INSP.type.hints.cannot.use.class.scope.type.variables.in.annotation.for.self.parameter.of__init__=Class-scoped type variables should not be used in the annotation for 'self' parameter of '__init__' method
|
||||
INSP.type.hints.ellipsis.allowed.only.as.second.argument='...' is allowed only as the second of two arguments
|
||||
INSP.type.hints.empty.tuple.only.as.lone.argument=Empty tuple is allowed only as a sole argument
|
||||
INSP.type.hints.ellipsis.cannot.be.used.with.unpacked.type='...' cannot be used with an unpacked 'TypeVarTuple' or tuple
|
||||
INSP.type.hints.optional.must.have.exactly.one.argument='Optional' must have exactly one argument
|
||||
QFIX.remove.function.annotations=Remove function annotations
|
||||
QFIX.replace.with.target.name=Replace with the target name
|
||||
QFIX.remove.generic.parameters=Remove generic parameters
|
||||
|
||||
+36
-9
@@ -1654,9 +1654,14 @@ class PyTypingTypeProvider : PyTypeProviderWithCustomContext<Context?>() {
|
||||
private fun getOptionalType(element: PsiElement, context: Context): Ref<PyType?>? {
|
||||
if (element is PySubscriptionExpression) {
|
||||
if (resolvesToQualifiedNames(element.operand, context.typeContext, OPTIONAL)) {
|
||||
val indexExpr = element.indexExpression
|
||||
if (indexExpr != null) {
|
||||
val typeRef: Ref<PyType?>? = getType(indexExpr, context)
|
||||
val indexExpr = PyPsiUtils.flattenParens(element.indexExpression)
|
||||
val argExpr: PyExpression? = when (indexExpr) {
|
||||
is PyTupleExpression -> indexExpr.elements.singleOrNull() ?: indexExpr
|
||||
else -> indexExpr
|
||||
}
|
||||
|
||||
if (argExpr != null) {
|
||||
val typeRef: Ref<PyType?>? = getType(argExpr, context)
|
||||
if (typeRef != null) {
|
||||
return Ref(PyUnionType.union(typeRef.get(), getInstance(element).noneType))
|
||||
}
|
||||
@@ -2049,7 +2054,7 @@ class PyTypingTypeProvider : PyTypeProviderWithCustomContext<Context?>() {
|
||||
private fun getUnionType(element: PsiElement, context: Context): Ref<PyType?>? {
|
||||
if (element is PySubscriptionExpression) {
|
||||
if (resolvesToQualifiedNames(element.operand, context.typeContext, UNION)) {
|
||||
val union = PyUnionType.union(getIndexTypes(element, context))
|
||||
val union = PyUnionType.unionOrNever(getIndexTypes(element, context))
|
||||
return if (union != null) Ref(union) else null
|
||||
}
|
||||
}
|
||||
@@ -2448,7 +2453,7 @@ class PyTypingTypeProvider : PyTypeProviderWithCustomContext<Context?>() {
|
||||
|
||||
private fun getIndexTypes(expression: PySubscriptionExpression, context: Context): MutableList<PyType?> {
|
||||
val types: MutableList<PyType?> = ArrayList()
|
||||
val indexExpr = expression.indexExpression
|
||||
val indexExpr = PyPsiUtils.flattenParens(expression.indexExpression)
|
||||
if (indexExpr is PyTupleExpression) {
|
||||
for (expr in indexExpr.elements) {
|
||||
types.add(Ref.deref<PyType?>(getType(expr, context)))
|
||||
@@ -2578,12 +2583,34 @@ class PyTypingTypeProvider : PyTypeProviderWithCustomContext<Context?>() {
|
||||
if (operandType is PyClassType) {
|
||||
if (operandType !is PyTupleType && PyNames.TUPLE == operandType.pyClass.qualifiedName) {
|
||||
if (indexExpr is PyTupleExpression) {
|
||||
val elements = indexExpr.elements
|
||||
if (elements.size == 2 && elements[1] is PyEllipsisLiteralExpression) {
|
||||
return PyTupleType.createHomogeneous(element, indexTypes[0])
|
||||
val indexElements = indexExpr.elements.map { PyPsiUtils.flattenParens(it) }
|
||||
|
||||
val lastIsEllipsis =
|
||||
indexElements.isNotEmpty() && indexElements.last() is PyEllipsisLiteralExpression
|
||||
|
||||
if (lastIsEllipsis) {
|
||||
if (indexElements.size != 2) return null
|
||||
if (indexElements.first() is PyEllipsisLiteralExpression) return null
|
||||
|
||||
val indexType = indexTypes.first()
|
||||
if (indexType is PyPositionalVariadicType) return null
|
||||
|
||||
return PyTupleType.createHomogeneous(element, indexType)
|
||||
}
|
||||
else {
|
||||
for (indexElement in indexElements) {
|
||||
if (indexElement is PyEllipsisLiteralExpression) return null
|
||||
if (indexElement is PyTupleExpression && indexElement.elements.isEmpty()) {
|
||||
if (indexElements.size != 1) return null
|
||||
}
|
||||
}
|
||||
return PyTupleType.create(element, indexTypes)
|
||||
}
|
||||
}
|
||||
return PyTupleType.create(element, indexTypes)
|
||||
else {
|
||||
if (indexExpr is PyEllipsisLiteralExpression) return null
|
||||
return PyTupleType.create(element, indexTypes)
|
||||
}
|
||||
}
|
||||
|
||||
if (isGeneric(operandType, context.typeContext)) {
|
||||
|
||||
+164
-46
@@ -101,6 +101,7 @@ import com.jetbrains.python.psi.types.PyTypeChecker.collectGenerics
|
||||
import com.jetbrains.python.psi.types.PyTypeChecker.hasGenerics
|
||||
import com.jetbrains.python.psi.types.PyTypeParameterMapping
|
||||
import com.jetbrains.python.psi.types.PyTypeParameterType
|
||||
import com.jetbrains.python.psi.types.PyTypeParser
|
||||
import com.jetbrains.python.psi.types.PyTypeVarTupleType
|
||||
import com.jetbrains.python.psi.types.PyTypeVarType
|
||||
import com.jetbrains.python.psi.types.PyTypeVarType.Variance
|
||||
@@ -1087,6 +1088,9 @@ class PyTypeHintsInspection : PyInspection() {
|
||||
val typeAliasExtQName = QualifiedName.fromDottedString(PyTypingTypeProvider.TYPE_ALIAS_EXT)
|
||||
val typingSelf = QualifiedName.fromDottedString(PyTypingTypeProvider.SELF)
|
||||
val typingExtSelf = QualifiedName.fromDottedString(PyTypingTypeProvider.SELF_EXT)
|
||||
val unionQName = QualifiedName.fromDottedString(PyTypingTypeProvider.UNION)
|
||||
val optionalQName = QualifiedName.fromDottedString(PyTypingTypeProvider.OPTIONAL)
|
||||
|
||||
val qNames = PyResolveUtil.resolveImportedElementQNameLocally(operand)
|
||||
|
||||
var typingOnly = true
|
||||
@@ -1100,8 +1104,14 @@ class PyTypeHintsInspection : PyInspection() {
|
||||
annotatedQName, annotatedExtQName -> checkAnnotatedParameter(index)
|
||||
typeAliasQName, typeAliasExtQName -> reportParameterizedTypeAlias(index)
|
||||
typingSelf, typingExtSelf -> reportParameterizedSelf(index)
|
||||
unionQName -> checkGenericTypeArguments(node)
|
||||
optionalQName -> {
|
||||
checkGenericTypeArguments(node)
|
||||
checkOptionalParameter(index)
|
||||
}
|
||||
callableQName -> {
|
||||
callableExists = true
|
||||
checkGenericTypeArguments(node, isCallable = true)
|
||||
checkCallableParameters(index)
|
||||
}
|
||||
else -> checkGenericTypeParameterization(node)
|
||||
@@ -1166,7 +1176,19 @@ class PyTypeHintsInspection : PyInspection() {
|
||||
}.firstOrNull()
|
||||
|
||||
when (declaration) {
|
||||
is PyTargetExpression -> checkTypeAliasParameterization(node, declaration)
|
||||
is PyTargetExpression -> {
|
||||
val builtinName = declaration.qualifiedName?.let { PyTypingTypeProvider.BUILTIN_COLLECTION_CLASSES[it] }
|
||||
if (builtinName != null) {
|
||||
val builtinType = PyTypeParser.getTypeByName(node, builtinName, myTypeEvalContext)
|
||||
val builtinTypeClass = (builtinType as? PyClassType)?.pyClass
|
||||
if (builtinTypeClass != null) {
|
||||
checkGenericClassParameterization(node, builtinTypeClass)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
checkTypeAliasParameterization(node, declaration)
|
||||
}
|
||||
is PyTypeAliasStatement -> checkTypeAliasStatementParameterization(node, declaration)
|
||||
is PyClass -> checkGenericClassParameterization(node, declaration)
|
||||
else -> return
|
||||
@@ -1184,9 +1206,14 @@ class PyTypeHintsInspection : PyInspection() {
|
||||
}
|
||||
return
|
||||
}
|
||||
val typeArguments = checkGenericTypeArguments(node)
|
||||
|
||||
if (typeArguments == null || genericDefinitionType.pyClass.qualifiedName == PyNames.TUPLE) return
|
||||
val typeArguments = checkGenericTypeArguments(node) ?: return
|
||||
|
||||
if (genericDefinitionType.pyClass.qualifiedName == PyNames.TUPLE) {
|
||||
checkTupleTypeForm(node)
|
||||
return
|
||||
}
|
||||
|
||||
val typeParameters = genericDefinitionType.elementTypes
|
||||
|
||||
val typeParameterListRepresentation = typeParameters.joinToString(prefix = "[", postfix = "]") { it.name!! }
|
||||
@@ -1241,51 +1268,105 @@ class PyTypeHintsInspection : PyInspection() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkGenericTypeArguments(node: PySubscriptionExpression, isCallable: Boolean = false): List<PyType?>? {
|
||||
val flatIndexExpr = PyPsiUtils.flattenParens(node.indexExpression) ?: return null
|
||||
val arguments = (flatIndexExpr as? PyTupleExpression)?.elements ?: arrayOf(flatIndexExpr)
|
||||
val argumentTypes = mutableListOf<PyType?>()
|
||||
|
||||
private fun checkGenericTypeArguments(node: PySubscriptionExpression): List<PyType?>? {
|
||||
val indexExpression = node.indexExpression ?: return null
|
||||
val parameters = (indexExpression as? PyTupleExpression)?.elements ?: arrayOf(indexExpression)
|
||||
val typeArgumentTypes = mutableListOf<PyType?>()
|
||||
|
||||
parameters.forEach {
|
||||
when (it) {
|
||||
for ((index, argument) in arguments.withIndex()) {
|
||||
val argumentType = when (val flatArgument = PyPsiUtils.flattenParens(argument)) {
|
||||
is PyReferenceExpression,
|
||||
is PySubscriptionExpression,
|
||||
is PyBinaryExpression,
|
||||
is PyStarExpression,
|
||||
is PyStringLiteralExpression,
|
||||
is PyListLiteralExpression,
|
||||
is PyCallExpression,
|
||||
-> {
|
||||
val typeRef = PyTypingTypeProvider.getType(it, myTypeEvalContext)
|
||||
val typeRef = PyTypingTypeProvider.getType(argument, myTypeEvalContext)
|
||||
if (typeRef == null) {
|
||||
val shouldReportError = when {
|
||||
it is PyReferenceExpression -> {
|
||||
val isUnresolved = PyResolveUtil.resolveDeclaration(it.reference, resolveContext) == null
|
||||
val isOpaque = PyTypingTypeProvider.resolveToQualifiedNames(it, myTypeEvalContext)
|
||||
val message = when {
|
||||
argument is PyReferenceExpression -> {
|
||||
val isResolved = PyResolveUtil.resolveDeclaration(argument.reference, resolveContext) != null
|
||||
val isOpaque = PyTypingTypeProvider.resolveToQualifiedNames(argument, myTypeEvalContext)
|
||||
.any { qName -> PyTypingTypeProvider.OPAQUE_NAMES.contains(qName) }
|
||||
!isOpaque && !isUnresolved
|
||||
|
||||
if (isResolved && !isOpaque) PyPsiBundle.message("INSP.type.hints.parameters.to.generic.types.must.be.types") else null
|
||||
}
|
||||
else -> true
|
||||
else -> PyPsiBundle.message("INSP.type.hints.invalid.type.argument")
|
||||
}
|
||||
if (shouldReportError) {
|
||||
registerProblem(it, PyPsiBundle.message("INSP.type.hints.invalid.type.argument"))
|
||||
if (message != null) {
|
||||
registerProblem(flatArgument, message, ProblemHighlightType.GENERIC_ERROR)
|
||||
}
|
||||
}
|
||||
typeArgumentTypes.add(Ref.deref(typeRef))
|
||||
Ref.deref(typeRef)
|
||||
}
|
||||
is PyNoneLiteralExpression -> {
|
||||
typeArgumentTypes.add(PyBuiltinCache.getInstance(node).noneType)
|
||||
PyBuiltinCache.getInstance(node).noneType
|
||||
}
|
||||
is PyEllipsisLiteralExpression -> {
|
||||
typeArgumentTypes.add(null)
|
||||
is PyEllipsisLiteralExpression if (
|
||||
node.isBuiltinTupleTypeForm(myTypeEvalContext) ||
|
||||
node.isParamSpecArgument(index, myTypeEvalContext) ||
|
||||
(isCallable && index == 0)
|
||||
) -> {
|
||||
null
|
||||
}
|
||||
is PyTupleExpression if (
|
||||
(node.isBuiltinTupleTypeForm(myTypeEvalContext) && flatArgument.elements.isEmpty()) ||
|
||||
(isCallable && index == 0)
|
||||
) -> {
|
||||
null
|
||||
}
|
||||
else -> {
|
||||
registerProblem(it, PyPsiBundle.message("INSP.type.hints.invalid.type.argument"))
|
||||
typeArgumentTypes.add(null)
|
||||
registerProblem(argument, PyPsiBundle.message("INSP.type.hints.invalid.type.argument"), ProblemHighlightType.GENERIC_ERROR)
|
||||
null
|
||||
}
|
||||
}
|
||||
argumentTypes.add(argumentType)
|
||||
}
|
||||
return argumentTypes
|
||||
}
|
||||
|
||||
private fun checkTupleTypeForm(node: PySubscriptionExpression) {
|
||||
if (!node.isBuiltinTupleTypeForm(myTypeEvalContext)) return
|
||||
|
||||
val flatIndexExpr = PyPsiUtils.flattenParens(node.indexExpression)
|
||||
val arguments = (flatIndexExpr as? PyTupleExpression)?.elements ?: arrayOf(flatIndexExpr)
|
||||
|
||||
for ((index, argument) in arguments.withIndex()) {
|
||||
when (val flatArgument = PyPsiUtils.flattenParens(argument)) {
|
||||
is PyEllipsisLiteralExpression if (index != arguments.lastIndex || arguments.size != 2) -> {
|
||||
registerProblem(flatArgument,
|
||||
PyPsiBundle.message("INSP.type.hints.ellipsis.allowed.only.as.second.argument"),
|
||||
ProblemHighlightType.GENERIC_ERROR)
|
||||
}
|
||||
is PyTupleExpression if flatArgument.elements.isEmpty() && arguments.size != 1 -> {
|
||||
registerProblem(flatArgument,
|
||||
PyPsiBundle.message("INSP.type.hints.empty.tuple.only.as.lone.argument"),
|
||||
ProblemHighlightType.GENERIC_ERROR)
|
||||
}
|
||||
}
|
||||
}
|
||||
return typeArgumentTypes
|
||||
|
||||
val lastArgument = PyPsiUtils.flattenParens(arguments.lastOrNull())
|
||||
if (lastArgument is PyEllipsisLiteralExpression) {
|
||||
val type = Ref.deref(PyTypingTypeProvider.getType(arguments.first(), myTypeEvalContext))
|
||||
if (type is PyPositionalVariadicType) {
|
||||
registerProblem(lastArgument,
|
||||
PyPsiBundle.message("INSP.type.hints.ellipsis.cannot.be.used.with.unpacked.type"),
|
||||
ProblemHighlightType.GENERIC_ERROR)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkOptionalParameter(index: PyExpression) {
|
||||
val flatIndexExpr = PyPsiUtils.flattenParens(index)
|
||||
val elements = (flatIndexExpr as? PyTupleExpression)?.elements ?: arrayOf(index)
|
||||
if (elements.size != 1) {
|
||||
registerProblem(flatIndexExpr,
|
||||
PyPsiBundle.message("INSP.type.hints.optional.must.have.exactly.one.argument"),
|
||||
ProblemHighlightType.GENERIC_ERROR)
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkTypingGenericParameters(node: PySubscriptionExpression, isProtocol: Boolean) {
|
||||
@@ -1441,25 +1522,25 @@ class PyTypeHintsInspection : PyInspection() {
|
||||
parameters
|
||||
.asSequence()
|
||||
.drop(if (isCallable) 1 else 0)
|
||||
.forEach {
|
||||
if (it is PyListLiteralExpression) {
|
||||
registerProblem(it,
|
||||
PyPsiBundle.message("INSP.type.hints.parameters.to.generic.types.must.be.types"),
|
||||
ProblemHighlightType.GENERIC_ERROR,
|
||||
null,
|
||||
RemoveSquareBracketsQuickFix())
|
||||
}
|
||||
else if (it is PyReferenceExpression && multiFollowAssignmentsChain(it).any { resolved -> resolved is PyListLiteralExpression }) {
|
||||
registerProblem(it, PyPsiBundle.message("INSP.type.hints.parameters.to.generic.types.must.be.types"),
|
||||
ProblemHighlightType.GENERIC_ERROR)
|
||||
}
|
||||
else if (it is PyStarExpression) {
|
||||
if (alreadyHaveUnpacking) {
|
||||
registerProblem(it, PyPsiBundle.message("INSP.type.hints.parameters.to.generic.types.cannot.contain.more.than.one.unpacking"),
|
||||
ProblemHighlightType.GENERIC_ERROR)
|
||||
.forEach { argument ->
|
||||
val flatArgument = PyPsiUtils.flattenParens(argument)
|
||||
when (flatArgument) {
|
||||
is PyListLiteralExpression -> {
|
||||
registerProblem(flatArgument,
|
||||
PyPsiBundle.message("INSP.type.hints.parameters.to.generic.types.must.be.types"),
|
||||
ProblemHighlightType.GENERIC_ERROR,
|
||||
null,
|
||||
RemoveSquareBracketsQuickFix())
|
||||
}
|
||||
else {
|
||||
alreadyHaveUnpacking = true
|
||||
is PyStarExpression -> {
|
||||
if (alreadyHaveUnpacking) {
|
||||
registerProblem(flatArgument,
|
||||
PyPsiBundle.message("INSP.type.hints.parameters.to.generic.types.cannot.contain.more.than.one.unpacking"),
|
||||
ProblemHighlightType.GENERIC_ERROR)
|
||||
}
|
||||
else {
|
||||
alreadyHaveUnpacking = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1569,11 +1650,12 @@ class PyTypeHintsInspection : PyInspection() {
|
||||
typeArguments: List<PyType?>,
|
||||
@InspectionMessage message: String,
|
||||
) {
|
||||
val flatIndexExpr = PyPsiUtils.flattenParens(node.indexExpression)
|
||||
val mapping = PyTypeParameterMapping.mapByShape(typeParameters,
|
||||
typeArguments,
|
||||
PyTypeParameterMapping.Option.USE_DEFAULTS)
|
||||
if (mapping == null) {
|
||||
registerProblem(node.indexExpression, message, ProblemHighlightType.WARNING)
|
||||
registerProblem(flatIndexExpr, message, ProblemHighlightType.WARNING)
|
||||
}
|
||||
else {
|
||||
for (pair in mapping.mappedTypes) {
|
||||
@@ -1582,7 +1664,9 @@ class PyTypeHintsInspection : PyInspection() {
|
||||
if (!matched) {
|
||||
val expectedName = PythonDocumentationProvider.getVerboseTypeName(pair.getFirst(), myTypeEvalContext)
|
||||
val actualName = PythonDocumentationProvider.getTypeName(pair.getSecond(), myTypeEvalContext)
|
||||
registerProblem(node.indexExpression, PyPsiBundle.message("INSP.type.checker.expected.type.got.type.instead", expectedName, actualName), ProblemHighlightType.WARNING)
|
||||
registerProblem(flatIndexExpr,
|
||||
PyPsiBundle.message("INSP.type.checker.expected.type.got.type.instead", expectedName, actualName),
|
||||
ProblemHighlightType.WARNING)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -1876,3 +1960,37 @@ class PyTypeHintsInspection : PyInspection() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun PySubscriptionExpression.isBuiltinTupleTypeForm(context: TypeEvalContext): Boolean {
|
||||
val operandType = context.getType(operand)
|
||||
return operandType is PyClassType && operandType !is PyTupleType && operandType.classQName == PyNames.TUPLE
|
||||
}
|
||||
|
||||
private fun PySubscriptionExpression.isParamSpecArgument(argIndex: Int, context: TypeEvalContext): Boolean {
|
||||
// Generic class parameterization (class Foo[T, **P]: ...)
|
||||
val operandType = context.getType(this.operand) as? PyClassType
|
||||
if (operandType != null) {
|
||||
val genericDefinitionType = PyTypeChecker.findGenericDefinitionType(operandType.pyClass, context)
|
||||
if (genericDefinitionType != null) {
|
||||
val typeParameters = genericDefinitionType.elementTypes
|
||||
if (argIndex in typeParameters.indices && typeParameters[argIndex] is PyParamSpecType) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PEP-695 type alias: `type Alias[S1, **S2] = ...`
|
||||
val resolveContext = PyResolveContext.defaultContext(context)
|
||||
val operandRef = this.operand as? PyReferenceExpression
|
||||
if (operandRef != null) {
|
||||
val aliasStatement = PyResolveUtil.resolveDeclaration(operandRef.reference, resolveContext) as? PyTypeAliasStatement
|
||||
if (aliasStatement != null) {
|
||||
val typeParams = aliasStatement.typeParameterList?.typeParameters ?: emptyList()
|
||||
if (argIndex in typeParams.indices) {
|
||||
return typeParams[argIndex].kind == PyAstTypeParameter.Kind.ParamSpec
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -46,5 +46,4 @@ specialtypes_any.py
|
||||
specialtypes_never.py
|
||||
specialtypes_promotions.py
|
||||
specialtypes_type.py
|
||||
tuples_type_form.py
|
||||
typeddicts_extra_items.py
|
||||
@@ -913,7 +913,7 @@ public class PyTypeHintsInspectionTest extends PyInspectionTestCase {
|
||||
def d(x: AnnotatedExt[AnnotatedExt[str, dict(key="value")], ""]):
|
||||
pass
|
||||
|
||||
def e(x: Annotated[str, list[<warning descr="Invalid type argument">dict(key="value")</warning>]]):
|
||||
def e(x: Annotated[str, list[<error descr="Invalid type argument">dict(key="value")</error>]]):
|
||||
pass
|
||||
|
||||
def f(x: Annotated[<warning descr="Generics should be specified through square brackets">dict(key="value")</warning>, ""]):
|
||||
@@ -2251,22 +2251,22 @@ public class PyTypeHintsInspectionTest extends PyInspectionTestCase {
|
||||
|
||||
class A:...
|
||||
|
||||
c1 = Clazz[<warning descr="Invalid type argument">print()</warning>, int]()
|
||||
c2 = Clazz[int, <warning descr="Invalid type argument">print()</warning>]()
|
||||
c3 = Clazz[<warning descr="Invalid type argument">1</warning>]
|
||||
c1 = Clazz[<error descr="Invalid type argument">print()</error>, int]()
|
||||
c2 = Clazz[int, <error descr="Invalid type argument">print()</error>]()
|
||||
c3 = Clazz[<error descr="Invalid type argument">1</error>]
|
||||
c4 = Clazz["int", "str"]
|
||||
c5 = Clazz[dict[int, str]]
|
||||
c7 = Clazz[<warning descr="Invalid type argument">True</warning>]
|
||||
c8 = Clazz[<warning descr="Invalid type argument">list or set</warning>]
|
||||
c7 = Clazz[<error descr="Invalid type argument">True</error>]
|
||||
c8 = Clazz[<error descr="Invalid type argument">list or set</error>]
|
||||
c9 = Clazz[Literal[3]]
|
||||
c10 = Clazz[<warning descr="Invalid type argument">var</warning>]
|
||||
c10 = Clazz[<error descr="Parameters to generic types must be types">var</error>]
|
||||
c11 = Clazz[myInt]
|
||||
c12 = Clazz[myIntOrStr]
|
||||
c13 = Clazz[myIntAlias]
|
||||
c14 = Clazz[A]
|
||||
c15 = Clazz[<warning descr="Invalid type argument">{"a": "b"}</warning>]
|
||||
c16 = Clazz[<warning descr="Invalid type argument">(lambda: int)()</warning>]
|
||||
c17 = Clazz[<warning descr="Invalid type argument">(int, str)</warning>]
|
||||
c15 = Clazz[<error descr="Invalid type argument">{"a": "b"}</error>]
|
||||
c16 = Clazz[<error descr="Invalid type argument">(lambda: int)()</error>]
|
||||
c17 = Clazz[(int, str)]
|
||||
""");
|
||||
}
|
||||
|
||||
@@ -3200,6 +3200,171 @@ public class PyTypeHintsInspectionTest extends PyInspectionTestCase {
|
||||
""");
|
||||
}
|
||||
|
||||
public void testSubscriptionParenthesesFlattening() {
|
||||
generateVariableTypeAssertions(new Object[][]{
|
||||
{"list[((int))]", "list[int]"},
|
||||
// TODO: type: list[Any]
|
||||
{"list[((<warning descr=\"Passed type arguments do not match type parameters [_T] of class 'list'\">int, int</warning>))]"},
|
||||
|
||||
{"tuple[((int, int))]", "tuple[int, int]"},
|
||||
{"tuple[<error descr=\"Invalid type argument\">((int, int))</error>, int]", "tuple[Any, int]"},
|
||||
|
||||
{"set[((int))]", "set[int]"},
|
||||
// TODO: type: set[Any]
|
||||
{"set[((<warning descr=\"Passed type arguments do not match type parameters [_T] of class 'set'\">int, int</warning>))]"},
|
||||
|
||||
{"dict[((int)), (((str)))]", "dict[int, str]"},
|
||||
// TODO: type: dict[Any, Any]
|
||||
{"dict[((<warning descr=\"Passed type arguments do not match type parameters [_KT, _VT] of class 'dict'\">int</warning>))]"},
|
||||
|
||||
{"List[((int))]", "list[int]"},
|
||||
// TODO: type: list[Any]
|
||||
{"List[((<warning descr=\"Passed type arguments do not match type parameters [_T] of class 'list'\">int, int</warning>))]"},
|
||||
|
||||
{"Tuple[((int)), (((str)))]", "tuple[int, str]"},
|
||||
{"Tuple[<error descr=\"Invalid type argument\">((int, int))</error>, int]", "tuple[Any, int]"},
|
||||
|
||||
{"Set[((int))]", "set[int]"},
|
||||
// TODO: type: set[Any]
|
||||
{"Set[((<warning descr=\"Passed type arguments do not match type parameters [_T] of class 'set'\">int, int</warning>))]"},
|
||||
|
||||
{"Dict[((int)), (((str)))]", "dict[int, str]"},
|
||||
// TODO: type: dict[Any, Any]
|
||||
{"Dict[((<warning descr=\"Passed type arguments do not match type parameters [_KT, _VT] of class 'dict'\">int</warning>))]"},
|
||||
|
||||
{"Union[((int, (((str)))))]", "int | str"},
|
||||
{"Union[<error descr=\"Invalid type argument\">((int, int))</error>, <error descr=\"Invalid type argument\">(int, int)</error>]",
|
||||
"Any"},
|
||||
|
||||
{"Optional[((int))]", "int | None"},
|
||||
{"Optional[((<error descr=\"'Optional' must have exactly one argument\">int, int</error>))]", "Any"},
|
||||
|
||||
{"tuple[((int)), ((...))]", "tuple[int, ...]"},
|
||||
{"tuple[((<error descr=\"'...' is allowed only as the second of two arguments\">...</error>)), ((int))]", "Any"},
|
||||
{"tuple[<error descr=\"Invalid type argument\">(int,)</error>, ...]", "tuple[Any, ...]"},
|
||||
|
||||
{"C[(((int)))]", "C[int]"},
|
||||
{"C2[(((int), (str)))]", "C2[int, str]"},
|
||||
});
|
||||
}
|
||||
|
||||
public void testSubscriptionEmptyParentheses() {
|
||||
generateVariableTypeAssertions(new Object[][]{
|
||||
{"tuple[()]", "tuple[()]"},
|
||||
{"tuple[int, <error descr=\"Empty tuple is allowed only as a sole argument\">()</error>]", "Any"},
|
||||
|
||||
{"tuple[<error descr=\"Empty tuple is allowed only as a sole argument\">()</error>, int]", "Any"},
|
||||
{"tuple[<error descr=\"Empty tuple is allowed only as a sole argument\">()</error>, ...]", "tuple[Any, ...]"},
|
||||
{"tuple[<error descr=\"Empty tuple is allowed only as a sole argument\">()</error>, <error descr=\"Empty tuple is allowed only as a sole argument\">()</error>]",
|
||||
"Any"},
|
||||
|
||||
{"Tuple[()]", "tuple[()]"},
|
||||
{"Tuple[int, <error descr=\"Empty tuple is allowed only as a sole argument\">()</error>]", "Any"},
|
||||
|
||||
// TODO: type: list[Any]
|
||||
{"list[<warning descr=\"Passed type arguments do not match type parameters [_T] of class 'list'\">()</warning>]"},
|
||||
// TODO: type: set[Any]
|
||||
{"set[<warning descr=\"Passed type arguments do not match type parameters [_T] of class 'set'\">()</warning>]"},
|
||||
// TODO: type: dict[Any, Any]
|
||||
{"dict[<error descr=\"Invalid type argument\">()</error>, <error descr=\"Invalid type argument\">()</error>]"},
|
||||
|
||||
// TODO: type: list[Any]
|
||||
{"List[<warning descr=\"Passed type arguments do not match type parameters [_T] of class 'list'\">()</warning>]"},
|
||||
// TODO: type: set[Any]
|
||||
{"Set[<warning descr=\"Passed type arguments do not match type parameters [_T] of class 'set'\">()</warning>]"},
|
||||
// TODO: type: dict[Any, Any]
|
||||
{"Dict[<error descr=\"Invalid type argument\">()</error>, <error descr=\"Invalid type argument\">()</error>]"},
|
||||
|
||||
{"Union[()]", "Never"},
|
||||
|
||||
{"Optional[<error descr=\"'Optional' must have exactly one argument\">()</error>]", "Any"},
|
||||
|
||||
// TODO: type: C[Any]
|
||||
{"C[<warning descr=\"Passed type arguments do not match type parameters [T] of class 'C'\">()</warning>]"},
|
||||
});
|
||||
}
|
||||
|
||||
public void testSubscriptionTypeForm() {
|
||||
generateVariableTypeAssertions(new Object[][]{
|
||||
{"list[((int,))]", "list[int]"},
|
||||
|
||||
{"tuple[((int,))]", "tuple[int]"},
|
||||
{"tuple[<error descr=\"Invalid type argument\">(int,)</error>, int]", "tuple[Any, int]"},
|
||||
{"tuple[<error descr=\"Invalid type argument\">(int, str)</error>, <error descr=\"Invalid type argument\">(int, str)</error>]",
|
||||
"tuple[Any, Any]"},
|
||||
|
||||
{"set[((int,))]", "set[int]"},
|
||||
|
||||
{"dict[<error descr=\"Invalid type argument\">((int,))</error>, str]", "dict[Any, str]"},
|
||||
{"dict[int, <error descr=\"Invalid type argument\">(int, str)</error>]", "dict[int, Any]"},
|
||||
// TODO: type: dict[Any, Any]
|
||||
{"dict[((<warning descr=\"Passed type arguments do not match type parameters [_KT, _VT] of class 'dict'\">[int]</warning>))]"},
|
||||
|
||||
{"List[((int,))]", "list[int]"},
|
||||
|
||||
{"Tuple[((int,))]", "tuple[int]"},
|
||||
{"Tuple[<error descr=\"Invalid type argument\">(int,)</error>, int]", "tuple[Any, int]"},
|
||||
|
||||
{"Set[((int,))]", "set[int]"},
|
||||
|
||||
{"Dict[<error descr=\"Invalid type argument\">((int,))</error>, str]", "dict[Any, str]"},
|
||||
{"Dict[int, <error descr=\"Invalid type argument\">(int, str)</error>]", "dict[int, Any]"},
|
||||
{"Dict[((<error descr=\"Parameters to generic types must be types\">[int]</error>))]"}, // TODO: type: dict[Any, Any]
|
||||
|
||||
{"tuple[Tuple[int, str]]", "tuple[tuple[int, str]]"},
|
||||
{"Tuple[tuple[int], ...]", "tuple[tuple[int], ...]"},
|
||||
{"tuple[*Tuple[*tuple[int]]]", "tuple[int]"},
|
||||
{"tuple[int, *Tuple[*Tuple[int, str]], str]", "tuple[int, int, str, str]"},
|
||||
{"tuple[*tuple[int], *Tuple[int]]", "tuple[int, int]"},
|
||||
|
||||
{"Union[((int, int,))]", "int"},
|
||||
{"Union[<error descr=\"Invalid type argument\">(int, int,)</error>, int]", "int | Any"},
|
||||
|
||||
{"Optional[<error descr=\"'Optional' must have exactly one argument\">int, int</error>]", "Any"},
|
||||
{"Optional[(int,)]", "int | None"},
|
||||
|
||||
{"Callable[<error descr=\"'Callable' first parameter must be a parameter expression\">int</error>, int]", "Any"},
|
||||
{"Callable[[int], ((<error descr=\"Parameters to generic types must be types\">[int]</error>))]", "Callable[[int], Any]"},
|
||||
|
||||
// TODO: type: list[Any]
|
||||
{"list[((<warning descr=\"Passed type arguments do not match type parameters [_T] of class 'list'\">[int]</warning>))]"},
|
||||
{"List[((<error descr=\"Parameters to generic types must be types\">[int]</error>))]"},
|
||||
|
||||
{"C[((int,))]", "C[int]"},
|
||||
});
|
||||
}
|
||||
|
||||
public void testSubscriptionEllipsisTypeForm() {
|
||||
generateVariableTypeAssertions(new Object[][]{
|
||||
{"tuple[int, ...]", "tuple[int, ...]"},
|
||||
|
||||
{"tuple[<error descr=\"'...' is allowed only as the second of two arguments\">...</error>, int]", "Any"},
|
||||
{"tuple[int, int, <error descr=\"'...' is allowed only as the second of two arguments\">...</error>]", "Any"},
|
||||
{"tuple[int, <error descr=\"'...' is allowed only as the second of two arguments\">...</error>, int]", "Any"},
|
||||
{"tuple[<error descr=\"'...' is allowed only as the second of two arguments\">...</error>]", "Any"},
|
||||
{"tuple[<error descr=\"'...' is allowed only as the second of two arguments\">...</error>, ...]", "Any"},
|
||||
|
||||
{"set[<error descr=\"Invalid type argument\">...</error>]", "set[Any]"},
|
||||
{"list[<error descr=\"Invalid type argument\">...</error>]", "list[Any]"},
|
||||
{"dict[<error descr=\"Invalid type argument\">...</error>]"}, // TODO: type: "dict[Any, Any]"
|
||||
|
||||
{"Union[int, <error descr=\"Invalid type argument\">...</error>]", "int | Any"},
|
||||
{"Optional[<error descr=\"Invalid type argument\">...</error>]", "Any"},
|
||||
|
||||
{"tuple[*tuple[str], <error descr=\"'...' cannot be used with an unpacked 'TypeVarTuple' or tuple\">...</error>]", "Any"},
|
||||
{"tuple[*tuple[str, ...], <error descr=\"'...' cannot be used with an unpacked 'TypeVarTuple' or tuple\">...</error>]",
|
||||
"Any"},
|
||||
|
||||
{"Set[<error descr=\"Invalid type argument\">...</error>]", "set[Any]"},
|
||||
{"List[<error descr=\"Invalid type argument\">...</error>]", "list[Any]"},
|
||||
{"Dict[<error descr=\"Invalid type argument\">...</error>]"}, // TODO: type: "dict[Any, Any]"
|
||||
|
||||
{"Tuple[int, ...]", "tuple[int, ...]"},
|
||||
{"Tuple[<error descr=\"'...' is allowed only as the second of two arguments\">...</error>]", "Any"},
|
||||
|
||||
{"C[<error descr=\"Invalid type argument\">...</error>]", "C[Any]"},
|
||||
});
|
||||
}
|
||||
|
||||
// PY-84289
|
||||
public void testExponentialAnalysisTimeWhenMapLookupKeyEqualsVariableName() {
|
||||
@@ -3264,6 +3429,36 @@ public class PyTypeHintsInspectionTest extends PyInspectionTestCase {
|
||||
""");
|
||||
}
|
||||
|
||||
private void generateVariableTypeAssertions(@NotNull Object @NotNull [][] cases) {
|
||||
StringBuilder body = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < cases.length; i++) {
|
||||
Object[] c = cases[i];
|
||||
|
||||
String annotationText = (String)c[0];
|
||||
String variableName = "variable_" + (i + 1);
|
||||
|
||||
body.append(variableName).append(": ").append(annotationText).append("\n");
|
||||
|
||||
if (c.length > 1) {
|
||||
String expectedTypeText = (String)c[1];
|
||||
body.append("assert_type(").append(variableName).append(", ").append(expectedTypeText).append(")\n");
|
||||
}
|
||||
}
|
||||
|
||||
myFixture.enableInspections(PyAssertTypeInspection.class);
|
||||
|
||||
doTestByText(
|
||||
("""
|
||||
from typing import assert_type, Any, Never, Generic, List, Set, Dict, Tuple, Union, Optional, Callable
|
||||
|
||||
class C[T]: ...
|
||||
class C2[T1, T2]: ...
|
||||
"""
|
||||
+ body).trim()
|
||||
);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected Class<? extends PyInspection> getInspectionClass() {
|
||||
|
||||
Reference in New Issue
Block a user