Resolve callee to its type and then to its callable if possible.

It allows to calculate arguments-to-parameters mapping for types that don't have psi callable (i.e. typing.Callable).
Update PyParameterInfoHandler to work with callable types instead of callables.
This commit is contained in:
Semyon Proshev
2017-06-09 21:29:08 +03:00
committed by Semyon Proshev
parent f79376a519
commit edb827e655
17 changed files with 246 additions and 122 deletions
@@ -25,13 +25,12 @@ import com.jetbrains.python.nameResolver.NameResolverTools;
import com.jetbrains.python.psi.resolve.PyResolveContext;
import com.jetbrains.python.psi.resolve.RatedResolveResult;
import com.jetbrains.python.psi.types.PyCallableParameter;
import com.jetbrains.python.psi.types.PyCallableType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.*;
import java.util.stream.Collectors;
/**
* Represents an entire call expression, like <tt>foo()</tt> or <tt>foo.bar[1]('x')</tt>.
@@ -195,7 +194,11 @@ public interface PyCallExpression extends PyCallSiteExpression {
*/
@NotNull
default List<PyCallable> multiResolveCalleeFunction(@NotNull PyResolveContext resolveContext) {
return ContainerUtil.map(multiResolveRatedCallee(resolveContext, 0), PyRatedMarkedCallee::getElement);
return multiResolveRatedCallee(resolveContext, 0)
.stream()
.map(PyRatedMarkedCallee::getElement)
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
/**
@@ -233,7 +236,9 @@ public interface PyCallExpression extends PyCallSiteExpression {
@NotNull
default List<PyRatedCallee> multiResolveRatedCalleeFunction(@NotNull PyResolveContext resolveContext) {
return ContainerUtil.map(multiResolveRatedCallee(resolveContext, 0),
markedCallee -> new PyRatedCallee(markedCallee.getElement(), markedCallee.getRate()));
markedCallee -> new PyRatedCallee(markedCallee.getMarkedCallee().getCallableType(),
markedCallee.getElement(),
markedCallee.getRate()));
}
/**
@@ -431,7 +436,8 @@ public interface PyCallExpression extends PyCallSiteExpression {
* Couples function with a flag describing the way it is called.
*/
class PyMarkedCallee {
@NotNull private final PyCallable myCallable;
@NotNull private final PyCallableType myCallableType;
@Nullable private final PyCallable myCallable;
@Nullable private final PyFunction.Modifier myModifier;
private final int myImplicitOffset;
private final boolean myImplicitlyResolved;
@@ -439,12 +445,18 @@ public interface PyCallExpression extends PyCallSiteExpression {
/**
* Method-oriented constructor.
*
* @param callableType type describing callable object
* @param function the method (or any other callable, but why bother then).
* @param modifier classmethod or staticmethod modifier
* @param offset implicit argument offset; parameters up to this are implicitly filled in the call.
* @param implicitlyResolved value for {@link #isImplicitlyResolved()}
*/
public PyMarkedCallee(@NotNull PyCallable function, @Nullable PyFunction.Modifier modifier, int offset, boolean implicitlyResolved) {
public PyMarkedCallee(@NotNull PyCallableType callableType,
@Nullable PyCallable function,
@Nullable PyFunction.Modifier modifier,
int offset,
boolean implicitlyResolved) {
myCallableType = callableType;
myCallable = function;
myModifier = modifier;
myImplicitOffset = offset;
@@ -452,6 +464,11 @@ public interface PyCallExpression extends PyCallSiteExpression {
}
@NotNull
public PyCallableType getCallableType() {
return myCallableType;
}
@Nullable
public PyCallable getCallable() {
return myCallable;
}
@@ -480,12 +497,21 @@ public interface PyCallExpression extends PyCallSiteExpression {
class PyRatedCallee extends RatedResolveResult {
public PyRatedCallee(@NotNull PyCallable callable, int rate) {
@NotNull
private final PyCallableType myCallableType;
public PyRatedCallee(@NotNull PyCallableType callableType, @Nullable PyCallable callable, int rate) {
super(rate, callable);
myCallableType = callableType;
}
@NotNull
public PyCallableType getCallableType() {
return myCallableType;
}
@Override
@NotNull
@Nullable
public PyCallable getElement() {
//noinspection ConstantConditions
return (PyCallable)super.getElement();
@@ -508,7 +534,7 @@ public interface PyCallExpression extends PyCallSiteExpression {
}
@Override
@NotNull
@Nullable
public PyCallable getElement() {
//noinspection ConstantConditions
return (PyCallable)super.getElement();
@@ -74,17 +74,15 @@ public class PyParameterInfoHandler implements ParameterInfoHandler<PyArgumentLi
final TypeEvalContext typeEvalContext = TypeEvalContext.userInitiated(argumentList.getProject(), argumentList.getContainingFile());
final PyResolveContext resolveContext = PyResolveContext.noImplicits().withRemote().withTypeEvalContext(typeEvalContext);
final List<PyCallExpression.PyRatedMarkedCallee> ratedMarkedCallees =
PyUtil.filterTopPriorityResults(call.multiResolveRatedCallee(resolveContext));
final Object[] items = new Object[ratedMarkedCallees.size()];
int currentPosition = 0;
for (PyCallExpression.PyRatedMarkedCallee ratedMarkedCallee : ratedMarkedCallees) {
items[currentPosition] = Pair.createNonNull(call, ratedMarkedCallee.getMarkedCallee());
currentPosition++;
}
context.setItemsToShow(items);
context.setItemsToShow(
PyUtil
.filterTopPriorityResults(call.multiResolveRatedCallee(resolveContext))
.stream()
.map(PyCallExpression.PyRatedMarkedCallee::getMarkedCallee)
.filter(markedCallee -> markedCallee.getCallableType().getParameters(typeEvalContext) != null)
.map(markedCallee -> Pair.createNonNull(call, markedCallee))
.toArray()
);
return argumentList;
}
@@ -177,13 +175,14 @@ public class PyParameterInfoHandler implements ParameterInfoHandler<PyArgumentLi
PyPsiUtils.assertValid(callExpression);
final TypeEvalContext typeEvalContext = TypeEvalContext.userInitiated(callExpression.getProject(), callExpression.getContainingFile());
final PyMarkedCallee markedCallee = callAndCallee.getSecond();
final PyCallExpression.PyArgumentsMapping mapping =
PyCallExpressionHelper.mapArguments(callExpression, callAndCallee.getSecond(), typeEvalContext);
final PyMarkedCallee markedCallee = mapping.getMarkedCallee();
if (markedCallee == null) return;
final List<PyCallableParameter> parameters = markedCallee.getCallableType().getParameters(typeEvalContext);
if (parameters == null) return;
final PyCallExpression.PyArgumentsMapping mapping = PyCallExpressionHelper.mapArguments(callExpression, markedCallee, typeEvalContext);
if (mapping.getMarkedCallee() == null) return;
final List<PyCallableParameter> parameters = PyUtil.getParameters(markedCallee.getCallable(), typeEvalContext);
final Map<Integer, PyCallableParameter> indexToNamedParameter = new HashMap<>(parameters.size());
// param -> hint index. indexes are not contiguous, because some hints are parentheses.
@@ -34,6 +34,7 @@ import com.jetbrains.python.inspections.quickfix.PyChangeSignatureQuickFix;
import com.jetbrains.python.inspections.quickfix.PyRemoveArgumentQuickFix;
import com.jetbrains.python.inspections.quickfix.PyRenameArgumentQuickFix;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.ParamHelper;
import com.jetbrains.python.psi.resolve.PyResolveContext;
import com.jetbrains.python.psi.types.*;
import com.jetbrains.python.refactoring.changeSignature.PyChangeSignatureHandler;
@@ -85,8 +86,10 @@ public class PyArgumentListInspection extends PyInspection {
final PyCallExpression.PyMarkedCallee markedCallee = deco.resolveCallee(getResolveContext());
if (markedCallee != null && !markedCallee.isImplicitlyResolved()) {
final PyCallable callable = markedCallee.getCallable();
if (callable == null) return;
final int firstParamOffset = markedCallee.getImplicitOffset();
final List<PyCallableParameter> params = PyUtil.getParameters(callable, myTypeEvalContext);
final List<PyCallableParameter> params = markedCallee.getCallableType().getParameters(myTypeEvalContext);
if (params == null) return;
final PyCallableParameter allegedFirstParam = ContainerUtil.getOrElse(params, firstParamOffset - 1, null);
if (allegedFirstParam == null || allegedFirstParam.isKeywordContainer()) {
@@ -280,18 +283,22 @@ public class PyArgumentListInspection extends PyInspection {
.of(mappings)
.map(PyCallExpression.PyArgumentsMapping::getMarkedCallee)
.nonNull()
.map(markedCallee -> calculatePossibleCalleeRepresentation(markedCallee.getCallable(), context))
.map(markedCallee -> calculatePossibleCalleeRepresentation(markedCallee, context))
.nonNull()
.collect(Collectors.joining("<br>"));
}
@NotNull
private static String calculatePossibleCalleeRepresentation(@NotNull PyCallable callable, @NotNull TypeEvalContext context) {
final String name = callable.getName();
final String parameters = callable.getParameterList().getPresentableText(true, context);
@Nullable
private static String calculatePossibleCalleeRepresentation(@NotNull PyCallExpression.PyMarkedCallee markedCallee, @NotNull TypeEvalContext context) {
final String name = markedCallee.getCallable() != null ? markedCallee.getCallable().getName() : "";
final List<PyCallableParameter> callableParameters = markedCallee.getCallableType().getParameters(context);
if (callableParameters == null) return null;
final String parameters = ParamHelper.getPresentableText(callableParameters, true, context);
final String callableNameAndParameters = name + parameters;
return Optional
.ofNullable(PyUtil.as(callable, PyFunction.class))
.ofNullable(PyUtil.as(markedCallee.getCallable(), PyFunction.class))
.map(PyFunction::getContainingClass)
.map(PyClass::getName)
.map(className -> PyNames.INIT.equals(name) ? className + parameters : className + "." + callableNameAndParameters)
@@ -93,7 +93,7 @@ public class PyCallByClassInspection extends PyInspection {
final PyCallExpression.PyMarkedCallee markedCallee = mapping.getMarkedCallee();
if (markedCallee != null && markedCallee.getModifier() != STATICMETHOD) {
final PyCallableParameter firstParameter =
ContainerUtil.getFirstItem(PyUtil.getParameters(markedCallee.getCallable(), myTypeEvalContext));
ContainerUtil.getFirstItem(markedCallee.getCallableType().getParameters(myTypeEvalContext));
if (firstParameter != null) {
for (Map.Entry<PyExpression, PyCallableParameter> entry : mapping.getMappedParameters().entrySet()) {
// we ignore *arg and **arg which we cannot analyze
@@ -19,7 +19,9 @@ import com.intellij.util.containers.ContainerUtil;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.types.PyCallableParameter;
import com.jetbrains.python.psi.types.PyCallableParameterImpl;
import com.jetbrains.python.psi.types.TypeEvalContext;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
@@ -68,6 +70,57 @@ public class ParamHelper {
}
}
@NotNull
public static String getPresentableText(@NotNull PyParameter[] parameters,
boolean includeDefaultValue,
@Nullable TypeEvalContext context) {
return getPresentableText(ContainerUtil.map(parameters, PyCallableParameterImpl::new), includeDefaultValue, context);
}
@NotNull
public static String getPresentableText(@NotNull List<PyCallableParameter> parameters,
boolean includeDefaultValue,
@Nullable TypeEvalContext context) {
final StringBuilder result = new StringBuilder();
result.append("(");
walkDownParameters(
parameters,
new ParamHelper.ParamWalker() {
@Override
public void enterTupleParameter(PyTupleParameter param, boolean first, boolean last) {
result.append("(");
}
@Override
public void leaveTupleParameter(PyTupleParameter param, boolean first, boolean last) {
result.append(")");
if (!last) result.append(", ");
}
@Override
public void visitNamedParameter(PyNamedParameter param, boolean first, boolean last) {
visitNonPsiParameter(new PyCallableParameterImpl(param), first, last);
}
@Override
public void visitSingleStarParameter(PySingleStarParameter param, boolean first, boolean last) {
result.append('*');
if (!last) result.append(", ");
}
@Override
public void visitNonPsiParameter(@NotNull PyCallableParameter parameter, boolean first, boolean last) {
result.append(parameter.getPresentableText(includeDefaultValue, context));
if (!last) result.append(", ");
}
}
);
result.append(")");
return result.toString();
}
public interface ParamWalker {
/**
* Is called when a tuple parameter is encountered, before visiting any parameters nested in it.
@@ -132,8 +132,8 @@ public class PyCallExpressionHelper {
}
return forEveryScopeTakeOverloadsOtherwiseImplementations(ratedMarkedCallees, PyCallExpression.PyRatedMarkedCallee::getElement, context)
// while clarifying resolve results we could get duplicate callables so we have to group them and select result with highest rate
.collect(Collectors.groupingBy(markedCallee -> markedCallee.getElement(), LinkedHashMap::new, Collectors.toList()))
// while clarifying resolve results we could get duplicate callable types so we have to group them and select result with highest rate
.collect(Collectors.groupingBy(markedCallee -> markedCallee.getMarkedCallee().getCallableType(), LinkedHashMap::new, Collectors.toList()))
.entrySet()
.stream()
.map(entry -> entry.getValue().stream().max(Comparator.comparingInt(PyCallExpression.PyRatedMarkedCallee::getRate)).orElse(null))
@@ -198,8 +198,14 @@ public class PyCallExpressionHelper {
private static PyCallExpression.PyRatedMarkedCallee markResolveResult(@NotNull ClarifiedResolveResult resolveResult,
@NotNull TypeEvalContext context,
int implicitOffset) {
if (resolveResult.myClarifiedResolved instanceof PyCallable) {
final PyCallable callable = (PyCallable)resolveResult.myClarifiedResolved;
final PsiElement clarifiedResolved = resolveResult.myClarifiedResolved;
if (!(clarifiedResolved instanceof PyTypedElement)) return null;
final PyCallableType callableType = PyUtil.as(context.getType((PyTypedElement)clarifiedResolved), PyCallableType.class);
if (callableType == null) return null;
if (clarifiedResolved instanceof PyCallable) {
final PyCallable callable = (PyCallable)clarifiedResolved;
final PyFunction.Modifier originalModifier = callable instanceof PyFunction ? ((PyFunction)callable).getModifier() : null;
final PyFunction.Modifier resolvedModifier = ObjectUtils.chooseNotNull(originalModifier, resolveResult.myWrappedModifier);
@@ -218,6 +224,7 @@ public class PyCallExpressionHelper {
implicitOffset + getImplicitArgumentCount(callable, resolvedModifier, isConstructorCall, isByInstance, isByClass);
final PyCallExpression.PyMarkedCallee markedCallee = new PyCallExpression.PyMarkedCallee(
callableType,
callable,
resolvedModifier,
Math.max(0, resolvedImplicitOffset), // wrong source can trigger strange behaviour
@@ -227,7 +234,10 @@ public class PyCallExpressionHelper {
return new PyCallExpression.PyRatedMarkedCallee(markedCallee, resolveResult.myOriginalResolveResult.getRate());
}
return null;
return new PyCallExpression.PyRatedMarkedCallee(
new PyCallExpression.PyMarkedCallee(callableType, null, null, implicitOffset, resolveResult.myOriginalResolveResult.isImplicit()),
resolveResult.myOriginalResolveResult.getRate()
);
}
/**
@@ -647,7 +657,9 @@ public class PyCallExpressionHelper {
@NotNull PyArgumentList argumentList,
@NotNull PyCallExpression.PyMarkedCallee markedCallee,
@NotNull TypeEvalContext context) {
final List<PyCallableParameter> parameters = PyUtil.getParameters(markedCallee.getCallable(), context);
final List<PyCallableParameter> parameters = markedCallee.getCallableType().getParameters(context);
if (parameters == null) return PyCallExpression.PyArgumentsMapping.empty(callExpression);
final List<PyCallableParameter> explicitParameters = dropImplicitParameters(parameters, markedCallee.getImplicitOffset());
final List<PyExpression> arguments = Arrays.asList(argumentList.getArguments());
final ArgumentMappingResults mappingResults = analyzeArguments(arguments, explicitParameters);
@@ -679,8 +691,7 @@ public class PyCallExpressionHelper {
public static ArgumentMappingResults mapArguments(@NotNull PyCallSiteExpression callSite,
@NotNull PyCallable callable,
@NotNull TypeEvalContext context) {
final List<PyCallableParameter> parameters = PyUtil.getParameters(callable, context);
return mapArguments(callSite, callable, parameters, context);
return mapArguments(callSite, callable, callable.getParameters(context), context);
}
@NotNull
@@ -879,7 +890,10 @@ public class PyCallExpressionHelper {
boolean containsImplementations = false;
for (E element : elements) {
final boolean overload = PyiUtil.isOverload(mapper.apply(element), context);
final PsiElement mapped = mapper.apply(element);
if (mapped == null) continue;
final boolean overload = PyiUtil.isOverload(mapped, context);
containsOverloads |= overload;
containsImplementations |= !overload;
@@ -59,11 +59,13 @@ public class PyDecoratorImpl extends StubBasedPsiElementBase<PyDecoratorStub> im
return qname != null ? qname.getLastComponent() : null;
}
@Override
@Nullable
public PyFunction getTarget() {
return PsiTreeUtil.getParentOfType(this, PyFunction.class);
}
@Override
public boolean isBuiltin() {
ASTNode node = getNode().findChildByType(PythonDialectsTokenSetProvider.INSTANCE.getReferenceExpressionTokens());
if (node != null) {
@@ -74,11 +76,14 @@ public class PyDecoratorImpl extends StubBasedPsiElementBase<PyDecoratorStub> im
return false;
}
@Override
public boolean hasArgumentList() {
final ASTNode arglistNode = getNode().findChildByType(PyElementTypes.ARGUMENT_LIST);
return (arglistNode != null) && (arglistNode.findChildByType(PyTokenTypes.LPAR) != null);
}
@Override
@Nullable
public QualifiedName getQualifiedName() {
final PyDecoratorStub stub = getStub();
if (stub != null) {
@@ -93,6 +98,8 @@ public class PyDecoratorImpl extends StubBasedPsiElementBase<PyDecoratorStub> im
}
}
@Override
@Nullable
public PyExpression getCallee() {
try {
return (PyExpression)getFirstChild().getNextSibling(); // skip the @ before call
@@ -112,7 +119,8 @@ public class PyDecoratorImpl extends StubBasedPsiElementBase<PyDecoratorStub> im
if (!hasArgumentList()) {
// NOTE: that +1 thing looks fishy
final PyMarkedCallee oldMarkedCallee = ratedMarkedCallee.getMarkedCallee();
final PyMarkedCallee newMarkedCallee = new PyMarkedCallee(oldMarkedCallee.getCallable(),
final PyMarkedCallee newMarkedCallee = new PyMarkedCallee(oldMarkedCallee.getCallableType(),
oldMarkedCallee.getCallable(),
oldMarkedCallee.getModifier(),
oldMarkedCallee.getImplicitOffset() + 1,
oldMarkedCallee.isImplicitlyResolved());
@@ -150,6 +158,8 @@ public class PyDecoratorImpl extends StubBasedPsiElementBase<PyDecoratorStub> im
}
}
@Override
@Nullable
public PyType getType(@NotNull TypeEvalContext context, @NotNull TypeEvalContext.Key key) {
return PyCallExpressionHelper.getCallType(this, context);
}
@@ -27,7 +27,6 @@ import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.PlatformIcons;
import com.intellij.util.Processor;
import com.intellij.util.containers.ContainerUtil;
import com.jetbrains.python.PyElementTypes;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.PyTokenTypes;
@@ -184,20 +183,7 @@ public class PyNamedParameterImpl extends PyBaseElementImpl<PyNamedParameterStub
@Override
@Nullable
public PyType getArgumentType(@NotNull TypeEvalContext context) {
final PyType parameterType = context.getType(this);
if (parameterType instanceof PyCollectionType) {
final PyCollectionType paramCollectionType = (PyCollectionType)parameterType;
if (isPositionalContainer()) {
return paramCollectionType.getIteratedItemType();
}
else if (isKeywordContainer()) {
return ContainerUtil.getOrElse(paramCollectionType.getElementTypes(context), 1, null);
}
}
return parameterType;
return new PyCallableParameterImpl(this).getArgumentType(context);
}
@Override
@@ -123,38 +123,7 @@ public class PyParameterListImpl extends PyBaseElementImpl<PyParameterListStub>
@Override
@NotNull
public String getPresentableText(boolean includeDefaultValue, @Nullable TypeEvalContext context) {
final StringBuilder target = new StringBuilder();
final String COMMA = ", ";
target.append("(");
ParamHelper.walkDownParamArray(
getParameters(),
new ParamHelper.ParamVisitor() {
@Override
public void enterTupleParameter(PyTupleParameter param, boolean first, boolean last) {
target.append("(");
}
@Override
public void leaveTupleParameter(PyTupleParameter param, boolean first, boolean last) {
target.append(")");
if (!last) target.append(COMMA);
}
@Override
public void visitNamedParameter(PyNamedParameter param, boolean first, boolean last) {
target.append(param.getRepr(includeDefaultValue, context));
if (!last) target.append(COMMA);
}
@Override
public void visitSingleStarParameter(PySingleStarParameter param, boolean first, boolean last) {
target.append('*');
if (!last) target.append(COMMA);
}
}
);
target.append(")");
return target.toString();
return ParamHelper.getPresentableText(getParameters(), includeDefaultValue, context);
}
@Nullable
@@ -17,6 +17,7 @@ package com.jetbrains.python.psi.types;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.containers.ContainerUtil;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.documentation.PythonDocumentationProvider;
import com.jetbrains.python.psi.*;
@@ -107,7 +108,8 @@ public class PyCallableParameterImpl implements PyCallableParameter {
if (isPositionalContainer()) sb.append("*");
else if (isKeywordContainer()) sb.append("**");
sb.append(getName());
final String name = getName();
sb.append(name != null ? name : "...");
final PyType argumentType = context == null ? null : getArgumentType(context);
if (argumentType != null) {
@@ -142,8 +144,20 @@ public class PyCallableParameterImpl implements PyCallableParameter {
@Nullable
@Override
public PyType getArgumentType(@NotNull TypeEvalContext context) {
final PyNamedParameter namedParameter = PyUtil.as(myElement, PyNamedParameter.class);
return namedParameter == null ? null : namedParameter.getArgumentType(context);
final PyType parameterType = getType(context);
if (parameterType instanceof PyCollectionType) {
final PyCollectionType collectionType = (PyCollectionType)parameterType;
if (isPositionalContainer()) {
return collectionType.getIteratedItemType();
}
else if (isKeywordContainer()) {
return ContainerUtil.getOrElse(collectionType.getElementTypes(context), 1, null);
}
}
return parameterType;
}
@Override
@@ -28,7 +28,6 @@ import com.jetbrains.python.psi.resolve.RatedResolveResult;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -42,9 +41,11 @@ import static com.jetbrains.python.psi.PyUtil.as;
*/
public class PyFunctionTypeImpl implements PyFunctionType {
@NotNull private final PyCallable myCallable;
@NotNull private final List<PyCallableParameter> myCallableParameters;
public PyFunctionTypeImpl(@NotNull PyCallable callable) {
myCallable = callable;
myCallableParameters = ContainerUtil.map(callable.getParameterList().getParameters(), PyCallableParameterImpl::new);
}
@Nullable
@@ -62,11 +63,7 @@ public class PyFunctionTypeImpl implements PyFunctionType {
@Nullable
@Override
public List<PyCallableParameter> getParameters(@NotNull TypeEvalContext context) {
final List<PyCallableParameter> result = new ArrayList<>();
for (PyParameter parameter : myCallable.getParameterList().getParameters()) {
result.add(new PyCallableParameterImpl(parameter));
}
return result;
return myCallableParameters;
}
@Override
@@ -621,6 +621,7 @@ public class PyTypeChecker {
public static List<AnalyzeCallResults> analyzeCallSite(@NotNull PyCallSiteExpression callSite, @NotNull TypeEvalContext context) {
final List<AnalyzeCallResults> results = new ArrayList<>();
for (PyCallable callable : multiResolveCallee(callSite, context)) {
if (callable == null) continue;
final PyExpression receiver = getReceiver(callSite, callable);
final ArgumentMappingResults mapping = mapArguments(callSite, callable, context);
results.add(new AnalyzeCallResults(callable, receiver, mapping));
@@ -17,7 +17,6 @@ package com.jetbrains.python.pyi;
import com.intellij.openapi.util.Ref;
import com.intellij.psi.PsiElement;
import com.intellij.util.containers.ContainerUtil;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.PyCallExpressionHelper;
import com.jetbrains.python.psi.types.*;
@@ -115,15 +114,17 @@ public class PyiTypeProvider extends PyTypeProviderBase {
final PyType returnType = context.getReturnType(overload);
allReturnTypes.add(PyTypeChecker.substitute(returnType, new HashMap<>(), context));
final PyExpression receiver = PyTypeChecker.getReceiver(callSite, overload);
final PyCallExpressionHelper.ArgumentMappingResults mapping = mapArguments(callSite, overload, context);
if (mapping == null) {
final PyCallExpressionHelper.ArgumentMappingResults mapping = PyCallExpressionHelper.mapArguments(callSite, overload, context);
if (!mapping.getUnmappedArguments().isEmpty() || !mapping.getUnmappedParameters().isEmpty()) {
continue;
}
final PyExpression receiver = PyTypeChecker.getReceiver(callSite, overload);
final Map<PyGenericType, PyType> substitutions = PyTypeChecker.unifyGenericCall(receiver, mapping.getMappedParameters(), context);
if (substitutions == null) {
continue;
}
final PyType unifiedType = PyTypeChecker.substitute(returnType, substitutions, context);
matchedReturnTypes.add(unifiedType);
}
@@ -160,21 +161,4 @@ public class PyiTypeProvider extends PyTypeProviderBase {
}
return null;
}
@Nullable
private static PyCallExpressionHelper.ArgumentMappingResults mapArguments(@NotNull PyCallSiteExpression callSite,
@NotNull PyFunction function,
@NotNull TypeEvalContext context) {
final List<PyCallableParameter> parameters =
ContainerUtil.map(function.getParameterList().getParameters(), PyCallableParameterImpl::new);
final PyCallExpressionHelper.ArgumentMappingResults mapping =
PyCallExpressionHelper.mapArguments(callSite, function, parameters, context);
if (!mapping.getUnmappedArguments().isEmpty() || !mapping.getUnmappedParameters().isEmpty()) {
return null;
}
return mapping;
}
}
@@ -0,0 +1,9 @@
from typing import Callable
def f() -> Callable:
pass
c = f()
print(c(<arg1>))
@@ -0,0 +1,9 @@
from typing import Callable
def f() -> Callable[[int, str], int]:
pass
c = f()
print(c(<arg1>))
@@ -0,0 +1,9 @@
from typing import Callable
def f() -> Callable[..., int]:
pass
c = f()
print(c(<arg1>))
@@ -613,6 +613,43 @@ public class PyParameterInfoTest extends LightMarkedTestCase {
feignCtrlP(offset).check("p: str=\"\\n\", t: str=\"\\t\", r: str=\"\\r\"", new String[]{"p: str=\"\\n\", "});
}
public void testJustTypingCallable() {
runWithLanguageLevel(
LanguageLevel.PYTHON35,
() -> {
final int offset = loadTest(1).get("<arg1>").getTextOffset();
feignCtrlP(offset).check(Collections.emptyList(), Collections.emptyList(), Collections.emptyList());
}
);
}
public void testTypingCallableWithUnknownParameters() {
runWithLanguageLevel(
LanguageLevel.PYTHON35,
() -> {
final int offset = loadTest(1).get("<arg1>").getTextOffset();
feignCtrlP(offset).check(Collections.emptyList(), Collections.emptyList(), Collections.emptyList());
}
);
}
public void testTypingCallableWithKnownParameters() {
runWithLanguageLevel(
LanguageLevel.PYTHON35,
() -> {
final int offset = loadTest(1).get("<arg1>").getTextOffset();
final List<String> texts = Collections.singletonList("...: int, ...: str");
final List<String[]> highlighted = Collections.singletonList(new String[]{"...: int, "});
feignCtrlP(offset).check(texts, highlighted, Collections.singletonList(ArrayUtil.EMPTY_STRING_ARRAY));
}
);
}
/**
* Imitates pressing of Ctrl+P; fails if results are not as expected.
* @param offset offset of 'cursor' where Ctrl+P is pressed.