mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
PY-53105 Support TypeVarTuple
GitOrigin-RevId: f8160e9d802b09991daa710ed9a20f3e30d455da
This commit is contained in:
committed by
intellij-monorepo-bot
parent
4d18b83ba9
commit
a75c569022
@@ -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
|
||||
|
||||
+45
-4
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -113,7 +113,7 @@ public class PyDocStringTypeProvider extends PyTypeProviderBase {
|
||||
}
|
||||
}
|
||||
|
||||
for (PyGenericType typeVar : typeParameters.getAllTypeVars()) {
|
||||
for (PyGenericType typeVar : typeParameters.getAllTypeVarsAndTypeVarTuples()) {
|
||||
typeVar.setScopeOwner(findScopeOwner.apply(typeVar));
|
||||
}
|
||||
}
|
||||
|
||||
+63
-8
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+9
-4
@@ -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()
|
||||
|
||||
|
||||
+43
-4
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+32
@@ -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);
|
||||
}
|
||||
}
|
||||
+55
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+15
-1
@@ -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 {
|
||||
|
||||
+4
-1
@@ -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
+4
-2
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+10
@@ -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
|
||||
+11
@@ -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>]):
|
||||
...
|
||||
+10
@@ -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]]):
|
||||
...
|
||||
+10
@@ -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
|
||||
+10
@@ -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
|
||||
+12
@@ -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
|
||||
+12
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user