PY-71002 PEP-696: Add an inspection that checks that non-default Type Parameters in declarations cannot follow the default ones

GitOrigin-RevId: 29c63024a67802457e031821e33925287f21a2ab
This commit is contained in:
Daniil Kalinin
2024-09-06 16:59:20 +02:00
committed by intellij-monorepo-bot
parent 0819e6d3d4
commit 82fca2f070
5 changed files with 77 additions and 2 deletions

View File

@@ -1119,6 +1119,7 @@ INSP.type.hints.some.type.variables.are.not.listed.in.generic=Some type variable
INSP.type.hints.illegal.literal.parameter='Literal' may be parameterized with literal ints, byte and unicode strings, bools, Enum values, None, other literal types, or type aliases to other literal types
INSP.type.hints.parameters.to.generic.must.all.be.type.variables=Parameters to 'Generic[...]' must all be type variables
INSP.type.hints.parameters.to.generic.must.all.be.unique=Parameters to 'Generic[...]' must all be unique
INSP.type.hints.non.default.type.vars.cannot.follow.defaults=Non-default TypeVars cannot follow ones with defaults
INSP.type.hints.illegal.callable.format='Callable' must be used as 'Callable[[arg, ...], result]'
INSP.type.hints.illegal.first.parameter='Callable' first parameter must be a parameter expression
INSP.type.hints.parameters.to.generic.types.must.be.types=Parameters to generic types must be types

View File

@@ -43,6 +43,21 @@ class PyNewStyleGenericSyntaxInspection : PyInspection() {
}
}
override fun visitPyTypeParameterList(node: PyTypeParameterList) {
val typeParameters = node.typeParameters
var lastIsDefault = false
for (typeParameter in typeParameters) {
if (typeParameter.defaultExpressionText != null) {
lastIsDefault = true
}
else if (lastIsDefault) {
registerProblem(typeParameter,
PyPsiBundle.message("INSP.type.hints.non.default.type.vars.cannot.follow.defaults"),
ProblemHighlightType.GENERIC_ERROR)
}
}
}
override fun visitPyTypeAliasStatement(node: PyTypeAliasStatement) {
val typeExpression = node.typeExpression
if (typeExpression != null) {

View File

@@ -38,6 +38,7 @@ import com.jetbrains.python.psi.*
import com.jetbrains.python.psi.impl.PyBuiltinCache
import com.jetbrains.python.psi.impl.PyEvaluator
import com.jetbrains.python.psi.impl.PyPsiUtils
import com.jetbrains.python.psi.impl.stubs.PyTypingAliasStubType
import com.jetbrains.python.psi.resolve.PyResolveContext
import com.jetbrains.python.psi.resolve.PyResolveUtil
import com.jetbrains.python.psi.types.*
@@ -760,7 +761,7 @@ class PyTypeHintsInspection : PyInspection() {
private fun checkGenericParameters(index: PyExpression) {
val parameters = (index as? PyTupleExpression)?.elements ?: arrayOf(index)
val genericParameters = mutableSetOf<PsiElement>()
var lastIsDefault = false
parameters.forEach {
if (it !is PyReferenceExpression && it !is PyStarExpression && it !is PySubscriptionExpression) {
registerProblem(it, PyPsiBundle.message("INSP.type.hints.parameters.to.generic.must.all.be.type.variables"),
@@ -780,11 +781,36 @@ class PyTypeHintsInspection : PyInspection() {
registerProblem(it, PyPsiBundle.message("INSP.type.hints.parameters.to.generic.must.all.be.type.variables"),
ProblemHighlightType.GENERIC_ERROR)
}
if (type is PyTypeParameterType) {
val typeVarDeclaration = type.declarationElement
if (hasDefault(typeVarDeclaration)) {
lastIsDefault = true
} else if (lastIsDefault) {
registerProblem(it,
PyPsiBundle.message("INSP.type.hints.non.default.type.vars.cannot.follow.defaults"),
ProblemHighlightType.GENERIC_ERROR)
}
}
}
}
}
}
private fun hasDefault(declarationElement: PyQualifiedNameOwner?): Boolean {
if (declarationElement is PyTargetExpression) {
val expression = PyTypingAliasStubType.getAssignedValueStubLike(declarationElement)
if (expression is PyCallExpression) {
expression.arguments.forEach {
if (it is PyKeywordArgument && it.keyword.equals("default") && it.valueExpression != null) {
return true
}
}
}
}
return false
}
private fun checkCallableParameters(index: PyExpression) {
if (index !is PyTupleExpression) {