diff --git a/python/pluginSrc/com/jetbrains/python/psi/impl/PyJavaClassType.java b/python/pluginSrc/com/jetbrains/python/psi/impl/PyJavaClassType.java index 7c3a3ac34cc1..83f9e1476d87 100644 --- a/python/pluginSrc/com/jetbrains/python/psi/impl/PyJavaClassType.java +++ b/python/pluginSrc/com/jetbrains/python/psi/impl/PyJavaClassType.java @@ -98,7 +98,7 @@ public class PyJavaClassType implements PyClassLikeType { @Nullable @Override - public List getParameterTypes(@NotNull TypeEvalContext context) { + public List getParameters(@NotNull TypeEvalContext context) { return null; } diff --git a/python/pluginSrc/com/jetbrains/python/psi/impl/PyJavaMethodType.java b/python/pluginSrc/com/jetbrains/python/psi/impl/PyJavaMethodType.java index 2594a1eed1f4..9916559f07d1 100644 --- a/python/pluginSrc/com/jetbrains/python/psi/impl/PyJavaMethodType.java +++ b/python/pluginSrc/com/jetbrains/python/psi/impl/PyJavaMethodType.java @@ -41,7 +41,7 @@ public class PyJavaMethodType implements PyCallableType { @Nullable @Override - public List getParameterTypes(@NotNull TypeEvalContext context) { + public List getParameters(@NotNull TypeEvalContext context) { return null; } diff --git a/python/psi-api/src/com/jetbrains/python/psi/types/PyCallableType.java b/python/psi-api/src/com/jetbrains/python/psi/types/PyCallableType.java index 83c544fe9762..482df90af817 100644 --- a/python/psi-api/src/com/jetbrains/python/psi/types/PyCallableType.java +++ b/python/psi-api/src/com/jetbrains/python/psi/types/PyCallableType.java @@ -1,5 +1,6 @@ package com.jetbrains.python.psi.types; +import com.intellij.openapi.util.Pair; import com.jetbrains.python.psi.PyQualifiedExpression; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -31,8 +32,8 @@ public interface PyCallableType extends PyType { /** * Returns the list of parameter types. * - * @return list of positional parameters or null if not applicable. + * @return list of (name, type) pairs or null if not applicable. Name and type in pair may be null. */ @Nullable - List getParameterTypes(@NotNull TypeEvalContext context); + List> getParameters(@NotNull TypeEvalContext context); } diff --git a/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java b/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java index fec9dc880c2d..3876dc6300dd 100644 --- a/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java +++ b/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java @@ -4,9 +4,10 @@ import com.google.common.base.Function; import com.google.common.collect.Collections2; import com.google.common.collect.Lists; import com.google.common.collect.Maps; +import com.intellij.openapi.util.Pair; import com.intellij.psi.PsiElement; import com.jetbrains.python.PyNames; -import com.jetbrains.python.psi.PyFunction; +import com.jetbrains.python.psi.Callable; import com.jetbrains.python.psi.PyNamedParameter; import com.jetbrains.python.psi.PyParameter; import com.jetbrains.python.psi.types.*; @@ -108,10 +109,15 @@ public class PyTypeModelBuilder { static class FunctionType extends TypeModel { private TypeModel returnType; - private Collection parameters; + @Nullable private Collection parameters; - FunctionType(@NotNull TypeModel returnType, Collection parameters) { - this.returnType = returnType; + FunctionType(@Nullable TypeModel returnType, @Nullable Collection parameters) { + if (returnType != null) { + this.returnType = returnType; + } + else { + this.returnType = _(PyNames.UNKNOWN_TYPE); + } this.parameters = parameters; } @@ -188,6 +194,9 @@ public class PyTypeModelBuilder { })); } } + else if (type instanceof PyCallableType && !(type instanceof PyClassLikeType)) { + result = build((PyCallableType)type); + } if (result == null) { result = type != null ? _(type.getName()) : _(PyNames.UNKNOWN_TYPE); } @@ -195,9 +204,23 @@ public class PyTypeModelBuilder { return result; } - public TypeModel build(PyFunction function) { - final PyType returnType = function.getReturnType(myContext, null); - return new FunctionType(build(returnType, true), Collections2.transform(Lists.newArrayList(function.getParameterList().getParameters()), + private TypeModel build(@NotNull PyCallableType type) { + List parameterModels = null; + final List> parameters = type.getParameters(myContext); + if (parameters != null) { + parameterModels = new ArrayList(); + for (Pair parameter : parameters) { + parameterModels.add(new ParamType(parameter.getFirst(), build(parameter.getSecond(), true))); + } + } + final PyType ret = type.getCallType(myContext, null); + final TypeModel returnType = build(ret, true); + return new FunctionType(returnType, parameterModels); + } + + public TypeModel build(Callable callable) { + final PyType returnType = callable.getReturnType(myContext, null); + return new FunctionType(build(returnType, true), Collections2.transform(Lists.newArrayList(callable.getParameterList().getParameters()), new Function() { @Override public TypeModel apply(PyParameter p) { @@ -208,7 +231,8 @@ public class PyTypeModelBuilder { if (t != null) { paramType = build(t, true); } - return new ParamType(np.getName(), paramType); + final String name = PyFunctionType.getParameterName(np); + return new ParamType(name, paramType); } return new ParamType(p.toString(), null); } @@ -343,10 +367,15 @@ public class PyTypeModelBuilder { return; } add("("); - processListCommaSeparated(function.parameters, ", "); + final Collection parameters = function.parameters; + if (parameters != null) { + processListCommaSeparated(parameters, ", "); + } + else { + add("..."); + } add(") -> "); function.returnType.accept(this); - add("\n"); myDepth--; } diff --git a/python/src/com/jetbrains/python/documentation/PythonDocumentationProvider.java b/python/src/com/jetbrains/python/documentation/PythonDocumentationProvider.java index 6ee637fe6f10..9a040841ae74 100644 --- a/python/src/com/jetbrains/python/documentation/PythonDocumentationProvider.java +++ b/python/src/com/jetbrains/python/documentation/PythonDocumentationProvider.java @@ -126,6 +126,7 @@ public class PythonDocumentationProvider extends AbstractDocumentationProvider i if (!PyNames.INIT.equals(name)) { cat.addItem(escaper.apply("\nInferred type: ")); getTypeDescription(fun, cat); + cat.addItem(BR); } return cat; } diff --git a/python/src/com/jetbrains/python/psi/types/PyCallableTypeImpl.java b/python/src/com/jetbrains/python/psi/types/PyCallableTypeImpl.java index e57ad127db8a..8b3cb754b437 100644 --- a/python/src/com/jetbrains/python/psi/types/PyCallableTypeImpl.java +++ b/python/src/com/jetbrains/python/psi/types/PyCallableTypeImpl.java @@ -1,5 +1,6 @@ package com.jetbrains.python.psi.types; +import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.text.StringUtil; import com.intellij.util.Function; import com.intellij.util.ProcessingContext; @@ -18,11 +19,11 @@ import java.util.List; * @author vlan */ public class PyCallableTypeImpl implements PyCallableType { - @Nullable private final List myParameterTypes; + @Nullable private final List> myParameters; @Nullable private final PyType myReturnType; - public PyCallableTypeImpl(@Nullable List parameterTypes, @Nullable PyType returnType) { - myParameterTypes = parameterTypes; + public PyCallableTypeImpl(@Nullable List> parameters, @Nullable PyType returnType) { + myParameters = parameters; myReturnType = returnType; } @@ -39,8 +40,8 @@ public class PyCallableTypeImpl implements PyCallableType { @Nullable @Override - public List getParameterTypes(@NotNull TypeEvalContext context) { - return myParameterTypes; + public List> getParameters(@NotNull TypeEvalContext context) { + return myParameters; } @Nullable @@ -61,12 +62,25 @@ public class PyCallableTypeImpl implements PyCallableType { @Override public String getName() { return String.format("(%s) -> %s", - myParameterTypes != null ? - StringUtil.join(myParameterTypes, - new Function() { + myParameters != null ? + StringUtil.join(myParameters, + new Function, String>() { @Override - public String fun(PyType type) { - return type != null ? type.getName() : PyNames.UNKNOWN_TYPE; + public String fun(Pair param) { + if (param != null) { + final StringBuilder builder = new StringBuilder(); + final String name = param.getFirst(); + final PyType type = param.getSecond(); + if (name != null) { + builder.append(name); + if (type != null) { + builder.append(": "); + } + } + builder.append(type != null ? type.getName() : PyNames.UNKNOWN_TYPE); + return builder.toString(); + } + return PyNames.UNKNOWN_TYPE; } }, ", ") : diff --git a/python/src/com/jetbrains/python/psi/types/PyClassTypeImpl.java b/python/src/com/jetbrains/python/psi/types/PyClassTypeImpl.java index 2ca0fbcf2c02..446aba417165 100644 --- a/python/src/com/jetbrains/python/psi/types/PyClassTypeImpl.java +++ b/python/src/com/jetbrains/python/psi/types/PyClassTypeImpl.java @@ -279,7 +279,7 @@ public class PyClassTypeImpl extends UserDataHolderBase implements PyClassType { @Nullable @Override - public List getParameterTypes(@NotNull TypeEvalContext context) { + public List> getParameters(@NotNull TypeEvalContext context) { return null; } diff --git a/python/src/com/jetbrains/python/psi/types/PyFunctionType.java b/python/src/com/jetbrains/python/psi/types/PyFunctionType.java index f0d3a7b76372..20072ba86284 100644 --- a/python/src/com/jetbrains/python/psi/types/PyFunctionType.java +++ b/python/src/com/jetbrains/python/psi/types/PyFunctionType.java @@ -1,5 +1,6 @@ package com.jetbrains.python.psi.types; +import com.intellij.openapi.util.Pair; import com.intellij.util.ProcessingContext; import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.resolve.PyResolveContext; @@ -7,6 +8,7 @@ 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; @@ -35,8 +37,15 @@ public class PyFunctionType implements PyCallableType { @Nullable @Override - public List getParameterTypes(@NotNull TypeEvalContext context) { - return null; + public List> getParameters(@NotNull TypeEvalContext context) { + final List> result = new ArrayList>(); + for (PyParameter parameter : myCallable.getParameterList().getParameters()) { + if (parameter instanceof PyNamedParameter) { + final PyNamedParameter namedParameter = (PyNamedParameter)parameter; + result.add(Pair.create(getParameterName(namedParameter), context.getType(namedParameter))); + } + } + return result; } @Override @@ -70,4 +79,16 @@ public class PyFunctionType implements PyCallableType { public Callable getCallable() { return myCallable; } + + @Nullable + public static String getParameterName(@NotNull PyNamedParameter namedParameter) { + String name = namedParameter.getName(); + if (namedParameter.isPositionalContainer()) { + name = "*" + name; + } + else if (namedParameter.isKeywordContainer()) { + name = "**" + name; + } + return name; + } } diff --git a/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java b/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java index bbbf097021f5..4c3660dcbdb3 100644 --- a/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java +++ b/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java @@ -1,5 +1,6 @@ package com.jetbrains.python.psi.types; +import com.intellij.openapi.util.Pair; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiPolyVariantReference; import com.intellij.psi.PsiReference; @@ -144,12 +145,15 @@ public class PyTypeChecker { final PyCallableType expectedCallable = (PyCallableType)expected; final PyCallableType actualCallable = (PyCallableType)actual; if (expectedCallable.isCallable() && actualCallable.isCallable()) { - final List expectedParameters = expectedCallable.getParameterTypes(context); - final List actualParameters = actualCallable.getParameterTypes(context); + final List> expectedParameters = expectedCallable.getParameters(context); + final List> actualParameters = actualCallable.getParameters(context); if (expectedParameters != null && actualParameters != null) { final int size = Math.min(expectedParameters.size(), actualParameters.size()); for (int i = 0; i < size; i++) { - if (!match(expectedParameters.get(i), actualParameters.get(i), context, substitutions, recursive)) { + final Pair expectedParam = expectedParameters.get(i); + final Pair actualParam = actualParameters.get(i); + // TODO: Check named and star params, not only positional ones + if (!match(expectedParam.getSecond(), actualParam.getSecond(), context, substitutions, recursive)) { return false; } } @@ -230,10 +234,12 @@ public class PyTypeChecker { } else if (type instanceof PyCallableType) { final PyCallableType callable = (PyCallableType)type; - final List parameters = callable.getParameterTypes(context); + final List> parameters = callable.getParameters(context); if (parameters != null) { - for (PyType parameter : parameters) { - collectGenerics(parameter, context, collected, visited); + for (Pair parameter : parameters) { + if (parameter != null) { + collectGenerics(parameter.getSecond(), context, collected, visited); + } } } collectGenerics(callable.getCallType(context, null), context, collected, visited); @@ -274,12 +280,12 @@ public class PyTypeChecker { } else if (type instanceof PyCallableType) { final PyCallableType callable = (PyCallableType)type; - List substParams = null; - final List parameters = callable.getParameterTypes(context); + List> substParams = null; + final List> parameters = callable.getParameters(context); if (parameters != null) { - substParams = new ArrayList(); - for (PyType parameter : parameters) { - substParams.add(substitute(parameter, substitutions, context)); + substParams = new ArrayList>(); + for (Pair parameter : parameters) { + substParams.add(Pair.create(parameter.getFirst(), substitute(parameter.getSecond(), substitutions, context))); } } final PyType substResult = substitute(callable.getCallType(context, null), substitutions, context); diff --git a/python/src/com/jetbrains/python/psi/types/PyTypeParser.java b/python/src/com/jetbrains/python/psi/types/PyTypeParser.java index 1e57982053b9..7aab2400b333 100644 --- a/python/src/com/jetbrains/python/psi/types/PyTypeParser.java +++ b/python/src/com/jetbrains/python/psi/types/PyTypeParser.java @@ -230,7 +230,7 @@ public class PyTypeParser { new Function>, ParseResult>, ParseResult>() { @Override public ParseResult fun(Pair>, ParseResult> value) { - final List parameterTypes = new ArrayList(); + final List> parameters = new ArrayList>(); final ParseResult returnResult = value.getSecond(); ParseResult result; final Pair> firstPair = value.getFirst(); @@ -238,17 +238,17 @@ public class PyTypeParser { final ParseResult first = firstPair.getFirst(); final List second = firstPair.getSecond(); result = first; - parameterTypes.add(first.getType()); + parameters.add(Pair.create(null, first.getType())); for (ParseResult r : second) { result = result.merge(r); - parameterTypes.add(r.getType()); + parameters.add(Pair.create(null, r.getType())); } result = result.merge(returnResult); } else { result = returnResult; } - return result.withType(new PyCallableTypeImpl(parameterTypes, returnResult.getType())); + return result.withType(new PyCallableTypeImpl(parameters, returnResult.getType())); } }) .named("callable-expr"); diff --git a/python/testData/inspections/PyTypeCheckerInspection/FunctionParameterReturnType.py b/python/testData/inspections/PyTypeCheckerInspection/FunctionParameterReturnType.py new file mode 100644 index 000000000000..6d43c2ce0a27 --- /dev/null +++ b/python/testData/inspections/PyTypeCheckerInspection/FunctionParameterReturnType.py @@ -0,0 +1,28 @@ +def func(f, seq): + """ + :param f: my param + :type f: (unknown) -> str + :rtype: list[str] + """ + return [f(v) for v in seq] + + +def f(x): + return int(x) + + +def test(): + for item in func(f, []): + pass + + for item in func(int, []): + pass + + for item in func(lambda x: int(x), []): + pass + + for item in func(lambda x: str(x), []): + pass + + for item in func(str, []): + pass diff --git a/python/testSrc/com/jetbrains/python/PyTypeParserTest.java b/python/testSrc/com/jetbrains/python/PyTypeParserTest.java index ed6850b99fea..105893830283 100644 --- a/python/testSrc/com/jetbrains/python/PyTypeParserTest.java +++ b/python/testSrc/com/jetbrains/python/PyTypeParserTest.java @@ -1,5 +1,6 @@ package com.jetbrains.python; +import com.intellij.openapi.util.Pair; import com.jetbrains.python.fixtures.PyTestCase; import com.jetbrains.python.psi.types.*; @@ -236,11 +237,11 @@ public class PyTypeParserTest extends PyTestCase { assertNotNull(callableType); final PyType returnType = callableType.getCallType(getTypeEvalContext(), null); assertInstanceOf(returnType, PyGenericType.class); - final List parameterTypes = callableType.getParameterTypes(getTypeEvalContext()); + final List> parameterTypes = callableType.getParameters(getTypeEvalContext()); assertNotNull(parameterTypes); assertEquals(2, parameterTypes.size()); - assertEquals("int", parameterTypes.get(0).getName()); - assertEquals("T", parameterTypes.get(1).getName()); + assertEquals("int", parameterTypes.get(0).getSecond().getName()); + assertEquals("T", parameterTypes.get(1).getSecond().getName()); } public void testCallableWithoutArgs() { @@ -252,7 +253,7 @@ public class PyTypeParserTest extends PyTestCase { final PyType returnType = callableType.getCallType(getTypeEvalContext(), null); assertNotNull(returnType); assertEquals("int", returnType.getName()); - final List parameterTypes = callableType.getParameterTypes(getTypeEvalContext()); + final List> parameterTypes = callableType.getParameters(getTypeEvalContext()); assertNotNull(parameterTypes); assertEquals(0, parameterTypes.size()); } diff --git a/python/testSrc/com/jetbrains/python/inspections/PyTypeCheckerInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyTypeCheckerInspectionTest.java index 1bd48b30da31..98441d80bd33 100644 --- a/python/testSrc/com/jetbrains/python/inspections/PyTypeCheckerInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/PyTypeCheckerInspectionTest.java @@ -198,4 +198,9 @@ public class PyTypeCheckerInspectionTest extends PyTestCase { public void testMapReturnElementType() { doTest(); } + + // PY-10413 + public void testFunctionParameterReturnType() { + doTest(); + } }