mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
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:
committed by
Semyon Proshev
parent
f79376a519
commit
edb827e655
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user