diff --git a/python/helpers/StdlibTypes.properties b/python/helpers/StdlibTypes.properties index 8a25a725d5ec..9e4299b0d484 100644 --- a/python/helpers/StdlibTypes.properties +++ b/python/helpers/StdlibTypes.properties @@ -10,14 +10,6 @@ __builtin__.open = \ :type errors: string or None \n\ :rtype: file \n\ -__builtin__.open.1 = \ - :type mode: unicode \n\ - :rtype: io.TextIOWrapper of unicode \n\ - -__builtin__.open.2 = \ - :type mode: bytes \n\ - :rtype: io.FileIO of bytes \n\ - ## 5.4. Numeric types @@ -1291,8 +1283,8 @@ struct.Struct.unpack_from = \ ## 7.5. StringIO StringIO.StringIO.__init__ = \ - :type buf: T \n\ - :rtype: StringIO of T (bytes or unicode) \n\ + :type buf: T <= bytes or unicode \n\ + :rtype: StringIO of T \n\ StringIO.StringIO.getvalue = \ :rtype: T \n\ @@ -1334,19 +1326,11 @@ StringIO.StringIO.__iter__ = \ ## 7.6. cStringIO cStringIO.StringIO = \ - :type s: string \n\ - :rtype: cStringIO.OutputType of string \n\ - -cStringIO.StringIO.1 = \ - :type s: bytes \n\ - :rtype: cStringIO.OutputType of bytes \n\ - -cStringIO.StringIO.2 = \ - :type s: unicode \n\ - :rtype: cStringIO.OutputType of unicode \n\ + :type s: T <= bytes or unicode \n\ + :rtype: cStringIO.OutputType of T \n\ cStringIO.OutputType.__init__ = \ - :rtype: cStringIO.OutputType of T \n\ + :rtype: cStringIO.OutputType of T <= bytes or unicode \n\ cStringIO.OutputType.getvalue = \ :rtype: T \n\ @@ -1435,16 +1419,6 @@ datetime.date.__sub__ = \ :type other: datetime.date or datetime.timedelta \n\ :rtype: datetime.date or datetime.timedelta \n\ -datetime.date.__sub__.1 = \ - :type y: datetime.date \n\ - :type other: datetime.date \n\ - :rtype: datetime.timedelta \n\ - -datetime.date.__sub__.2 = \ - :type y: datetime.timedelta \n\ - :type other: datetime.timedelta \n\ - :rtype: datetime.date \n\ - datetime.date.__rsub__ = \ :type y: datetime.date \n\ :type other: datetime.date \n\ @@ -1467,24 +1441,9 @@ datetime.ime.tzname = \ :rtype: string \n\ datetime.timedelta.__add__ = \ - :type y: datetime.timedelta or datetime.date or datetime.datetime \n\ - :type other: datetime.timedelta or datetime.date or datetime.datetime \n\ - :rtype: datetime.timedelta or datetime.date or datetime.datetime \n\ - -datetime.timedelta.__add__.1 = \ - :type y: datetime.timedelta \n\ - :type other: datetime.timedelta \n\ - :rtype: datetime.timedelta \n\ - -datetime.timedelta.__add__.2 = \ - :type y: datetime.datetime \n\ - :type other: datetime.datetime \n\ - :rtype: datetime.datetime \n\ - -datetime.timedelta.__add__.3 = \ - :type y: datetime.date \n\ - :type other: datetime.date \n\ - :rtype: datetime.date \n\ + :type y: T <= datetime.timedelta or datetime.date or datetime.datetime \n\ + :type other: T <= datetime.timedelta or datetime.date or datetime.datetime \n\ + :rtype: T \n\ datetime.timedelta.__radd__ = \ :type y: datetime.timedelta or datetime.date or datetime.datetime \n\ @@ -1616,21 +1575,6 @@ datetime.datetime.__sub__ = \ :type other: datetime.datetime or datetime.timedelta \n\ :rtype: datetime.datetime or datetime.timedelta \n\ -datetime.datetime.__sub__.1 = \ - :type y: datetime.datetime \n\ - :type other: datetime.datetime \n\ - :rtype: datetime.timedelta \n\ - -datetime.datetime.__sub__.2 = \ - :type y: datetime.timedelta \n\ - :type other: datetime.timedelta \n\ - :rtype: datetime.datetime \n\ - -datetime.datetime.__rsub__ = \ - :type y: datetime.datetime \n\ - :type other: datetime.datetime \n\ - :rtype: datetime.timedelta \n\ - ## 8.3. collections @@ -1892,52 +1836,20 @@ decimal.Decimal.__neg__ = \ # 10.1. os.path os.path.abspath = \ - :type path: bytes or unicode \n\ - :rtype: bytes or unicode \n\ - -os.path.abspath.1 = \ - :type path: bytes \n\ - :rtype: bytes \n\ - -os.path.abspath.2 = \ - :type path: unicode \n\ - :rtype: unicode \n\ + :type path: T <= bytes or unicode \n\ + :rtype: T \n\ os.path.basename = \ - :type p: bytes or unicode \n\ - :rtype: bytes or unicode \n\ - -os.path.basename.1 = \ - :type p: bytes \n\ - :rtype: bytes \n\ - -os.path.basename.2 = \ - :type p: unicode \n\ - :rtype: unicode \n\ + :type p: T <= bytes or unicode \n\ + :rtype: T \n\ os.path.commonprefix = \ - :type m: collections.Iterable of (bytes or unicode) \n\ - :rtype: bytes or unicode \n\ - -os.path.commonprefix.1 = \ - :type m: collections.Iterable of bytes \n\ - :rtype: bytes \n\ - -os.path.commonprefix.2 = \ - :type m: collections.Iterable of unicode \n\ - :rtype: unicode \n\ + :type m: collections.Iterable of T <= bytes or unicode \n\ + :rtype: T \n\ os.path.dirname = \ - :type p: bytes or unicode \n\ - :rtype: bytes or unicode \n\ - -os.path.dirname.1 = \ - :type p: bytes \n\ - :rtype: bytes \n\ - -os.path.dirname.2 = \ - :type p: unicode \n\ - :rtype: unicode \n\ + :type p: T <= bytes or unicode \n\ + :rtype: T \n\ os.path.exists = \ :type path: bytes or unicode \n\ @@ -1948,28 +1860,12 @@ os.path.lexists = \ :rtype: bool \n\ os.path.expanduser = \ - :type path: bytes or unicode \n\ - :rtype: bytes or unicode \n\ - -os.path.expanduser.1 = \ - :type path: bytes \n\ - :rtype: bytes \n\ - -os.path.expanduser.2 = \ - :type path: unicode \n\ - :rtype: unicode \n\ + :type path: T <= bytes or unicode \n\ + :rtype: T \n\ os.path.expandvars = \ - :type path: bytes or unicode \n\ - :rtype: bytes or unicode \n\ - -os.path.expandvars.1 = \ - :type path: bytes \n\ - :rtype: bytes \n\ - -os.path.expandvars.2 = \ - :type path: unicode \n\ - :rtype: unicode \n\ + :type path: T <= bytes or unicode \n\ + :rtype: T \n\ os.path.getatime = \ :type filename: bytes or unicode \n\ @@ -2008,65 +1904,25 @@ os.path.ismount = \ :rtype: bool \n\ os.path.join = \ - :type a: bytes or unicode \n\ - :rtype: bytes or unicode \n\ - -os.path.join.1 = \ - :type a: bytes \n\ - :rtype: bytes \n\ - -os.path.join.2 = \ - :type a: unicode \n\ - :rtype: unicode \n\ + :type a: T <= bytes or unicode \n\ + :rtype: T \n\ os.path.normcase = \ - :type s: bytes or unicode \n\ - :rtype: bytes or unicode \n\ - -os.path.normcase.1 = \ - :type s: bytes \n\ - :rtype: bytes \n\ - -os.path.normcase.2 = \ - :type s: unicode \n\ - :rtype: unicode \n\ + :type s: T <= bytes or unicode \n\ + :rtype: T \n\ os.path.normpath = \ - :type path: bytes or unicode \n\ - :rtype: bytes or unicode \n\ - -os.path.normpath.1 = \ - :type path: bytes \n\ - :rtype: bytes \n\ - -os.path.normpath.2 = \ - :type path: unicode \n\ - :rtype: unicode \n\ + :type path: T <= bytes or unicode \n\ + :rtype: T \n\ os.path.realpath = \ - :type filename: bytes or unicode \n\ + :type filename: T <= bytes or unicode \n\ :rtype: bytes or unicode \n\ -os.path.realpath.1 = \ - :type filename: bytes \n\ - :rtype: bytes \n\ - -os.path.realpath.2 = \ - :type filename: unicode \n\ - :rtype: unicode \n\ - os.path.relpath = \ - :type path: bytes or unicode \n\ + :type path: T <= bytes or unicode \n\ :type start: bytes or unicode \n\ - :rtype: bytes or unicode \n\ - -os.path.relpath.1 = \ - :type path: bytes \n\ - :rtype: bytes \n\ - -os.path.relpath.2 = \ - :type path: unicode \n\ - :rtype: unicode \n\ + :rtype: T \n\ os.path.samefile = \ :type f1: bytes or unicode \n\ @@ -2084,52 +1940,20 @@ os.path.samestat = \ :rtype: bool \n\ os.path.split = \ - :type p: bytes or unicode \n\ - :rtype: (bytes or unicode, bytes or unicode) \n\ - -os.path.split.1 = \ - :type p: bytes \n\ - :rtype: (bytes, bytes) \n\ - -os.path.split.2 = \ - :type p: unicode \n\ - :rtype: (unicode, unicode) \n\ + :type p: T <= bytes or unicode \n\ + :rtype: (T, T) \n\ os.path.splitdrive = \ - :type p: bytes or unicode \n\ - :rtype: (bytes or unicode, bytes or unicode) \n\ - -os.path.splitdrive.1 = \ - :type p: bytes \n\ - :rtype: (bytes, bytes) \n\ - -os.path.splitdrive.2 = \ - :type p: unicode \n\ - :rtype: (unicode, unicode) \n\ + :type p: T <= bytes or unicode \n\ + :rtype: (T, T) \n\ os.path.splitext = \ - :type p: bytes or unicode \n\ - :rtype: (bytes or unicode, bytes or unicode) \n\ - -os.path.splitext.1 = \ - :type p: bytes \n\ - :rtype: (bytes, bytes) \n\ - -os.path.splitext.2 = \ - :type p: unicode \n\ - :rtype: (unicode, unicode) \n\ + :type p: T <= bytes or unicode \n\ + :rtype: (T, T) \n\ os.path.splitunc = \ - :type p: bytes or unicode \n\ - :rtype: (bytes or unicode, bytes or unicode) \n\ - -os.path.splitunc.1 = \ - :type p: bytes \n\ - :rtype: (bytes, bytes) \n\ - -os.path.splitunc.2 = \ - :type p: unicode \n\ - :rtype: (unicode, unicode) \n\ + :type p: T <= bytes or unicode \n\ + :rtype: (T, T) \n\ os.path.walk = \ :type top: bytes or unicode \n\ @@ -2345,14 +2169,6 @@ os.fdopen = \ :type bufsize: int \n\ :rtype: file \n\ -os.fdopen.1 = \ - :type mode: unicode \n\ - :rtype: io.TextIOWrapper of unicode \n\ - -os.fdopen.2 = \ - :type mode: bytes \n\ - :rtype: io.FileIO of bytes \n\ - os.popen = \ :type command: string \n\ :type mode: string \n\ @@ -2525,12 +2341,8 @@ os.link = \ :rtype: None \n\ os.listdir = \ - :type path: bytes or unicode \n\ - :rtype: list of string \n\ - -os.listdir.1 = \ - :type path: unicode \n\ - :rtype: list of unicode \n\ + :type path: T <= bytes or unicode \n\ + :rtype: list of T \n\ os.lstat = \ :type path: bytes or unicode \n\ @@ -2575,12 +2387,8 @@ os.pathconf = \ :type name: int or string \n\ os.readlink = \ - :type path: bytes or unicode \n\ - :rtype: string \n\ - -os.readlink.1 = \ - :type path: unicode \n\ - :rtype: unicode \n\ + :type path: T <= bytes or unicode \n\ + :rtype: T \n\ os.remove = \ :type path: bytes or unicode \n\ @@ -2640,22 +2448,10 @@ os.utime = \ :rtype: None \n\ os.walk = \ - :type top: bytes or unicode \n\ + :type top: T <= bytes or unicode \n\ :type topdown: bool \n\ :type followlinks: bool \n\ - :rtype: collections.Iterable of (string, list of string, list of string) \n\ - -os.walk.1 = \ - :type top: bytes \n\ - :type topdown: bool \n\ - :type followlinks: bool \n\ - :rtype: collections.Iterable of (bytes, list of bytes, list of bytes) \n\ - -os.walk.2 = \ - :type top: unicode \n\ - :type topdown: bool \n\ - :type followlinks: bool \n\ - :rtype: collections.Iterable of (unicode, list of unicode, list of unicode) \n\ + :rtype: collections.Iterable of (T, list of T, list of T) \n\ os.execl = \ :type file: bytes or unicode \n\ @@ -2836,14 +2632,6 @@ io.open = \ :type errors: string or None \n\ :rtype: file \n\ -io.open.1 = \ - :type mode: unicode \n\ - :rtype: io.TextIOWrapper of unicode \n\ - -io.open.2 = \ - :type mode: bytes \n\ - :rtype: io.FileIO of bytes \n\ - io.TextIOWrapper.__init__ = \ :rtype: io.TextIOWrapper of T \n\ diff --git a/python/pluginSrc/com/jetbrains/python/psi/impl/PyJavaClassType.java b/python/pluginSrc/com/jetbrains/python/psi/impl/PyJavaClassType.java index 56e5ac47c285..7c3a3ac34cc1 100644 --- a/python/pluginSrc/com/jetbrains/python/psi/impl/PyJavaClassType.java +++ b/python/pluginSrc/com/jetbrains/python/psi/impl/PyJavaClassType.java @@ -96,6 +96,12 @@ public class PyJavaClassType implements PyClassLikeType { return null; } + @Nullable + @Override + public List getParameterTypes(@NotNull TypeEvalContext context) { + return null; + } + @Override public boolean isDefinition() { return myDefinition; diff --git a/python/pluginSrc/com/jetbrains/python/psi/impl/PyJavaMethodType.java b/python/pluginSrc/com/jetbrains/python/psi/impl/PyJavaMethodType.java index cd186fb95758..2594a1eed1f4 100644 --- a/python/pluginSrc/com/jetbrains/python/psi/impl/PyJavaMethodType.java +++ b/python/pluginSrc/com/jetbrains/python/psi/impl/PyJavaMethodType.java @@ -39,6 +39,12 @@ public class PyJavaMethodType implements PyCallableType { return PyJavaTypeProvider.asPyType(myMethod.getReturnType()); } + @Nullable + @Override + public List getParameterTypes(@NotNull TypeEvalContext context) { + return null; + } + @Nullable @Override public List resolveMember(@NotNull String name, 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 74d0c2e8637c..83c544fe9762 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 @@ -4,6 +4,8 @@ import com.jetbrains.python.psi.PyQualifiedExpression; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.List; + /** * A type instances of which can possibly be called. For example, a class definition can be called, and the result of a call is a class * instance. @@ -25,4 +27,12 @@ public interface PyCallableType extends PyType { */ @Nullable PyType getCallType(@NotNull TypeEvalContext context, @Nullable PyQualifiedExpression callSite); + + /** + * Returns the list of parameter types. + * + * @return list of positional parameters or null if not applicable. + */ + @Nullable + List getParameterTypes(@NotNull TypeEvalContext context); } diff --git a/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibTypeProvider.java b/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibTypeProvider.java index 68d329868e26..375124d3db71 100644 --- a/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibTypeProvider.java +++ b/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibTypeProvider.java @@ -33,6 +33,8 @@ public class PyStdlibTypeProvider extends PyTypeProviderBase { @NotNull private Properties myStdlibTypes = new Properties(); private static final Set OPEN_FUNCTIONS = ImmutableSet.of("__builtin__.open", "io.open", "os.fdopen"); + private static final String BINARY_FILE_TYPE = "io.FileIO[bytes]"; + private static final String TEXT_FILE_TYPE = "io.TextIOWrapper[unicode]"; @Nullable public static PyStdlibTypeProvider getInstance() { @@ -78,12 +80,12 @@ public class PyStdlibTypeProvider extends PyTypeProviderBase { public PyType getReturnType(@NotNull PyFunction function, @Nullable PyQualifiedExpression callSite, @NotNull TypeEvalContext context) { final String qname = getQualifiedName(function, callSite); if (qname != null) { - if (callSite != null) { - PyTypeChecker.AnalyzeCallResults results = PyTypeChecker.analyzeCallSite(callSite, context); + if (OPEN_FUNCTIONS.contains(qname) && callSite != null) { + final PyTypeChecker.AnalyzeCallResults results = PyTypeChecker.analyzeCallSite(callSite, context); if (results != null) { - final PyType overloaded = getOverloadedReturnTypeByQName(results.getArguments(), qname, function, context); - if (overloaded != null) { - return overloaded; + final PyType type = getOpenFunctionType(qname, results.getArguments(), callSite); + if (type != null) { + return type; } } } @@ -167,56 +169,9 @@ public class PyStdlibTypeProvider extends PyTypeProviderBase { } @Nullable - private PyType getOverloadedReturnTypeByQName(@NotNull Map arguments, - @NotNull String qname, - @NotNull PsiElement anchor, - @NotNull TypeEvalContext context) { - int i = 1; - PyType rtype; - do { - final String overloadedQName = String.format("%s.%d", qname, i); - rtype = getReturnTypeByQName(overloadedQName, anchor, context); - if (rtype != null) { - boolean matched = true; - boolean notNullParameterMatch = false; - // Special case for the 'mode' argument of the '*open()' functions - if (OPEN_FUNCTIONS.contains(qname)) { - matched = matchOpenFunctionType(qname, overloadedQName, arguments, anchor, context); - notNullParameterMatch = true; - } - else { - for (Map.Entry entry : arguments.entrySet()) { - final PyNamedParameter p = entry.getValue(); - final String name = p.getName(); - if (p.isPositionalContainer() || p.isKeywordContainer() || name == null) { - continue; - } - final PyType argType = context.getType(entry.getKey()); - final PyType paramType = getParameterTypeByQName(overloadedQName, name, anchor, context); - if (PyTypeChecker.match(paramType, argType, context)) { - if (paramType != null && !PyTypeChecker.isUnknown(argType)) { - notNullParameterMatch = true; - } - } - else { - matched = false; - break; - } - } - } - if (matched && notNullParameterMatch) { - return rtype; - } - } - i++; - } while (rtype != null); - return null; - } - - private boolean matchOpenFunctionType(@NotNull String callQName, - @NotNull String overloadedQName, - @NotNull Map arguments, - @NotNull PsiElement anchor, @NotNull TypeEvalContext context) { + private static PyType getOpenFunctionType(@NotNull String callQName, + @NotNull Map arguments, + @NotNull PsiElement anchor) { String mode = "r"; for (Map.Entry entry : arguments.entrySet()) { final PyNamedParameter parameter = entry.getValue(); @@ -228,24 +183,20 @@ public class PyStdlibTypeProvider extends PyTypeProviderBase { } } } - final PyBuiltinCache cache = PyBuiltinCache.getInstance(anchor); final LanguageLevel level = LanguageLevel.forElement(anchor); - final PyType paramType = getParameterTypeByQName(overloadedQName, "mode", anchor, context); - final PyType argType; // Binary mode if (mode.contains("b")) { - argType = cache.getBytesType(level); + return PyTypeParser.getTypeByName(anchor, BINARY_FILE_TYPE); } // Text mode else { if (level.isPy3K() || "io.open".equals(callQName)) { - argType = cache.getUnicodeType(level); + return PyTypeParser.getTypeByName(anchor, TEXT_FILE_TYPE); } else { - argType = cache.getBytesType(level); + return PyTypeParser.getTypeByName(anchor, BINARY_FILE_TYPE); } } - return PyTypeChecker.match(paramType, argType, context); } @Nullable diff --git a/python/src/com/jetbrains/python/console/PydevConsoleCommunication.java b/python/src/com/jetbrains/python/console/PydevConsoleCommunication.java index d0e3f373171d..be635c4b86bd 100644 --- a/python/src/com/jetbrains/python/console/PydevConsoleCommunication.java +++ b/python/src/com/jetbrains/python/console/PydevConsoleCommunication.java @@ -14,7 +14,9 @@ import com.jetbrains.django.util.VirtualFileUtil; import com.jetbrains.python.console.parsing.PythonConsoleData; import com.jetbrains.python.console.pydev.*; import com.jetbrains.python.debugger.PydevXmlUtils; -import org.apache.xmlrpc.*; +import org.apache.xmlrpc.WebServer; +import org.apache.xmlrpc.XmlRpcException; +import org.apache.xmlrpc.XmlRpcHandler; import org.jetbrains.annotations.NotNull; import java.net.MalformedURLException; @@ -78,7 +80,7 @@ public class PydevConsoleCommunication extends AbstractConsoleCommunication impl super(project); //start the server that'll handle input requests - myWebServer = new IdeaAwareWebServer(clientPort, null, new IdeaAwareXmlRpcServer()); + myWebServer = new WebServer(clientPort, null); myWebServer.addHandler("$default", this); this.myWebServer.start(); diff --git a/python/src/com/jetbrains/python/psi/impl/PyNamedParameterImpl.java b/python/src/com/jetbrains/python/psi/impl/PyNamedParameterImpl.java index d8af020b1ace..ddf4f1445291 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyNamedParameterImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyNamedParameterImpl.java @@ -220,6 +220,10 @@ public class PyNamedParameterImpl extends PyPresentableElementImpl ref = Ref.create(null); diff --git a/python/src/com/jetbrains/python/psi/impl/PyTargetExpressionImpl.java b/python/src/com/jetbrains/python/psi/impl/PyTargetExpressionImpl.java index b0ead32e818a..eb278620b1a4 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyTargetExpressionImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyTargetExpressionImpl.java @@ -40,6 +40,8 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; +import java.util.ArrayList; +import java.util.Collection; import java.util.List; /** @@ -314,58 +316,8 @@ public class PyTargetExpressionImpl extends PyPresentableElementImpl members = ((PyUnionType)iterableType).getMembers(); + final List iterationTypes = new ArrayList(); + for (PyType member : members) { + iterationTypes.add(getIterationType(member, source, context)); + } + return PyUnionType.union(iterationTypes); + } + else if (iterableType instanceof PyClassType) { + final PyClass pyClass = ((PyClassType)iterableType).getPyClass(); + for (PyTypeProvider provider: Extensions.getExtensions(PyTypeProvider.EP_NAME)) { + final PyType iterationType = provider.getIterationType(pyClass); + if (iterationType != null) { + result = iterationType; + break; + } + } + if (PyABCUtil.isSubclass(pyClass, PyNames.ITERATOR)) { + final PyFunction iterateMethod = pyClass.findMethodByName(PyNames.ITER, true); + PyType iterateMethodType = null; + if (iterateMethod != null) { + iterateMethodType = getContextSensitiveType(iterateMethod, context, source); + } + if (iterateMethodType instanceof PyCollectionType) { + final PyCollectionType collectionType = (PyCollectionType)iterateMethodType; + result = collectionType.getElementType(context); + } + if (result == null) { + PyFunction next = pyClass.findMethodByName(PyNames.NEXT, true); + if (next == null) { + next = pyClass.findMethodByName(PyNames.DUNDER_NEXT, true); + } + if (next != null) { + result = getContextSensitiveType(next, context, source); + } + } + if (result == null) { + final PyFunction getItem = pyClass.findMethodByName(PyNames.GETITEM, true); + if (getItem != null) { + result = getContextSensitiveType(getItem, context, source); + } + } + } + } + return result; + } + @Nullable private static PyType getContextSensitiveType(@NotNull PyFunction function, @NotNull TypeEvalContext context, - @NotNull PyExpression source) { + @Nullable PyExpression source) { if (function instanceof PyFunctionImpl) { return ((PyFunctionImpl)function).getReturnTypeWithoutCallSite(context, source); } diff --git a/python/src/com/jetbrains/python/psi/types/PyCallableTypeImpl.java b/python/src/com/jetbrains/python/psi/types/PyCallableTypeImpl.java new file mode 100644 index 000000000000..e1c51ed92ef8 --- /dev/null +++ b/python/src/com/jetbrains/python/psi/types/PyCallableTypeImpl.java @@ -0,0 +1,82 @@ +package com.jetbrains.python.psi.types; + +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.util.Function; +import com.intellij.util.ProcessingContext; +import com.jetbrains.python.psi.AccessDirection; +import com.jetbrains.python.psi.PyExpression; +import com.jetbrains.python.psi.PyQualifiedExpression; +import com.jetbrains.python.psi.resolve.PyResolveContext; +import com.jetbrains.python.psi.resolve.RatedResolveResult; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +/** + * @author vlan + */ +public class PyCallableTypeImpl implements PyCallableType { + @NotNull private final List myParameterTypes; + @Nullable private final PyType myReturnType; + + public PyCallableTypeImpl(@NotNull List parameterTypes, @Nullable PyType returnType) { + myParameterTypes = parameterTypes; + myReturnType = returnType; + } + + @Override + public boolean isCallable() { + return true; + } + + @Nullable + @Override + public PyType getCallType(@NotNull TypeEvalContext context, @Nullable PyQualifiedExpression callSite) { + return myReturnType; + } + + @Nullable + @Override + public List getParameterTypes(@NotNull TypeEvalContext context) { + return myParameterTypes; + } + + @Nullable + @Override + public List resolveMember(@NotNull String name, + @Nullable PyExpression location, + @NotNull AccessDirection direction, + @NotNull PyResolveContext resolveContext) { + return null; + } + + @Override + public Object[] getCompletionVariants(String completionPrefix, PyExpression location, ProcessingContext context) { + return new Object[0]; + } + + @Nullable + @Override + public String getName() { + return String.format("(%s) -> %s", + StringUtil.join(myParameterTypes, + new Function() { + @Override + public String fun(PyType type) { + return type != null ? type.getName() : "unknown"; + } + }, + ", "), + myReturnType != null ? myReturnType.getName() : "unknown"); + } + + @Override + public boolean isBuiltin(TypeEvalContext context) { + return false; + } + + @Override + public void assertValid(String message) { + } +} diff --git a/python/src/com/jetbrains/python/psi/types/PyClassTypeImpl.java b/python/src/com/jetbrains/python/psi/types/PyClassTypeImpl.java index 555acaeb49f9..2ca0fbcf2c02 100644 --- a/python/src/com/jetbrains/python/psi/types/PyClassTypeImpl.java +++ b/python/src/com/jetbrains/python/psi/types/PyClassTypeImpl.java @@ -277,6 +277,12 @@ public class PyClassTypeImpl extends UserDataHolderBase implements PyClassType { return null; } + @Nullable + @Override + public List getParameterTypes(@NotNull TypeEvalContext context) { + return null; + } + @Nullable private static PsiElement resolveClassMember(@NotNull PyClass cls, boolean isDefinition, diff --git a/python/src/com/jetbrains/python/psi/types/PyFunctionType.java b/python/src/com/jetbrains/python/psi/types/PyFunctionType.java index 70123ad3c96a..f0d3a7b76372 100644 --- a/python/src/com/jetbrains/python/psi/types/PyFunctionType.java +++ b/python/src/com/jetbrains/python/psi/types/PyFunctionType.java @@ -33,6 +33,12 @@ public class PyFunctionType implements PyCallableType { return myCallable.getReturnType(context, callSite); } + @Nullable + @Override + public List getParameterTypes(@NotNull TypeEvalContext context) { + return null; + } + @Override public List resolveMember(@NotNull String name, @Nullable PyExpression location, diff --git a/python/src/com/jetbrains/python/psi/types/PyType.flex b/python/src/com/jetbrains/python/psi/types/PyType.flex index d340b102e809..5f302214c5e8 100644 --- a/python/src/com/jetbrains/python/psi/types/PyType.flex +++ b/python/src/com/jetbrains/python/psi/types/PyType.flex @@ -26,6 +26,6 @@ import static com.jetbrains.python.psi.types.PyTypeTokenTypes.*; [\r\n]+ { return NL; } [\ \t] { return SPACE; } (":py"?":class:`"[~!]?)|("`")|([A-Z]"{")|("}") { return MARKUP; } -("or")|("of")|("from")|("to")|[,\(\)\.\[\]|] { return OP; } +("or")|("of")|("from")|("to")|("<=")|("->")|[,\(\)\.\[\]|] { return OP; } [T-Z] { return PARAMETER; } [A-Za-z_][A-Za-z_0-9]* { return IDENTIFIER; } diff --git a/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java b/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java index 6cc3e53ebf0e..429524e49c24 100644 --- a/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java +++ b/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java @@ -140,6 +140,27 @@ public class PyTypeChecker { return true; } } + if (actual instanceof PyCallableType && expected instanceof PyCallableType) { + 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); + 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)) { + return false; + } + } + } + if (!match(expectedCallable.getCallType(context, null), actualCallable.getCallType(context, null), context, substitutions, + recursive)) { + return false; + } + return true; + } + } final String superName = expected.getName(); final String subName = actual.getName(); // TODO: No inheritance check for builtin numerics at this moment diff --git a/python/src/com/jetbrains/python/psi/types/PyTypeLexer.java b/python/src/com/jetbrains/python/psi/types/PyTypeLexer.java index 9d786f5373d1..eea07acda234 100644 --- a/python/src/com/jetbrains/python/psi/types/PyTypeLexer.java +++ b/python/src/com/jetbrains/python/psi/types/PyTypeLexer.java @@ -1,4 +1,4 @@ -/* The following code was generated by JFlex 1.4.3 on 7/19/13 2:41 PM */ +/* The following code was generated by JFlex 1.4.3 on 7/23/13 5:34 PM */ package com.jetbrains.python.psi.types; @@ -11,7 +11,7 @@ import static com.jetbrains.python.psi.types.PyTypeTokenTypes.*; /** * This class is a scanner generated by * JFlex 1.4.3 - * on 7/19/13 2:41 PM from the specification file + * on 7/23/13 5:34 PM from the specification file * /home/user/src/idea/tools/lexer/../../python/src/com/jetbrains/python/psi/types/PyType.flex */ public class PyTypeLexer implements FlexLexer { @@ -35,11 +35,12 @@ public class PyTypeLexer implements FlexLexer { * Translates characters to character classes */ private static final String ZZ_CMAP_PACKED = - "\11\0\1\2\1\1\2\0\1\1\22\0\1\2\1\13\6\0\2\24"+ - "\2\0\1\24\1\0\1\24\1\0\12\27\1\3\6\0\23\14\7\25"+ - "\1\24\1\0\1\24\1\0\1\26\1\12\1\10\1\26\1\6\2\26"+ - "\1\21\5\26\1\7\1\22\1\26\1\17\1\4\1\26\1\20\1\11"+ - "\1\23\4\26\1\5\1\26\1\15\1\24\1\16\1\13\uff81\0"; + "\11\0\1\2\1\1\2\0\1\1\22\0\1\2\1\13\6\0\2\30"+ + "\2\0\1\30\1\26\1\30\1\0\12\33\1\3\1\0\1\24\1\25"+ + "\1\27\2\0\23\14\7\31\1\30\1\0\1\30\1\0\1\32\1\12"+ + "\1\10\1\32\1\6\2\32\1\21\5\32\1\7\1\22\1\32\1\17"+ + "\1\4\1\32\1\20\1\11\1\23\4\32\1\5\1\32\1\15\1\30"+ + "\1\16\1\13\uff81\0"; /** * Translates characters to character classes @@ -52,11 +53,12 @@ public class PyTypeLexer implements FlexLexer { private static final int [] ZZ_ACTION = zzUnpackAction(); private static final String ZZ_ACTION_PACKED_0 = - "\1\0\1\1\1\2\1\0\1\3\1\4\4\3\1\5"+ - "\1\6\2\0\1\5\1\3\2\0\1\3\5\0\1\4"; + "\1\0\1\1\1\2\1\0\1\3\1\4\4\3\2\0"+ + "\1\5\1\6\2\0\1\5\1\3\2\0\1\3\5\0"+ + "\1\4"; private static int [] zzUnpackAction() { - int [] result = new int[25]; + int [] result = new int[27]; int offset = 0; offset = zzUnpackAction(ZZ_ACTION_PACKED_0, offset, result); return result; @@ -81,13 +83,13 @@ public class PyTypeLexer implements FlexLexer { private static final int [] ZZ_ROWMAP = zzUnpackRowMap(); private static final String ZZ_ROWMAP_PACKED_0 = - "\0\0\0\30\0\60\0\110\0\140\0\60\0\170\0\220"+ - "\0\250\0\300\0\60\0\170\0\330\0\360\0\140\0\u0108"+ - "\0\u0120\0\u0138\0\u0150\0\u0168\0\u0180\0\u0198\0\u01b0\0\u01c8"+ - "\0\u01e0"; + "\0\0\0\34\0\70\0\124\0\160\0\70\0\214\0\250"+ + "\0\304\0\340\0\374\0\u0118\0\70\0\214\0\u0134\0\u0150"+ + "\0\160\0\u016c\0\u0188\0\u01a4\0\u01c0\0\u01dc\0\u01f8\0\u0214"+ + "\0\u0230\0\u024c\0\u0268"; private static int [] zzUnpackRowMap() { - int [] result = new int[25]; + int [] result = new int[27]; int offset = 0; offset = zzUnpackRowMap(ZZ_ROWMAP_PACKED_0, offset, result); return result; @@ -112,21 +114,22 @@ public class PyTypeLexer implements FlexLexer { private static final String ZZ_TRANS_PACKED_0 = "\1\0\1\2\1\3\1\4\6\5\1\6\1\0\1\7"+ "\1\0\1\6\1\10\1\5\1\11\1\5\1\12\1\13"+ - "\1\14\1\5\2\0\1\2\62\0\1\15\1\0\1\16"+ - "\25\0\6\5\2\0\1\5\2\0\5\5\1\0\3\5"+ - "\4\0\6\5\2\0\1\5\1\6\1\0\5\5\1\0"+ - "\3\5\4\0\6\5\2\0\1\5\2\0\1\5\2\17"+ - "\2\5\1\0\3\5\4\0\6\5\2\0\1\5\2\0"+ - "\1\5\1\20\3\5\1\0\3\5\4\0\6\5\2\0"+ - "\1\5\2\0\1\17\4\5\1\0\3\5\5\0\1\21"+ - "\31\0\1\22\24\0\6\5\2\0\1\5\2\0\1\23"+ - "\4\5\1\0\3\5\3\0\1\24\34\0\1\25\23\0"+ - "\6\5\2\0\1\5\2\0\3\5\1\17\1\5\1\0"+ - "\3\5\6\0\1\16\32\0\1\26\27\0\1\27\21\0"+ - "\1\30\36\0\1\31\30\0\1\6\14\0"; + "\1\0\1\14\1\0\1\15\1\16\1\5\2\0\1\2"+ + "\72\0\1\17\1\0\1\20\31\0\6\5\2\0\1\5"+ + "\2\0\5\5\5\0\3\5\4\0\6\5\2\0\1\5"+ + "\1\6\1\0\5\5\5\0\3\5\4\0\6\5\2\0"+ + "\1\5\2\0\1\5\2\21\2\5\5\0\3\5\4\0"+ + "\6\5\2\0\1\5\2\0\1\5\1\22\3\5\5\0"+ + "\3\5\4\0\6\5\2\0\1\5\2\0\1\21\4\5"+ + "\5\0\3\5\25\0\1\15\35\0\1\15\11\0\1\23"+ + "\35\0\1\24\30\0\6\5\2\0\1\5\2\0\1\25"+ + "\4\5\5\0\3\5\3\0\1\26\40\0\1\27\27\0"+ + "\6\5\2\0\1\5\2\0\3\5\1\21\1\5\5\0"+ + "\3\5\6\0\1\20\36\0\1\30\33\0\1\31\25\0"+ + "\1\32\42\0\1\33\34\0\1\6\20\0"; private static int [] zzUnpackTrans() { - int [] result = new int[504]; + int [] result = new int[644]; int offset = 0; offset = zzUnpackTrans(ZZ_TRANS_PACKED_0, offset, result); return result; @@ -167,11 +170,11 @@ public class PyTypeLexer implements FlexLexer { private static final int [] ZZ_ATTRIBUTE = zzUnpackAttribute(); private static final String ZZ_ATTRIBUTE_PACKED_0 = - "\1\0\1\1\1\11\1\0\1\1\1\11\4\1\1\11"+ - "\1\1\2\0\2\1\2\0\1\1\5\0\1\1"; + "\1\0\1\1\1\11\1\0\1\1\1\11\4\1\2\0"+ + "\1\11\1\1\2\0\2\1\2\0\1\1\5\0\1\1"; private static int [] zzUnpackAttribute() { - int [] result = new int[25]; + int [] result = new int[27]; int offset = 0; offset = zzUnpackAttribute(ZZ_ATTRIBUTE_PACKED_0, offset, result); return result; @@ -254,7 +257,7 @@ public class PyTypeLexer implements FlexLexer { char [] map = new char[0x10000]; int i = 0; /* index in packed string */ int j = 0; /* index in unpacked array */ - while (i < 98) { + while (i < 106) { int count = packed.charAt(i++); char value = packed.charAt(i++); do map[j++] = value; while (--count > 0); diff --git a/python/src/com/jetbrains/python/psi/types/PyTypeParser.java b/python/src/com/jetbrains/python/psi/types/PyTypeParser.java index 88d45ad11106..8af0776f1825 100644 --- a/python/src/com/jetbrains/python/psi/types/PyTypeParser.java +++ b/python/src/com/jetbrains/python/psi/types/PyTypeParser.java @@ -108,10 +108,11 @@ public class PyTypeParser { final ForwardDeclaration typeExpr = ForwardDeclaration.create(); - final FunctionalParser simpleType = + final FunctionalParser classType = token(IDENTIFIER).then(many(op(".").skipThen(token(IDENTIFIER)))) .map(new MakeSimpleType(anchor)) - .named("simple-type"); + .cached() + .named("class-type"); final FunctionalParser tupleType = op("(").skipThen(typeExpr).then(many(op(",").skipThen(typeExpr))).thenSkip(op(")")) @@ -135,7 +136,7 @@ public class PyTypeParser { .named("tuple-type"); final FunctionalParser typeParameter = - token(PARAMETER).then(maybe(op("(").skipThen(typeExpr).thenSkip(op(")")))) + token(PARAMETER).then(maybe(op("<=").skipThen(typeExpr))) .map(new Function, ParseResult>, ParseResult>() { @Override public ParseResult fun(Pair, ParseResult> value) { @@ -154,14 +155,13 @@ public class PyTypeParser { .named("type-parameter"); final FunctionalParser simpleExpr = - simpleType + classType .or(tupleType) .or(typeParameter) - .cached() .named("simple-expr"); final FunctionalParser paramExpr = - simpleExpr.thenSkip(op("[")).then(typeExpr).then(many(op(",").skipThen(typeExpr))).thenSkip(op("]")) + classType.thenSkip(op("[")).then(typeExpr).then(many(op(",").skipThen(typeExpr))).thenSkip(op("]")) .map(new Function, List>, ParseResult>() { @Override public ParseResult fun(Pair, List> value) { @@ -187,7 +187,7 @@ public class PyTypeParser { return EMPTY_RESULT; } }) - .or(simpleExpr.thenSkip(op("of")).then(simpleExpr) + .or(classType.thenSkip(op("of")).then(simpleExpr) .map(new Function, ParseResult>() { @Override public ParseResult fun(Pair value) { @@ -205,7 +205,7 @@ public class PyTypeParser { return EMPTY_RESULT; } })) - .or(simpleExpr.thenSkip(op("from")).then(simpleExpr).thenSkip(op("to")).then(simpleExpr) + .or(classType.thenSkip(op("from")).then(simpleExpr).thenSkip(op("to")).then(simpleExpr) .map(new Function, ParseResult>, ParseResult>() { @Override public ParseResult fun(Pair, ParseResult> value) { @@ -222,11 +222,45 @@ public class PyTypeParser { return EMPTY_RESULT; } })) - .or(simpleExpr) .named("param-expr"); + final FunctionalParser callableExpr = + op("(").skipThen(maybe(typeExpr.then(many(op(",").skipThen(typeExpr))))).thenSkip(op(")")).thenSkip(op("->")).then(typeExpr) + .map( + new Function>, ParseResult>, ParseResult>() { + @Override + public ParseResult fun(Pair>, ParseResult> value) { + final List parameterTypes = new ArrayList(); + final ParseResult returnResult = value.getSecond(); + ParseResult result; + final Pair> firstPair = value.getFirst(); + if (firstPair != null) { + final ParseResult first = firstPair.getFirst(); + final List second = firstPair.getSecond(); + result = first; + parameterTypes.add(first.getType()); + for (ParseResult r : second) { + result = result.merge(r); + parameterTypes.add(r.getType()); + } + result = result.merge(returnResult); + } + else { + result = returnResult; + } + return result.withType(new PyCallableTypeImpl(parameterTypes, returnResult.getType())); + } + }) + .named("callable-expr"); + + final FunctionalParser singleExpr = + paramExpr + .or(callableExpr) + .or(simpleExpr) + .named("single-expr"); + final FunctionalParser unionExpr = - paramExpr.then(many(op("or").or(op("|")).skipThen(paramExpr))) + singleExpr.then(many(op("or").or(op("|")).skipThen(singleExpr))) .map(new Function>, ParseResult>() { @Override public ParseResult fun(Pair> value) { diff --git a/python/testData/inspections/PyTypeCheckerInspection/BoundedGeneric.py b/python/testData/inspections/PyTypeCheckerInspection/BoundedGeneric.py index 14202d5d177a..3d79685273a2 100644 --- a/python/testData/inspections/PyTypeCheckerInspection/BoundedGeneric.py +++ b/python/testData/inspections/PyTypeCheckerInspection/BoundedGeneric.py @@ -1,7 +1,7 @@ def test(): def f(x): """ - :type x: T (int or str) + :type x: T <= int or str :rtype: T """ pass diff --git a/python/testData/inspections/PyTypeCheckerInspection/MapReturnElementType.py b/python/testData/inspections/PyTypeCheckerInspection/MapReturnElementType.py new file mode 100644 index 000000000000..39ea03805b54 --- /dev/null +++ b/python/testData/inspections/PyTypeCheckerInspection/MapReturnElementType.py @@ -0,0 +1,5 @@ +def test(): + xs = map(lambda x: x + 1, [1, 2, 3]) + print('foo' + xs[0]) + ys = map(str, iter([1, 2, 3])) + print(1 + ys[0], 'bar' + ys[1]) diff --git a/python/testSrc/com/jetbrains/python/PyTypeParserTest.java b/python/testSrc/com/jetbrains/python/PyTypeParserTest.java index 178482300029..ed6850b99fea 100644 --- a/python/testSrc/com/jetbrains/python/PyTypeParserTest.java +++ b/python/testSrc/com/jetbrains/python/PyTypeParserTest.java @@ -177,7 +177,7 @@ public class PyTypeParserTest extends PyTestCase { public void testBoundedGeneric() { myFixture.configureByFile("typeParser/typeParser.py"); - final PyType type = PyTypeParser.getTypeByName(myFixture.getFile(), "T (str or unicode)"); + final PyType type = PyTypeParser.getTypeByName(myFixture.getFile(), "T <= str or unicode"); assertNotNull(type); assertInstanceOf(type, PyGenericType.class); final PyGenericType genericType = (PyGenericType)type; @@ -227,4 +227,33 @@ public class PyTypeParserTest extends PyTestCase { assertClassType(list.get(1), "str"); assertClassType(list.get(2), "unicode"); } + + public void testCallableType() { + myFixture.configureByFile("typeParser/typeParser.py"); + final PyType type = PyTypeParser.getTypeByName(myFixture.getFile(), "(int, T) -> T"); + assertInstanceOf(type, PyCallableType.class); + final PyCallableType callableType = (PyCallableType)type; + assertNotNull(callableType); + final PyType returnType = callableType.getCallType(getTypeEvalContext(), null); + assertInstanceOf(returnType, PyGenericType.class); + final List parameterTypes = callableType.getParameterTypes(getTypeEvalContext()); + assertNotNull(parameterTypes); + assertEquals(2, parameterTypes.size()); + assertEquals("int", parameterTypes.get(0).getName()); + assertEquals("T", parameterTypes.get(1).getName()); + } + + public void testCallableWithoutArgs() { + myFixture.configureByFile("typeParser/typeParser.py"); + final PyType type = PyTypeParser.getTypeByName(myFixture.getFile(), "() -> int"); + assertInstanceOf(type, PyCallableType.class); + final PyCallableType callableType = (PyCallableType)type; + assertNotNull(callableType); + final PyType returnType = callableType.getCallType(getTypeEvalContext(), null); + assertNotNull(returnType); + assertEquals("int", returnType.getName()); + final List parameterTypes = callableType.getParameterTypes(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 4a9e613763bb..3daeb4fd9c57 100644 --- a/python/testSrc/com/jetbrains/python/PyTypeTest.java +++ b/python/testSrc/com/jetbrains/python/PyTypeTest.java @@ -564,7 +564,7 @@ public class PyTypeTest extends PyTestCase { doTest("int or str", "def foo(x):\n" + " '''\n" + - " :type x: T (int or str)\n" + + " :type x: T <= int or str\n" + " :rtype: T\n" + " '''\n" + "def bar(x):\n" + @@ -582,6 +582,53 @@ public class PyTypeTest extends PyTestCase { " pass\n"); } + public void testFunctionTypeAsUnificationArgument() { + doTest("int", + "def map2(f, xs):\n" + + " '''\n" + + " :type f: (T) -> V | None\n" + + " :type xs: collections.Iterable[T] | bytes | unicode\n" + + " :rtype: list[V] | bytes | unicode\n" + + " '''\n" + + " pass\n" + + "\n" + + "expr = map2(lambda x: 10, ['1', '2', '3'])[0]\n"); + } + + public void testFunctionTypeAsUnificationResult() { + doTest("int", + "def f(x):\n" + + " '''\n" + + " :type x: T\n" + + " :rtype: () -> T\n" + + " '''\n" + + " pass\n" + + "\n" + + "g = f(10)\n" + + "expr = g()\n"); + } + + public void testUnionIteration() { + final String text = "def f(c):\n" + + " if c < 0:\n" + + " return [1, 2, 3]\n" + + " elif c == 0:\n" + + " return 0.0\n" + + " else:\n" + + " return 'foo'\n" + + "\n" + + "def g(c):\n" + + " for expr in f(c):\n" + + " pass\n"; + final PyExpression expr = parseExpr(text); + final TypeEvalContext context = getTypeEvalContext(expr); + final PyType type = context.getType(expr); + assertInstanceOf(type, PyUnionType.class); + assertTrue(PyTypeChecker.match(PyTypeParser.getTypeByName(expr, "int"), type, context)); + assertTrue(PyTypeChecker.match(PyTypeParser.getTypeByName(expr, "str"), type, context)); + assertTrue(PyTypeChecker.isUnknown(type)); + } + private static TypeEvalContext getTypeEvalContext(@NotNull PyExpression element) { return TypeEvalContext.userInitiated(element.getContainingFile()).withTracing(); } diff --git a/python/testSrc/com/jetbrains/python/inspections/PyTypeCheckerInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyTypeCheckerInspectionTest.java index 94f2502e020e..1bd48b30da31 100644 --- a/python/testSrc/com/jetbrains/python/inspections/PyTypeCheckerInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/PyTypeCheckerInspectionTest.java @@ -193,4 +193,9 @@ public class PyTypeCheckerInspectionTest extends PyTestCase { public void testForLoopIteration() { doTest(); } + + // PY-4285 + public void testMapReturnElementType() { + doTest(); + } }