mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
PY-50401 Impl checking name argument in ParamSpec
GitOrigin-RevId: 655026dceea03b1697e5d39ce897f2ef1f25948c
This commit is contained in:
committed by
intellij-monorepo-bot
parent
eb7114c66a
commit
a66dea2997
@@ -1127,7 +1127,9 @@ INSP.type.hints.type.specified.both.in.type.comment.and.annotation=Types specifi
|
||||
INSP.type.hints.typevar.expression.must.be.always.directly.assigned.to.variable=A 'TypeVar()' expression must always directly be assigned to a variable
|
||||
INSP.type.hints.type.variables.must.not.be.redefined=Type variables must not be redefined
|
||||
INSP.type.hints.typevar.expects.string.literal.as.first.argument='TypeVar()' expects a string literal as first argument
|
||||
INSP.type.hints.paramspec.expects.string.literal.as.first.argument='ParamSpec()' expects a string literal as first argument
|
||||
INSP.type.hints.argument.to.typevar.must.be.string.equal.to.variable.name=The argument to 'TypeVar()' must be a string equal to the variable name to which it is assigned
|
||||
INSP.type.hints.argument.to.paramspec.must.be.string.equal.to.variable.name=The argument to 'ParamSpec()' must be a string equal to the variable name to which it is assigned
|
||||
INSP.type.hints.bivariant.type.variables.are.not.supported=Bivariant type variables are not supported
|
||||
INSP.type.hints.typevar.constraints.cannot.be.combined.with.bound=Constraints cannot be combined with bound=\u2026
|
||||
INSP.type.hints.single.typevar.constraint.not.allowed=A single constraint is not allowed
|
||||
|
||||
+65
-29
@@ -3,6 +3,7 @@ package com.jetbrains.python.inspections
|
||||
|
||||
import com.intellij.codeInsight.controlflow.ControlFlowUtil
|
||||
import com.intellij.codeInspection.*
|
||||
import com.intellij.codeInspection.util.InspectionMessage
|
||||
import com.intellij.codeInspection.util.IntentionFamilyName
|
||||
import com.intellij.openapi.module.ModuleUtilCore
|
||||
import com.intellij.openapi.project.Project
|
||||
@@ -52,18 +53,29 @@ class PyTypeHintsInspection : PyInspection() {
|
||||
val calleeQName = callee?.let { PyResolveUtil.resolveImportedElementQNameLocally(it) } ?: emptyList()
|
||||
|
||||
if (QualifiedName.fromDottedString(PyTypingTypeProvider.TYPE_VAR) in calleeQName) {
|
||||
val target = (node.parent as? PyAssignmentStatement)?.targetsToValuesMapping?.firstOrNull { it.second == node }?.first
|
||||
val target = getTargetFromAssignment(node)
|
||||
|
||||
checkTypeVarPlacement(node, target)
|
||||
checkTypeVarArguments(node, target)
|
||||
checkTypeVarRedefinition(target)
|
||||
}
|
||||
|
||||
if (QualifiedName.fromDottedString(PyTypingTypeProvider.TYPING_PARAM_SPEC) in calleeQName) {
|
||||
val target = getTargetFromAssignment(node)
|
||||
checkParamSpecArguments(node, target)
|
||||
}
|
||||
|
||||
checkInstanceAndClassChecks(node)
|
||||
|
||||
checkParenthesesOnGenerics(node)
|
||||
}
|
||||
|
||||
private fun getTargetFromAssignment(node: PyCallExpression): PyExpression? {
|
||||
val assignmentStatement = node.parent as? PyAssignmentStatement
|
||||
if (assignmentStatement == null) return null
|
||||
return assignmentStatement.targetsToValuesMapping.firstOrNull { it.second == node }?.first
|
||||
}
|
||||
|
||||
override fun visitPyClass(node: PyClass) {
|
||||
super.visitPyClass(node)
|
||||
|
||||
@@ -210,41 +222,34 @@ class PyTypeHintsInspection : PyInspection() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkParamSpecArguments(call: PyCallExpression, target: PyExpression?) {
|
||||
processMatchedArgument(call) { name, argument ->
|
||||
if (name == "name") {
|
||||
checkNameIsTheSameAsTarget(argument, target,
|
||||
PyPsiBundle.message("INSP.type.hints.paramspec.expects.string.literal.as.first.argument"),
|
||||
PyPsiBundle.message("INSP.type.hints.argument.to.paramspec.must.be.string.equal.to.variable.name"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkTypeVarArguments(call: PyCallExpression, target: PyExpression?) {
|
||||
val resolveContext = PyResolveContext.defaultContext(myTypeEvalContext)
|
||||
var covariant = false
|
||||
var contravariant = false
|
||||
var bound: PyExpression? = null
|
||||
val constraints = mutableListOf<PyExpression?>()
|
||||
|
||||
call
|
||||
.multiMapArguments(resolveContext)
|
||||
.firstOrNull { it.unmappedArguments.isEmpty() && it.unmappedParameters.isEmpty() }
|
||||
?.let { mapping ->
|
||||
mapping.mappedParameters.entries.forEach {
|
||||
val name = it.value.name
|
||||
val argument = PyUtil.peelArgument(it.key)
|
||||
|
||||
when (name) {
|
||||
"name" ->
|
||||
if (argument !is PyStringLiteralExpression) {
|
||||
registerProblem(argument, PyPsiBundle.message("INSP.type.hints.typevar.expects.string.literal.as.first.argument"))
|
||||
}
|
||||
else {
|
||||
val targetName = target?.name
|
||||
if (targetName != null && targetName != argument.stringValue) {
|
||||
registerProblem(argument,
|
||||
PyPsiBundle.message("INSP.type.hints.argument.to.typevar.must.be.string.equal.to.variable.name"),
|
||||
ReplaceWithTargetNameQuickFix(targetName))
|
||||
}
|
||||
}
|
||||
"covariant" -> covariant = PyEvaluator.evaluateAsBoolean(argument, false)
|
||||
"contravariant" -> contravariant = PyEvaluator.evaluateAsBoolean(argument, false)
|
||||
"bound" -> bound = argument
|
||||
"constraints" -> constraints.add(argument)
|
||||
}
|
||||
}
|
||||
processMatchedArgument(call) { name, argument ->
|
||||
when (name) {
|
||||
"name" ->
|
||||
checkNameIsTheSameAsTarget(argument, target,
|
||||
PyPsiBundle.message("INSP.type.hints.typevar.expects.string.literal.as.first.argument"),
|
||||
PyPsiBundle.message("INSP.type.hints.argument.to.typevar.must.be.string.equal.to.variable.name"))
|
||||
"covariant" -> covariant = PyEvaluator.evaluateAsBoolean(argument, false)
|
||||
"contravariant" -> contravariant = PyEvaluator.evaluateAsBoolean(argument, false)
|
||||
"bound" -> bound = argument
|
||||
"constraints" -> constraints.add(argument)
|
||||
}
|
||||
}
|
||||
|
||||
if (covariant && contravariant) {
|
||||
registerProblem(call, PyPsiBundle.message("INSP.type.hints.bivariant.type.variables.are.not.supported"),
|
||||
@@ -272,6 +277,37 @@ class PyTypeHintsInspection : PyInspection() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkNameIsTheSameAsTarget(argument: PyExpression?, target: PyExpression?,
|
||||
@InspectionMessage notStringLiteralMessage: String,
|
||||
@InspectionMessage notEqualMessage: String) {
|
||||
if (argument !is PyStringLiteralExpression) {
|
||||
registerProblem(argument, notStringLiteralMessage)
|
||||
}
|
||||
else {
|
||||
val targetName = target?.name
|
||||
if (targetName != null && targetName != argument.stringValue) {
|
||||
registerProblem(argument,
|
||||
notEqualMessage,
|
||||
ReplaceWithTargetNameQuickFix(targetName))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun processMatchedArgument(call: PyCallExpression,
|
||||
processor: (name: String?, argument: PyExpression?) -> Unit) {
|
||||
val resolveContext = PyResolveContext.defaultContext(myTypeEvalContext)
|
||||
call
|
||||
.multiMapArguments(resolveContext)
|
||||
.firstOrNull { it.unmappedArguments.isEmpty() && it.unmappedParameters.isEmpty() }
|
||||
?.let { mapping ->
|
||||
mapping.mappedParameters.entries.forEach {
|
||||
val name = it.value.name
|
||||
val argument = PyUtil.peelArgument(it.key)
|
||||
processor(name, argument)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkInstanceAndClassChecks(call: PyCallExpression) {
|
||||
if (call.isCalleeText(PyNames.ISINSTANCE, PyNames.ISSUBCLASS)) {
|
||||
val base = call.arguments.getOrNull(1) ?: return
|
||||
|
||||
@@ -1111,6 +1111,23 @@ public class PyTypeHintsInspectionTest extends PyInspectionTestCase {
|
||||
});
|
||||
}
|
||||
|
||||
// PY-50401
|
||||
public void testParamSpecNameAsLiteral() {
|
||||
doTestByText("from typing import ParamSpec\n" +
|
||||
"\n" +
|
||||
"name = 'T0'\n" +
|
||||
"T0 = ParamSpec(<warning descr=\"'ParamSpec()' expects a string literal as first argument\">name</warning>)\n" +
|
||||
"T1 = ParamSpec('T1')");
|
||||
}
|
||||
|
||||
// PY-50401
|
||||
public void testParamSpecNameAndTargetNameEquality() {
|
||||
doTestByText("from typing import ParamSpec\n" +
|
||||
"\n" +
|
||||
"T0 = ParamSpec(<warning descr=\"The argument to 'ParamSpec()' must be a string equal to the variable name to which it is assigned\">'T1'</warning>)\n" +
|
||||
"T1 = ParamSpec('T1')");
|
||||
}
|
||||
|
||||
// PY-50930
|
||||
public void testNoInspectionInCallableParameterParamSpecFromTypingExpressions() {
|
||||
doTestByText("from typing import Callable, TypeVar\n" +
|
||||
|
||||
Reference in New Issue
Block a user