Merge branch 'function-types'

This commit is contained in:
Andrey Vlasovskikh
2013-07-23 22:03:48 +04:00
19 changed files with 432 additions and 419 deletions
+43 -255
View File
@@ -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\
@@ -96,6 +96,12 @@ public class PyJavaClassType implements PyClassLikeType {
return null;
}
@Nullable
@Override
public List<PyType> getParameterTypes(@NotNull TypeEvalContext context) {
return null;
}
@Override
public boolean isDefinition() {
return myDefinition;
@@ -39,6 +39,12 @@ public class PyJavaMethodType implements PyCallableType {
return PyJavaTypeProvider.asPyType(myMethod.getReturnType());
}
@Nullable
@Override
public List<PyType> getParameterTypes(@NotNull TypeEvalContext context) {
return null;
}
@Nullable
@Override
public List<? extends RatedResolveResult> resolveMember(@NotNull String name,
@@ -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<PyType> getParameterTypes(@NotNull TypeEvalContext context);
}
@@ -33,6 +33,8 @@ public class PyStdlibTypeProvider extends PyTypeProviderBase {
@NotNull private Properties myStdlibTypes = new Properties();
private static final Set<String> 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<PyExpression, PyNamedParameter> 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<PyExpression, PyNamedParameter> 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<PyExpression, PyNamedParameter> arguments,
@NotNull PsiElement anchor, @NotNull TypeEvalContext context) {
private static PyType getOpenFunctionType(@NotNull String callQName,
@NotNull Map<PyExpression, PyNamedParameter> arguments,
@NotNull PsiElement anchor) {
String mode = "r";
for (Map.Entry<PyExpression, PyNamedParameter> 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
@@ -220,6 +220,10 @@ public class PyNamedParameterImpl extends PyPresentableElementImpl<PyNamedParame
return PyTypeParser.getTypeByName(this, typeName);
}
}
for(PyTypeProvider provider: Extensions.getExtensions(PyTypeProvider.EP_NAME)) {
PyType result = provider.getParameterType(this, func, context);
if (result != null) return result;
}
if (context.maySwitchToAST(this)) {
final PyExpression defaultValue = getDefaultValue();
if (defaultValue != null) {
@@ -229,10 +233,6 @@ public class PyNamedParameterImpl extends PyPresentableElementImpl<PyNamedParame
}
}
}
for(PyTypeProvider provider: Extensions.getExtensions(PyTypeProvider.EP_NAME)) {
PyType result = provider.getParameterType(this, func, context);
if (result != null) return result;
}
// Guess the type from file-local usages
if (context.allowLocalUsages(this)) {
final Ref<PyType> ref = Ref.create(null);
@@ -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<PyTargetExp
}
}
if (source != null && target != null) {
PyType type = null;
final PyType sourceType = context.getType(source);
if (sourceType instanceof PyCollectionType) {
type = ((PyCollectionType)sourceType).getElementType(context);
if (sourceType instanceof PyClassType) {
final PyClass cls = ((PyClassType)sourceType).getPyClass();
if (cls != null && type instanceof PyTupleType && PyABCUtil.isSubclass(cls, PyNames.MAPPING)) {
final PyTupleType mappingType = (PyTupleType)type;
if (mappingType.getElementCount() == 2) {
return mappingType.getElementType(0);
}
}
}
}
else if (sourceType instanceof PyClassType) {
final PyClass pyClass = ((PyClassType)sourceType).getPyClass();
if (pyClass != null) {
for (PyTypeProvider provider: Extensions.getExtensions(PyTypeProvider.EP_NAME)) {
final PyType iterType = provider.getIterationType(pyClass);
if (iterType != null) {
type = iterType;
break;
}
}
if (PyABCUtil.isSubclass(pyClass, PyNames.ITERATOR)) {
final PyFunction iter = pyClass.findMethodByName(PyNames.ITER, true);
PyType iterMethodType = null;
if (iter != null) {
iterMethodType = getContextSensitiveType(iter, context, source);
}
if (iterMethodType instanceof PyCollectionType) {
final PyCollectionType collectionType = (PyCollectionType)iterMethodType;
type = collectionType.getElementType(context);
}
if (type == null) {
PyFunction next = pyClass.findMethodByName(PyNames.NEXT, true);
if (next == null) {
next = pyClass.findMethodByName(PyNames.DUNDER_NEXT, true);
}
if (next != null) {
type = getContextSensitiveType(next, context, source);
}
}
if (type == null) {
final PyFunction getItem = pyClass.findMethodByName(PyNames.GETITEM, true);
if (getItem != null) {
type = getContextSensitiveType(getItem, context, source);
}
}
}
}
}
final PyType type = getIterationType(sourceType, source, context);
final PsiElement parent = getParent();
if (type instanceof PyTupleType && parent instanceof PyTupleExpression) {
return getTypeFromTupleAssignment((PyTupleExpression)parent, (PyTupleType)type);
@@ -377,9 +329,71 @@ public class PyTargetExpressionImpl extends PyPresentableElementImpl<PyTargetExp
return null;
}
@Nullable
private static PyType getIterationType(@Nullable PyType iterableType, @Nullable PyExpression source, @NotNull TypeEvalContext context) {
PyType result = null;
if (iterableType instanceof PyCollectionType) {
result = ((PyCollectionType)iterableType).getElementType(context);
if (iterableType instanceof PyClassType) {
final PyClass cls = ((PyClassType)iterableType).getPyClass();
if (result instanceof PyTupleType && PyABCUtil.isSubclass(cls, PyNames.MAPPING)) {
final PyTupleType mappingType = (PyTupleType)result;
if (mappingType.getElementCount() == 2) {
result = mappingType.getElementType(0);
}
}
}
}
else if (iterableType instanceof PyUnionType) {
final Collection<PyType> members = ((PyUnionType)iterableType).getMembers();
final List<PyType> iterationTypes = new ArrayList<PyType>();
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);
}
@@ -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<PyType> myParameterTypes;
@Nullable private final PyType myReturnType;
public PyCallableTypeImpl(@NotNull List<PyType> 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<PyType> getParameterTypes(@NotNull TypeEvalContext context) {
return myParameterTypes;
}
@Nullable
@Override
public List<? extends RatedResolveResult> 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<PyType, String>() {
@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) {
}
}
@@ -277,6 +277,12 @@ public class PyClassTypeImpl extends UserDataHolderBase implements PyClassType {
return null;
}
@Nullable
@Override
public List<PyType> getParameterTypes(@NotNull TypeEvalContext context) {
return null;
}
@Nullable
private static PsiElement resolveClassMember(@NotNull PyClass cls,
boolean isDefinition,
@@ -33,6 +33,12 @@ public class PyFunctionType implements PyCallableType {
return myCallable.getReturnType(context, callSite);
}
@Nullable
@Override
public List<PyType> getParameterTypes(@NotNull TypeEvalContext context) {
return null;
}
@Override
public List<? extends RatedResolveResult> resolveMember(@NotNull String name,
@Nullable PyExpression location,
@@ -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; }
@@ -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<PyType> expectedParameters = expectedCallable.getParameterTypes(context);
final List<PyType> 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
@@ -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
* <a href="http://www.jflex.de/">JFlex</a> 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
* <tt>/home/user/src/idea/tools/lexer/../../python/src/com/jetbrains/python/psi/types/PyType.flex</tt>
*/
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);
@@ -108,10 +108,11 @@ public class PyTypeParser {
final ForwardDeclaration<ParseResult, PyElementType> typeExpr = ForwardDeclaration.create();
final FunctionalParser<ParseResult, PyElementType> simpleType =
final FunctionalParser<ParseResult, PyElementType> classType =
token(IDENTIFIER).then(many(op(".").skipThen(token(IDENTIFIER))))
.map(new MakeSimpleType(anchor))
.named("simple-type");
.cached()
.named("class-type");
final FunctionalParser<ParseResult, PyElementType> tupleType =
op("(").skipThen(typeExpr).then(many(op(",").skipThen(typeExpr))).thenSkip(op(")"))
@@ -135,7 +136,7 @@ public class PyTypeParser {
.named("tuple-type");
final FunctionalParser<ParseResult, PyElementType> typeParameter =
token(PARAMETER).then(maybe(op("(").skipThen(typeExpr).thenSkip(op(")"))))
token(PARAMETER).then(maybe(op("<=").skipThen(typeExpr)))
.map(new Function<Pair<Token<PyElementType>, ParseResult>, ParseResult>() {
@Override
public ParseResult fun(Pair<Token<PyElementType>, ParseResult> value) {
@@ -154,14 +155,13 @@ public class PyTypeParser {
.named("type-parameter");
final FunctionalParser<ParseResult, PyElementType> simpleExpr =
simpleType
classType
.or(tupleType)
.or(typeParameter)
.cached()
.named("simple-expr");
final FunctionalParser<ParseResult, PyElementType> 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<Pair<Pair<ParseResult, ParseResult>, List<ParseResult>>, ParseResult>() {
@Override
public ParseResult fun(Pair<Pair<ParseResult, ParseResult>, List<ParseResult>> 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<Pair<ParseResult, ParseResult>, ParseResult>() {
@Override
public ParseResult fun(Pair<ParseResult, ParseResult> 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<Pair<Pair<ParseResult, ParseResult>, ParseResult>, ParseResult>() {
@Override
public ParseResult fun(Pair<Pair<ParseResult, ParseResult>, ParseResult> value) {
@@ -222,11 +222,45 @@ public class PyTypeParser {
return EMPTY_RESULT;
}
}))
.or(simpleExpr)
.named("param-expr");
final FunctionalParser<ParseResult, PyElementType> callableExpr =
op("(").skipThen(maybe(typeExpr.then(many(op(",").skipThen(typeExpr))))).thenSkip(op(")")).thenSkip(op("->")).then(typeExpr)
.map(
new Function<Pair<Pair<ParseResult, List<ParseResult>>, ParseResult>, ParseResult>() {
@Override
public ParseResult fun(Pair<Pair<ParseResult, List<ParseResult>>, ParseResult> value) {
final List<PyType> parameterTypes = new ArrayList<PyType>();
final ParseResult returnResult = value.getSecond();
ParseResult result;
final Pair<ParseResult, List<ParseResult>> firstPair = value.getFirst();
if (firstPair != null) {
final ParseResult first = firstPair.getFirst();
final List<ParseResult> 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<ParseResult, PyElementType> singleExpr =
paramExpr
.or(callableExpr)
.or(simpleExpr)
.named("single-expr");
final FunctionalParser<ParseResult, PyElementType> unionExpr =
paramExpr.then(many(op("or").or(op("|")).skipThen(paramExpr)))
singleExpr.then(many(op("or").or(op("|")).skipThen(singleExpr)))
.map(new Function<Pair<ParseResult, List<ParseResult>>, ParseResult>() {
@Override
public ParseResult fun(Pair<ParseResult, List<ParseResult>> value) {
@@ -1,7 +1,7 @@
def test():
def f(x):
"""
:type x: T (int or str)
:type x: T <= int or str
:rtype: T
"""
pass
@@ -0,0 +1,5 @@
def test():
xs = map(lambda x: x + 1, [1, 2, 3])
print('foo' + <warning descr="Expected type 'one of (str, unicode)', got 'int' instead">xs[0]</warning>)
ys = map(str, iter([1, 2, 3]))
print(1 + <warning descr="Expected type 'one of (int, long, float, complex)', got 'str' instead">ys[0]</warning>, 'bar' + ys[1])
@@ -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<PyType> 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<PyType> parameterTypes = callableType.getParameterTypes(getTypeEvalContext());
assertNotNull(parameterTypes);
assertEquals(0, parameterTypes.size());
}
}
@@ -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();
}
@@ -193,4 +193,9 @@ public class PyTypeCheckerInspectionTest extends PyTestCase {
public void testForLoopIteration() {
doTest();
}
// PY-4285
public void testMapReturnElementType() {
doTest();
}
}