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:
andrey.matveev
2021-08-23 17:52:45 +00:00
committed by intellij-monorepo-bot
parent de7f4d9f91
commit 997b58df49
31 changed files with 1389 additions and 109 deletions
@@ -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
@@ -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<>();
@@ -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);
@@ -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;
@@ -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)
@@ -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);
}
@@ -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,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,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")))&nbsp;-&gt;&nbsp;U,
attr: U) -&gt; 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")))&nbsp;-&gt;&nbsp;<a href="psi_element://#typename#int">int</a>) -&gt; (ParamSpec("P"))&nbsp;-&gt;&nbsp;<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"))&nbsp;-&gt;&nbsp;<a href="psi_element://#typename#int">int</a>) -&gt; (Concatenate(str, ParamSpec("P")))&nbsp;-&gt;&nbsp;<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")))&nbsp;-&gt;&nbsp;<a href="psi_element://#typename#int">int</a>)
-&gt; (ParamSpec("P"))&nbsp;-&gt;&nbsp;<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"))&nbsp;-&gt;&nbsp;<a href="psi_element://#typename#int">int</a>,
y: (ParamSpec("P"))&nbsp;-&gt;&nbsp;<a href="psi_element://#typename#int">int</a>) -&gt; (ParamSpec("P"))&nbsp;-&gt;&nbsp;<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" +