diff --git a/python/helpers/StdlibTypes.properties b/python/helpers/StdlibTypes.properties index 0cc9d7d78116..e823e665a859 100644 --- a/python/helpers/StdlibTypes.properties +++ b/python/helpers/StdlibTypes.properties @@ -777,7 +777,7 @@ __builtin__.str.replace = \ :type old: string \n\ :type new: string \n\ :type count: int or long \n\ - :rtype: bytes \n\ + :rtype: string \n\ __builtin__.str.rfind = \ :type sub: string \n\ 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/PyNames.java b/python/psi-api/src/com/jetbrains/python/PyNames.java index 526a77b57c6c..f70a06c33ef1 100644 --- a/python/psi-api/src/com/jetbrains/python/PyNames.java +++ b/python/psi-api/src/com/jetbrains/python/PyNames.java @@ -136,6 +136,7 @@ public class PyNames { public static final String NOT_IMPLEMENTED_ERROR = "NotImplementedError"; + public static final String UNKNOWN_TYPE = "unknown"; /** * Contains all known predefined names of "__foo__" form. 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/debugger/PySignatureUtil.java b/python/src/com/jetbrains/python/debugger/PySignatureUtil.java index e44b0573b0f6..fbacc612ef07 100644 --- a/python/src/com/jetbrains/python/debugger/PySignatureUtil.java +++ b/python/src/com/jetbrains/python/debugger/PySignatureUtil.java @@ -4,6 +4,7 @@ import com.google.common.base.Function; import com.google.common.collect.Collections2; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiElement; +import com.jetbrains.python.PyNames; import com.jetbrains.python.psi.PyClass; import com.jetbrains.python.psi.PyFunction; import com.jetbrains.python.psi.types.PyClassType; @@ -49,7 +50,7 @@ public class PySignatureUtil { return type.getName(); } else { - return "unknown"; + return PyNames.UNKNOWN_TYPE; } } diff --git a/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java b/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java index c92dfc33bb92..8e6db24d6d8b 100644 --- a/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java +++ b/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java @@ -4,17 +4,20 @@ 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.psi.PyFunction; +import com.jetbrains.python.PyNames; +import com.jetbrains.python.psi.Callable; import com.jetbrains.python.psi.PyNamedParameter; import com.jetbrains.python.psi.PyParameter; import com.jetbrains.python.psi.types.*; import com.jetbrains.python.toolbox.ChainIterable; -import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.ArrayList; import java.util.Collection; +import java.util.List; import java.util.Map; import static com.jetbrains.python.documentation.DocumentationBuilderKit.$; @@ -24,7 +27,6 @@ import static com.jetbrains.python.documentation.DocumentationBuilderKit.combUp; * @author traff */ public class PyTypeModelBuilder { - @NonNls static final String UNKNOWN = "unknown"; private final Map myVisited = Maps.newHashMap(); private final TypeEvalContext myContext; @@ -62,11 +64,11 @@ public class PyTypeModelBuilder { static class CollectionOf extends TypeModel { private String collectionName; - private TypeModel elementType; + private List elementTypes; - private CollectionOf(String collectionName, TypeModel elementType) { + private CollectionOf(String collectionName, List elementTypes) { this.collectionName = collectionName; - this.elementType = elementType; + this.elementTypes = elementTypes; } @Override @@ -88,16 +90,34 @@ public class PyTypeModelBuilder { } } + static class UnknownType extends TypeModel { + private final TypeModel type; + + private UnknownType(TypeModel type) { + this.type = type; + } + + @Override + void accept(TypeVisitor visitor) { + visitor.unknown(this); + } + } + private static TypeModel _(String name) { return new NamedType(name); } 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; } @@ -137,7 +157,7 @@ public class PyTypeModelBuilder { return evaluated; } if (myVisited.containsKey(type)) { //already evaluating? - return type != null ? _(type.getName()) : _(UNKNOWN); + return type != null ? _(type.getName()) : _(PyNames.UNKNOWN_TYPE); } myVisited.put(type, null); //mark as evaluating @@ -145,13 +165,24 @@ public class PyTypeModelBuilder { if (type instanceof PyCollectionType) { final String name = type.getName(); final PyType elementType = ((PyCollectionType)type).getElementType(myContext); - if (elementType != null) { - result = new CollectionOf(name, build(elementType, true)); + final List elementTypes = new ArrayList(); + if (elementType instanceof PyTupleType) { + final PyTupleType tupleType = (PyTupleType)elementType; + final int n = tupleType.getElementCount(); + for (int i = 0; i < n; i++) { + elementTypes.add(build(tupleType.getElementType(i), true)); + } + } + else if (elementType != null) { + elementTypes.add(build(elementType, true)); + } + if (!elementTypes.isEmpty()) { + result = new CollectionOf(name, elementTypes); } } else if (type instanceof PyUnionType && allowUnions) { - if (type instanceof PyDynamicallyEvaluatedType) { - result = build(((PyDynamicallyEvaluatedType)type).exclude(null, myContext), true); + if (type instanceof PyDynamicallyEvaluatedType || PyTypeChecker.isUnknown(type)) { + result = new UnknownType(build(((PyUnionType)type).excludeNull(), true)); } else { result = new OneOf( @@ -163,29 +194,45 @@ public class PyTypeModelBuilder { })); } } + else if (type instanceof PyCallableType && !(type instanceof PyClassLikeType)) { + result = build((PyCallableType)type); + } if (result == null) { - result = type != null ? _(type.getName()) : _(UNKNOWN); + result = type != null ? _(type.getName()) : _(PyNames.UNKNOWN_TYPE); } myVisited.put(type, result); return result; } + 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(PyFunction function) { - final PyType returnType = function.getReturnType(myContext, null); - return new FunctionType(build(returnType, true), Collections2.transform(Lists.newArrayList(function.getParameterList().getParameters()), + 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) { final PyNamedParameter np = p.getAsNamed(); if (np != null) { - TypeModel paramType = - _(UNKNOWN); + TypeModel paramType = _(PyNames.UNKNOWN_TYPE); final PyType t = myContext.getType(np); 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); } @@ -202,6 +249,8 @@ public class PyTypeModelBuilder { void function(FunctionType type); void param(ParamType text); + + void unknown(UnknownType type); } private static class TypeToStringVisitor extends TypeNameVisitor { @@ -220,6 +269,15 @@ public class PyTypeModelBuilder { public String getString() { return myStringBuilder.toString(); } + + @Override + public void unknown(UnknownType type) { + final TypeModel nested = type.type; + if (nested != null) { + nested.accept(this); + } + add(" | " + PyNames.UNKNOWN_TYPE); + } } private static class TypeToBodyWithLinksVisitor extends TypeNameVisitor { @@ -260,17 +318,15 @@ public class PyTypeModelBuilder { add("..."); return; } - add("one of ("); - processListCommaSeparated(oneOf.oneOfTypes); - add(")"); + processList(oneOf.oneOfTypes, " | "); myDepth--; } - private void processListCommaSeparated(Collection list) { + private void processList(Collection list, String separator) { boolean first = true; for (TypeModel t : list) { if (!first) { - add(", "); + add(separator); } else { first = false; @@ -290,8 +346,9 @@ public class PyTypeModelBuilder { return; } addType(collectionOf.collectionName); - add(" of "); - collectionOf.elementType.accept(this); + add("["); + processList(collectionOf.elementTypes, ", "); + add("]"); myDepth--; } @@ -310,10 +367,15 @@ public class PyTypeModelBuilder { return; } add("("); - processListCommaSeparated(function.parameters); + final Collection parameters = function.parameters; + if (parameters != null) { + processList(parameters, ", "); + } + else { + add("..."); + } add(") -> "); function.returnType.accept(this); - add("\n"); myDepth--; } @@ -335,5 +397,10 @@ public class PyTypeModelBuilder { } myDepth--; } + + @Override + public void unknown(UnknownType type) { + type.type.accept(this); + } } } 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/inspections/PyTypeCheckerInspection.java b/python/src/com/jetbrains/python/inspections/PyTypeCheckerInspection.java index ffb8dc978b02..9002e762105f 100644 --- a/python/src/com/jetbrains/python/inspections/PyTypeCheckerInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyTypeCheckerInspection.java @@ -63,7 +63,7 @@ public class PyTypeCheckerInspection extends PyInspection { final PyExpression source = node.getForPart().getSource(); if (source != null) { final PyType type = myTypeEvalContext.getType(source); - if (type != null && !PyABCUtil.isSubtype(type, PyNames.ITERABLE)) { + if (!PyTypeChecker.isUnknown(type) && !PyABCUtil.isSubtype(type, PyNames.ITERABLE)) { registerProblem(source, String.format("Expected 'collections.Iterable', got '%s' instead", PythonDocumentationProvider.getTypeName(type, myTypeEvalContext))); } diff --git a/python/src/com/jetbrains/python/psi/impl/PyFunctionImpl.java b/python/src/com/jetbrains/python/psi/impl/PyFunctionImpl.java index c61693c075d7..81392e382669 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyFunctionImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyFunctionImpl.java @@ -252,7 +252,7 @@ public class PyFunctionImpl extends PyPresentableElementImpl imp final PyBuiltinCache cache = PyBuiltinCache.getInstance(this); final PyClass listClass = cache.getClass("list"); final PyStatementList statements = getStatementList(); - final Set types = new HashSet(); + final Set types = new LinkedHashSet(); if (statements != null && listClass != null) { statements.accept(new PyRecursiveElementVisitor() { @Override diff --git a/python/src/com/jetbrains/python/psi/impl/PyNamedParameterImpl.java b/python/src/com/jetbrains/python/psi/impl/PyNamedParameterImpl.java index ddf4f1445291..f9f175265e56 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyNamedParameterImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyNamedParameterImpl.java @@ -2,7 +2,6 @@ package com.jetbrains.python.psi.impl; import com.intellij.lang.ASTNode; import com.intellij.openapi.extensions.Extensions; -import com.intellij.openapi.util.Ref; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiReference; @@ -27,6 +26,8 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; +import java.util.ArrayList; +import java.util.List; import java.util.Map; /** @@ -235,7 +236,7 @@ public class PyNamedParameterImpl extends PyPresentableElementImpl ref = Ref.create(null); + final List types = new ArrayList(); processLocalCalls(func, new Processor() { @Override public boolean process(@NotNull PyCallExpression call) { @@ -247,8 +248,8 @@ public class PyNamedParameterImpl extends PyPresentableElementImpl myParameterTypes; + @Nullable private final List> myParameters; @Nullable private final PyType myReturnType; - public PyCallableTypeImpl(@NotNull List parameterTypes, @Nullable PyType returnType) { - myParameterTypes = parameterTypes; + public PyCallableTypeImpl(@Nullable List> parameters, @Nullable PyType returnType) { + myParameters = parameters; myReturnType = returnType; } @@ -38,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 @@ -60,15 +62,30 @@ public class PyCallableTypeImpl implements PyCallableType { @Override public String getName() { return String.format("(%s) -> %s", - StringUtil.join(myParameterTypes, - new Function() { + myParameters != null ? + StringUtil.join(myParameters, + new Function, String>() { @Override - public String fun(PyType type) { - return type != null ? type.getName() : "unknown"; + 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; } }, - ", "), - myReturnType != null ? myReturnType.getName() : "unknown"); + ", ") : + "...", + myReturnType != null ? myReturnType.getName() : PyNames.UNKNOWN_TYPE); } @Override 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/PyDynamicallyEvaluatedType.java b/python/src/com/jetbrains/python/psi/types/PyDynamicallyEvaluatedType.java index 837a861be096..a8b83501373f 100644 --- a/python/src/com/jetbrains/python/psi/types/PyDynamicallyEvaluatedType.java +++ b/python/src/com/jetbrains/python/psi/types/PyDynamicallyEvaluatedType.java @@ -1,5 +1,6 @@ package com.jetbrains.python.psi.types; +import com.jetbrains.python.PyNames; import org.jetbrains.annotations.NotNull; import java.util.ArrayList; @@ -34,6 +35,6 @@ public class PyDynamicallyEvaluatedType extends PyUnionType { @Override public String getName() { PyType res = excludeNull(); - return res != null ? res.getName() : "unknown"; + return res != null ? res.getName() : PyNames.UNKNOWN_TYPE; } } 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/PyGenericType.java b/python/src/com/jetbrains/python/psi/types/PyGenericType.java index 20e1986872dc..bf0c5565a14c 100644 --- a/python/src/com/jetbrains/python/psi/types/PyGenericType.java +++ b/python/src/com/jetbrains/python/psi/types/PyGenericType.java @@ -39,7 +39,7 @@ public class PyGenericType implements PyType { @NotNull @Override public String getName() { - return myBound != null ? myName + " (" + myBound.getName() + ")" : myName; + return myBound != null ? myName + " <= " + myBound.getName() : myName; } @Override diff --git a/python/src/com/jetbrains/python/psi/types/PyTupleType.java b/python/src/com/jetbrains/python/psi/types/PyTupleType.java index c4157f1d3ce4..533116388006 100644 --- a/python/src/com/jetbrains/python/psi/types/PyTupleType.java +++ b/python/src/com/jetbrains/python/psi/types/PyTupleType.java @@ -42,9 +42,9 @@ public class PyTupleType extends PyClassTypeImpl implements PySubscriptableType return "(" + StringUtil.join(myElementTypes, new Function() { @Nullable public String fun(PyType pyType) { - return pyType == null ? "unknown" : pyType.getName(); + return pyType == null ? PyNames.UNKNOWN_TYPE : pyType.getName(); } - }, ",") + ")"; + }, ", ") + ")"; } @Override diff --git a/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java b/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java index 429524e49c24..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; } } @@ -228,6 +232,18 @@ public class PyTypeChecker { collectGenerics(tuple.getElementType(i), context, collected, visited); } } + else if (type instanceof PyCallableType) { + final PyCallableType callable = (PyCallableType)type; + final List> parameters = callable.getParameters(context); + if (parameters != null) { + for (Pair parameter : parameters) { + if (parameter != null) { + collectGenerics(parameter.getSecond(), context, collected, visited); + } + } + } + collectGenerics(callable.getCallType(context, null), context, collected, visited); + } } @Nullable @@ -262,6 +278,19 @@ public class PyTypeChecker { } return new PyTupleType((PyTupleType)type, results.toArray(new PyType[results.size()])); } + else if (type instanceof PyCallableType) { + final PyCallableType callable = (PyCallableType)type; + List> substParams = null; + final List> parameters = callable.getParameters(context); + if (parameters != null) { + 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); + return new PyCallableTypeImpl(substParams, substResult); + } } return type; } diff --git a/python/src/com/jetbrains/python/psi/types/PyTypeParser.java b/python/src/com/jetbrains/python/psi/types/PyTypeParser.java index 8af0776f1825..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"); @@ -449,7 +449,7 @@ public class PyTypeParser { final String name = token.getText().toString(); final TextRange range = token.getRange(); - if ("unknown".equals(name)) { + if (PyNames.UNKNOWN_TYPE.equals(name)) { return EMPTY_RESULT; } else if (PyNames.NONE.equals(name)) { diff --git a/python/src/com/jetbrains/python/psi/types/PyUnionType.java b/python/src/com/jetbrains/python/psi/types/PyUnionType.java index 2d56e8f291b2..c69beca25529 100644 --- a/python/src/com/jetbrains/python/psi/types/PyUnionType.java +++ b/python/src/com/jetbrains/python/psi/types/PyUnionType.java @@ -53,11 +53,11 @@ public class PyUnionType implements PyType { } public String getName() { - return "one of (" + StringUtil.join(myMembers, new NullableFunction() { - public String fun(PyType pyType) { - return pyType == null ? "unknown" : pyType.getName(); + return StringUtil.join(myMembers, new NullableFunction() { + public String fun(PyType type) { + return type != null ? type.getName() : null; } - }, ", ") + ")"; + }, " | "); } /** diff --git a/python/testData/inspections/PyCallingNonCallableInspection/tupleNonCallable.py b/python/testData/inspections/PyCallingNonCallableInspection/tupleNonCallable.py index a4e04009336a..3433394d67c6 100644 --- a/python/testData/inspections/PyCallingNonCallableInspection/tupleNonCallable.py +++ b/python/testData/inspections/PyCallingNonCallableInspection/tupleNonCallable.py @@ -1 +1 @@ -(1,2)() +(1,2)() diff --git a/python/testData/inspections/PyTypeCheckerInspection/BoundedGeneric.py b/python/testData/inspections/PyTypeCheckerInspection/BoundedGeneric.py index 3d79685273a2..7fe25baa2b2a 100644 --- a/python/testData/inspections/PyTypeCheckerInspection/BoundedGeneric.py +++ b/python/testData/inspections/PyTypeCheckerInspection/BoundedGeneric.py @@ -1,12 +1,12 @@ def test(): def f(x): """ - :type x: T <= int or str + :type x: T <= int | str :rtype: T """ pass x = f(10) y = f('foo') - z = f([]) - return x + y + z = f([]) + return x + y diff --git a/python/testData/inspections/PyTypeCheckerInspection/BuiltinNumeric.py b/python/testData/inspections/PyTypeCheckerInspection/BuiltinNumeric.py index 27b7db5cf6e8..41a6d03b9ae9 100644 --- a/python/testData/inspections/PyTypeCheckerInspection/BuiltinNumeric.py +++ b/python/testData/inspections/PyTypeCheckerInspection/BuiltinNumeric.py @@ -9,4 +9,4 @@ def test(): u'bar') pow(False, True) round(False, - 'foo') + 'foo') diff --git a/python/testData/inspections/PyTypeCheckerInspection/DictGenerics.py b/python/testData/inspections/PyTypeCheckerInspection/DictGenerics.py index 46f6ff06ac25..028614f594ff 100644 --- a/python/testData/inspections/PyTypeCheckerInspection/DictGenerics.py +++ b/python/testData/inspections/PyTypeCheckerInspection/DictGenerics.py @@ -5,4 +5,4 @@ def test_dict_generics(d): xs = d.items() d2 = dict(xs) for k, v in d2.items(): - print k + v + print k + v diff --git a/python/testData/inspections/PyTypeCheckerInspection/DictLiterals.py b/python/testData/inspections/PyTypeCheckerInspection/DictLiterals.py index f6d79460c4e4..533bccc2235f 100644 --- a/python/testData/inspections/PyTypeCheckerInspection/DictLiterals.py +++ b/python/testData/inspections/PyTypeCheckerInspection/DictLiterals.py @@ -1,8 +1,8 @@ def test(): xs = {'foo': 1, 'bar': 2} for v in xs.values(): - print(v + None) + print(v + None) for k in xs.keys(): - print(k + None) + print(k + None) for k in xs: - print(k + None) + print(k + None) diff --git a/python/testData/inspections/PyTypeCheckerInspection/ForLoopIteration.py b/python/testData/inspections/PyTypeCheckerInspection/ForLoopIteration.py index 7182a08b8a92..7c67dc4b5e67 100644 --- a/python/testData/inspections/PyTypeCheckerInspection/ForLoopIteration.py +++ b/python/testData/inspections/PyTypeCheckerInspection/ForLoopIteration.py @@ -1,6 +1,16 @@ -def test(): +def test(p1): for x in 'foo': pass for x in 42: pass + + for x in f('foo', p1): + pass + + +def f(c, x): + if c: + return 10 + else: + return x 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/testData/inspections/PyTypeCheckerInspection/Generator.py b/python/testData/inspections/PyTypeCheckerInspection/Generator.py index b14de72cb06d..870b822aad6b 100644 --- a/python/testData/inspections/PyTypeCheckerInspection/Generator.py +++ b/python/testData/inspections/PyTypeCheckerInspection/Generator.py @@ -79,19 +79,19 @@ def test(): return xs return [ ''.join(gen(10)), - f_1(gen(11)), - f_2(gen(11)), - f_3(gen(11)), - f_4(gen(11)), - f_5(gen(11)), - f_6(gen(11)), - f_7(gen(11)), - f_8(gen(11)), + f_1(gen(11)), + f_2(gen(11)), + f_3(gen(11)), + f_4(gen(11)), + f_5(gen(11)), + f_6(gen(11)), + f_7(gen(11)), + f_8(gen(11)), f_9(gen(11)), f_10(gen(11)), - f_11(gen(11)), - f_12(gen(11)), - f_13(gen(11)), + f_11(gen(11)), + f_12(gen(11)), + f_13(gen(11)), f_14(gen(11)), f_15(gen(11)), f_15('foo'.split('o')), diff --git a/python/testData/inspections/PyTypeCheckerInspection/GenericUserClasses.py b/python/testData/inspections/PyTypeCheckerInspection/GenericUserClasses.py index d8a5472a0408..c114d411997a 100644 --- a/python/testData/inspections/PyTypeCheckerInspection/GenericUserClasses.py +++ b/python/testData/inspections/PyTypeCheckerInspection/GenericUserClasses.py @@ -20,7 +20,7 @@ def test_generic_user_class(): self.x = value c = User1(10) - print(c.get() + 'foo') + print(c.get() + 'foo') c.put(14) c.put('foo') diff --git a/python/testData/inspections/PyTypeCheckerInspection/GenericUserFunctions.py b/python/testData/inspections/PyTypeCheckerInspection/GenericUserFunctions.py index f2a882768c90..65590ceca6fe 100644 --- a/python/testData/inspections/PyTypeCheckerInspection/GenericUserFunctions.py +++ b/python/testData/inspections/PyTypeCheckerInspection/GenericUserFunctions.py @@ -38,9 +38,9 @@ def test(): result = f1([1, 2, 3]) print(result) - print(result + 'foo') + print(result + 'foo') - f2(1, ['foo'], 'bar') + f2(1, ['foo'], 'bar') result = f3(1, 'foo', True) - f4(result) + f4(result) diff --git a/python/testData/inspections/PyTypeCheckerInspection/ListTuple.py b/python/testData/inspections/PyTypeCheckerInspection/ListTuple.py index 95306f40c8eb..2dfd05166f70 100644 --- a/python/testData/inspections/PyTypeCheckerInspection/ListTuple.py +++ b/python/testData/inspections/PyTypeCheckerInspection/ListTuple.py @@ -7,5 +7,5 @@ def f(spam, eggs): def test(): - f([1, 2, 3], - (False, 2, '')) + f([1, 2, 3], + (False, 2, '')) diff --git a/python/testData/inspections/PyTypeCheckerInspection/LocalTypeResolve.py b/python/testData/inspections/PyTypeCheckerInspection/LocalTypeResolve.py index 9d86b619c827..01bf260199ce 100644 --- a/python/testData/inspections/PyTypeCheckerInspection/LocalTypeResolve.py +++ b/python/testData/inspections/PyTypeCheckerInspection/LocalTypeResolve.py @@ -5,4 +5,4 @@ def test(): c = C() x = c.f() y = x - return y + 'foo' + return y + 'foo' diff --git a/python/testData/inspections/PyTypeCheckerInspection/MapReturnElementType.py b/python/testData/inspections/PyTypeCheckerInspection/MapReturnElementType.py index 39ea03805b54..85f08d6dfc18 100644 --- a/python/testData/inspections/PyTypeCheckerInspection/MapReturnElementType.py +++ b/python/testData/inspections/PyTypeCheckerInspection/MapReturnElementType.py @@ -1,5 +1,5 @@ def test(): xs = map(lambda x: x + 1, [1, 2, 3]) - print('foo' + xs[0]) + print('foo' + xs[0]) ys = map(str, iter([1, 2, 3])) - print(1 + ys[0], 'bar' + ys[1]) + print(1 + ys[0], 'bar' + ys[1]) diff --git a/python/testData/inspections/PyTypeCheckerInspection/PropertyAndFactoryFunction.py b/python/testData/inspections/PyTypeCheckerInspection/PropertyAndFactoryFunction.py index 008864cede03..8cf0bfcce4a9 100644 --- a/python/testData/inspections/PyTypeCheckerInspection/PropertyAndFactoryFunction.py +++ b/python/testData/inspections/PyTypeCheckerInspection/PropertyAndFactoryFunction.py @@ -7,4 +7,4 @@ def f(): return C() def test(): - f().foo + 1 + f().foo + 1 diff --git a/python/testData/inspections/PyTypeCheckerInspection/RightOperators.py b/python/testData/inspections/PyTypeCheckerInspection/RightOperators.py index 5a90ed0890ce..3e58c273927c 100644 --- a/python/testData/inspections/PyTypeCheckerInspection/RightOperators.py +++ b/python/testData/inspections/PyTypeCheckerInspection/RightOperators.py @@ -5,5 +5,5 @@ class C(object): def test_right_operators(): o = C() xs = [ - o * [], + o * [], ] diff --git a/python/testData/inspections/PyTypeCheckerInspection/StringInteger.py b/python/testData/inspections/PyTypeCheckerInspection/StringInteger.py index a551543eddec..ce379047b8ed 100644 --- a/python/testData/inspections/PyTypeCheckerInspection/StringInteger.py +++ b/python/testData/inspections/PyTypeCheckerInspection/StringInteger.py @@ -1,8 +1,8 @@ def test(): print('foo' + 'bar') print(2 + 3) - print('foo' + 3) - print(3 + 'foo') + print('foo' + 3) + print(3 + 'foo') print('foo' + 'bar' * 3) print('foo' + 3 * 'bar') - print('foo' + 2 * 3) + print('foo' + 2 * 3) diff --git a/python/testData/inspections/PyTypeCheckerInspection/UnionReturnTypes.py b/python/testData/inspections/PyTypeCheckerInspection/UnionReturnTypes.py index 76888e9a902f..ba8ec2d9f536 100644 --- a/python/testData/inspections/PyTypeCheckerInspection/UnionReturnTypes.py +++ b/python/testData/inspections/PyTypeCheckerInspection/UnionReturnTypes.py @@ -17,7 +17,7 @@ def test(): """ x1 = f1(42) f2(x1) # Weaker union types - f3(x1) + f3(x1) f2(x1.count('')) f3(x1.count('')) diff --git a/python/testData/quickdoc/HoverOverControlFlowUnion.html b/python/testData/quickdoc/HoverOverControlFlowUnion.html index 056d6ba8ce3c..5b9ce6b5ca02 100644 --- a/python/testData/quickdoc/HoverOverControlFlowUnion.html +++ b/python/testData/quickdoc/HoverOverControlFlowUnion.html @@ -1,2 +1,2 @@ variable "x" -Inferred type: one of (str, int) +Inferred type: str | int diff --git a/python/testData/quickdoc/Variable.html b/python/testData/quickdoc/Variable.html index 62d6e80474b1..eac55f8d9c13 100644 --- a/python/testData/quickdoc/Variable.html +++ b/python/testData/quickdoc/Variable.html @@ -1 +1 @@ -Assigned to y

Inferred type: one of (int, str)
\ No newline at end of file +Assigned to y

Inferred type: int | str
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/PyTypeTest.java b/python/testSrc/com/jetbrains/python/PyTypeTest.java index 2348a1039500..2046a3230983 100644 --- a/python/testSrc/com/jetbrains/python/PyTypeTest.java +++ b/python/testSrc/com/jetbrains/python/PyTypeTest.java @@ -25,18 +25,18 @@ public class PyTypeTest extends PyTestCase { } public void testBinaryExprType() { - doTest("int or long or float or complex", + doTest("int", "expr = 1 + 2"); - doTest("str or unicode", + doTest("str | unicode", "expr = '1' + '2'"); - doTest("str or unicode", + doTest("str", "expr = '%s' % ('a')"); - doTest("list", + doTest("list[int]", "expr = [1] + [2]"); } public void testAssignmentChainBinaryExprType() { - doTest("int or long or float or complex", + doTest("int", "class C(object):\n" + " def __add__(self, other):\n" + " return -1\n" + @@ -56,7 +56,7 @@ public class PyTypeTest extends PyTestCase { } public void testUnionOfTuples() { - doTest("(int or str, str or int)", + doTest("(int | str, str | int)", "def x():\n" + " if True:\n" + " return (1, 'a')\n" + @@ -80,7 +80,7 @@ public class PyTypeTest extends PyTestCase { } public void testSet() { - doTest("set of int", + doTest("set[int]", "expr = {1, 2, 3}"); } @@ -112,10 +112,18 @@ public class PyTypeTest extends PyTestCase { } public void testPropertyType() { - doTest("unknown", + doTest("property", "class C:\n" + - " x = property(lambda self: object(), None, None)\n" + - "expr = C.x"); + " x = property(lambda self: 'foo', None, None)\n" + + "expr = C.x\n"); + } + + public void testPropertyInstanceType() { + doTest("str", + "class C:\n" + + " x = property(lambda self: 'foo', None, None)\n" + + "c = C()\n" + + "expr = c.x\n"); } public void testIterationType() { @@ -129,7 +137,7 @@ public class PyTypeTest extends PyTestCase { } public void testSliceType() { - doTest("list of int", + doTest("list[int]", "l = [1, 2, 3]; expr = l[0:1]"); } @@ -215,15 +223,12 @@ public class PyTypeTest extends PyTestCase { } public void testUnknownTypeInUnion() { - final String text = "def f(c, x):\n" + - " if c:\n" + - " return 1\n" + - " return x\n" + - "expr = f(1, g())\n"; - PyExpression expr = parseExpr(text); - PyType t = getTypeEvalContext(expr).getType(expr); - assertTrue(PyTypeChecker.isUnknown(t)); - doTest("int", text); + doTest("int | unknown", + "def f(c, x):\n" + + " if c:\n" + + " return 1\n" + + " return x\n" + + "expr = f(1, g())\n"); } public void testIsInstance() { @@ -341,7 +346,7 @@ public class PyTypeTest extends PyTestCase { public void testYieldFromType() { PythonLanguageLevelPusher.setForcedLanguageLevel(myFixture.getProject(), LanguageLevel.PYTHON33); try { - doTest("str or int or float", + doTest("str | int | float", "def subgen():\n" + " for i in [1, 2, 3]:\n" + " yield i\n" + @@ -429,7 +434,7 @@ public class PyTypeTest extends PyTestCase { // PY-7215 public void testFunctionWithNestedGenerator() { - doTest("list of int", + doTest("list[int]", "def f():\n" + " def g():\n" + " yield 10\n" + @@ -468,7 +473,7 @@ public class PyTypeTest extends PyTestCase { // EA-40207 public void testRecursion() { - doTest("list of list", + doTest("list[list]", "def f():\n" + " return [f()]\n" + "expr = f()\n"); @@ -533,7 +538,7 @@ public class PyTypeTest extends PyTestCase { } public void testLogicalAndExpression() { - doTest("str or int", + doTest("str | int", "expr = 'foo' and 2"); } @@ -552,16 +557,17 @@ public class PyTypeTest extends PyTestCase { } public void testParameterFromUsages() { - doTest("int", + doTest("int | str | unknown", "def foo(bar):\n" + " expr = bar\n" + "def use_foo(x):\n" + " foo(x)\n" + - " foo(3)\n"); + " foo(3)\n" + + " foo('bar')\n"); } public void testUpperBoundGeneric() { - doTest("int or str", + doTest("int | str", "def foo(x):\n" + " '''\n" + " :type x: T <= int or str\n" + @@ -649,20 +655,11 @@ public class PyTypeTest extends PyTestCase { return myFixture.findElementByText("expr", PyExpression.class); } - private static String msg(PyType expected, PyType actual, TypeEvalContext context) { - return String.format("Expected: %s, actual: %s", - PythonDocumentationProvider.getTypeName(expected, context), - PythonDocumentationProvider.getTypeName(actual, context)); - } - private void doTest(final String expectedType, final String text) { PyExpression expr = parseExpr(text); TypeEvalContext context = getTypeEvalContext(expr); PyType actual = context.getType(expr); - PyType expected = PyTypeParser.getTypeByName(expr, expectedType); - if (expected != null) { - assertNotNull(context.printTrace(), actual); - assertTrue(msg(expected, actual, context), PyTypeChecker.match(expected, actual, context)); - } + final String actualType = PythonDocumentationProvider.getTypeName(actual, context); + assertEquals(expectedType, actualType); } } 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(); + } }