mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-18 09:34:34 +07:00
PY-49935 Impl type inference and type checking for PEP 612
Support type hints and type checking for typing.ParamSpec and typing.Concatenate (cherry picked from commit 7854b3386ccdffc0091664e0923622cd8c093fc9) IJ-MR-12970 GitOrigin-RevId: 4578cb463b6ab8fc244766bfaccb122d0e2b7479
This commit is contained in:
committed by
intellij-monorepo-bot
parent
de7f4d9f91
commit
997b58df49
@@ -1065,6 +1065,7 @@ INSP.type.hints.illegal.literal.parameter='Literal' may be parameterized with li
|
||||
INSP.type.hints.parameters.to.generic.must.all.be.type.variables=Parameters to 'Generic[...]' must all be type variables
|
||||
INSP.type.hints.parameters.to.generic.must.all.be.unique=Parameters to 'Generic[...]' must all be unique
|
||||
INSP.type.hints.illegal.callable.format='Callable' must be used as 'Callable[[arg, ...], result]'
|
||||
INSP.type.hints.illegal.first.parameter='Callable' first parameter must be parameter expression
|
||||
INSP.type.hints.parameters.to.generic.types.must.be.types=Parameters to generic types must be types
|
||||
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
|
||||
|
||||
+107
-6
@@ -84,8 +84,10 @@ public class PyTypingTypeProvider extends PyTypeProviderBase {
|
||||
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 PARAM_SPEC = "typing.ParamSpec";
|
||||
private static final String CHAIN_MAP = "typing.ChainMap";
|
||||
public static final String UNION = "typing.Union";
|
||||
public static final String CONCATENATE = "typing.Concatenate";
|
||||
public static final String OPTIONAL = "typing.Optional";
|
||||
public static final String NO_RETURN = "typing.NoReturn";
|
||||
public static final String FINAL = "typing.Final";
|
||||
@@ -152,6 +154,8 @@ public class PyTypingTypeProvider extends PyTypeProviderBase {
|
||||
.add(ANY)
|
||||
.add(TYPE_VAR)
|
||||
.add(GENERIC)
|
||||
.add(PARAM_SPEC)
|
||||
.add(CONCATENATE)
|
||||
.add(TUPLE)
|
||||
.add(CALLABLE)
|
||||
.add(TYPE)
|
||||
@@ -598,7 +602,8 @@ public class PyTypingTypeProvider extends PyTypeProviderBase {
|
||||
@Nullable
|
||||
@Override
|
||||
public PyType getGenericType(@NotNull PyClass cls, @NotNull TypeEvalContext context) {
|
||||
final List<PyType> genericTypes = collectGenericTypes(cls, new Context(context));
|
||||
final var typingContext = new Context(context);
|
||||
final var genericTypes = collectGenericTypes(cls, typingContext);
|
||||
if (genericTypes.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
@@ -697,7 +702,7 @@ public class PyTypingTypeProvider extends PyTypeProviderBase {
|
||||
if (!isGeneric(cls, context.getTypeContext())) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
final TypeEvalContext typeEvalContext = context.getTypeContext();
|
||||
final var typeEvalContext = context.getTypeContext();
|
||||
return StreamEx.of(PyClassElementType.getSubscriptedSuperClassesStubLike(cls))
|
||||
.map(PySubscriptionExpression::getIndexExpression)
|
||||
.flatMap(e -> {
|
||||
@@ -706,7 +711,11 @@ public class PyTypingTypeProvider extends PyTypeProviderBase {
|
||||
})
|
||||
.nonNull()
|
||||
.flatMap(e -> tryResolving(e, typeEvalContext).stream())
|
||||
.map(e -> getGenericTypeFromTypeVar(e, context))
|
||||
.map(e -> {
|
||||
final var typeVar = getGenericTypeFromTypeVar(e, context);
|
||||
if (typeVar != null) return typeVar;
|
||||
return getParamSpecType(e, context);
|
||||
})
|
||||
.select(PyType.class)
|
||||
.distinct()
|
||||
.toList();
|
||||
@@ -803,6 +812,10 @@ public class PyTypingTypeProvider extends PyTypeProviderBase {
|
||||
if (unionType != null) {
|
||||
return Ref.create(unionType);
|
||||
}
|
||||
final PyType concatenateType = getConcatenateType(resolved, context);
|
||||
if (concatenateType != null) {
|
||||
return Ref.create(concatenateType);
|
||||
}
|
||||
final Ref<PyType> optionalType = getOptionalType(resolved, context);
|
||||
if (optionalType != null) {
|
||||
return optionalType;
|
||||
@@ -813,7 +826,7 @@ public class PyTypingTypeProvider extends PyTypeProviderBase {
|
||||
}
|
||||
final Ref<PyType> classObjType = getClassObjectType(resolved, context);
|
||||
if (classObjType != null) {
|
||||
return Ref.create(addTypeVarAlias(classObjType.get(), alias));
|
||||
return Ref.create(addGenericAlias(classObjType.get(), alias));
|
||||
}
|
||||
final Ref<PyType> finalType = getFinalType(resolved, context);
|
||||
if (finalType != null) {
|
||||
@@ -841,7 +854,11 @@ public class PyTypingTypeProvider extends PyTypeProviderBase {
|
||||
}
|
||||
final PyType genericType = getGenericTypeFromTypeVar(resolved, context);
|
||||
if (genericType != null) {
|
||||
return Ref.create(addTypeVarAlias(genericType, alias));
|
||||
return Ref.create(addGenericAlias(genericType, alias));
|
||||
}
|
||||
final PyType paramSpecType = getParamSpecType(resolved, context);
|
||||
if (paramSpecType != null) {
|
||||
return Ref.create(addGenericAlias(paramSpecType, alias));
|
||||
}
|
||||
final PyType stringBasedType = getStringLiteralType(resolved, context);
|
||||
if (stringBasedType != null) {
|
||||
@@ -918,11 +935,15 @@ public class PyTypingTypeProvider extends PyTypeProviderBase {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static PyType addTypeVarAlias(@Nullable PyType type, @Nullable PyTargetExpression alias) {
|
||||
private static PyType addGenericAlias(@Nullable PyType type, @Nullable PyTargetExpression alias) {
|
||||
final PyGenericType typeVar = as(type, PyGenericType.class);
|
||||
if (typeVar != null) {
|
||||
return new PyGenericType(typeVar.getName(), typeVar.getBound(), typeVar.isDefinition(), alias);
|
||||
}
|
||||
final PyParamSpecType paramSpec = as(type, PyParamSpecType.class);
|
||||
if (paramSpec != null) {
|
||||
return paramSpec.withTargetExpression(alias);
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
@@ -1275,6 +1296,21 @@ public class PyTypingTypeProvider extends PyTypeProviderBase {
|
||||
if (isEllipsis(parametersExpr)) {
|
||||
return new PyCallableTypeImpl(null, Ref.deref(getType(returnTypeExpr, context)));
|
||||
}
|
||||
if (isParamSpec(parametersExpr, context.myContext)) {
|
||||
final var name = parametersExpr.getName();
|
||||
if (name != null) {
|
||||
final var parameter = PyCallableParameterImpl.nonPsi(parametersExpr.getName(), new PyParamSpecType(name));
|
||||
return new PyCallableTypeImpl(Collections.singletonList(parameter), Ref.deref(getType(returnTypeExpr, context)));
|
||||
}
|
||||
}
|
||||
if (parametersExpr instanceof PySubscriptionExpression && isConcatenate(parametersExpr, context.myContext)) {
|
||||
final var concatenateParameters = getConcatenateParametersTypes((PySubscriptionExpression)parametersExpr, context.myContext);
|
||||
if (concatenateParameters != null) {
|
||||
final var concatenate = new PyConcatenateType(concatenateParameters.first, concatenateParameters.second);
|
||||
final var parameter = PyCallableParameterImpl.nonPsi(parametersExpr.getName(), concatenate);
|
||||
return new PyCallableTypeImpl(Collections.singletonList(parameter), Ref.deref(getType(returnTypeExpr, context)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1291,6 +1327,22 @@ public class PyTypingTypeProvider extends PyTypeProviderBase {
|
||||
return parametersExpr instanceof PyNoneLiteralExpression && ((PyNoneLiteralExpression)parametersExpr).isEllipsis();
|
||||
}
|
||||
|
||||
public static boolean isParamSpec(@NotNull PyExpression parametersExpr, @NotNull TypeEvalContext context) {
|
||||
final var resolveContext = PyResolveContext.defaultContext(context);
|
||||
return PyUtil.multiResolveTopPriority(parametersExpr, resolveContext).stream().anyMatch(it -> {
|
||||
if (!(it instanceof PyTypedElement)) return false;
|
||||
final var type = context.getType((PyTypedElement)it);
|
||||
if (!(type instanceof PyClassLikeType)) return false;
|
||||
return PARAM_SPEC.equals(((PyClassLikeType)type).getClassQName());
|
||||
});
|
||||
}
|
||||
|
||||
public static boolean isConcatenate(@NotNull PyExpression parametersExpr, @NotNull TypeEvalContext context) {
|
||||
if (!(parametersExpr instanceof PySubscriptionExpression)) return false;
|
||||
final var type = Ref.deref(getType(parametersExpr, context));
|
||||
return type instanceof PyConcatenateType;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static PyType getUnionType(@NotNull PsiElement element, @NotNull Context context) {
|
||||
if (element instanceof PySubscriptionExpression) {
|
||||
@@ -1304,6 +1356,34 @@ public class PyTypingTypeProvider extends PyTypeProviderBase {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static PyType getConcatenateType(@NotNull PsiElement element, @NotNull Context context) {
|
||||
if (!(element instanceof PySubscriptionExpression)) return null;
|
||||
|
||||
final var subscriptionExpr = (PySubscriptionExpression)element;
|
||||
final var operand = subscriptionExpr.getOperand();
|
||||
final var operandNames = resolveToQualifiedNames(operand, context.myContext);
|
||||
if (!operandNames.contains(CONCATENATE)) return null;
|
||||
|
||||
final var parameters = getConcatenateParametersTypes(subscriptionExpr, context.myContext);
|
||||
if (parameters == null) return null;
|
||||
|
||||
return new PyConcatenateType(parameters.first, parameters.second);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Pair<List<PyType>, PyParamSpecType> getConcatenateParametersTypes(@NotNull PySubscriptionExpression subscriptionExpression,
|
||||
@NotNull TypeEvalContext context) {
|
||||
final var tuple = subscriptionExpression.getIndexExpression();
|
||||
if (!(tuple instanceof PyTupleExpression)) return null;
|
||||
final var result = ContainerUtil.mapNotNull(((PyTupleExpression)tuple).getElements(),
|
||||
it -> Ref.deref(getType(it, context)));
|
||||
if (result.size() < 2) return null;
|
||||
PyType lastParameter = result.get(result.size() - 1);
|
||||
if (!(lastParameter instanceof PyParamSpecType)) return null;
|
||||
return new Pair<>(result.subList(0, result.size() - 1), (PyParamSpecType)lastParameter);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static PyGenericType getGenericTypeFromTypeVar(@NotNull PsiElement element, @NotNull Context context) {
|
||||
if (element instanceof PyCallExpression) {
|
||||
@@ -1326,6 +1406,27 @@ public class PyTypingTypeProvider extends PyTypeProviderBase {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static PyParamSpecType getParamSpecType(@NotNull PsiElement element, @NotNull Context context) {
|
||||
if (!(element instanceof PyCallExpression)) return null;
|
||||
|
||||
final var assignedCall = (PyCallExpression)element;
|
||||
final var callee = assignedCall.getCallee();
|
||||
if (callee == null) return null;
|
||||
|
||||
final var calleeQNames = resolveToQualifiedNames(callee, context.getTypeContext());
|
||||
if (!calleeQNames.contains(PARAM_SPEC)) return null;
|
||||
|
||||
final var arguments = assignedCall.getArguments();
|
||||
if (arguments.length == 0) return null;
|
||||
|
||||
final var firstArgument = arguments[0];
|
||||
if (!(firstArgument instanceof PyStringLiteralExpression)) return null;
|
||||
|
||||
final var name = ((PyStringLiteralExpression)firstArgument).getStringValue();
|
||||
return new PyParamSpecType(name);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static PyType getGenericTypeBound(PyExpression @NotNull [] typeVarArguments, @NotNull Context context) {
|
||||
final List<PyType> types = new ArrayList<>();
|
||||
|
||||
+7
-2
@@ -6,7 +6,6 @@ import com.google.common.collect.Maps;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.util.Ref;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.jetbrains.python.PyNames;
|
||||
import com.jetbrains.python.codeInsight.typing.PyTypingTypeProvider;
|
||||
@@ -383,7 +382,13 @@ public class PyTypeModelBuilder {
|
||||
if (parameters != null) {
|
||||
parameterModels = new ArrayList<>();
|
||||
for (PyCallableParameter parameter : parameters) {
|
||||
parameterModels.add(new ParamType(parameter.getName(), build(parameter.getType(myContext), true)));
|
||||
final var paramType = parameter.getType(myContext);
|
||||
if (paramType instanceof PyParamSpecType || paramType instanceof PyConcatenateType) {
|
||||
parameterModels.add(new ParamType(null, build(parameter.getType(myContext), true)));
|
||||
}
|
||||
else {
|
||||
parameterModels.add(new ParamType(parameter.getName(), build(parameter.getType(myContext), true)));
|
||||
}
|
||||
}
|
||||
}
|
||||
final PyType ret = type.getReturnType(myContext);
|
||||
|
||||
+60
-9
@@ -218,18 +218,45 @@ public class PyTypeCheckerInspection extends PyInspection {
|
||||
|
||||
final List<AnalyzeArgumentResult> result = new ArrayList<>();
|
||||
|
||||
final PyExpression receiver = callSite.getReceiver(callableType.getCallable());
|
||||
final Map<PyGenericType, PyType> substitutions = PyTypeChecker.unifyReceiver(receiver, myTypeEvalContext);
|
||||
final Map<PyExpression, PyCallableParameter> mappedParameters = mapping.getMappedParameters();
|
||||
final var receiver = callSite.getReceiver(callableType.getCallable());
|
||||
final var substitutions = PyTypeChecker.unifyReceiver(receiver, myTypeEvalContext);
|
||||
final var mappedParameters = mapping.getMappedParameters();
|
||||
final var regularMappedParameters = getRegularMappedParameters(mappedParameters);
|
||||
|
||||
for (Map.Entry<PyExpression, PyCallableParameter> entry : getRegularMappedParameters(mappedParameters).entrySet()) {
|
||||
for (Map.Entry<PyExpression, PyCallableParameter> entry : regularMappedParameters.entrySet()) {
|
||||
final PyExpression argument = entry.getKey();
|
||||
final PyCallableParameter parameter = entry.getValue();
|
||||
final PyType expected = parameter.getArgumentType(myTypeEvalContext);
|
||||
final PyType promotedToLiteral = PyLiteralType.Companion.promoteToLiteral(argument, expected, myTypeEvalContext, substitutions);
|
||||
final var actual = promotedToLiteral != null ? promotedToLiteral : myTypeEvalContext.getType(argument);
|
||||
final boolean matched = matchParameterAndArgument(expected, actual, substitutions);
|
||||
result.add(new AnalyzeArgumentResult(argument, expected, substituteGenerics(expected, substitutions), actual, matched));
|
||||
|
||||
if (expected instanceof PyParamSpecType) {
|
||||
final var allArguments = callSite.getArguments(callableType.getCallable());
|
||||
analyzeParamSpec((PyParamSpecType)expected, allArguments, substitutions, result);
|
||||
break;
|
||||
}
|
||||
else if (expected instanceof PyConcatenateType) {
|
||||
final var allArguments = callSite.getArguments(callableType.getCallable());
|
||||
if (allArguments.isEmpty()) break;
|
||||
|
||||
final var concatenateType = (PyConcatenateType)expected;
|
||||
final var firstExpectedTypes = concatenateType.getFirstTypes();
|
||||
final var argumentRightBound = Math.min(firstExpectedTypes.size(), allArguments.size());
|
||||
final var firstArguments = allArguments.subList(0, argumentRightBound);
|
||||
matchArgumentsAndTypes(firstArguments, firstExpectedTypes, substitutions, result);
|
||||
|
||||
if (argumentRightBound < allArguments.size()) {
|
||||
final var paramSpec = concatenateType.getParamSpec();
|
||||
final var restArguments = allArguments.subList(argumentRightBound, allArguments.size());
|
||||
analyzeParamSpec(paramSpec, restArguments, substitutions, result);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
else {
|
||||
final boolean matched = matchParameterAndArgument(expected, actual, substitutions);
|
||||
result.add(new AnalyzeArgumentResult(argument, expected, substituteGenerics(expected, substitutions), actual, matched));
|
||||
}
|
||||
}
|
||||
final PyCallableParameter positionalContainer = getMappedPositionalContainer(mappedParameters);
|
||||
if (positionalContainer != null) {
|
||||
@@ -243,10 +270,34 @@ public class PyTypeCheckerInspection extends PyInspection {
|
||||
return new AnalyzeCalleeResults(callableType, callableType.getCallable(), result);
|
||||
}
|
||||
|
||||
private void analyzeParamSpec(@NotNull PyParamSpecType paramSpec, @NotNull List<PyExpression> arguments,
|
||||
@NotNull PyTypeChecker.GenericSubstitutions substitutions,
|
||||
@NotNull List<AnalyzeArgumentResult> result) {
|
||||
final var substParamSpec = substitutions.getParamSpecs().get(paramSpec);
|
||||
paramSpec = substParamSpec == null ? paramSpec : substParamSpec;
|
||||
final var parameters = paramSpec.getParameters();
|
||||
if (parameters == null) return;
|
||||
final var parametersTypes = ContainerUtil.map(parameters, it -> it.getType(myTypeEvalContext));
|
||||
matchArgumentsAndTypes(arguments, parametersTypes, substitutions, result);
|
||||
}
|
||||
|
||||
private void matchArgumentsAndTypes(@NotNull List<PyExpression> arguments, @NotNull List<PyType> types,
|
||||
@NotNull PyTypeChecker.GenericSubstitutions substitutions,
|
||||
@NotNull List<AnalyzeArgumentResult> result) {
|
||||
final var size = Math.min(arguments.size(), types.size());
|
||||
for (int i = 0; i < size; ++i) {
|
||||
final var expected = types.get(i);
|
||||
final var argument = arguments.get(i);
|
||||
final var actual = myTypeEvalContext.getType(argument);
|
||||
final var matched = matchParameterAndArgument(expected, actual, substitutions);
|
||||
result.add(new AnalyzeArgumentResult(argument, expected, substituteGenerics(expected, substitutions), actual, matched));
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private List<AnalyzeArgumentResult> analyzeContainerMapping(@NotNull PyCallableParameter container,
|
||||
@NotNull List<PyExpression> arguments,
|
||||
@NotNull Map<PyGenericType, PyType> substitutions) {
|
||||
@NotNull PyTypeChecker.GenericSubstitutions substitutions) {
|
||||
final PyType expected = container.getArgumentType(myTypeEvalContext);
|
||||
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
|
||||
@@ -270,13 +321,13 @@ public class PyTypeCheckerInspection extends PyInspection {
|
||||
|
||||
private boolean matchParameterAndArgument(@Nullable PyType parameterType,
|
||||
@Nullable PyType argumentType,
|
||||
@NotNull Map<PyGenericType, PyType> substitutions) {
|
||||
@NotNull PyTypeChecker.GenericSubstitutions substitutions) {
|
||||
return PyTypeChecker.match(parameterType, argumentType, myTypeEvalContext, substitutions) &&
|
||||
!PyProtocolsKt.matchingProtocolDefinitions(parameterType, argumentType, myTypeEvalContext);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private PyType substituteGenerics(@Nullable PyType expectedArgumentType, @NotNull Map<PyGenericType, PyType> substitutions) {
|
||||
private PyType substituteGenerics(@Nullable PyType expectedArgumentType, @NotNull PyTypeChecker.GenericSubstitutions substitutions) {
|
||||
return PyTypeChecker.hasGenerics(expectedArgumentType, myTypeEvalContext)
|
||||
? PyTypeChecker.substitute(expectedArgumentType, substitutions, myTypeEvalContext)
|
||||
: null;
|
||||
|
||||
+13
-4
@@ -4,6 +4,7 @@ package com.jetbrains.python.inspections
|
||||
import com.intellij.codeInsight.controlflow.ControlFlowUtil
|
||||
import com.intellij.codeInspection.*
|
||||
import com.intellij.codeInspection.util.IntentionFamilyName
|
||||
import com.intellij.openapi.module.ModuleUtilCore
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import com.intellij.psi.PsiElement
|
||||
@@ -32,6 +33,7 @@ import com.jetbrains.python.psi.impl.PyPsiUtils
|
||||
import com.jetbrains.python.psi.resolve.PyResolveContext
|
||||
import com.jetbrains.python.psi.resolve.PyResolveUtil
|
||||
import com.jetbrains.python.psi.types.*
|
||||
import com.jetbrains.python.sdk.PythonSdkUtil
|
||||
|
||||
class PyTypeHintsInspection : PyInspection() {
|
||||
|
||||
@@ -612,7 +614,7 @@ class PyTypeHintsInspection : PyInspection() {
|
||||
|
||||
private fun checkGenericParameters(index: PyExpression) {
|
||||
val parameters = (index as? PyTupleExpression)?.elements ?: arrayOf(index)
|
||||
val typeVars = mutableSetOf<PsiElement>()
|
||||
val genericParameters = mutableSetOf<PsiElement>()
|
||||
|
||||
parameters.forEach {
|
||||
if (it !is PyReferenceExpression) {
|
||||
@@ -623,8 +625,8 @@ class PyTypeHintsInspection : PyInspection() {
|
||||
val type = myTypeEvalContext.getType(it)
|
||||
|
||||
if (type != null) {
|
||||
if (type is PyGenericType) {
|
||||
if (!typeVars.addAll(multiFollowAssignmentsChain(it))) {
|
||||
if (type is PyGenericType || isParamSpecOrConcatenate(it, myTypeEvalContext)) {
|
||||
if (!genericParameters.addAll(multiFollowAssignmentsChain(it))) {
|
||||
registerProblem(it, PyPsiBundle.message("INSP.type.hints.parameters.to.generic.must.all.be.unique"),
|
||||
ProblemHighlightType.GENERIC_ERROR)
|
||||
}
|
||||
@@ -661,10 +663,11 @@ class PyTypeHintsInspection : PyInspection() {
|
||||
}
|
||||
else {
|
||||
val first = parameters.first()
|
||||
if (!isSdkAvailable(first) || isParamSpecOrConcatenate(first, myTypeEvalContext)) return
|
||||
|
||||
if (first !is PyListLiteralExpression && !(first is PyNoneLiteralExpression && first.isEllipsis)) {
|
||||
registerProblem(first,
|
||||
PyPsiBundle.message("INSP.type.hints.illegal.callable.format"),
|
||||
PyPsiBundle.message("INSP.type.hints.illegal.first.parameter"),
|
||||
ProblemHighlightType.GENERIC_ERROR,
|
||||
null,
|
||||
if (first is PyParenthesizedExpression) ReplaceWithListQuickFix() else SurroundElementWithSquareBracketsQuickFix())
|
||||
@@ -672,6 +675,12 @@ class PyTypeHintsInspection : PyInspection() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun isSdkAvailable(element: PsiElement): Boolean =
|
||||
PythonSdkUtil.findPythonSdk(ModuleUtilCore.findModuleForPsiElement(element)) != null
|
||||
|
||||
private fun isParamSpecOrConcatenate(expression: PyExpression, context: TypeEvalContext) : Boolean =
|
||||
PyTypingTypeProvider.isConcatenate(expression, context) || PyTypingTypeProvider.isParamSpec(expression, context)
|
||||
|
||||
private fun checkTypingMemberParameters(index: PyExpression, isCallable: Boolean) {
|
||||
val parameters = if (index is PyTupleExpression) index.elements else arrayOf(index)
|
||||
|
||||
|
||||
+18
-3
@@ -755,7 +755,7 @@ public final class PyCallExpressionHelper {
|
||||
final int safeImplicitOffset = Math.min(callableType.getImplicitOffset(), parameters.size());
|
||||
final List<PyCallableParameter> explicitParameters = parameters.subList(safeImplicitOffset, parameters.size());
|
||||
final List<PyCallableParameter> implicitParameters = parameters.subList(0, safeImplicitOffset);
|
||||
final ArgumentMappingResults mappingResults = analyzeArguments(arguments, explicitParameters);
|
||||
final ArgumentMappingResults mappingResults = analyzeArguments(arguments, explicitParameters, context);
|
||||
|
||||
return new PyCallExpression.PyArgumentsMapping(callSite,
|
||||
callableType,
|
||||
@@ -822,7 +822,7 @@ public final class PyCallExpressionHelper {
|
||||
final List<PyCallableParameter> explicitParameters = filterExplicitParameters(parameters, callable, callSite, resolveContext);
|
||||
final List<PyCallableParameter> implicitParameters = parameters.subList(0, parameters.size() - explicitParameters.size());
|
||||
|
||||
final ArgumentMappingResults mappingResults = analyzeArguments(arguments, explicitParameters);
|
||||
final ArgumentMappingResults mappingResults = analyzeArguments(arguments, explicitParameters, context);
|
||||
|
||||
return new PyCallExpression.PyArgumentsMapping(callSite,
|
||||
callableType,
|
||||
@@ -953,7 +953,8 @@ public final class PyCallExpressionHelper {
|
||||
|
||||
@NotNull
|
||||
private static ArgumentMappingResults analyzeArguments(@NotNull List<PyExpression> arguments,
|
||||
@NotNull List<PyCallableParameter> parameters) {
|
||||
@NotNull List<PyCallableParameter> parameters,
|
||||
@NotNull TypeEvalContext context) {
|
||||
boolean positionalOnlyMode = ContainerUtil.exists(parameters, p -> p.getParameter() instanceof PySlashParameter);
|
||||
boolean seenSingleStar = false;
|
||||
boolean mappedVariadicArgumentsToParameters = false;
|
||||
@@ -1015,6 +1016,15 @@ public final class PyCallExpressionHelper {
|
||||
mappedVariadicArgumentsToParameters = true;
|
||||
}
|
||||
}
|
||||
else if (isParamSpecOrConcatenate(parameter, context)) {
|
||||
for (var argument: arguments) {
|
||||
mappedParameters.put(argument, parameter);
|
||||
}
|
||||
allPositionalArguments.clear();
|
||||
keywordArguments.clear();
|
||||
variadicPositionalArguments.clear();
|
||||
variadicKeywordArguments.clear();
|
||||
}
|
||||
else {
|
||||
if (positionalOnlyMode) {
|
||||
final PyExpression positionalArgument = next(allPositionalArguments);
|
||||
@@ -1102,6 +1112,11 @@ public final class PyCallExpressionHelper {
|
||||
tupleMappedParameters);
|
||||
}
|
||||
|
||||
private static boolean isParamSpecOrConcatenate(@NotNull PyCallableParameter parameter, @NotNull TypeEvalContext context) {
|
||||
final var type = parameter.getType(context);
|
||||
return type instanceof PyParamSpecType || type instanceof PyConcatenateType;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static List<PsiElement> forEveryScopeTakeOverloadsOtherwiseImplementations(@NotNull List<? extends ResolveResult> results,
|
||||
@NotNull TypeEvalContext context) {
|
||||
|
||||
@@ -271,9 +271,9 @@ public class PyFunctionImpl extends PyBaseElementImpl<PyFunctionStub> implements
|
||||
@NotNull Map<PyExpression, PyCallableParameter> parameters,
|
||||
@NotNull TypeEvalContext context) {
|
||||
if (PyTypeChecker.hasGenerics(type, context)) {
|
||||
Map<PyGenericType, PyType> substitutions = PyTypeChecker.unifyGenericCall(receiver, parameters, context);
|
||||
final var substitutions = PyTypeChecker.unifyGenericCall(receiver, parameters, context);
|
||||
if (substitutions != null) {
|
||||
Map<PyGenericType, PyType> substitutionsWithUnresolvedReturnGenerics =
|
||||
final var substitutionsWithUnresolvedReturnGenerics =
|
||||
PyTypeChecker.getSubstitutionsWithUnresolvedReturnGenerics(getParameters(context), type, substitutions, context);
|
||||
type = PyTypeChecker.substitute(type, substitutionsWithUnresolvedReturnGenerics, context);
|
||||
}
|
||||
|
||||
+3
-2
@@ -404,8 +404,9 @@ public class PyReferenceExpressionImpl extends PyElementImpl implements PyRefere
|
||||
PyType qualifierType = context.getType(qualifier);
|
||||
boolean possiblyParameterizedQualifier = !(qualifierType instanceof PyModuleType || qualifierType instanceof PyImportedModuleType);
|
||||
if (possiblyParameterizedQualifier && PyTypeChecker.hasGenerics(type, context)) {
|
||||
final Map<PyGenericType, PyType> substitutions = PyTypeChecker.unifyGenericCall(qualifier, Collections.emptyMap(), context);
|
||||
if (!ContainerUtil.isEmpty(substitutions)) {
|
||||
final var substitutions =
|
||||
PyTypeChecker.unifyGenericCall(qualifier, Collections.emptyMap(), context);
|
||||
if (substitutions != null) {
|
||||
final PyType substituted = PyTypeChecker.substitute(type, substitutions, context);
|
||||
if (substituted != null) {
|
||||
return substituted;
|
||||
|
||||
@@ -14,9 +14,7 @@ import com.jetbrains.python.psi.resolve.RatedResolveResult;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author vlan
|
||||
@@ -56,19 +54,21 @@ public class PyCallableTypeImpl implements PyCallableType {
|
||||
return myReturnType;
|
||||
}
|
||||
|
||||
PyCallExpression.PyArgumentsMapping fullMapping = PyCallExpressionHelper.mapArguments(callSite, this, context);
|
||||
Map<PyExpression, PyCallableParameter> actualParameters = fullMapping.getMappedParameters();
|
||||
List<PyCallableParameter> allParameters = ContainerUtil.notNullize(getParameters(context));
|
||||
return analyzeCallType(myReturnType, actualParameters, allParameters, context);
|
||||
final var fullMapping = PyCallExpressionHelper.mapArguments(callSite, this, context);
|
||||
final var actualParameters = fullMapping.getMappedParameters();
|
||||
final var allParameters = ContainerUtil.notNullize(getParameters(context));
|
||||
final var receiver = callSite.getReceiver(this.myCallable);
|
||||
return analyzeCallType(myReturnType, actualParameters, allParameters, receiver, context);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static PyType analyzeCallType(@Nullable PyType type,
|
||||
@NotNull Map<PyExpression, PyCallableParameter> actualParameters,
|
||||
@NotNull Collection<PyCallableParameter> allParameters,
|
||||
@Nullable PyExpression receiver,
|
||||
@NotNull TypeEvalContext context) {
|
||||
Map<PyGenericType, PyType> substitutions = PyTypeChecker.unifyGenericCall(null, actualParameters, context);
|
||||
Map<PyGenericType, PyType> substitutionsWithUnresolvedReturnGenerics =
|
||||
final var substitutions = PyTypeChecker.unifyGenericCall(receiver, actualParameters, context);
|
||||
final var substitutionsWithUnresolvedReturnGenerics =
|
||||
PyTypeChecker.getSubstitutionsWithUnresolvedReturnGenerics(allParameters, type, substitutions, context);
|
||||
return PyTypeChecker.substitute(type, substitutionsWithUnresolvedReturnGenerics, context);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.jetbrains.python.psi.types
|
||||
|
||||
import com.intellij.psi.PsiElement
|
||||
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
|
||||
|
||||
/**
|
||||
* Type of typing.Concatenate to store corresponding first type and parameter specification
|
||||
*/
|
||||
class PyConcatenateType(val firstTypes: List<PyType?>, val paramSpec: PyParamSpecType): PyType {
|
||||
|
||||
override fun resolveMember(name: String,
|
||||
location: PyExpression?,
|
||||
direction: AccessDirection,
|
||||
resolveContext: PyResolveContext): MutableList<out RatedResolveResult>? {
|
||||
return null
|
||||
}
|
||||
|
||||
override fun getCompletionVariants(completionPrefix: String?, location: PsiElement?, context: ProcessingContext?): Array<Any> =
|
||||
emptyArray()
|
||||
|
||||
override fun getName(): String = "Concatenate(${firstTypes.joinToString { it?.name ?: "Any" }}, ${paramSpec.name})"
|
||||
|
||||
override fun isBuiltin(): Boolean = true
|
||||
|
||||
override fun assertValid(message: String?) {
|
||||
}
|
||||
}
|
||||
@@ -95,7 +95,7 @@ class PyLiteralType private constructor(cls: PyClass, val expression: PyExpressi
|
||||
fun promoteToLiteral(expression: PyExpression,
|
||||
expected: PyType?,
|
||||
context: TypeEvalContext,
|
||||
substitutions: Map<PyGenericType, PyType>?): PyType? {
|
||||
substitutions: PyTypeChecker.GenericSubstitutions?): PyType? {
|
||||
if (expected is PyTypedDictType) {
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
package com.jetbrains.python.psi.types;
|
||||
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.util.ArrayUtilRt;
|
||||
import com.intellij.util.ProcessingContext;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.jetbrains.python.PyNames;
|
||||
import com.jetbrains.python.psi.AccessDirection;
|
||||
import com.jetbrains.python.psi.PyExpression;
|
||||
import com.jetbrains.python.psi.PyTargetExpression;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Type of typing.ParamSpec using in type checker to unify parameters of generic calls
|
||||
*/
|
||||
public class PyParamSpecType implements PyType {
|
||||
@NotNull private final String myName;
|
||||
@Nullable private final PyTargetExpression myTargetExpression;
|
||||
@Nullable private final List<PyCallableParameter> myParameters;
|
||||
|
||||
public PyParamSpecType(@NotNull String name) {
|
||||
this(name, null, null);
|
||||
}
|
||||
|
||||
private PyParamSpecType(@NotNull String name, @Nullable PyTargetExpression target, @Nullable List<PyCallableParameter> parameters) {
|
||||
myName = name;
|
||||
myTargetExpression = target;
|
||||
myParameters = parameters;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public PyParamSpecType withParameters(@Nullable List<PyCallableParameter> parameters, @NotNull TypeEvalContext context) {
|
||||
return new PyParamSpecType(myName, myTargetExpression, getNamelessParameters(parameters, context));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public PyParamSpecType withTargetExpression(@Nullable PyTargetExpression target) {
|
||||
return new PyParamSpecType(myName, target, myParameters);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static List<PyCallableParameter> getNamelessParameters(@Nullable List<PyCallableParameter> parameters,
|
||||
@NotNull TypeEvalContext context) {
|
||||
if (parameters == null) return null;
|
||||
return ContainerUtil.map(parameters, it -> {
|
||||
if (it.isPositionalContainer()) return PyCallableParameterImpl.positionalNonPsi(null, it.getType(context));
|
||||
if (it.isKeywordContainer()) return PyCallableParameterImpl.keywordNonPsi(null, it.getType(context));
|
||||
return PyCallableParameterImpl.nonPsi(it.getType(context));
|
||||
});
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public List<PyCallableParameter> getParameters() {
|
||||
return myParameters;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public PyTargetExpression getDeclarationElement() {
|
||||
return myTargetExpression;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public List<? extends RatedResolveResult> resolveMember(@NotNull 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;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getName() {
|
||||
if (myParameters == null) {
|
||||
return String.format("ParamSpec(\"%s\")", myName);
|
||||
}
|
||||
else {
|
||||
final TypeEvalContext context = TypeEvalContext.codeInsightFallback(null);
|
||||
return String.format("[%s]",
|
||||
StringUtil.join(myParameters, param -> {
|
||||
if (param != null) {
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
final PyType type = param.getType(context);
|
||||
builder.append(type != null ? type.getName() : PyNames.UNKNOWN_TYPE);
|
||||
return builder.toString();
|
||||
}
|
||||
return PyNames.UNKNOWN_TYPE;
|
||||
},
|
||||
", "));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBuiltin() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void assertValid(String message) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
final PyParamSpecType type = (PyParamSpecType)o;
|
||||
return myName.equals(type.myName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return myName.hashCode();
|
||||
}
|
||||
}
|
||||
@@ -69,6 +69,14 @@ public final class PyTypeChecker {
|
||||
return match(expected, actual, new MatchContext(context, substitutions)).orElse(true);
|
||||
}
|
||||
|
||||
public static boolean match(@Nullable PyType expected,
|
||||
@Nullable PyType actual,
|
||||
@NotNull TypeEvalContext context,
|
||||
@NotNull GenericSubstitutions substitutions) {
|
||||
return match(expected, actual, new MatchContext(context, substitutions.typeVars, substitutions.paramSpecs, false))
|
||||
.orElse(true);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static Optional<Boolean> match(@Nullable PyType expected, @Nullable PyType actual, @NotNull MatchContext context) {
|
||||
final Optional<Boolean> result = RecursionManager.doPreventingRecursion(
|
||||
@@ -93,7 +101,7 @@ public final class PyTypeChecker {
|
||||
@NotNull
|
||||
private static Optional<Boolean> matchImpl(@Nullable PyType expected, @Nullable PyType actual, @NotNull MatchContext context) {
|
||||
for (PyTypeCheckerExtension extension : PyTypeCheckerExtension.EP_NAME.getExtensionList()) {
|
||||
final Optional<Boolean> result = extension.match(expected, actual, context.context, context.substitutions);
|
||||
final Optional<Boolean> result = extension.match(expected, actual, context.context, context.genericSubstitutions);
|
||||
if (result.isPresent()) {
|
||||
return result;
|
||||
}
|
||||
@@ -114,6 +122,10 @@ public final class PyTypeChecker {
|
||||
return Optional.of(match((PyGenericType)expected, actual, context));
|
||||
}
|
||||
|
||||
if (expected instanceof PyParamSpecType) {
|
||||
return Optional.of(match((PyParamSpecType)expected, actual, context));
|
||||
}
|
||||
|
||||
if (expected == null || actual == null || isUnknown(actual, context.context)) {
|
||||
return Optional.of(true);
|
||||
}
|
||||
@@ -201,7 +213,7 @@ public final class PyTypeChecker {
|
||||
return false;
|
||||
}
|
||||
|
||||
final PyType substitution = context.substitutions.get(expected);
|
||||
final PyType substitution = context.genericSubstitutions.get(expected);
|
||||
PyType bound = expected.getBound();
|
||||
// Promote int in Type[TypeVar('T', int)] to Type[int] before checking that bounds match
|
||||
if (expected.isDefinition()) {
|
||||
@@ -228,15 +240,25 @@ public final class PyTypeChecker {
|
||||
}
|
||||
|
||||
if (actual != null) {
|
||||
context.substitutions.put(expected, actual);
|
||||
context.genericSubstitutions.put(expected, actual);
|
||||
}
|
||||
else if (bound != null) {
|
||||
context.substitutions.put(expected, PyUnionType.createWeakType(bound));
|
||||
context.genericSubstitutions.put(expected, PyUnionType.createWeakType(bound));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean match(@NotNull PyParamSpecType expected, @Nullable PyType actual, @NotNull MatchContext context) {
|
||||
if (actual == null) return true;
|
||||
if (!(actual instanceof PyParamSpecType)) return false;
|
||||
final var callableActual = (PyParamSpecType)actual;
|
||||
final var parameters = callableActual.getParameters();
|
||||
if (parameters == null) return false;
|
||||
context.paramSpecSubstitutions.put(expected, expected.withParameters(parameters, context.context));
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean match(@NotNull PyType expected, @NotNull PyUnionType actual, @NotNull MatchContext context) {
|
||||
if (expected instanceof PyTupleType) {
|
||||
Optional<Boolean> match = match((PyTupleType)expected, actual, context);
|
||||
@@ -479,10 +501,49 @@ public final class PyTypeChecker {
|
||||
if (expectedParameters != null && actualParameters != null) {
|
||||
final int size = Math.min(expectedParameters.size(), actualParameters.size());
|
||||
for (int i = 0; i < size; i++) {
|
||||
final PyCallableParameter expectedParam = expectedParameters.get(i);
|
||||
final PyCallableParameter actualParam = actualParameters.get(i);
|
||||
final var expectedParam = expectedParameters.get(i);
|
||||
final var actualParam = actualParameters.get(i);
|
||||
final var expectedParamType = expectedParam.getType(context);
|
||||
// TODO: Check named and star params, not only positional ones
|
||||
if (expectedParam.isSelf() && actualParam.isSelf()) {
|
||||
if (expectedParamType instanceof PyParamSpecType && expectedParameters.size() == 1) {
|
||||
final var expectedParamSpecType = (PyParamSpecType)expectedParamType;
|
||||
matchContext.paramSpecSubstitutions.put(expectedParamSpecType, expectedParamSpecType.withParameters(actualParameters, context));
|
||||
break;
|
||||
}
|
||||
else if (expectedParamType instanceof PyConcatenateType && expectedParameters.size() == 1) {
|
||||
if (i != 0) break;
|
||||
|
||||
final var actualParamType = actualParam.getType(context);
|
||||
final var expectedConcatenateType = (PyConcatenateType)expectedParamType;
|
||||
final var expectedFirstTypes = expectedConcatenateType.getFirstTypes();
|
||||
|
||||
if (actualParamType instanceof PyConcatenateType) {
|
||||
final var actualConcatenateType = (PyConcatenateType)actualParamType;
|
||||
final var actualFirstType = actualConcatenateType.getFirstTypes();
|
||||
if (!match(expectedFirstTypes, actualFirstType, matchContext)) {
|
||||
return Optional.of(false);
|
||||
}
|
||||
}
|
||||
else {
|
||||
final var actualParamRightBound = Math.min(expectedFirstTypes.size(), actualParameters.size());
|
||||
final var actualFirstParamTypes = ContainerUtil
|
||||
.map(actualParameters.subList(0, actualParamRightBound), it -> it.getType(context));
|
||||
|
||||
if (!match(expectedFirstTypes, actualFirstParamTypes, matchContext)) {
|
||||
return Optional.of(false);
|
||||
}
|
||||
|
||||
if (actualParamRightBound < actualParameters.size()) {
|
||||
final var expectedParamSpecType = expectedConcatenateType.getParamSpec();
|
||||
final var restActualParameters = actualParameters.subList(actualParamRightBound, actualParameters.size());
|
||||
final var parametersSubst = expectedParamSpecType.withParameters(restActualParameters, context);
|
||||
matchContext.paramSpecSubstitutions.put(expectedParamSpecType, parametersSubst);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
else if (expectedParam.isSelf() && actualParam.isSelf()) {
|
||||
if (!match(expectedParam.getType(context), actualParam.getType(context), matchContext).orElse(true)) {
|
||||
return Optional.of(false);
|
||||
}
|
||||
@@ -508,6 +569,14 @@ public final class PyTypeChecker {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private static boolean match(@NotNull List<PyType> expected, @NotNull List<PyType> actual, @NotNull MatchContext matchContext) {
|
||||
final var size = Math.min(expected.size(), actual.size());
|
||||
for (int i = 0; i < size; ++i) {
|
||||
if (!match(expected.get(i), actual.get(i), matchContext).orElse(true)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean isCallableProtocol(@NotNull PyClassLikeType expected, @NotNull TypeEvalContext context) {
|
||||
return PyProtocolsKt.isProtocol(expected, context) && expected.getMemberNames(false, context).contains(PyNames.CALL);
|
||||
}
|
||||
@@ -639,23 +708,21 @@ public final class PyTypeChecker {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static Map<PyGenericType, PyType>
|
||||
public static GenericSubstitutions
|
||||
getSubstitutionsWithUnresolvedReturnGenerics(@NotNull Collection<PyCallableParameter> parameters,
|
||||
@Nullable PyType returnType,
|
||||
@Nullable Map<PyGenericType, PyType> substitutions,
|
||||
@Nullable GenericSubstitutions substitutions,
|
||||
@NotNull TypeEvalContext context) {
|
||||
Map<PyGenericType, PyType> result = ContainerUtil.isEmpty(substitutions)
|
||||
? new HashMap<>()
|
||||
: new HashMap<>(substitutions);
|
||||
Set<Object> visited = new HashSet<>();
|
||||
Set<PyGenericType> returnTypeGenerics = new HashSet<>();
|
||||
final var result = substitutions == null ? new GenericSubstitutions() : substitutions;
|
||||
final var visited = new HashSet<>();
|
||||
final var returnTypeGenerics = new Generics();
|
||||
collectGenerics(returnType, context, returnTypeGenerics, visited);
|
||||
if (returnTypeGenerics.isEmpty()) {
|
||||
if (returnTypeGenerics.typeVars.isEmpty()) {
|
||||
return result;
|
||||
}
|
||||
for (PyGenericType alreadyKnown : result.keySet()) {
|
||||
if (!returnTypeGenerics.remove(alreadyKnown)) {
|
||||
returnTypeGenerics.remove(invert(alreadyKnown));
|
||||
for (PyGenericType alreadyKnown : result.typeVars.keySet()) {
|
||||
if (!returnTypeGenerics.typeVars.remove(alreadyKnown)) {
|
||||
returnTypeGenerics.typeVars.remove(invert(alreadyKnown));
|
||||
}
|
||||
}
|
||||
if (returnTypeGenerics.isEmpty()) {
|
||||
@@ -663,15 +730,14 @@ public final class PyTypeChecker {
|
||||
}
|
||||
|
||||
visited.clear();
|
||||
Set<PyGenericType> paramGenerics = new HashSet<>();
|
||||
final var paramGenerics = new Generics();
|
||||
for (PyCallableParameter parameter : parameters) {
|
||||
PyType paramType = parameter.getArgumentType(context);
|
||||
collectGenerics(paramType, context, paramGenerics, visited);
|
||||
collectGenerics(parameter.getArgumentType(context), context, paramGenerics, visited);
|
||||
}
|
||||
|
||||
for (PyGenericType returnTypeGeneric : returnTypeGenerics) {
|
||||
if (!paramGenerics.contains(returnTypeGeneric) && !paramGenerics.contains(invert(returnTypeGeneric))) {
|
||||
result.put(returnTypeGeneric, returnTypeGeneric);
|
||||
for (PyGenericType returnTypeGeneric : returnTypeGenerics.typeVars) {
|
||||
if (!paramGenerics.typeVars.contains(returnTypeGeneric) && !paramGenerics.typeVars.contains(invert(returnTypeGeneric))) {
|
||||
result.typeVars.put(returnTypeGeneric, returnTypeGeneric);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
@@ -683,39 +749,51 @@ public final class PyTypeChecker {
|
||||
}
|
||||
|
||||
public static boolean hasGenerics(@Nullable PyType type, @NotNull TypeEvalContext context) {
|
||||
final Set<PyGenericType> collected = new HashSet<>();
|
||||
collectGenerics(type, context, collected, new HashSet<>());
|
||||
return !collected.isEmpty();
|
||||
return !collectGenerics(type, context).isEmpty();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static Generics collectGenerics(@Nullable PyType type,
|
||||
@NotNull TypeEvalContext context) {
|
||||
final var result = new Generics();
|
||||
collectGenerics(type, context, result, new HashSet<>());
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void collectGenerics(@Nullable PyType type,
|
||||
@NotNull TypeEvalContext context,
|
||||
@NotNull Set<? super PyGenericType> collected,
|
||||
@NotNull Generics generics,
|
||||
@NotNull Set<? super PyType> visited) {
|
||||
if (visited.contains(type)) {
|
||||
return;
|
||||
}
|
||||
visited.add(type);
|
||||
if (type instanceof PyGenericType) {
|
||||
collected.add((PyGenericType)type);
|
||||
generics.typeVars.add((PyGenericType)type);
|
||||
}
|
||||
if (type instanceof PyParamSpecType) {
|
||||
generics.paramSpecs.add((PyParamSpecType)type);
|
||||
}
|
||||
if (type instanceof PyConcatenateType) {
|
||||
generics.concatenates.add((PyConcatenateType)type);
|
||||
}
|
||||
else if (type instanceof PyUnionType) {
|
||||
final PyUnionType union = (PyUnionType)type;
|
||||
for (PyType t : union.getMembers()) {
|
||||
collectGenerics(t, context, collected, visited);
|
||||
collectGenerics(t, context, generics, visited);
|
||||
}
|
||||
}
|
||||
else if (type instanceof PyTupleType) {
|
||||
final PyTupleType tuple = (PyTupleType)type;
|
||||
final int n = tuple.isHomogeneous() ? 1 : tuple.getElementCount();
|
||||
for (int i = 0; i < n; i++) {
|
||||
collectGenerics(tuple.getElementType(i), context, collected, visited);
|
||||
collectGenerics(tuple.getElementType(i), context, generics, visited);
|
||||
}
|
||||
}
|
||||
else if (type instanceof PyCollectionType) {
|
||||
final PyCollectionType collection = (PyCollectionType)type;
|
||||
for (PyType elementType : collection.getElementTypes()) {
|
||||
collectGenerics(elementType, context, collected, visited);
|
||||
collectGenerics(elementType, context, generics, visited);
|
||||
}
|
||||
}
|
||||
else if (type instanceof PyCallableType && !(type instanceof PyClassLikeType)) {
|
||||
@@ -724,29 +802,30 @@ public final class PyTypeChecker {
|
||||
if (parameters != null) {
|
||||
for (PyCallableParameter parameter : parameters) {
|
||||
if (parameter != null) {
|
||||
collectGenerics(parameter.getType(context), context, collected, visited);
|
||||
collectGenerics(parameter.getType(context), context, generics, visited);
|
||||
}
|
||||
}
|
||||
}
|
||||
collectGenerics(callable.getReturnType(context), context, collected, visited);
|
||||
collectGenerics(callable.getReturnType(context), context, generics, visited);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static PyType substitute(@Nullable PyType type, @NotNull Map<PyGenericType, PyType> substitutions,
|
||||
public static PyType substitute(@Nullable PyType type,
|
||||
@NotNull GenericSubstitutions substitutions,
|
||||
@NotNull TypeEvalContext context) {
|
||||
if (hasGenerics(type, context)) {
|
||||
if (type instanceof PyGenericType) {
|
||||
final PyGenericType typeVar = (PyGenericType)type;
|
||||
PyType substitution = substitutions.get(typeVar);
|
||||
PyType substitution = substitutions.typeVars.get(typeVar);
|
||||
if (substitution == null) {
|
||||
final PyInstantiableType<?> invertedTypeVar = invert(typeVar);
|
||||
final PyInstantiableType<?> invertedSubstitution = as(substitutions.get(invertedTypeVar), PyInstantiableType.class);
|
||||
final PyInstantiableType<?> invertedSubstitution = as(substitutions.typeVars.get(invertedTypeVar), PyInstantiableType.class);
|
||||
if (invertedSubstitution != null) {
|
||||
substitution = invert(invertedSubstitution);
|
||||
}
|
||||
}
|
||||
if (substitution instanceof PyGenericType && !typeVar.equals(substitution) && substitutions.containsKey(substitution)) {
|
||||
if (substitution instanceof PyGenericType && !typeVar.equals(substitution) && substitutions.typeVars.containsKey(substitution)) {
|
||||
return substitute(substitution, substitutions, context);
|
||||
}
|
||||
return substitution;
|
||||
@@ -759,7 +838,16 @@ public final class PyTypeChecker {
|
||||
final List<PyType> elementTypes = collection.getElementTypes();
|
||||
final List<PyType> substitutes = new ArrayList<>();
|
||||
for (PyType elementType : elementTypes) {
|
||||
substitutes.add(substitute(elementType, substitutions, context));
|
||||
if (elementType instanceof PyParamSpecType) {
|
||||
final var paramSpecType = (PyParamSpecType)elementType;
|
||||
final var paramSpecTypeSubst = substitutions.paramSpecs.get(paramSpecType);
|
||||
if (paramSpecTypeSubst != null && paramSpecTypeSubst.getParameters() != null) {
|
||||
substitutes.add(paramSpecTypeSubst);
|
||||
}
|
||||
}
|
||||
else {
|
||||
substitutes.add(substitute(elementType, substitutions, context));
|
||||
}
|
||||
}
|
||||
return new PyCollectionTypeImpl(collection.getPyClass(), collection.isDefinition(), substitutes);
|
||||
}
|
||||
@@ -783,6 +871,26 @@ public final class PyTypeChecker {
|
||||
if (parameters != null) {
|
||||
substParams = new ArrayList<>();
|
||||
for (PyCallableParameter parameter : parameters) {
|
||||
final var parameterType = parameter.getType(context);
|
||||
if (parameters.size() == 1 && parameterType instanceof PyParamSpecType) {
|
||||
final var parameterTypeSubst = substitutions.paramSpecs.get(parameterType);
|
||||
if (parameterTypeSubst != null && parameterTypeSubst.getParameters() != null) {
|
||||
substParams = parameterTypeSubst.getParameters();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (parameters.size() == 1 && parameterType instanceof PyConcatenateType) {
|
||||
final var concatenateType = (PyConcatenateType)parameterType;
|
||||
final var paramSpecType = concatenateType.getParamSpec();
|
||||
final var paramSpecTypeSubst = substitutions.paramSpecs.get(paramSpecType);
|
||||
if (paramSpecTypeSubst != null && paramSpecTypeSubst.getParameters() != null) {
|
||||
final var firstParameters = ContainerUtil
|
||||
.map(concatenateType.getFirstTypes(), it -> PyCallableParameterImpl.nonPsi(it));
|
||||
substParams.addAll(firstParameters);
|
||||
substParams.addAll(paramSpecTypeSubst.getParameters());
|
||||
break;
|
||||
}
|
||||
}
|
||||
final PyType substType = substitute(parameter.getType(context), substitutions, context);
|
||||
final PyParameter psi = parameter.getParameter();
|
||||
final PyCallableParameter subst = psi != null ?
|
||||
@@ -799,10 +907,10 @@ public final class PyTypeChecker {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Map<PyGenericType, PyType> unifyGenericCall(@Nullable PyExpression receiver,
|
||||
@NotNull Map<PyExpression, PyCallableParameter> arguments,
|
||||
@NotNull TypeEvalContext context) {
|
||||
final Map<PyGenericType, PyType> substitutions = unifyReceiver(receiver, context);
|
||||
public static GenericSubstitutions unifyGenericCall(@Nullable PyExpression receiver,
|
||||
@NotNull Map<PyExpression, PyCallableParameter> arguments,
|
||||
@NotNull TypeEvalContext context) {
|
||||
final var substitutions = unifyReceiver(receiver, context);
|
||||
for (Map.Entry<PyExpression, PyCallableParameter> entry : getRegularMappedParameters(arguments).entrySet()) {
|
||||
final PyCallableParameter paramWrapper = entry.getValue();
|
||||
final PyType expectedType = paramWrapper.getArgumentType(context);
|
||||
@@ -833,11 +941,12 @@ public final class PyTypeChecker {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (!matchContainer(getMappedPositionalContainer(arguments), getArgumentsMappedToPositionalContainer(arguments), substitutions,
|
||||
context)) {
|
||||
if (!matchContainer(getMappedPositionalContainer(arguments), getArgumentsMappedToPositionalContainer(arguments),
|
||||
substitutions.typeVars, context)) {
|
||||
return null;
|
||||
}
|
||||
if (!matchContainer(getMappedKeywordContainer(arguments), getArgumentsMappedToKeywordContainer(arguments), substitutions, context)) {
|
||||
if (!matchContainer(getMappedKeywordContainer(arguments), getArgumentsMappedToKeywordContainer(arguments),
|
||||
substitutions.typeVars, context)) {
|
||||
return null;
|
||||
}
|
||||
return substitutions;
|
||||
@@ -852,25 +961,26 @@ public final class PyTypeChecker {
|
||||
return match(container.getArgumentType(context), PyUnionType.union(types), context, substitutions);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static Map<PyGenericType, PyType> unifyReceiver(@Nullable PyExpression receiver, @NotNull TypeEvalContext context) {
|
||||
final Map<PyGenericType, PyType> substitutions = new LinkedHashMap<>();
|
||||
public static GenericSubstitutions unifyReceiver(@Nullable PyExpression receiver, @NotNull TypeEvalContext context) {
|
||||
// Collect generic params of object type
|
||||
final Set<PyGenericType> generics = new LinkedHashSet<>();
|
||||
final var substitutions = new GenericSubstitutions();
|
||||
final PyType qualifierType = receiver != null ? context.getType(receiver) : null;
|
||||
collectGenerics(qualifierType, context, generics, new HashSet<>());
|
||||
for (PyGenericType t : generics) {
|
||||
substitutions.put(t, t);
|
||||
final var generics = collectGenerics(qualifierType, context);
|
||||
for (PyGenericType t : generics.typeVars) {
|
||||
substitutions.typeVars.put(t, t);
|
||||
}
|
||||
for (PyParamSpecType p : generics.paramSpecs) {
|
||||
substitutions.paramSpecs.put(p, p);
|
||||
}
|
||||
if (qualifierType != null) {
|
||||
for (PyClassType type : PyTypeUtil.toStream(qualifierType).select(PyClassType.class)) {
|
||||
for (PyTypeProvider provider : PyTypeProvider.EP_NAME.getExtensionList()) {
|
||||
final PyType genericType = provider.getGenericType(type.getPyClass(), context);
|
||||
final Set<PyGenericType> providedTypeGenerics = new LinkedHashSet<>();
|
||||
Generics providedGenerics = new Generics();
|
||||
|
||||
if (genericType != null) {
|
||||
match(genericType, type, context, substitutions);
|
||||
collectGenerics(genericType, context, providedTypeGenerics, new HashSet<>());
|
||||
providedGenerics = collectGenerics(genericType, context);
|
||||
}
|
||||
|
||||
for (Map.Entry<PyType, PyType> entry : provider.getGenericSubstitutions(type.getPyClass(), context).entrySet()) {
|
||||
@@ -879,16 +989,16 @@ public final class PyTypeChecker {
|
||||
|
||||
if (genericKey != null &&
|
||||
value != null &&
|
||||
!substitutions.containsKey(genericKey) &&
|
||||
!providedTypeGenerics.contains(genericKey)) {
|
||||
substitutions.put(genericKey, value);
|
||||
!substitutions.typeVars.containsKey(genericKey) &&
|
||||
!providedGenerics.typeVars.contains(genericKey)) {
|
||||
substitutions.typeVars.put(genericKey, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
replaceUnresolvedGenericsWithAny(substitutions);
|
||||
replaceUnresolvedGenericsWithAny(substitutions.typeVars);
|
||||
return substitutions;
|
||||
}
|
||||
|
||||
@@ -1059,32 +1169,96 @@ public final class PyTypeChecker {
|
||||
return null;
|
||||
}
|
||||
|
||||
private static class Generics {
|
||||
@NotNull
|
||||
private final Set<PyGenericType> typeVars;
|
||||
|
||||
@NotNull
|
||||
private final Set<PyParamSpecType> paramSpecs;
|
||||
|
||||
@NotNull
|
||||
private final Set<PyConcatenateType> concatenates;
|
||||
|
||||
Generics() {
|
||||
this(new HashSet<>(), new HashSet<>(), new HashSet<>());
|
||||
}
|
||||
|
||||
Generics(@NotNull Set<PyGenericType> generics, @NotNull Set<PyParamSpecType> paramSpecs, @NotNull Set<PyConcatenateType> concatenates) {
|
||||
this.typeVars = generics;
|
||||
this.paramSpecs = paramSpecs;
|
||||
this.concatenates = concatenates;
|
||||
}
|
||||
|
||||
boolean isEmpty() {
|
||||
return typeVars.isEmpty() && paramSpecs.isEmpty() && concatenates.isEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
public static class GenericSubstitutions {
|
||||
@NotNull
|
||||
private final Map<PyGenericType, PyType> typeVars;
|
||||
|
||||
@NotNull
|
||||
private final Map<PyParamSpecType, PyParamSpecType> paramSpecs;
|
||||
|
||||
GenericSubstitutions() {
|
||||
this(new LinkedHashMap<>(), new LinkedHashMap<>());
|
||||
}
|
||||
|
||||
GenericSubstitutions(@NotNull Map<PyGenericType, PyType> typeVars,
|
||||
@NotNull Map<PyParamSpecType, PyParamSpecType> paramSpecs) {
|
||||
this.typeVars = typeVars;
|
||||
this.paramSpecs = paramSpecs;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Map<PyParamSpecType, PyParamSpecType> getParamSpecs() {
|
||||
return paramSpecs;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Map<PyGenericType, PyType> getTypeVars() {
|
||||
return typeVars;
|
||||
}
|
||||
}
|
||||
|
||||
private static class MatchContext {
|
||||
|
||||
@NotNull
|
||||
private final TypeEvalContext context;
|
||||
|
||||
@NotNull
|
||||
private final Map<PyGenericType, PyType> substitutions; // mutable
|
||||
private final Map<PyGenericType, PyType> genericSubstitutions; // mutable
|
||||
|
||||
@NotNull
|
||||
private final Map<PyParamSpecType, PyParamSpecType> paramSpecSubstitutions; // mutable
|
||||
|
||||
private final boolean reversedSubstitutions;
|
||||
|
||||
MatchContext(@NotNull TypeEvalContext context,
|
||||
@NotNull Map<PyGenericType, PyType> substitutions) {
|
||||
this(context, substitutions, false);
|
||||
@NotNull Map<PyGenericType, PyType> genericSubstitutions) {
|
||||
this(context, genericSubstitutions, false);
|
||||
}
|
||||
|
||||
private MatchContext(@NotNull TypeEvalContext context,
|
||||
@NotNull Map<PyGenericType, PyType> substitutions,
|
||||
@NotNull Map<PyGenericType, PyType> genericSubstitutions,
|
||||
boolean reversedSubstitutions) {
|
||||
this(context, genericSubstitutions, new HashMap<>(), reversedSubstitutions);
|
||||
}
|
||||
|
||||
private MatchContext(@NotNull TypeEvalContext context,
|
||||
@NotNull Map<PyGenericType, PyType> genericSubstitutions,
|
||||
@NotNull Map<PyParamSpecType, PyParamSpecType> paramSpecSubstitutions,
|
||||
boolean reversedSubstitutions) {
|
||||
this.context = context;
|
||||
this.substitutions = substitutions;
|
||||
this.genericSubstitutions = genericSubstitutions;
|
||||
this.paramSpecSubstitutions = paramSpecSubstitutions;
|
||||
this.reversedSubstitutions = reversedSubstitutions;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public MatchContext reverseSubstitutions() {
|
||||
return new MatchContext(context, substitutions, !reversedSubstitutions);
|
||||
return new MatchContext(context, genericSubstitutions, !reversedSubstitutions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
from typing import Callable
|
||||
|
||||
e: Callable[<error descr="'Callable' must be used as 'Callable[[arg, ...], result]'">i<caret>nt</error>, str]
|
||||
e: Callable[<error descr="'Callable' first parameter must be parameter expression">i<caret>nt</error>, str]
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
from typing import Callable
|
||||
|
||||
g: Callable[<error descr="'Callable' must be used as 'Callable[[arg, ...], result]'">(int<caret>)</error>, str]
|
||||
g: Callable[<error descr="'Callable' first parameter must be parameter expression">(int<caret>)</error>, str]
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
from typing import Callable
|
||||
|
||||
g: Callable[<error descr="'Callable' must be used as 'Callable[[arg, ...], result]'">(int<caret>, str)</error>, str]
|
||||
g: Callable[<error descr="'Callable' first parameter must be parameter expression">(int<caret>, str)</error>, str]
|
||||
@@ -0,0 +1,3 @@
|
||||
<html><body><div class='definition'><pre>ConcatenateInGeneric.Y<br>def <b>__init__</b>(self,
|
||||
f: (Concatenate(int, ParamSpec("P"))) -> U,
|
||||
attr: U) -> None</pre></div></body></html>
|
||||
@@ -0,0 +1,19 @@
|
||||
from typing import TypeVar, Generic, Callable, ParamSpec, Concatenate
|
||||
|
||||
U = TypeVar("U")
|
||||
P = ParamSpec("P")
|
||||
|
||||
|
||||
class Y(Generic[U, P]):
|
||||
f: Callable[Concatenate[int, P], U]
|
||||
attr: U
|
||||
|
||||
def __i<the_ref>nit__(self, f: Callable[Concatenate[int, P], U], attr: U) -> None:
|
||||
self.f = f
|
||||
self.attr = attr
|
||||
|
||||
|
||||
def a(q: int, s: str, b: bool) -> str: ...
|
||||
|
||||
|
||||
expr = Y(a, '1').f(42, "42" , True)
|
||||
@@ -0,0 +1 @@
|
||||
<html><body><div class='definition'><pre><a href="psi_element://#module#ConcatenateInParam">ConcatenateInParam</a><br>def <b>remove</b>(x: (Concatenate(int, ParamSpec("P"))) -> <a href="psi_element://#typename#int">int</a>) -> (ParamSpec("P")) -> <a href="psi_element://#typename#bool">bool</a></pre></div></body></html>
|
||||
@@ -0,0 +1,12 @@
|
||||
from typing import Callable, Concatenate, ParamSpec
|
||||
|
||||
P = ParamSpec("P")
|
||||
|
||||
|
||||
def bar(x: int, *args: bool) -> int: ...
|
||||
|
||||
|
||||
def rem<the_ref>ove(x: Callable[Concatenate[int, P], int]) -> Callable[P, bool]: ...
|
||||
|
||||
|
||||
expression = remove(bar)(True)
|
||||
@@ -0,0 +1 @@
|
||||
<html><body><div class='definition'><pre><a href="psi_element://#module#ConcatenateInReturn">ConcatenateInReturn</a><br>def <b>add</b>(x: (ParamSpec("P")) -> <a href="psi_element://#typename#int">int</a>) -> (Concatenate(str, ParamSpec("P"))) -> <a href="psi_element://#typename#bool">bool</a></pre></div></body></html>
|
||||
@@ -0,0 +1,12 @@
|
||||
from typing import Callable, Concatenate, ParamSpec
|
||||
|
||||
P = ParamSpec("P")
|
||||
|
||||
|
||||
def bar(x: int, *args: bool) -> int: ...
|
||||
|
||||
|
||||
def ad<the_ref>d(x: Callable[P, int]) -> Callable[Concatenate[str, P], bool]: ...
|
||||
|
||||
|
||||
expression = add(bar)(42, 42, 42)
|
||||
@@ -0,0 +1,2 @@
|
||||
<html><body><div class='definition'><pre><a href="psi_element://#module#ConcatenateSeveralFirstParamInParam">ConcatenateSeveralFirstParamInParam</a><br>def <b>remove</b>(x: (Concatenate(int, bool, list, ParamSpec("P"))) -> <a href="psi_element://#typename#int">int</a>)
|
||||
-> (ParamSpec("P")) -> <a href="psi_element://#typename#bool">bool</a></pre></div></body></html>
|
||||
@@ -0,0 +1,12 @@
|
||||
from typing import Callable, Concatenate, ParamSpec
|
||||
|
||||
P = ParamSpec("P")
|
||||
|
||||
|
||||
def bar(x: int, *args: bool) -> int: ...
|
||||
|
||||
|
||||
def rem<the_ref>ove(x: Callable[Concatenate[int, bool, list[str], P], int]) -> Callable[P, bool]: ...
|
||||
|
||||
|
||||
expression = remove(bar)(True)
|
||||
@@ -0,0 +1,2 @@
|
||||
<html><body><div class='definition'><pre><a href="psi_element://#module#SeveralParamSpecs">SeveralParamSpecs</a><br>def <b>foo</b>(x: (ParamSpec("P")) -> <a href="psi_element://#typename#int">int</a>,
|
||||
y: (ParamSpec("P")) -> <a href="psi_element://#typename#int">int</a>) -> (ParamSpec("P")) -> <a href="psi_element://#typename#bool">bool</a></pre></div></body></html>
|
||||
@@ -0,0 +1,15 @@
|
||||
from typing import ParamSpec, Callable
|
||||
|
||||
P = ParamSpec("P")
|
||||
|
||||
|
||||
def fo<the_ref>o(x: Callable[P, int], y: Callable[P, int]) -> Callable[P, bool]: ...
|
||||
|
||||
|
||||
def x_y(x: int, y: str) -> int: ...
|
||||
|
||||
|
||||
def y_x(y: int, x: str) -> int: ...
|
||||
|
||||
|
||||
expr = foo(x_y, y_x)
|
||||
@@ -0,0 +1,98 @@
|
||||
// Copyright 2000-2018 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;
|
||||
|
||||
import com.intellij.codeInsight.documentation.DocumentationManager;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.testFramework.LightProjectDescriptor;
|
||||
import com.jetbrains.python.documentation.PyDocumentationSettings;
|
||||
import com.jetbrains.python.documentation.PythonDocumentationProvider;
|
||||
import com.jetbrains.python.documentation.docstrings.DocStringFormat;
|
||||
import com.jetbrains.python.fixtures.LightMarkedTestCase;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class Py3QuickDocTest extends LightMarkedTestCase {
|
||||
private PythonDocumentationProvider myProvider;
|
||||
private DocStringFormat myFormat;
|
||||
|
||||
@Override
|
||||
protected @Nullable LightProjectDescriptor getProjectDescriptor() {
|
||||
return ourPyLatestDescriptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
// the provider is stateless, can be reused, as in real life
|
||||
myProvider = new PythonDocumentationProvider();
|
||||
final PyDocumentationSettings documentationSettings = PyDocumentationSettings.getInstance(myFixture.getModule());
|
||||
myFormat = documentationSettings.getFormat();
|
||||
documentationSettings.setFormat(DocStringFormat.PLAIN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tearDown() throws Exception {
|
||||
try {
|
||||
final PyDocumentationSettings documentationSettings = PyDocumentationSettings.getInstance(myFixture.getModule());
|
||||
documentationSettings.setFormat(myFormat);
|
||||
}
|
||||
catch (Throwable e) {
|
||||
addSuppressedException(e);
|
||||
}
|
||||
finally {
|
||||
super.tearDown();
|
||||
}
|
||||
}
|
||||
|
||||
private void checkByHTML(@NotNull String text) {
|
||||
assertSameLinesWithFile(getTestDataPath() + getTestName(false) + ".html", text);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Map<String, PsiElement> loadTest() {
|
||||
return configureByFile(getTestName(false) + ".py");
|
||||
}
|
||||
|
||||
protected void checkHTMLOnly() {
|
||||
final Map<String, PsiElement> marks = loadTest();
|
||||
final PsiElement originalElement = marks.get("<the_ref>");
|
||||
assertNotNull("<the_ref> marker is missing in test data", originalElement);
|
||||
final DocumentationManager manager = DocumentationManager.getInstance(myFixture.getProject());
|
||||
final PsiElement target = manager.findTargetElement(myFixture.getEditor(),
|
||||
originalElement.getTextOffset(),
|
||||
myFixture.getFile(),
|
||||
originalElement);
|
||||
checkByHTML(myProvider.generateDoc(target, originalElement));
|
||||
}
|
||||
|
||||
// PY-49935
|
||||
public void testConcatenateInReturn() {
|
||||
checkHTMLOnly();
|
||||
}
|
||||
|
||||
// PY-49935
|
||||
public void testConcatenateInParam() {
|
||||
checkHTMLOnly();
|
||||
}
|
||||
|
||||
// PY-49935
|
||||
public void testConcatenateSeveralFirstParamInParam() {
|
||||
checkHTMLOnly();
|
||||
}
|
||||
|
||||
// PY-49935
|
||||
public void testConcatenateInGeneric() {
|
||||
checkHTMLOnly();
|
||||
}
|
||||
|
||||
public void testSeveralParamSpecs() {
|
||||
checkHTMLOnly();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTestDataPath() {
|
||||
return super.getTestDataPath() + "/quickdoc/";
|
||||
}
|
||||
}
|
||||
@@ -1062,6 +1062,267 @@ public class Py3TypeTest extends PyTestCase {
|
||||
" expr = m");
|
||||
}
|
||||
|
||||
// PY-49935
|
||||
public void testParamSpecExample() {
|
||||
doTest("(str, bool) -> str",
|
||||
"from collections.abc import Callable\n" +
|
||||
"from typing import ParamSpec\n" +
|
||||
"\n" +
|
||||
"P = ParamSpec(\"P\")\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"def changes_return_type_to_str(x: Callable[P, int]) -> Callable[P, str]: ...\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"def returns_int(a: str, b: bool) -> int:\n" +
|
||||
" return 42\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"expr = changes_return_type_to_str(returns_int)");
|
||||
}
|
||||
|
||||
// PY-49935
|
||||
public void testParamSpecSeveral() {
|
||||
doTest("(int, str) -> bool",
|
||||
"from typing import ParamSpec, Callable\n" +
|
||||
"\n" +
|
||||
"P = ParamSpec(\"P\")\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"def foo(x: Callable[P, int], y: Callable[P, int]) -> Callable[P, bool]: ...\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"def x_y(x: int, y: str) -> int: ...\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"def y_x(y: int, x: str) -> int: ...\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"expr = foo(x_y, y_x)");
|
||||
}
|
||||
|
||||
// PY-49935
|
||||
public void testParamSpecUserGenericClass() {
|
||||
doTest("Y[int, [int, str, bool]]",
|
||||
"from typing import TypeVar, Generic, Callable, ParamSpec\n" +
|
||||
"\n" +
|
||||
"U = TypeVar(\"U\")\n" +
|
||||
"P = ParamSpec(\"P\")\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"class Y(Generic[U, P]):\n" +
|
||||
" f: Callable[P, str]\n" +
|
||||
" attr: U\n" +
|
||||
"\n" +
|
||||
" def __init__(self, f: Callable[P, str], attr: U) -> None:\n" +
|
||||
" self.f = f\n" +
|
||||
" self.attr = attr\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"def a(q: int, p: str, r: bool) -> str: ...\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"expr = Y(a, 1)\n");
|
||||
}
|
||||
|
||||
// PY-49935
|
||||
public void testParamSpecUserGenericClassMethod() {
|
||||
doTest("(int) -> str",
|
||||
"from typing import TypeVar, Generic, Callable, ParamSpec\n" +
|
||||
"\n" +
|
||||
"U = TypeVar(\"U\")\n" +
|
||||
"P = ParamSpec(\"P\")\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"class Y(Generic[U, P]):\n" +
|
||||
" f: Callable[P, U]\n" +
|
||||
" attr: U\n" +
|
||||
"\n" +
|
||||
" def __init__(self, f: Callable[P, U], attr: U) -> None:\n" +
|
||||
" self.f = f\n" +
|
||||
" self.attr = attr\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"def a(q: int) -> str: ...\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"expr = Y(a, '1').f\n");
|
||||
}
|
||||
|
||||
// PY-49935
|
||||
public void testParamSpecUserGenericClassMethodConcatenate() {
|
||||
doTest("(int, str, bool) -> str",
|
||||
"from typing import TypeVar, Generic, Callable, ParamSpec, Concatenate\n" +
|
||||
"\n" +
|
||||
"U = TypeVar(\"U\")\n" +
|
||||
"P = ParamSpec(\"P\")\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"class Y(Generic[U, P]):\n" +
|
||||
" f: Callable[Concatenate[int, P], U]\n" +
|
||||
" attr: U\n" +
|
||||
"\n" +
|
||||
" def __init__(self, f: Callable[Concatenate[int, P], U], attr: U) -> None:\n" +
|
||||
" self.f = f\n" +
|
||||
" self.attr = attr\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"def a(q: int, s: str, b: bool) -> str: ...\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"expr = Y(a, '1').f\n");
|
||||
}
|
||||
|
||||
// PY-49935
|
||||
public void testParamSpecUserGenericClassMethodConcatenateSeveralParameters() {
|
||||
doTest("(int, bool, str, bool) -> str",
|
||||
"from typing import TypeVar, Generic, Callable, ParamSpec, Concatenate\n" +
|
||||
"\n" +
|
||||
"U = TypeVar(\"U\")\n" +
|
||||
"P = ParamSpec(\"P\")\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"class Y(Generic[U, P]):\n" +
|
||||
" f: Callable[Concatenate[int, bool, P], U]\n" +
|
||||
" attr: U\n" +
|
||||
"\n" +
|
||||
" def __init__(self, f: Callable[Concatenate[int, bool, P], U], attr: U) -> None:\n" +
|
||||
" self.f = f\n" +
|
||||
" self.attr = attr\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"def a(q: int, r: bool, s: str, b: bool) -> str: ...\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"expr = Y(a, '1').f\n");
|
||||
}
|
||||
|
||||
// PY-49935
|
||||
public void testParamSpecUserGenericClassMethodConcatenateOtherFunction() {
|
||||
doTest("(bool, dict[str, list[str]], str, bool) -> str",
|
||||
"from typing import TypeVar, Generic, Callable, ParamSpec, Concatenate\n" +
|
||||
"\n" +
|
||||
"U = TypeVar(\"U\")\n" +
|
||||
"P = ParamSpec(\"P\")\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"class Y(Generic[U, P]):\n" +
|
||||
" f: Callable[Concatenate[int, bool, P], U]\n" +
|
||||
" g: Callable[Concatenate[bool, dict[str, list[str]], P], U]\n" +
|
||||
" attr: U\n" +
|
||||
"\n" +
|
||||
" def __init__(self, f: Callable[Concatenate[int, bool, P], U], attr: U) -> None:\n" +
|
||||
" self.f = f\n" +
|
||||
" self.attr = attr\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"def a(q: int, r: bool, s: str, b: bool) -> str: ...\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"expr = Y(a, '1').g\n");
|
||||
}
|
||||
|
||||
// PY-49935
|
||||
public void testParamSpecUserGenericClassAttribute() {
|
||||
doTest("str",
|
||||
"from typing import TypeVar, Generic, Callable, ParamSpec\n" +
|
||||
"\n" +
|
||||
"U = TypeVar(\"U\")\n" +
|
||||
"P = ParamSpec(\"P\")\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"class Y(Generic[U, P]):\n" +
|
||||
" f: Callable[P, U]\n" +
|
||||
" attr: U\n" +
|
||||
"\n" +
|
||||
" def __init__(self, f: Callable[P, U], attr: U) -> None:\n" +
|
||||
" self.f = f\n" +
|
||||
" self.attr = attr\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"def a(q: int) -> str: ...\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"expr = Y(a, '1').attr\n");
|
||||
}
|
||||
|
||||
// PY-49935
|
||||
public void testParamSpecConcatenateAdd() {
|
||||
doTest("(str, int, tuple[bool, ...]) -> bool",
|
||||
"from collections.abc import Callable\n" +
|
||||
"from typing import Concatenate, ParamSpec\n" +
|
||||
"\n" +
|
||||
"P = ParamSpec(\"P\")\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"def bar(x: int, *args: bool) -> int: ...\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"def add(x: Callable[P, int]) -> Callable[Concatenate[str, P], bool]: ...\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"expr = add(bar) # Should return (__a: str, x: int, *args: bool) -> bool");
|
||||
}
|
||||
|
||||
// PY-49935
|
||||
public void testParamSpecConcatenateAddSeveralParameters() {
|
||||
doTest("(str, bool, int, tuple[bool, ...]) -> bool",
|
||||
"from collections.abc import Callable\n" +
|
||||
"from typing import Concatenate, ParamSpec\n" +
|
||||
"\n" +
|
||||
"P = ParamSpec(\"P\")\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"def bar(x: int, *args: bool) -> int: ...\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"def add(x: Callable[P, int]) -> Callable[Concatenate[str, bool, P], bool]: ...\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"expr = add(bar) # Should return (__a: str, x: int, *args: bool) -> bool");
|
||||
}
|
||||
|
||||
// PY-49935
|
||||
public void testParamSpecConcatenateRemove() {
|
||||
doTest("(tuple[bool, ...]) -> bool",
|
||||
"from collections.abc import Callable\n" +
|
||||
"from typing import Concatenate, ParamSpec\n" +
|
||||
"\n" +
|
||||
"P = ParamSpec(\"P\")\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"def bar(x: int, *args: bool) -> int: ...\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"def remove(x: Callable[Concatenate[int, P], int]) -> Callable[P, bool]: ...\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"expr = remove(bar)");
|
||||
}
|
||||
|
||||
// PY-49935
|
||||
public void testParamSpecConcatenateTransform() {
|
||||
doTest("(str, tuple[bool, ...]) -> bool",
|
||||
"from collections.abc import Callable\n" +
|
||||
"from typing import Concatenate, ParamSpec\n" +
|
||||
"\n" +
|
||||
"P = ParamSpec(\"P\")\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"def bar(x: int, *args: bool) -> int: ...\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"def transform(\n" +
|
||||
" x: Callable[Concatenate[int, P], int]\n" +
|
||||
") -> Callable[Concatenate[str, P], bool]:\n" +
|
||||
" def inner(s: str, *args: P.args):\n" +
|
||||
" return True\n" +
|
||||
" return inner\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"expr = transform(bar)");
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #testRecursiveDictTopDown()
|
||||
* @see PyTypeCheckerInspectionTest#testRecursiveDictAttribute()
|
||||
|
||||
@@ -264,6 +264,68 @@ public class PyDecoratedFunctionTypeProviderTest extends PyTestCase {
|
||||
checkMultiFileTest("str", "(int) -> str", Context.USER_INITIATED);
|
||||
}
|
||||
|
||||
// PY-49935
|
||||
public void testParamSpec() {
|
||||
doTest("int", "(int, str) -> int",
|
||||
"from typing import Callable, ParamSpec, TypeVar\n" +
|
||||
"\n" +
|
||||
"P = ParamSpec(\"P\")\n" +
|
||||
"R = TypeVar(\"R\")\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"def log_to_database():\n" +
|
||||
" print('42')\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"def add_logging(f: Callable[P, R]) -> Callable[P, R]:\n" +
|
||||
" def inner(*args: P.args, **kwargs: P.kwargs) -> R:\n" +
|
||||
" log_to_database()\n" +
|
||||
" return f(*args, **kwargs)\n" +
|
||||
"\n" +
|
||||
" return inner\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"@add_logging\n" +
|
||||
"def takes_int_str(x: int, y: str) -> int:\n" +
|
||||
" return x + len(y)\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"value = takes_int_str(1, \"A\")\n" +
|
||||
"dec_func = takes_int_str\n");
|
||||
}
|
||||
|
||||
// PY-49935
|
||||
public void testParamSpecAndConcatenate() {
|
||||
doTest("int", "(int, str) -> int",
|
||||
"from typing import Concatenate, Callable, ParamSpec, TypeVar\n" +
|
||||
"\n" +
|
||||
"P = ParamSpec(\"P\")\n" +
|
||||
"R = TypeVar(\"R\")\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"class Request:\n" +
|
||||
" def foo(self):\n" +
|
||||
" pass\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"def with_request(f: Callable[Concatenate[Request, P], R]) -> Callable[P, R]:\n" +
|
||||
" def inner(*args: P.args, **kwargs: P.kwargs) -> R:\n" +
|
||||
" return f(Request(), *args, **kwargs)\n" +
|
||||
"\n" +
|
||||
" return inner\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"@with_request\n" +
|
||||
"def takes_int_str(request: Request, x: int, y: str) -> int:\n" +
|
||||
" request.foo()\n" +
|
||||
" return x + len(y)\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"value = takes_int_str(1, \"A\")\n" +
|
||||
"dec_func = takes_int_str\n");
|
||||
}
|
||||
|
||||
private void doTest(@NotNull String expectedValueType, @NotNull String expectedFuncType, @NotNull String text) {
|
||||
myFixture.configureByText(PythonFileType.INSTANCE, text);
|
||||
checkTypes(expectedValueType, expectedFuncType, allContexts());
|
||||
|
||||
@@ -486,4 +486,266 @@ public class Py3TypeCheckerInspectionTest extends PyInspectionTestCase {
|
||||
public void testBitwiseOrUnionsAndOldStyleUnionsAreEquivalent() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-49935
|
||||
public void testParamSpecExample() {
|
||||
doTestByText("from collections.abc import Callable\n" +
|
||||
"from typing import ParamSpec\n" +
|
||||
"\n" +
|
||||
"P = ParamSpec(\"P\")\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"def changes_return_type_to_str(x: Callable[P, int]) -> Callable[P, str]: ...\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"def returns_int(a: str, b: bool) -> int:\n" +
|
||||
" return 42\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"changes_return_type_to_str(returns_int)(\"42\", <warning descr=\"Expected type 'bool', got 'int' instead\">42</warning>)");
|
||||
}
|
||||
|
||||
// PY-49935
|
||||
public void testParamSpecUserGenericClassMethod() {
|
||||
doTestByText("from typing import TypeVar, Generic, Callable, ParamSpec\n" +
|
||||
"\n" +
|
||||
"U = TypeVar(\"U\")\n" +
|
||||
"P = ParamSpec(\"P\")\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"class Y(Generic[U, P]):\n" +
|
||||
" f: Callable[P, U]\n" +
|
||||
" attr: U\n" +
|
||||
"\n" +
|
||||
" def __init__(self, f: Callable[P, U], attr: U) -> None:\n" +
|
||||
" self.f = f\n" +
|
||||
" self.attr = attr\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"def a(q: int) -> str: ...\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"expr = Y(a, '1').f(<warning descr=\"Expected type 'int', got 'str' instead\">\"42\"</warning>)\n");
|
||||
}
|
||||
|
||||
// PY-49935
|
||||
public void testParamSpecUserGenericClassMethodSeveralParameters() {
|
||||
doTestByText("from typing import TypeVar, Generic, Callable, ParamSpec\n" +
|
||||
"\n" +
|
||||
"U = TypeVar(\"U\")\n" +
|
||||
"P = ParamSpec(\"P\")\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"class Y(Generic[U, P]):\n" +
|
||||
" f: Callable[P, U]\n" +
|
||||
" attr: U\n" +
|
||||
"\n" +
|
||||
" def __init__(self, f: Callable[P, U], attr: U) -> None:\n" +
|
||||
" self.f = f\n" +
|
||||
" self.attr = attr\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"def a(q: int, s: str) -> str: ...\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"expr = Y(a, '1').f(42, <warning descr=\"Expected type 'str', got 'int' instead\">42</warning>)\n");
|
||||
}
|
||||
|
||||
// PY-49935
|
||||
public void testParamSpecUserGenericClassMethodConcatenate() {
|
||||
doTestByText("from typing import TypeVar, Generic, Callable, ParamSpec, Concatenate\n" +
|
||||
"\n" +
|
||||
"U = TypeVar(\"U\")\n" +
|
||||
"P = ParamSpec(\"P\")\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"class Y(Generic[U, P]):\n" +
|
||||
" f: Callable[Concatenate[int, P], U]\n" +
|
||||
" attr: U\n" +
|
||||
"\n" +
|
||||
" def __init__(self, f: Callable[Concatenate[int, P], U], attr: U) -> None:\n" +
|
||||
" self.f = f\n" +
|
||||
" self.attr = attr\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"def a(q: int, s: str, b: bool) -> str: ...\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"expr = Y(a, '1').f(42, <warning descr=\"Expected type 'str', got 'int' instead\">42</warning>, <warning descr=\"Expected type 'bool', got 'int' instead\">42</warning>)\n");
|
||||
}
|
||||
|
||||
// PY-49935
|
||||
public void testParamSpecConcatenateAddThirdParameter() {
|
||||
doTestByText("from collections.abc import Callable\n" +
|
||||
"from typing import Concatenate, ParamSpec\n" +
|
||||
"\n" +
|
||||
"P = ParamSpec(\"P\")\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"def bar(x: int, *args: bool) -> int: ...\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"def add(x: Callable[P, int]) -> Callable[Concatenate[str, P], bool]: ...\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"add(bar)(\"42\", 42, <warning descr=\"Expected type 'bool', got 'int' instead\">42</warning>)");
|
||||
}
|
||||
|
||||
// PY-49935
|
||||
public void testParamSpecConcatenateAddSecondParameter() {
|
||||
doTestByText("from collections.abc import Callable\n" +
|
||||
"from typing import Concatenate, ParamSpec\n" +
|
||||
"\n" +
|
||||
"P = ParamSpec(\"P\")\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"def bar(x: int, *args: bool) -> int: ...\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"def add(x: Callable[P, int]) -> Callable[Concatenate[str, P], bool]: ...\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"add(bar)(\"42\", <warning descr=\"Expected type 'int', got 'str' instead\">\"42\"</warning>, True)");
|
||||
}
|
||||
|
||||
// PY-49935
|
||||
public void testParamSpecConcatenateAddFirstParameter() {
|
||||
doTestByText("from collections.abc import Callable\n" +
|
||||
"from typing import Concatenate, ParamSpec\n" +
|
||||
"\n" +
|
||||
"P = ParamSpec(\"P\")\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"def bar(x: int, *args: bool) -> int: ...\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"def add(x: Callable[P, int]) -> Callable[Concatenate[str, P], bool]: ...\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"add(bar)(<warning descr=\"Expected type 'str', got 'int' instead\">42</warning>, 42, True)");
|
||||
}
|
||||
|
||||
// PY-49935
|
||||
public void testParamSpecConcatenateAddFirstSeveralParameters() {
|
||||
doTestByText("from collections.abc import Callable\n" +
|
||||
"from typing import Concatenate, ParamSpec\n" +
|
||||
"\n" +
|
||||
"P = ParamSpec(\"P\")\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"def bar(x: int, *args: bool) -> int: ...\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"def add(x: Callable[P, int]) -> Callable[Concatenate[str, list[str], P], bool]: ...\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"add(bar)(<warning descr=\"Expected type 'str', got 'int' instead\">42</warning>, <warning descr=\"Expected type 'list[str]', got 'list[int]' instead\">[42]</warning>, 3, True)");
|
||||
}
|
||||
|
||||
// PY-49935
|
||||
public void testParamSpecConcatenateAddOk() {
|
||||
doTestByText("from collections.abc import Callable\n" +
|
||||
"from typing import Concatenate, ParamSpec\n" +
|
||||
"\n" +
|
||||
"P = ParamSpec(\"P\")\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"def bar(x: int, *args: bool) -> int: ...\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"def add(x: Callable[P, int]) -> Callable[Concatenate[str, P], bool]: ...\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"add(bar)(\"42\", 42, True, True, True)");
|
||||
}
|
||||
|
||||
// PY-49935
|
||||
public void testParamSpecConcatenateRemove() {
|
||||
doTestByText("from collections.abc import Callable\n" +
|
||||
"from typing import Concatenate, ParamSpec\n" +
|
||||
"\n" +
|
||||
"P = ParamSpec(\"P\")\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"def bar(x: int, *args: bool) -> int: ...\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"def remove(x: Callable[Concatenate[int, P], int]) -> Callable[P, bool]: ...\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"remove(bar)(<warning descr=\"Expected type 'bool', got 'int' instead\">42</warning>)");
|
||||
}
|
||||
|
||||
// PY-49935
|
||||
public void testParamSpecConcatenateRemoveOkOneBool() {
|
||||
doTestByText("from collections.abc import Callable\n" +
|
||||
"from typing import Concatenate, ParamSpec\n" +
|
||||
"\n" +
|
||||
"P = ParamSpec(\"P\")\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"def bar(x: int, *args: bool) -> int: ...\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"def remove(x: Callable[Concatenate[int, P], int]) -> Callable[P, bool]: ...\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"remove(bar)(True)");
|
||||
}
|
||||
|
||||
// PY-49935
|
||||
public void testParamSpecConcatenateRemoveOkTwoBools() {
|
||||
doTestByText("from collections.abc import Callable\n" +
|
||||
"from typing import Concatenate, ParamSpec\n" +
|
||||
"\n" +
|
||||
"P = ParamSpec(\"P\")\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"def bar(x: int, *args: bool) -> int: ...\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"def remove(x: Callable[Concatenate[int, P], int]) -> Callable[P, bool]: ...\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"remove(bar)(True, True)");
|
||||
}
|
||||
|
||||
// PY-49935
|
||||
public void testParamSpecConcatenateRemoveOkEmpty() {
|
||||
doTestByText("from collections.abc import Callable\n" +
|
||||
"from typing import Concatenate, ParamSpec\n" +
|
||||
"\n" +
|
||||
"P = ParamSpec(\"P\")\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"def bar(x: int, *args: bool) -> int: ...\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"def remove(x: Callable[Concatenate[int, P], int]) -> Callable[P, bool]: ...\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"remove(bar)()");
|
||||
}
|
||||
|
||||
// PY-49935
|
||||
public void testParamSpecConcatenateTransform() {
|
||||
doTestByText("from collections.abc import Callable\n" +
|
||||
"from typing import Concatenate, ParamSpec\n" +
|
||||
"\n" +
|
||||
"P = ParamSpec(\"P\")\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"def bar(x: int, *args: bool) -> int: ...\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"def transform(\n" +
|
||||
" x: Callable[Concatenate[int, P], int]\n" +
|
||||
") -> Callable[Concatenate[str, P], bool]:\n" +
|
||||
" def inner(s: str, *args: P.args):\n" +
|
||||
" return True\n" +
|
||||
" return inner\n" +
|
||||
"\n" +
|
||||
"\n" +
|
||||
"transform(bar)(<warning descr=\"Expected type 'str', got 'int' instead\">42</warning>)");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -721,11 +721,11 @@ public class PyTypeHintsInspectionTest extends PyInspectionTestCase {
|
||||
"c: Callable[[int, str], str]\n" +
|
||||
"\n" +
|
||||
"d: Callable[<error descr=\"'Callable' must be used as 'Callable[[arg, ...], result]'\">...</error>]\n" +
|
||||
"e: Callable[<error descr=\"'Callable' must be used as 'Callable[[arg, ...], result]'\">int</error>, str]\n" +
|
||||
"e: Callable[<error descr=\"'Callable' first parameter must be parameter expression\">int</error>, str]\n" +
|
||||
"f: Callable[<error descr=\"'Callable' must be used as 'Callable[[arg, ...], result]'\">int, str</error>, str]\n" +
|
||||
"g: Callable[<error descr=\"'Callable' must be used as 'Callable[[arg, ...], result]'\">(int, str)</error>, str]\n" +
|
||||
"g: Callable[<error descr=\"'Callable' first parameter must be parameter expression\">(int, str)</error>, str]\n" +
|
||||
"h: Callable[<error descr=\"'Callable' must be used as 'Callable[[arg, ...], result]'\">int</error>]\n" +
|
||||
"h: Callable[<error descr=\"'Callable' must be used as 'Callable[[arg, ...], result]'\">(int)</error>, str]\n" +
|
||||
"h: Callable[<error descr=\"'Callable' first parameter must be parameter expression\">(int)</error>, str]\n" +
|
||||
"\n" +
|
||||
"A1: TypeAlias = Callable[<error descr=\"'Callable' must be used as 'Callable[[arg, ...], result]'\">int</error>]\n" +
|
||||
"A2: TypeAlias = 'Callable[<error descr=\"'Callable' must be used as 'Callable[[arg, ...], result]'\">int</error>]'\n" +
|
||||
|
||||
Reference in New Issue
Block a user