PY-53105 Support TypeVarTuple

GitOrigin-RevId: f8160e9d802b09991daa710ed9a20f3e30d455da
This commit is contained in:
andrey.matveev
2023-10-20 13:38:04 +00:00
committed by intellij-monorepo-bot
parent 4d18b83ba9
commit a75c569022
30 changed files with 2996 additions and 188 deletions
+5 -5
View File
@@ -195,11 +195,11 @@ if sys.version_info >= (3, 11):
LiteralString: _SpecialForm
class TypeVarTuple:
__name__: str
def __init__(self, name: str) -> None: ...
def __iter__(self) -> Any: ...
def __typing_subst__(self, arg: Never) -> Never: ...
def __typing_prepare_subst__(self, alias: Incomplete, args: Incomplete) -> Incomplete: ...
__name__: str
def __init__(self, name: str) -> None: ...
def __iter__(self) -> Any: ...
def __typing_subst__(self, arg: Never) -> Never: ...
def __typing_prepare_subst__(self, alias: Incomplete, args: Incomplete) -> Incomplete: ...
if sys.version_info >= (3, 10):
class ParamSpecArgs:
@@ -173,6 +173,7 @@ public interface PyCallExpression extends PyCallSiteExpression {
@NotNull private final List<PyCallableParameter> myImplicitParameters;
@NotNull private final Map<PyExpression, PyCallableParameter> myMappedParameters;
@NotNull private final List<PyCallableParameter> myUnmappedParameters;
@NotNull private final List<PyCallableParameter> myUnmappedContainerParameters;
@NotNull private final List<PyExpression> myUnmappedArguments;
@NotNull private final List<PyCallableParameter> myParametersMappedToVariadicPositionalArguments;
@NotNull private final List<PyCallableParameter> myParametersMappedToVariadicKeywordArguments;
@@ -183,6 +184,7 @@ public interface PyCallExpression extends PyCallSiteExpression {
@NotNull List<PyCallableParameter> implicitParameters,
@NotNull Map<PyExpression, PyCallableParameter> mappedParameters,
@NotNull List<PyCallableParameter> unmappedParameters,
@NotNull List<PyCallableParameter> unmappedContainerParameters,
@NotNull List<PyExpression> unmappedArguments,
@NotNull List<PyCallableParameter> parametersMappedToVariadicPositionalArguments,
@NotNull List<PyCallableParameter> parametersMappedToVariadicKeywordArguments,
@@ -192,6 +194,7 @@ public interface PyCallExpression extends PyCallSiteExpression {
myImplicitParameters = implicitParameters;
myMappedParameters = mappedParameters;
myUnmappedParameters = unmappedParameters;
myUnmappedContainerParameters = unmappedContainerParameters;
myUnmappedArguments = unmappedArguments;
myParametersMappedToVariadicPositionalArguments = parametersMappedToVariadicPositionalArguments;
myParametersMappedToVariadicKeywordArguments = parametersMappedToVariadicKeywordArguments;
@@ -208,6 +211,7 @@ public interface PyCallExpression extends PyCallSiteExpression {
Collections.emptyList(),
Collections.emptyList(),
Collections.emptyList(),
Collections.emptyList(),
Collections.emptyMap());
}
@@ -236,6 +240,11 @@ public interface PyCallExpression extends PyCallSiteExpression {
return myUnmappedParameters;
}
@NotNull
public List<PyCallableParameter> getUnmappedContainerParameters() {
return myUnmappedContainerParameters;
}
@NotNull
public List<PyExpression> getUnmappedArguments() {
return myUnmappedArguments;
@@ -475,6 +475,9 @@ INSP.NAME.pytest.unpassed.fixture=Fixture is not requested by test functions
INSP.use.fixture.without.declaration.in.test.function=Fixture ''{0}'' is not requested by test functions or '@pytest.mark.usefixtures' marker
QFIX.add.fixture.to.test.function.parameters.list=Add fixture to test function parameters
# PyReplaceStarByUnpackQuickFix
QFIX.replace.star.by.unpack=Replace by typing_extensions.Unpack
# Actions and associated commands
ACT.CMD.use.import=Use an imported module
@@ -1055,6 +1058,8 @@ INSP.type.checker.type.does.not.have.expected.attribute=Type ''{0}'' doesn''t ha
INSP.type.checker.only.concrete.class.can.be.used.where.matched.protocol.expected=Only a concrete class can be used where ''{0}'' (matched generic type ''{1}'') protocol is expected
INSP.type.checker.only.concrete.class.can.be.used.where.protocol.expected=Only a concrete class can be used where ''{0}'' protocol is expected
INSP.type.checker.expected.matched.type.got.type.instead=Expected type ''{0}'' (matched generic type ''{1}''), got ''{2}'' instead
INSP.type.checker.no.expected.type.for=The type is not expected
INSP.type.checker.no.arguments.for.expected.type=No arguments for types (''{0}'')
INSP.type.checker.unexpected.types.prefix=Unexpected type(s):
INSP.type.checker.expected.types.prefix=Possible type(s):
INSP.type.checker.unmapped.argument.with.paramspec=Unexpected argument (from ParamSpec ''{0}'')
@@ -1094,8 +1099,10 @@ 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.typevar.tuple.expects.string.literal.as.first.argument='TypeVarTuple()' 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.typevar.tuple.must.be.string.equal.to.variable.name=The argument to 'TypeVarTuple()' 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
@@ -1114,6 +1121,7 @@ INSP.type.hints.parameters.to.generic.must.all.be.unique=Parameters to 'Generic[
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
INSP.type.hints.parameters.to.generic.types.cannot.contain.more.than.one.unpacking=Parameters to generic cannot contain more than one unpacking
INSP.type.hints.type.comment.cannot.be.matched.with.unpacked.variables=Type comment cannot be matched with unpacked variables
INSP.type.hints.type.signature.has.too.few.arguments=Type signature has too few arguments
INSP.type.hints.type.signature.has.too.many.arguments=Type signature has too many arguments
@@ -1127,6 +1135,7 @@ INSP.type.hints.self.use.in.staticmethod=Cannot use 'Self' in staticmethod
INSP.type.hints.self.use.outside.class=Cannot use 'Self' outside class
INSP.type.hints.self.use.for.self.parameter.with.self.annotation=Cannot use 'Self' if 'self' parameter is not 'Self' annotated
INSP.type.hints.self.use.for.cls.parameter.with.self.annotation=Cannot use 'Self' if 'cls' parameter is not 'Self' annotated
INSP.type.hints.type.var.tuple.must.always.be.unpacked=TypeVarTuple must always be unpacked
QFIX.remove.function.annotations=Remove function annotations
QFIX.replace.with.target.name=Replace with the target name
QFIX.remove.generic.parameters=Remove generic parameters
@@ -1139,6 +1148,7 @@ QFIX.remove.annotation=Remove the annotation
QFIX.replace.with.type.name=Replace with the type name
QFIX.replace.with.old.union.style=Replace with an old-style Union
QFIX.add.from.future.import.annotations=Add 'from __future__ import annotations'
QFIX.NAME.unpack.type.var.tuple=Unpack the TypeVarTuple
# PyInspectionsSuppressor
INSP.python.suppressor.suppress.for.function=Suppress for a function
@@ -84,6 +84,7 @@ public class PyTypingTypeProvider extends PyTypeProviderWithCustomContext<PyTypi
private static final String TUPLE = "typing.Tuple";
public static final String CLASS_VAR = "typing.ClassVar";
public static final String TYPE_VAR = "typing.TypeVar";
public static final String TYPE_VAR_TUPLE = "typing.TypeVarTuple";
public static final String TYPING_PARAM_SPEC = "typing.ParamSpec";
public static final String TYPING_EXTENSIONS_PARAM_SPEC = "typing_extensions.ParamSpec";
private static final String CHAIN_MAP = "typing.ChainMap";
@@ -168,6 +169,7 @@ public class PyTypingTypeProvider extends PyTypeProviderWithCustomContext<PyTypi
.add(PyKnownDecoratorUtil.KnownDecorator.TYPING_OVERLOAD.name())
.add(ANY)
.add(TYPE_VAR)
.add(TYPE_VAR_TUPLE)
.add(GENERIC)
.add(TYPING_PARAM_SPEC)
.add(TYPING_EXTENSIONS_PARAM_SPEC)
@@ -396,7 +398,8 @@ public class PyTypingTypeProvider extends PyTypeProviderWithCustomContext<PyTypi
}
final PyClass initializedClass = PyUtil.turnConstructorIntoClass(function);
if (initializedClass != null && TYPE_VAR.equals(initializedClass.getQualifiedName())) {
if (initializedClass != null && (TYPE_VAR.equals(initializedClass.getQualifiedName()) ||
TYPE_VAR_TUPLE.equals(initializedClass.getQualifiedName()))) {
// `typing.TypeVar` call should be assigned to a target and hence should be processed by [getReferenceType]
// but the corresponding type is also returned here to suppress type checker on `T = TypeVar("T")` assignment.
return Ref.create(getGenericTypeFromTypeVar(callSite, context));
@@ -681,7 +684,7 @@ public class PyTypingTypeProvider extends PyTypeProviderWithCustomContext<PyTypi
.map(Ref::deref)
.flatMap(type -> {
PyTypeChecker.Generics typeParams = PyTypeChecker.collectGenerics(type, context.myContext);
return StreamEx.<PyType>of(typeParams.getTypeVars()).append(StreamEx.of(typeParams.getParamSpecs()));
return StreamEx.<PyType>of(typeParams.getTypeVars()).append(typeParams.getTypeVarTuples()).append(StreamEx.of(typeParams.getParamSpecs()));
})
.select(PyTypeParameterType.class)
.distinct()
@@ -847,6 +850,10 @@ public class PyTypingTypeProvider extends PyTypeProviderWithCustomContext<PyTypi
if (genericType != null) {
return Ref.create(anchorTypeParameter(typeHint, genericType, context));
}
final PyType genericVariadicType = getGenericVariadicType(resolved, context.getTypeContext());
if (genericVariadicType != null) {
return Ref.create(anchorTypeParameter(typeHint, genericVariadicType, context));
}
final PyType paramSpecType = getParamSpecType(resolved, context);
if (paramSpecType != null) {
return Ref.create(anchorTypeParameter(typeHint, paramSpecType, context));
@@ -1460,13 +1467,18 @@ public class PyTypingTypeProvider extends PyTypeProviderWithCustomContext<PyTypi
final PyExpression callee = assignedCall.getCallee();
if (callee != null) {
final Collection<String> calleeQNames = resolveToQualifiedNames(callee, context.getTypeContext());
if (calleeQNames.contains(TYPE_VAR)) {
if (calleeQNames.contains(TYPE_VAR) || calleeQNames.contains(TYPE_VAR_TUPLE)) {
final PyExpression[] arguments = assignedCall.getArguments();
if (arguments.length > 0) {
final PyExpression firstArgument = arguments[0];
if (firstArgument instanceof PyStringLiteralExpression) {
final String name = ((PyStringLiteralExpression)firstArgument).getStringValue();
return new PyTypeVarTypeImpl(name, getGenericTypeBound(arguments, context));
if (calleeQNames.contains(TYPE_VAR_TUPLE)) {
return new PyGenericVariadicType(name);
}
else {
return new PyTypeVarTypeImpl(name, getGenericTypeBound(arguments, context));
}
}
}
}
@@ -1546,6 +1558,26 @@ public class PyTypingTypeProvider extends PyTypeProviderWithCustomContext<PyTypi
}
}
@Nullable
public static PyType getGenericVariadicType(@NotNull PsiElement element, @NotNull TypeEvalContext context) {
if (!(element instanceof PyStarExpression)) return null;
var expression = ((PyStarExpression)element).getExpression();
if (!(expression instanceof PyReferenceExpression) && !(expression instanceof PySubscriptionExpression)) return null;
var typeRef = getType(expression, context);
if (typeRef == null) return null;
var expressionType = typeRef.get();
if (expressionType instanceof final PyTupleType tupleType) {
return new PyGenericVariadicType("", tupleType.isHomogeneous(), tupleType.getElementTypes(), null);
}
if (expressionType instanceof PyGenericVariadicType) {
return expressionType;
}
return null;
}
@Nullable
private static PyParamSpecType getParamSpecType(@NotNull PsiElement element, @NotNull Context context) {
if (!(element instanceof PyCallExpression assignedCall)) return null;
@@ -1689,6 +1721,15 @@ public class PyTypingTypeProvider extends PyTypeProviderWithCustomContext<PyTypi
}
}
}
else if (expression instanceof PyStarExpression) {
var expressionExpression = ((PyStarExpression)expression).getExpression();
if (expressionExpression != null) {
var type = context.getType(expressionExpression);
if (type instanceof PyGenericVariadicType) {
return tryResolvingWithAliases(expressionExpression, context);
}
}
}
return !elements.isEmpty() ? elements : Collections.singletonList(Pair.create(null, expression));
}
@@ -113,7 +113,7 @@ public class PyDocStringTypeProvider extends PyTypeProviderBase {
}
}
for (PyGenericType typeVar : typeParameters.getAllTypeVars()) {
for (PyGenericType typeVar : typeParameters.getAllTypeVarsAndTypeVarTuples()) {
typeVar.setScopeOwner(findScopeOwner.apply(typeVar));
}
}
@@ -6,6 +6,7 @@ import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.Trinity;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiElementVisitor;
@@ -296,7 +297,7 @@ public class PyTypeCheckerInspection extends PyInspection {
final List<UnmatchedParameter> unmatchedParameters = new ArrayList<>();
final var receiver = callSite.getReceiver(callableType.getCallable());
final var substitutions = PyTypeChecker.unifyReceiverWithParamSpecs(receiver, myTypeEvalContext);
final var substitutions = PyTypeChecker.unifyReceiver(receiver, myTypeEvalContext);
final var mappedParameters = mapping.getMappedParameters();
final var regularMappedParameters = getRegularMappedParameters(mappedParameters);
@@ -354,6 +355,18 @@ public class PyTypeCheckerInspection extends PyInspection {
}
}
for (var unmappedContainer: mapping.getUnmappedContainerParameters()) {
PyType containerType = unmappedContainer.getArgumentType(myTypeEvalContext);
if (!(containerType instanceof PyGenericVariadicType)) continue;
var matchingResult = new ArrayList<Trinity<PyType, PyType, Boolean>>();
var matchContext = PyTypeChecker.getMatchContext(myTypeEvalContext, substitutions);
if (!PyTypeChecker.matchElementTypes(List.of(containerType), Collections.emptyList(), matchContext, true, false,
false, matchingResult)) {
PyType substContainerType = PyTypeChecker.substitute(containerType, substitutions, myTypeEvalContext);
result.add(new AnalyzeArgumentResult(callSite, containerType, substContainerType, PyNotMatchedType.INSTANCE, false));
}
}
return new AnalyzeCalleeResults(callableType, callableType.getCallable(), result, unmatchedArguments, unmatchedParameters);
}
@@ -404,13 +417,55 @@ public class PyTypeCheckerInspection extends PyInspection {
@NotNull List<PyExpression> arguments,
@NotNull PyTypeChecker.GenericSubstitutions substitutions) {
final PyType expected = container.getArgumentType(myTypeEvalContext);
if (expected instanceof PyGenericVariadicType) {
var matchContext = PyTypeChecker.getMatchContext(myTypeEvalContext, substitutions);
var expectedElementTypes = List.of(expected);
var actualElementTypes = ContainerUtil.map(arguments, it -> myTypeEvalContext.getType(it));
var matchingResult = new ArrayList<Trinity<PyType, PyType, Boolean>>();
PyTypeChecker.matchElementTypes(expectedElementTypes, actualElementTypes, matchContext, true, false,
false, matchingResult);
var result = new ArrayList<AnalyzeArgumentResult>();
for (int i = 0; i < actualElementTypes.size(); ++i) {
if (i >= matchingResult.size()) break;
var res = matchingResult.get(i);
boolean matched = res.third;
PyType actualType = res.first;
PyType expectedType = res.second;
PyType substitutedExpectedType = substituteGenerics(expectedType, substitutions);
result.add(new AnalyzeArgumentResult(arguments.get(i), expectedType, substitutedExpectedType, actualType, matched));
}
if (matchingResult.size() > actualElementTypes.size()) {
PsiElement lastArgNextSibling = arguments.get(arguments.size() - 1).getNextSibling();
var lastMatingRes = matchingResult.get(matchingResult.size() - 1);
boolean matched = lastMatingRes.third;
PyType actualType = lastMatingRes.first;
PyType expectedType = lastMatingRes.second;
PyType substitutedExpectedType = substituteGenerics(expectedType, substitutions);
result.add(new AnalyzeArgumentResult(lastArgNextSibling, expectedType, substitutedExpectedType, actualType, matched));
}
return result;
}
final PyType expectedWithSubstitutions = substituteGenerics(expected, substitutions);
// For an expected type with generics we have to match all the actual types against it in order to do proper generic unification
if (PyTypeChecker.hasGenerics(expected, myTypeEvalContext)) {
final PyType actual = PyUnionType.union(ContainerUtil.map(arguments, myTypeEvalContext::getType));
final boolean matched = matchParameterAndArgument(expected, actual, null, substitutions);
return ContainerUtil.map(arguments, argument ->
new AnalyzeArgumentResult(argument, expected, expectedWithSubstitutions, actual, matched));
if (container.isPositionalContainer()) {
PyType actual = PyGenericVariadicType.fromElementTypes(ContainerUtil.map(arguments, myTypeEvalContext::getType));
boolean matched = matchParameterAndArgument(PyGenericVariadicType.homogeneous(expected), actual, null, substitutions);
return ContainerUtil.map(arguments, argument ->
new AnalyzeArgumentResult(argument, expected, expectedWithSubstitutions, actual, matched));
}
else {
PyType actual = PyUnionType.union(ContainerUtil.map(arguments, myTypeEvalContext::getType));
boolean matched = matchParameterAndArgument(expected, actual, null, substitutions);
return ContainerUtil.map(arguments, argument ->
new AnalyzeArgumentResult(argument, expected, expectedWithSubstitutions, actual, matched));
}
}
else {
return ContainerUtil.map(
@@ -542,7 +597,7 @@ public class PyTypeCheckerInspection extends PyInspection {
static class AnalyzeArgumentResult {
@NotNull
private final PyExpression myArgument;
private final PsiElement myArgument;
@Nullable
private final PyType myExpectedType;
@@ -555,7 +610,7 @@ public class PyTypeCheckerInspection extends PyInspection {
private final boolean myIsMatched;
AnalyzeArgumentResult(@NotNull PyExpression argument,
AnalyzeArgumentResult(@NotNull PsiElement argument,
@Nullable PyType expectedType,
@Nullable PyType expectedTypeAfterSubstitution,
@Nullable PyType actualType,
@@ -568,7 +623,7 @@ public class PyTypeCheckerInspection extends PyInspection {
}
@NotNull
public PyExpression getArgument() {
public PsiElement getArgument() {
return myArgument;
}
@@ -31,10 +31,7 @@ import com.jetbrains.python.codeInsight.typing.PyProtocolsKt;
import com.jetbrains.python.documentation.PythonDocumentationProvider;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.PyPsiUtils;
import com.jetbrains.python.psi.types.PyClassLikeType;
import com.jetbrains.python.psi.types.PyStructuralType;
import com.jetbrains.python.psi.types.PyType;
import com.jetbrains.python.psi.types.TypeEvalContext;
import com.jetbrains.python.psi.types.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -112,6 +109,14 @@ final class PyTypeCheckerInspectionProblemRegistrar {
final PyType actualType = argumentResult.getActualType();
final PyType expectedType = argumentResult.getExpectedType();
if (expectedType instanceof PyNotMatchedType) {
return PyPsiBundle.message("INSP.type.checker.no.expected.type.for");
}
if (expectedType instanceof PyGenericVariadicType && actualType instanceof PyNotMatchedType) {
return PyPsiBundle.message("INSP.type.checker.no.arguments.for.expected.type",
((PyGenericVariadicType)expectedType).getElementTypesToStr());
}
assert actualType != null; // see PyTypeCheckerInspection.Visitor.analyzeArgument()
assert expectedType != null; // see PyTypeCheckerInspection.Visitor.analyzeArgument()
@@ -27,6 +27,7 @@ import com.jetbrains.python.codeInsight.typeHints.PyTypeHintFile
import com.jetbrains.python.codeInsight.typing.PyTypingTypeProvider
import com.jetbrains.python.codeInsight.typing.PyTypingTypeProvider.isBitwiseOrUnionAvailable
import com.jetbrains.python.documentation.PythonDocumentationProvider
import com.jetbrains.python.inspections.quickfix.PyUnpackTypeVarTupleQuickFix
import com.jetbrains.python.psi.*
import com.jetbrains.python.psi.impl.PyBuiltinCache
import com.jetbrains.python.psi.impl.PyEvaluator
@@ -61,8 +62,11 @@ class PyTypeHintsInspection : PyInspection() {
}
if (QualifiedName.fromDottedString(PyTypingTypeProvider.TYPING_PARAM_SPEC) in calleeQName) {
val target = getTargetFromAssignment(node)
checkParamSpecArguments(node, target)
checkParamSpecArguments(node, getTargetFromAssignment(node))
}
if (QualifiedName.fromDottedString(PyTypingTypeProvider.TYPE_VAR_TUPLE) in calleeQName) {
checkTypeVarTupleArguments(node, getTargetFromAssignment(node))
}
checkInstanceAndClassChecks(node)
@@ -107,6 +111,17 @@ class PyTypeHintsInspection : PyInspection() {
}
}
private fun checkTypeVarTupleUnpacked(node: PyReferenceExpression) {
if (PsiTreeUtil.getParentOfType(node, PyStarExpression::class.java) != null) return
val subscriptionExpr = PsiTreeUtil.getParentOfType(node, PySubscriptionExpression::class.java)
if (subscriptionExpr != null && subscriptionExpr.operand is PyQualifiedExpression &&
(subscriptionExpr.operand as PyQualifiedExpression).asQualifiedName()?.endsWith("Unpack") == true) {
return
}
holder?.registerProblem(node, PyPsiBundle.message("INSP.type.hints.type.var.tuple.must.always.be.unpacked"),
PyUnpackTypeVarTupleQuickFix())
}
override fun visitPyReferenceExpression(node: PyReferenceExpression) {
super.visitPyReferenceExpression(node)
@@ -114,6 +129,10 @@ class PyTypeHintsInspection : PyInspection() {
return
}
if (myTypeEvalContext.getType(node) is PyGenericVariadicType) {
checkTypeVarTupleUnpacked(node)
}
if (node.referencedName == PyNames.CANONICAL_SELF) {
val typeName = myTypeEvalContext.getType(node)?.name
if (typeName != null && typeName != PyNames.CANONICAL_SELF) {
@@ -277,6 +296,16 @@ class PyTypeHintsInspection : PyInspection() {
}
}
private fun checkTypeVarTupleArguments(call: PyCallExpression, target: PyExpression?) {
processMatchedArgument(call) { name, argument ->
if (name == "name") {
checkNameIsTheSameAsTarget(argument, target,
PyPsiBundle.message("INSP.type.hints.typevar.tuple.expects.string.literal.as.first.argument"),
PyPsiBundle.message("INSP.type.hints.argument.to.typevar.tuple.must.be.string.equal.to.variable.name"))
}
}
}
private fun checkTypeVarArguments(call: PyCallExpression, target: PyExpression?) {
var covariant = false
var contravariant = false
@@ -708,7 +737,7 @@ class PyTypeHintsInspection : PyInspection() {
val genericParameters = mutableSetOf<PsiElement>()
parameters.forEach {
if (it !is PyReferenceExpression) {
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"),
ProblemHighlightType.GENERIC_ERROR)
}
@@ -717,7 +746,7 @@ class PyTypeHintsInspection : PyInspection() {
if (type != null) {
if (type is PyGenericType || isParamSpecOrConcatenate(it, myTypeEvalContext)) {
if (!genericParameters.addAll(multiFollowAssignmentsChain(it))) {
if (it is PyReferenceExpression && !genericParameters.addAll(multiFollowAssignmentsChain(it))) {
registerProblem(it, PyPsiBundle.message("INSP.type.hints.parameters.to.generic.must.all.be.unique"),
ProblemHighlightType.GENERIC_ERROR)
}
@@ -775,6 +804,7 @@ class PyTypeHintsInspection : PyInspection() {
private fun checkTypingMemberParameters(index: PyExpression, isCallable: Boolean) {
val parameters = if (index is PyTupleExpression) index.elements else arrayOf(index)
var alreadyHaveUnpacking = false
parameters
.asSequence()
.drop(if (isCallable) 1 else 0)
@@ -790,6 +820,15 @@ class PyTypeHintsInspection : PyInspection() {
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)
}
else {
alreadyHaveUnpacking = true
}
}
}
}
@@ -0,0 +1,32 @@
package com.jetbrains.python.inspections.quickfix;
import com.intellij.codeInspection.LocalQuickFix;
import com.intellij.codeInspection.ProblemDescriptor;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.jetbrains.python.PyPsiBundle;
import com.jetbrains.python.psi.PyExpression;
import com.jetbrains.python.psi.PyStarExpression;
import org.jetbrains.annotations.NotNull;
public class PyReplaceStarByUnpackQuickFix implements LocalQuickFix {
@NotNull
@Override
public String getFamilyName() {
return PyPsiBundle.message("QFIX.replace.star.by.unpack");
}
@Override
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
PsiElement element = descriptor.getPsiElement();
if (!(element instanceof PyStarExpression)) return;
PyStarExpression starExpression = (PyStarExpression)element;
PyExpression expression = starExpression.getExpression();
if (expression == null) return;
PsiFile file = starExpression.getContainingFile();
PyUnpackTypeVarTupleQuickFix.replaceToTypingExtensionsUnpack(starExpression, expression, file, project);
}
}
@@ -0,0 +1,55 @@
package com.jetbrains.python.inspections.quickfix;
import com.intellij.codeInspection.LocalQuickFix;
import com.intellij.codeInspection.ProblemDescriptor;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.jetbrains.python.PyPsiBundle;
import com.jetbrains.python.codeInsight.imports.AddImportHelper;
import com.jetbrains.python.psi.LanguageLevel;
import com.jetbrains.python.psi.PyElementGenerator;
import com.jetbrains.python.psi.PyExpression;
import com.jetbrains.python.psi.PyExpressionStatement;
import org.jetbrains.annotations.NotNull;
public class PyUnpackTypeVarTupleQuickFix implements LocalQuickFix {
@Override
@NotNull
public String getFamilyName() {
return PyPsiBundle.message("QFIX.NAME.unpack.type.var.tuple");
}
public static void replaceToTypingExtensionsUnpack(@NotNull PsiElement elementTpReplace, @NotNull PsiElement elementInUnpack,
@NotNull PsiFile file, @NotNull Project project) {
AddImportHelper.addOrUpdateFromImportStatement(file, "typing_extensions", "Unpack", null,
AddImportHelper.ImportPriority.FUTURE, null);
String unpacked = "Unpack[" + elementInUnpack.getText() + "]";
PyElementGenerator elementGenerator = PyElementGenerator.getInstance(project);
PyExpressionStatement expressionStatement =
elementGenerator.createFromText(LanguageLevel.forElement(elementInUnpack), PyExpressionStatement.class, unpacked);
PyExpression newElement = expressionStatement.getExpression();
elementTpReplace.replace(newElement);
}
@Override
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
PsiElement psiElement = descriptor.getPsiElement();
if (psiElement == null) return;
var languageLevel = LanguageLevel.forElement(psiElement);
PyElementGenerator elementGenerator = PyElementGenerator.getInstance(project);
if (languageLevel.isAtLeast(LanguageLevel.PYTHON311)) {
String starred = "*" + psiElement.getText();
PyExpressionStatement expressionStatement = elementGenerator.createFromText(languageLevel, PyExpressionStatement.class, starred);
PyExpression newElement = expressionStatement.getExpression();
psiElement.replace(newElement);
}
else {
replaceToTypingExtensionsUnpack(psiElement, psiElement, psiElement.getContainingFile(), project);
}
}
}
@@ -813,6 +813,7 @@ public final class PyCallExpressionHelper {
implicitParameters,
mappingResults.getMappedParameters(),
mappingResults.getUnmappedParameters(),
mappingResults.getUnmappedContainerParameters(),
mappingResults.getUnmappedArguments(),
mappingResults.getParametersMappedToVariadicPositionalArguments(),
mappingResults.getParametersMappedToVariadicKeywordArguments(),
@@ -880,6 +881,7 @@ public final class PyCallExpressionHelper {
implicitParameters,
mappingResults.getMappedParameters(),
mappingResults.getUnmappedParameters(),
mappingResults.getUnmappedContainerParameters(),
mappingResults.getUnmappedArguments(),
mappingResults.getParametersMappedToVariadicPositionalArguments(),
mappingResults.getParametersMappedToVariadicKeywordArguments(),
@@ -1011,6 +1013,7 @@ public final class PyCallExpressionHelper {
boolean mappedVariadicArgumentsToParameters = false;
final Map<PyExpression, PyCallableParameter> mappedParameters = new LinkedHashMap<>();
final List<PyCallableParameter> unmappedParameters = new ArrayList<>();
final List<PyCallableParameter> unmappedContainerParameters = new ArrayList<>();
final List<PyExpression> unmappedArguments = new ArrayList<>();
final List<PyCallableParameter> parametersMappedToVariadicKeywordArguments = new ArrayList<>();
final List<PyCallableParameter> parametersMappedToVariadicPositionalArguments = new ArrayList<>();
@@ -1039,6 +1042,9 @@ public final class PyCallExpressionHelper {
if (variadicPositionalArguments.size() == 1) {
mappedParameters.put(variadicPositionalArguments.get(0), parameter);
}
if (variadicPositionalArguments.size() != 1 && allPositionalArguments.size() == 0) {
unmappedContainerParameters.add(parameter);
}
allPositionalArguments.clear();
variadicPositionalArguments.clear();
}
@@ -1158,7 +1164,7 @@ public final class PyCallExpressionHelper {
unmappedArguments.addAll(variadicPositionalArguments);
unmappedArguments.addAll(variadicKeywordArguments);
return new ArgumentMappingResults(mappedParameters, unmappedParameters, unmappedArguments,
return new ArgumentMappingResults(mappedParameters, unmappedParameters, unmappedContainerParameters, unmappedArguments,
parametersMappedToVariadicPositionalArguments, parametersMappedToVariadicKeywordArguments,
tupleMappedParameters);
}
@@ -1259,6 +1265,7 @@ public final class PyCallExpressionHelper {
public static class ArgumentMappingResults {
@NotNull private final Map<PyExpression, PyCallableParameter> myMappedParameters;
@NotNull private final List<PyCallableParameter> myUnmappedParameters;
@NotNull private final List<PyCallableParameter> myUnmappedContainerParameters;
@NotNull private final List<PyExpression> myUnmappedArguments;
@NotNull private final List<PyCallableParameter> myParametersMappedToVariadicPositionalArguments;
@NotNull private final List<PyCallableParameter> myParametersMappedToVariadicKeywordArguments;
@@ -1266,12 +1273,14 @@ public final class PyCallExpressionHelper {
ArgumentMappingResults(@NotNull Map<PyExpression, PyCallableParameter> mappedParameters,
@NotNull List<PyCallableParameter> unmappedParameters,
@NotNull List<PyCallableParameter> unmappedContainerParameters,
@NotNull List<PyExpression> unmappedArguments,
@NotNull List<PyCallableParameter> parametersMappedToVariadicPositionalArguments,
@NotNull List<PyCallableParameter> parametersMappedToVariadicKeywordArguments,
@NotNull Map<PyExpression, PyCallableParameter> mappedTupleParameters) {
myMappedParameters = mappedParameters;
myUnmappedParameters = unmappedParameters;
myUnmappedContainerParameters = unmappedContainerParameters;
myUnmappedArguments = unmappedArguments;
myParametersMappedToVariadicPositionalArguments = parametersMappedToVariadicPositionalArguments;
myParametersMappedToVariadicKeywordArguments = parametersMappedToVariadicKeywordArguments;
@@ -1307,6 +1316,11 @@ public final class PyCallExpressionHelper {
public Map<PyExpression, PyCallableParameter> getMappedTupleParameters() {
return myMappedTupleParameters;
}
@NotNull
public List<PyCallableParameter> getUnmappedContainerParameters() {
return myUnmappedContainerParameters;
}
}
private static class TupleMappingResults {
@@ -114,7 +114,10 @@ public class PyTypingAliasStubType extends CustomTargetExpressionStubType<PyTypi
final PyCallExpression call = as(expression, PyCallExpression.class);
if (call != null) {
final PyReferenceExpression callee = as(call.getCallee(), PyReferenceExpression.class);
return callee != null && ("TypeVar".equals(callee.getReferencedName()) || "ParamSpec".equals(callee.getReferencedName()));
return callee != null &&
("TypeVar".equals(callee.getReferencedName()) || "TypeVarTuple".equals(callee.getReferencedName()) ||
"ParamSpec".equals(callee.getReferencedName()));
}
final PyStringLiteralExpression pyString = as(expression, PyStringLiteralExpression.class);
@@ -23,7 +23,7 @@ import java.util.Objects;
*/
@Deprecated
public class PyGenericType implements PyTypeVarType {
@NotNull private final String myName;
@NotNull protected final String myName;
@Nullable private final PyType myBound;
private final boolean myIsDefinition;
@Nullable private final PyTargetExpression myTargetExpression;
@@ -37,7 +37,7 @@ public class PyGenericType implements PyTypeVarType {
this(name, bound, isDefinition, null);
}
private PyGenericType(@NotNull String name, @Nullable PyType bound, boolean isDefinition, @Nullable PyTargetExpression target) {
public PyGenericType(@NotNull String name, @Nullable PyType bound, boolean isDefinition, @Nullable PyTargetExpression target) {
this(name, bound, isDefinition, target, null);
}
@@ -164,6 +164,16 @@ public class PyGenericType implements PyTypeVarType {
myScopeOwner = scopeOwner;
}
@NotNull
public PyGenericType withAlias(@Nullable PyTargetExpression alias) {
return new PyGenericType(getName(), getBound(), isDefinition(), alias);
}
@NotNull
public PyGenericType toggleIsDefinition() {
return new PyGenericType(getName(), getBound(), !isDefinition(), getDeclarationElement());
}
@NotNull
@Override
public PyGenericType toInstance() {
@@ -0,0 +1,183 @@
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.jetbrains.python.psi.types;
import com.jetbrains.python.psi.PyQualifiedNameOwner;
import com.jetbrains.python.psi.PyTargetExpression;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
public class PyGenericVariadicType extends PyGenericType {
private final boolean myIsHomogeneous;
@Nullable
private final List<PyType> myElementTypes;
public PyGenericVariadicType(@NotNull String name) {
this(name, false, null, null);
}
public PyGenericVariadicType(@NotNull String name, boolean isHomogeneous, @Nullable List<PyType> elementTypes,
@Nullable PyQualifiedNameOwner scopeOwner) {
super(name, null, false, null);
myElementTypes = elementTypes;
myIsHomogeneous = isHomogeneous;
}
public PyGenericVariadicType(@NotNull String name, boolean isDefinition, @Nullable PyTargetExpression target,
boolean isHomogeneous, @Nullable List<PyType> elementTypes, @Nullable PyQualifiedNameOwner scopeOwner) {
super(name, null, isDefinition, target);
myElementTypes = elementTypes;
myIsHomogeneous = isHomogeneous;
if (scopeOwner != null) {
setScopeOwner(scopeOwner);
}
}
@NotNull
@Override
public PyGenericType withScopeOwner(@Nullable PyQualifiedNameOwner scopeOwner) {
return new PyGenericVariadicType(myName, isDefinition(), getDeclarationElement(), myIsHomogeneous, myElementTypes, scopeOwner);
}
@NotNull
@Override
public PyGenericType withTargetExpression(@Nullable PyTargetExpression targetExpression) {
return new PyGenericVariadicType(myName, isDefinition(), targetExpression, myIsHomogeneous, myElementTypes, getScopeOwner());
}
@NotNull
@Override
public PyGenericVariadicType withAlias(@Nullable PyTargetExpression alias) {
return new PyGenericVariadicType(myName, isDefinition(), alias, myIsHomogeneous, myElementTypes, getScopeOwner());
}
@NotNull
public PyGenericVariadicType withDifferentName() {
return new PyGenericVariadicType(myName + "142", isDefinition(), null, myIsHomogeneous, myElementTypes, getScopeOwner());
}
@NotNull
public PyGenericVariadicType withElementTypes(boolean isHomogeneous, @NotNull List<PyType> elementTypes) {
var resultElementTypes = new ArrayList<>(elementTypes);
for (int i = 0; i < elementTypes.size(); ++i) {
var elementType = elementTypes.get(i);
if (equals(elementType)) {
resultElementTypes.set(i, ((PyGenericVariadicType)elementType).withDifferentName());
}
}
return new PyGenericVariadicType(myName, isDefinition(), getDeclarationElement(), isHomogeneous, resultElementTypes, getScopeOwner());
}
@NotNull
@Override
public PyGenericVariadicType toggleIsDefinition() {
return new PyGenericVariadicType(myName, !isDefinition(), getDeclarationElement(), myIsHomogeneous, myElementTypes, getScopeOwner());
}
@NotNull
public static PyGenericVariadicType fromElementTypes(@NotNull List<PyType> elementTypes) {
return new PyGenericVariadicType("", false, elementTypes, null);
}
@NotNull
public static PyGenericVariadicType homogeneous(@Nullable PyType type) {
var elementTypes = new ArrayList<PyType>();
elementTypes.add(type);
return new PyGenericVariadicType("", true, elementTypes, null);
}
@NotNull
@Override
public String getName() {
if (myElementTypes == null) {
return "*" + myName;
}
else {
return "*" + getElementTypesToStr();
}
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final PyGenericVariadicType type = (PyGenericVariadicType)o;
return myName.equals(type.myName) && isDefinition() == type.isDefinition() && Objects.equals(getScopeOwner(), type.getScopeOwner()) &&
Objects.equals(myElementTypes, type.myElementTypes);
}
@Override
public int hashCode() {
int res = myName.hashCode();
if (myElementTypes == null) return res;
return res + myElementTypes.hashCode();
}
@NotNull
public String getElementTypesToStr() {
if (myElementTypes == null) return "";
StringBuilder res = new StringBuilder();
res.append("(");
for (int i = 0; i < myElementTypes.size(); ++i) {
var type = myElementTypes.get(i);
var name = type != null ? type.getName() : "Any";
res.append(name);
if (i < myElementTypes.size() - 1) {
res.append(",");
}
}
if (isHomogeneous()) {
res.append(", ...");
}
res.append(")");
return res.toString();
}
public boolean isHomogeneous() {
return myIsHomogeneous;
}
public boolean isMapped(@NotNull Map<PyGenericVariadicType, PyGenericVariadicType> typeVarTuples) {
if (myIsHomogeneous) return false;
if (myElementTypes != null && typeVarTuples.containsKey(this)) {
assert false;
}
if (myElementTypes != null) return true;
return typeVarTuples.containsKey(this) && typeVarTuples.get(this).myElementTypes != null;
}
@Nullable
public List<PyType> getMappedElementTypes(@NotNull Map<PyGenericVariadicType, PyGenericVariadicType> typeVarTuples) {
if (myIsHomogeneous) return null;
if (myElementTypes != null && typeVarTuples.containsKey(this)) {
assert false;
}
if (myElementTypes != null) return myElementTypes;
if (!typeVarTuples.containsKey(this)) return null;
var mapped = typeVarTuples.get(this);
if (mapped == null) return null;
if (mapped.isHomogeneous()) {
return List.of(mapped);
}
if (mapped.myElementTypes == null && !mapped.toString().equals(toString())) {
return List.of(mapped);
}
return mapped.myElementTypes;
}
@Nullable
public PyType getIteratedItemType() {
if (myElementTypes == null) return null;
return PyUnionType.union(myElementTypes);
}
}
@@ -0,0 +1,50 @@
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.jetbrains.python.psi.types;
import com.intellij.psi.PsiElement;
import com.intellij.util.ArrayUtilRt;
import com.intellij.util.ProcessingContext;
import com.jetbrains.python.psi.AccessDirection;
import com.jetbrains.python.psi.PyExpression;
import com.jetbrains.python.psi.resolve.PyResolveContext;
import com.jetbrains.python.psi.resolve.RatedResolveResult;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
public class PyNotMatchedType implements PyType {
public static final PyNotMatchedType INSTANCE = new PyNotMatchedType();
protected PyNotMatchedType() {
}
@Override
@Nullable
public List<? extends RatedResolveResult> resolveMember(@NotNull final String name,
@Nullable PyExpression location,
@NotNull AccessDirection direction,
@NotNull PyResolveContext resolveContext) {
return null;
}
@Override
public Object[] getCompletionVariants(String completionPrefix, PsiElement location, ProcessingContext context) {
return ArrayUtilRt.EMPTY_OBJECT_ARRAY;
}
@Override
public String getName() {
return "no type";
}
@Override
public boolean isBuiltin() {
return false;
}
@Override
public void assertValid(String message) {
}
}
File diff suppressed because it is too large Load Diff
@@ -161,7 +161,8 @@ public abstract class CompatibilityVisitor extends PyAnnotator {
level.isOlderThan(LanguageLevel.PYTHON311) &&
registerForLanguageLevel(level),
PyPsiBundle.message("INSP.compatibility.feature.support.starred.expressions.in.subscriptions"),
node);
node,
new PyReplaceStarByUnpackQuickFix());
}
}
if (node.getParent() instanceof PySubscriptionExpression || node.getParent() instanceof PySliceItem) {
@@ -169,7 +170,8 @@ public abstract class CompatibilityVisitor extends PyAnnotator {
level.isOlderThan(LanguageLevel.PYTHON311) &&
registerForLanguageLevel(level),
PyPsiBundle.message("INSP.compatibility.feature.support.starred.expressions.in.subscriptions"),
node);
node,
new PyReplaceStarByUnpackQuickFix());
}
}
@@ -0,0 +1,10 @@
from __future__ import annotations
from typing import Generic, TypeVar, TypeVarTuple
DType = TypeVar('DType')
Shape = TypeVarTuple('Shape')
class Array(Generic[DType, <error descr="Python version 3.10 does not support starred expressions in subscriptions">*Sh<caret>ape</error>]):
pass
@@ -0,0 +1,11 @@
from __future__ import annotations
from typing_extensions import Unpack
from typing import Generic, TypeVar, TypeVarTuple
DType = TypeVar('DType')
Shape = TypeVarTuple('Shape')
class Array(Generic[DType, Unpack[Shape]]):
pass
@@ -0,0 +1,8 @@
from typing import TypeVarTuple
from typing import Generic
Shape = TypeVarTuple("Shape")
class Array(Generic[<error descr="Python version 3.10 does not support starred expressions in subscriptions">*Sh<caret>ape</error>]):
...
@@ -0,0 +1,10 @@
from typing_extensions import Unpack
from typing import TypeVarTuple
from typing import Generic
Shape = TypeVarTuple("Shape")
class Array(Generic[Unp<caret>ack[Shape]]):
...
@@ -0,0 +1,10 @@
from typing import TypeVarTuple
from typing import Generic
from typing import Tuple
Shape = TypeVarTuple("Shape")
class Array(Generic[*Shape]):
def __init__(self, shape: Tuple[Sh<caret>ape]) -> None:
self.shape = shape
@@ -0,0 +1,10 @@
from typing import TypeVarTuple
from typing import Generic
from typing import Tuple
Shape = TypeVarTuple("Shape")
class Array(Generic[*Shape]):
def __init__(self, shape: Tuple[*Shape]) -> None:
self.shape = shape
@@ -0,0 +1,12 @@
from typing_extensions import Unpack
from typing import TypeVarTuple
from typing import Generic
from typing import Tuple
Shape = TypeVarTuple("Shape")
class Array(Generic[Unpack[Shape]]):
def __init__(self, shape: Tuple[Sh<caret>ape]) -> None:
self.shape = shape
@@ -0,0 +1,12 @@
from typing_extensions import Unpack
from typing import TypeVarTuple
from typing import Generic
from typing import Tuple
Shape = TypeVarTuple("Shape")
class Array(Generic[Unpack[Shape]]):
def __init__(self, shape: Tuple[Un<caret>pack[Shape]]) -> None:
self.shape = shape
File diff suppressed because it is too large Load Diff
@@ -1256,6 +1256,404 @@ public class Py3TypeCheckerInspectionTest extends PyInspectionTestCase {
""");
}
// PY-53105
public void testVariadicGenericInFunction() {
doTestByText("from typing import Tuple, TypeVarTuple, TypeVar\n" +
"\n" +
"T = TypeVar('T')\n" +
"Ts = TypeVarTuple('Ts')\n" +
"\n" +
"\n" +
"def foo(x: T, y: Tuple[*Ts]):\n" +
" pass\n" +
"\n" +
"\n" +
"foo(10, (1, '1', [1]))\n");
}
// PY-53105
public void testVariadicGenericArgumentByCallableInFunction() {
doTestByText("from typing import Callable, TypeVarTuple, Tuple\n" +
"\n" +
"Ts = TypeVarTuple('Ts')\n" +
"\n" +
"\n" +
"def foo(a: int, f: Callable[[*Ts], None], args: Tuple[*Ts]) -> None: ...\n" +
"def bar(a: int, b: str) -> None: ...\n" +
"\n" +
"\n" +
"foo(1, bar, args=(0, 'foo'))\n" +
"\n" +
"foo(1, bar, <warning descr=\"Expected type 'tuple[int, str]' (matched generic type 'tuple[*Ts]'), got 'tuple[LiteralString, int]' instead\">args=('foo', 0)</warning>)\n");
}
// PY-53105
public void testVariadicGenericCheckCallableInFunction() {
doTestByText("from typing import TypeVar, TypeVarTuple, Callable, Tuple\n" +
"\n" +
"T = TypeVar('T')\n" +
"Ts = TypeVarTuple('Ts')\n" +
"\n" +
"\n" +
"def foo(f: Callable[[int, *Ts, T], Tuple[T, *Ts]]) -> None: ...\n" +
"\n" +
"\n" +
"def ok1(a: int, b: str, c: bool, d: list[int]) -> Tuple[list[int], str, bool]: ...\n" +
"def ok2(a: int, b: str) -> Tuple[str]: ...\n" +
"\n" +
"\n" +
"foo(ok1)\n" +
"foo(ok2)\n" +
"\n" +
"\n" +
"def err1(a: int, b: str, c: bool, d: list[int]) -> Tuple[list[int], str, str]: ...\n" +
"def err2(a: int, b: str) -> Tuple[str, str]: ...\n" +
"\n" +
"\n" +
"foo(<warning descr=\"Expected type '(int, str, bool, list[int]) -> tuple[list[int], str, bool]' (matched generic type '(int, *Ts, T) -> tuple[T, *Ts]'), got '(a: int, b: str, c: bool, d: list[int]) -> tuple[list[int], str, str]' instead\">err1</warning>)\n" +
"foo(<warning descr=\"Expected type '(int, str) -> tuple[str]' (matched generic type '(int, *Ts, T) -> tuple[T, *Ts]'), got '(a: int, b: str) -> tuple[str, str]' instead\">err2</warning>)\n");
}
// PY-53105
public void testVariadicGenericTwoTsInFunction() {
doTestByText("from typing import TypeVarTuple, Generic\n" +
"\n" +
"Ts = TypeVarTuple('Ts')\n" +
"\n" +
"\n" +
"class Array(Generic[*Ts]):\n" +
" ...\n" +
"\n" +
"\n" +
"def foo(x: Array[*Ts], y: Array[*Ts]) -> Array[*Ts]:\n" +
" ...\n" +
"\n" +
"\n" +
"x: Array[int]\n" +
"y: Array[str]\n" +
"z: Array[int, str]\n" +
"\n" +
"foo(x, x)\n" +
"\n" +
"foo(x, <warning descr=\"Expected type 'Array[int]' (matched generic type 'Array[*Ts]'), got 'Array[str]' instead\">y</warning>)\n" +
"foo(x, <warning descr=\"Expected type 'Array[int]' (matched generic type 'Array[*Ts]'), got 'Array[int, str]' instead\">z</warning>)\n");
}
// PY-53105
public void testVariadicGenericUnboundTupleInFunction() {
doTestByText("from typing import Generic, TypeVarTuple, Tuple, Any\n" +
"\n" +
"Ts = TypeVarTuple('Ts')\n" +
"\n" +
"\n" +
"class Array(Generic[*Ts]):\n" +
" def __init__(self, shape: Tuple[*Ts]):\n" +
" ...\n" +
"\n" +
"\n" +
"def foo(x: Array[int, *Tuple[Any, ...], str]) -> None:\n" +
" ...\n" +
"\n" +
"\n" +
"x: Array[int, list[str], bool, str]\n" +
"foo(x)\n" +
"\n" +
"y: Array[int, str]\n" +
"foo(y)\n" +
"\n" +
"z: Array[int]\n" +
"foo(<warning descr=\"Expected type 'Array[int, *(Any, ...), str]' (matched generic type 'Array[int, *(Any, ...), str]'), got 'Array[int]' instead\">z</warning>)\n" +
"\n" +
"t: Array[str]\n" +
"foo(<warning descr=\"Expected type 'Array[int, *(Any, ...), str]' (matched generic type 'Array[int, *(Any, ...), str]'), got 'Array[str]' instead\">t</warning>)\n" +
"\n" +
"k: Array[int, int]\n" +
"foo(<warning descr=\"Expected type 'Array[int, *(Any, ...), str]' (matched generic type 'Array[int, *(Any, ...), str]'), got 'Array[int, int]' instead\">k</warning>)\n");
}
// PY-53105
public void testVariadicGenericStarArgsNamedParameters() {
doTestByText("from typing import Tuple, TypeVarTuple\n" +
"\n" +
"Ts = TypeVarTuple('Ts')\n" +
"\n" +
"\n" +
"def foo(a: str, *args: *Tuple[*Ts, int], b: str, c: bool) -> None: ...\n" +
"\n" +
"\n" +
"foo('', 1, True, [1], 42, b='', c=True)\n" +
"foo('', 42, b='', c=True)\n" +
"foo('', True, 42, c=True, b='')\n" +
"\n" +
"<warning descr=\"No arguments for types ('(*Ts,int)')\">foo('', b='', c=True)</warning>\n" +
"foo('', <warning descr=\"Expected type 'int', got 'LiteralString' instead\">''</warning>, b='', c=True)\n" +
"foo('', '', <warning descr=\"Expected type 'int', got 'list[bool]' instead\">[False]</warning>, b='', c=True)\n" +
"foo('', '', '', '', <warning descr=\"Expected type 'int', got 'float' instead\">1.1</warning>, b='', c=True)\n");
}
// PY-53105
public void testVariadicGenericStarArgsTupleAndUnpackedTuple() {
doTestByText("from typing import Tuple, TypeVarTuple\n" +
"\n" +
"Ts = TypeVarTuple('Ts')\n" +
"\n" +
"\n" +
"def foo(a: Tuple[*Ts], *args: *Tuple[str, *Ts, int], b: str) -> None: ...\n" +
"\n" +
"\n" +
"foo(('', 1), '', '', 1, 1, b='')\n" +
"foo((1,1), '', 1, 1, 1, b='')\n" +
"foo(('',), '', '', 1, b='')\n" +
"foo((), '', 1, b='')\n" +
"foo(([], {}), '', [], {}, 1, b='')\n" +
"\n" +
"<warning descr=\"No arguments for types ('(str,*Ts,int)')\">foo(('', 1), b='')</warning>\n" +
"foo(('', 1), '', '', <warning descr=\"Expected type 'int', got 'LiteralString' instead\">''</warning>, 1, b='')\n" +
"foo((1,1), '', 1, 1<warning descr=\"No arguments for types ('(int)')\">,</warning> b='')\n" +
"foo(('',), '', <warning descr=\"Expected type 'LiteralString', got 'int' instead\">1</warning>, 1, b='')\n" +
"x: Any\n" +
"foo((), '', 42, <warning descr=\"The type is not expected\">x</warning>, b='')\n" +
"foo(([], {}), '', [], {}<warning descr=\"No arguments for types ('(int)')\">,</warning> b='')\n");
}
// PY-53105
public void testVariadicGenericStarArgsOfVariadicGeneric() {
doTestByText("from typing import Tuple, TypeVarTuple\n" +
"\n" +
"Ts = TypeVarTuple('Ts')\n" +
"\n" +
"\n" +
"def foo(*args: Tuple[*Ts]): ...\n" +
"\n" +
"\n" +
"foo((0,), (1,))\n" +
"foo(<warning descr=\"Expected type 'tuple[*Ts]' (matched generic type 'tuple[*Ts]'), got '*((int),(int, int))' instead\">(0,)</warning>, <warning descr=\"Expected type 'tuple[*Ts]' (matched generic type 'tuple[*Ts]'), got '*((int),(int, int))' instead\">(1, 2)</warning>)\n" +
"foo(<warning descr=\"Expected type 'tuple[*Ts]' (matched generic type 'tuple[*Ts]'), got '*((int),(LiteralString))' instead\">(0,)</warning>, <warning descr=\"Expected type 'tuple[*Ts]' (matched generic type 'tuple[*Ts]'), got '*((int),(LiteralString))' instead\">('1',)</warning>)\n");
}
// PY-53105
public void testVariadicGenericStarArgsOfVariadicGenericPrefixSuffix() {
doTestByText("from typing import Tuple, TypeVarTuple\n" +
"\n" +
"Ts = TypeVarTuple('Ts')\n" +
"\n" +
"\n" +
"def foo(*args: Tuple[int, *Ts, str]): ...\n" +
"\n" +
"\n" +
"foo((0,''), (1,''))\n" +
"foo((0, [1], {}, ''), (1, [1], {}, ''))\n" +
"\n" +
"foo(<warning descr=\"Expected type 'tuple[int, *Ts, str]' (matched generic type 'tuple[int, *Ts, str]'), got '*((int, list, TypedDict, LiteralString),(int, list, LiteralString))' instead\">(0, [1], {}, '')</warning>, <warning descr=\"Expected type 'tuple[int, *Ts, str]' (matched generic type 'tuple[int, *Ts, str]'), got '*((int, list, TypedDict, LiteralString),(int, list, LiteralString))' instead\">(1, [1], '')</warning>)\n" +
"foo(<warning descr=\"Expected type 'tuple[int, *Ts, str]' (matched generic type 'tuple[int, *Ts, str]'), got '*((int, list, TypedDict, LiteralString),(int, list, TypedDict, LiteralString))' instead\">(0, [1], {}, '')</warning>, <warning descr=\"Expected type 'tuple[int, *Ts, str]' (matched generic type 'tuple[int, *Ts, str]'), got '*((int, list, TypedDict, LiteralString),(int, list, TypedDict, LiteralString))' instead\">(1, ['1'], {}, '')</warning>)\n" +
"foo(<warning descr=\"Expected type 'tuple[int, *Ts, str]' (matched generic type 'tuple[int, *Ts, str]'), got '*((int, list, TypedDict, LiteralString),(int, LiteralString))' instead\">(0, [1], {}, '')</warning>, <warning descr=\"Expected type 'tuple[int, *Ts, str]' (matched generic type 'tuple[int, *Ts, str]'), got '*((int, list, TypedDict, LiteralString),(int, LiteralString))' instead\">(1, '')</warning>)\n" +
"foo(<warning descr=\"Expected type 'tuple[int, *Ts, str]' (matched generic type 'tuple[int, *Ts, str]'), got '*((int, list, TypedDict, LiteralString),(int, list, TypedDict, int))' instead\">(0, [1], {}, '')</warning>, <warning descr=\"Expected type 'tuple[int, *Ts, str]' (matched generic type 'tuple[int, *Ts, str]'), got '*((int, list, TypedDict, LiteralString),(int, list, TypedDict, int))' instead\">(1, [1], {}, 1)</warning>)\n");
}
// PY-53105
public void testVariadicGenericStarArgsPrefixSuffix() {
doTestByText("from typing import Tuple, TypeVarTuple\n" +
"\n" +
"Ts = TypeVarTuple('Ts')\n" +
"\n" +
"\n" +
"def foo(*args: *Tuple[int, *Tuple[str, ...], str]) -> None: ...\n" +
"\n" +
"\n" +
"foo(1, '')\n" +
"foo(1, '', '')\n" +
"foo(1, '', '', '', '', '', '')\n" +
"\n" +
"foo(1, <warning descr=\"Expected type 'str', got 'int' instead\">1</warning>)\n" +
"foo(<warning descr=\"Expected type 'int', got 'LiteralString' instead\">''</warning>, '')\n" +
"foo(1, '', '', <warning descr=\"Expected type 'str', got 'int' instead\">1</warning>, '', '', '')\n");
}
// PY-53105
public void testVariadicGenericStarArgsUnboundTuple() {
doTestByText("from typing import Tuple\n" +
"\n" +
"\n" +
"def foo(*args: *Tuple[int, ...]) -> None: ...\n" +
"\n" +
"\n" +
"foo()\n" +
"foo(1)\n" +
"foo(1, 2, 3)\n" +
"\n" +
"foo(<warning descr=\"Expected type 'int', got 'LiteralString' instead\">''</warning>)\n" +
"foo(1, <warning descr=\"Expected type 'int', got 'LiteralString' instead\">''</warning>)\n");
}
// PY-53105
public void testVariadicGenericMatchWithHomogeneousGenericVariadic() {
doTestByText("""
from __future__ import annotations
from typing import TypeVarTuple
from typing import Generic
from typing import Any
Shape = TypeVarTuple("Shape")
class Array(Generic[*Shape]):
...
y: Array[*tuple[Any, ...]] = Array()
def expect_variadic_array(x: Array[int, *Shape]) -> None:
print(x)
expect_variadic_array(y)
""");
}
// PY-53105
public void testVariadicGenericMatchWithHomogeneousGenericVariadicAndOtherTypes() {
doTestByText("""
from __future__ import annotations
from typing import TypeVarTuple
from typing import Generic
from typing import Any
Shape = TypeVarTuple("Shape")
class Array(Generic[*Shape]):
...
y: Array[*tuple[Any, ...], int, str] = Array()
def expect_variadic_array(x: Array[int, *Shape]) -> None:
print(x)
expect_variadic_array(y)
""");
}
// PY-53105
public void testVariadicGenericCheckTypeAliasesMissingParameter() {
doTestByText("""
from typing import TypeVarTuple
from typing import TypeVar
from typing import Generic
from typing import NewType
Shape = TypeVarTuple("Shape")
Height = NewType("Height", int)
Width = NewType("Width", int)
DType = TypeVar("DType")
class Array(Generic[DType, *Shape]):
...
Float32Array = Array[float, *Shape]
def takes_float_array_of_specific_shape(arr: Float32Array[Height, Width]): ...
y: Float32Array[Height] = Array()
takes_float_array_of_specific_shape(<warning descr=\"Expected type 'Array[float, Height, Width]', got 'Array[float, Height]' instead\">y</warning>)
""");
}
// PY-53105
public void testVariadicGenericCheckTypeAliasesRedundantParameter() {
doTestByText("""
from typing import TypeVarTuple
from typing import TypeVar
from typing import Generic
from typing import NewType
Shape = TypeVarTuple("Shape")
Height = NewType("Height", int)
Width = NewType("Width", int)
DType = TypeVar("DType")
class Array(Generic[DType, *Shape]):
...
Float32Array = Array[float, *Shape]
def takes_float_array_of_specific_shape(arr: Float32Array[Height]): ...
y: Float32Array[Height, Width] = Array()
takes_float_array_of_specific_shape(<warning descr=\"Expected type 'Array[float, Height]', got 'Array[float, Height, Width]' instead\">y</warning>)
""");
}
// PY-53105
public void testVariadicGenericEmpty() {
doTestByText("""
from typing import TypeVarTuple
Ts = TypeVarTuple("Ts")
IntTuple = tuple[int, *Ts]
c: IntTuple[()] = <warning descr="Expected type 'tuple[int]', got 'tuple[int, LiteralString]' instead">(1, "")</warning>
""");
}
//// PY-53611
//public void testTypedDictRequiredNotRequiredKeys() {
// runWithLanguageLevel(LanguageLevel.getLatest(),
// () -> doTestByText("""
// from typing import TypedDict
// from typing_extensions import Required, NotRequired
// class WithTotalFalse(TypedDict, total=False):
// x: Required[int]
// class WithTotalTrue(TypedDict, total=True):
// x: NotRequired[int]
// class WithoutTotal(TypedDict):
// x: NotRequired[int]
// class WithoutTotalWithExplicitRequired(TypedDict):
// x: Required[int]
// y: NotRequired[int]
// AlternativeSyntax = TypedDict("AlternativeSyntax", {'x': NotRequired[int]})
// with_total_false: WithTotalFalse = <warning descr="TypedDict 'WithTotalFalse' has missing key: 'x'">{}</warning>
// with_total_true: WithTotalTrue = {}
// without_total: WithoutTotal = {}
// without_total_with_explicit_required: WithoutTotalWithExplicitRequired = <warning descr="TypedDict 'WithoutTotalWithExplicitRequired' has missing key: 'x'">{}</warning>
// alternative_syntax: AlternativeSyntax = {}
// """));
//}
//// PY-53611
//public void testTypedDictRequiredNotRequiredEquivalence() {
// runWithLanguageLevel(LanguageLevel.getLatest(), this::doTest);
//}
//
//// PY-53611
//public void testTypedDictRequiredNotRequiredMixedWithAnnotated() {
// runWithLanguageLevel(LanguageLevel.getLatest(),
// () -> doTestByText("""
// from typing_extensions import TypedDict, Required, NotRequired, Annotated
// class A(TypedDict):
// x: Annotated[NotRequired[int], 'Some constraint']
// def f(a: A):
// pass
// f({})
// class B(TypedDict, total=False):
// x: Annotated[Required[int], 'Some constraint']
// def g(b: B):
// pass
// g(<warning descr="TypedDict 'B' has missing key: 'x'">{}</warning>)
// """));
//}
//
//// PY-53611
//public void testTypingRequiredTypeSpecificationsMultiFile() {
// doMultiFileTest();
//}
// PY-56785
public void testTypingSelfNoInspectionReturnSelfMethod() {
doTestByText("""
@@ -1349,6 +1349,51 @@ public class PyTypeHintsInspectionTest extends PyInspectionTestCase {
""");
}
// PY-53105
public void testNoVariadicGenericErrorInClassDeclaration() {
doTestByText("""
from typing import Generic, TypeVarTuple
Shape = TypeVarTuple('Shape')
class Array(Generic[*Shape]):
...
""");
}
// PY-53105
public void testTypeVarTupleNameAsLiteral() {
doTestByText("from typing import TypeVarTuple\n" +
"\n" +
"name = 'Ts'\n" +
"Ts = TypeVarTuple(<warning descr=\"'TypeVarTuple()' expects a string literal as first argument\">name</warning>)\n" +
"Ts1 = TypeVarTuple('Ts1')");
}
// PY-53105
public void testTypeVarTupleNameAndTargetNameEquality() {
doTestByText("from typing import TypeVarTuple\n" +
"\n" +
"Ts = TypeVarTuple(<warning descr=\"The argument to 'TypeVarTuple()' must be a string equal to the variable name to which it is assigned\">'T'</warning>)\n" +
"Ts1 = TypeVarTuple('Ts1')");
}
// PY-53105
public void testTypeVarTupleMoreThanOneUnpacking() {
doTestByText("""
from typing import TypeVarTuple
from typing import Generic
Ts1 = TypeVarTuple("Ts1")
Ts2 = TypeVarTuple("Ts2")
class Array(Generic[*Ts1, <error descr="Parameters to generic cannot contain more than one unpacking">*Ts2</error>]):
...
""");
}
@NotNull
@Override
protected Class<? extends PyInspection> getInspectionClass() {
@@ -0,0 +1,32 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.python.quickFixes
import com.jetbrains.python.PyPsiBundle
import com.jetbrains.python.PyQuickFixTestCase
import com.jetbrains.python.inspections.PyCompatibilityInspection
import com.jetbrains.python.psi.LanguageLevel
class PyReplaceStarByUnpackQuickFixTest: PyQuickFixTestCase() {
// PY-53105
fun testTypeVarTupleAfterTypeVar() {
doTest(LanguageLevel.PYTHON310)
}
// PY-53105
fun testTypeVarTupleOnly() {
doTest(LanguageLevel.PYTHON310)
}
fun doTest(languageLevel: LanguageLevel) {
runWithLanguageLevel(languageLevel) {
val testFileName = getTestName(true)
myFixture.enableInspections(PyCompatibilityInspection::class.java)
myFixture.configureByFile("$testFileName.py")
myFixture.checkHighlighting(false, false, false)
val intentionAction = myFixture.getAvailableIntention(PyPsiBundle.message("QFIX.replace.star.by.unpack"))
assertNotNull(intentionAction)
myFixture.launchAction(intentionAction!!)
myFixture.checkResultByFile(testFileName + "_after.py", true)
}
}
}
@@ -0,0 +1,31 @@
package com.jetbrains.python.quickFixes
import com.jetbrains.python.PyPsiBundle
import com.jetbrains.python.PyQuickFixTestCase
import com.jetbrains.python.inspections.PyTypeHintsInspection
import com.jetbrains.python.psi.LanguageLevel
class PyUnpackTypeVarTupleQuickFixTest : PyQuickFixTestCase() {
// PY-53105
fun testUnpackWithTypingExtensionUnpackOlderVersions() {
doTest(LanguageLevel.PYTHON310)
}
// PY-53105
fun testUnpackWithStarExpression() {
doTest()
}
fun doTest(languageLevel: LanguageLevel = LanguageLevel.getLatest()) {
runWithLanguageLevel(languageLevel) {
val testFileName = getTestName(true)
myFixture.enableInspections(PyTypeHintsInspection::class.java)
myFixture.configureByFile("$testFileName.py")
myFixture.checkHighlighting(false, false, false)
val intentionAction = myFixture.getAvailableIntention(PyPsiBundle.message("QFIX.NAME.unpack.type.var.tuple"))
assertNotNull(intentionAction)
myFixture.launchAction(intentionAction!!)
myFixture.checkResultByFile(testFileName + "_after.py", true)
}
}
}