diff --git a/python/psi-api/src/com/jetbrains/python/psi/PyCallExpression.java b/python/psi-api/src/com/jetbrains/python/psi/PyCallExpression.java index 8b78522df015..bf42d69d6016 100644 --- a/python/psi-api/src/com/jetbrains/python/psi/PyCallExpression.java +++ b/python/psi-api/src/com/jetbrains/python/psi/PyCallExpression.java @@ -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 foo() or foo.bar[1]('x'). @@ -195,7 +194,11 @@ public interface PyCallExpression extends PyCallSiteExpression { */ @NotNull default List 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 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(); diff --git a/python/src/com/jetbrains/python/PyParameterInfoHandler.java b/python/src/com/jetbrains/python/PyParameterInfoHandler.java index fbd3280f64ea..7ddc6fbf58a7 100644 --- a/python/src/com/jetbrains/python/PyParameterInfoHandler.java +++ b/python/src/com/jetbrains/python/PyParameterInfoHandler.java @@ -74,17 +74,15 @@ public class PyParameterInfoHandler implements ParameterInfoHandler 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 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 parameters = PyUtil.getParameters(markedCallee.getCallable(), typeEvalContext); final Map indexToNamedParameter = new HashMap<>(parameters.size()); // param -> hint index. indexes are not contiguous, because some hints are parentheses. diff --git a/python/src/com/jetbrains/python/inspections/PyArgumentListInspection.java b/python/src/com/jetbrains/python/inspections/PyArgumentListInspection.java index bbfa68128e20..4dc94f61eec0 100644 --- a/python/src/com/jetbrains/python/inspections/PyArgumentListInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyArgumentListInspection.java @@ -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 params = PyUtil.getParameters(callable, myTypeEvalContext); + final List 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("
")); } - @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 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) diff --git a/python/src/com/jetbrains/python/inspections/PyCallByClassInspection.java b/python/src/com/jetbrains/python/inspections/PyCallByClassInspection.java index 4a4df4d305fc..da183c35f8fe 100644 --- a/python/src/com/jetbrains/python/inspections/PyCallByClassInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyCallByClassInspection.java @@ -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 entry : mapping.getMappedParameters().entrySet()) { // we ignore *arg and **arg which we cannot analyze diff --git a/python/src/com/jetbrains/python/psi/impl/ParamHelper.java b/python/src/com/jetbrains/python/psi/impl/ParamHelper.java index ed96f2fe3ca2..9321d433e9e3 100644 --- a/python/src/com/jetbrains/python/psi/impl/ParamHelper.java +++ b/python/src/com/jetbrains/python/psi/impl/ParamHelper.java @@ -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 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. diff --git a/python/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java b/python/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java index 3b4b33a7e5cb..c1ed6784dcd1 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java +++ b/python/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java @@ -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 parameters = PyUtil.getParameters(markedCallee.getCallable(), context); + final List parameters = markedCallee.getCallableType().getParameters(context); + if (parameters == null) return PyCallExpression.PyArgumentsMapping.empty(callExpression); + final List explicitParameters = dropImplicitParameters(parameters, markedCallee.getImplicitOffset()); final List 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 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; diff --git a/python/src/com/jetbrains/python/psi/impl/PyDecoratorImpl.java b/python/src/com/jetbrains/python/psi/impl/PyDecoratorImpl.java index 4b45067e4369..424e7e946d4e 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyDecoratorImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyDecoratorImpl.java @@ -59,11 +59,13 @@ public class PyDecoratorImpl extends StubBasedPsiElementBase 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 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 im } } + @Override + @Nullable public PyExpression getCallee() { try { return (PyExpression)getFirstChild().getNextSibling(); // skip the @ before call @@ -112,7 +119,8 @@ public class PyDecoratorImpl extends StubBasedPsiElementBase 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 im } } + @Override + @Nullable public PyType getType(@NotNull TypeEvalContext context, @NotNull TypeEvalContext.Key key) { return PyCallExpressionHelper.getCallType(this, context); } diff --git a/python/src/com/jetbrains/python/psi/impl/PyNamedParameterImpl.java b/python/src/com/jetbrains/python/psi/impl/PyNamedParameterImpl.java index 82d330e8929f..a8fc92ca43e1 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyNamedParameterImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyNamedParameterImpl.java @@ -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 @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 diff --git a/python/src/com/jetbrains/python/psi/types/PyCallableParameterImpl.java b/python/src/com/jetbrains/python/psi/types/PyCallableParameterImpl.java index 87328d37b1f5..73457f17eefe 100644 --- a/python/src/com/jetbrains/python/psi/types/PyCallableParameterImpl.java +++ b/python/src/com/jetbrains/python/psi/types/PyCallableParameterImpl.java @@ -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 diff --git a/python/src/com/jetbrains/python/psi/types/PyFunctionTypeImpl.java b/python/src/com/jetbrains/python/psi/types/PyFunctionTypeImpl.java index 8a8ad243a6cb..b05bfbd2d6dc 100644 --- a/python/src/com/jetbrains/python/psi/types/PyFunctionTypeImpl.java +++ b/python/src/com/jetbrains/python/psi/types/PyFunctionTypeImpl.java @@ -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 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 getParameters(@NotNull TypeEvalContext context) { - final List result = new ArrayList<>(); - for (PyParameter parameter : myCallable.getParameterList().getParameters()) { - result.add(new PyCallableParameterImpl(parameter)); - } - return result; + return myCallableParameters; } @Override diff --git a/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java b/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java index 2c929aa1aae8..c77d7aa8763f 100644 --- a/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java +++ b/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java @@ -621,6 +621,7 @@ public class PyTypeChecker { public static List analyzeCallSite(@NotNull PyCallSiteExpression callSite, @NotNull TypeEvalContext context) { final List 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)); diff --git a/python/src/com/jetbrains/python/pyi/PyiTypeProvider.java b/python/src/com/jetbrains/python/pyi/PyiTypeProvider.java index b2d316b012c9..7049da74d1fd 100644 --- a/python/src/com/jetbrains/python/pyi/PyiTypeProvider.java +++ b/python/src/com/jetbrains/python/pyi/PyiTypeProvider.java @@ -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 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 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; - } } diff --git a/python/testData/paramInfo/JustTypingCallable.py b/python/testData/paramInfo/JustTypingCallable.py new file mode 100644 index 000000000000..20a526021c44 --- /dev/null +++ b/python/testData/paramInfo/JustTypingCallable.py @@ -0,0 +1,9 @@ +from typing import Callable + + +def f() -> Callable: + pass + + +c = f() +print(c()) diff --git a/python/testData/paramInfo/TypingCallableWithKnownParameters.py b/python/testData/paramInfo/TypingCallableWithKnownParameters.py new file mode 100644 index 000000000000..cd84864b6606 --- /dev/null +++ b/python/testData/paramInfo/TypingCallableWithKnownParameters.py @@ -0,0 +1,9 @@ +from typing import Callable + + +def f() -> Callable[[int, str], int]: + pass + + +c = f() +print(c()) diff --git a/python/testData/paramInfo/TypingCallableWithUnknownParameters.py b/python/testData/paramInfo/TypingCallableWithUnknownParameters.py new file mode 100644 index 000000000000..e5f1b231b9ec --- /dev/null +++ b/python/testData/paramInfo/TypingCallableWithUnknownParameters.py @@ -0,0 +1,9 @@ +from typing import Callable + + +def f() -> Callable[..., int]: + pass + + +c = f() +print(c()) diff --git a/python/testSrc/com/jetbrains/python/PyParameterInfoTest.java b/python/testSrc/com/jetbrains/python/PyParameterInfoTest.java index c06179c45d66..5164fd8a81c3 100644 --- a/python/testSrc/com/jetbrains/python/PyParameterInfoTest.java +++ b/python/testSrc/com/jetbrains/python/PyParameterInfoTest.java @@ -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("").getTextOffset(); + + feignCtrlP(offset).check(Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); + } + ); + } + + public void testTypingCallableWithUnknownParameters() { + runWithLanguageLevel( + LanguageLevel.PYTHON35, + () -> { + final int offset = loadTest(1).get("").getTextOffset(); + + feignCtrlP(offset).check(Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); + } + ); + } + + public void testTypingCallableWithKnownParameters() { + runWithLanguageLevel( + LanguageLevel.PYTHON35, + () -> { + + final int offset = loadTest(1).get("").getTextOffset(); + + final List texts = Collections.singletonList("...: int, ...: str"); + final List 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.