Initial type checker inspection for Python

This commit is contained in:
Andrey Vlasovskikh
2011-06-15 21:21:09 +04:00
parent 922480df87
commit e5049069cc
28 changed files with 986 additions and 44 deletions
@@ -1,14 +1,71 @@
package com.jetbrains.python.codeInsight.stdlib;
import com.jetbrains.python.psi.PyClass;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement;
import com.intellij.util.containers.HashMap;
import com.jetbrains.python.documentation.StructuredDocString;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.PyBuiltinCache;
import com.jetbrains.python.psi.resolve.ResolveImportUtil;
import com.jetbrains.python.psi.types.PyType;
import com.jetbrains.python.psi.types.PyTypeParser;
import com.jetbrains.python.psi.types.PyTypeProviderBase;
import com.jetbrains.python.psi.types.TypeEvalContext;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import java.util.Properties;
/**
* @author yole
*/
public class PyStdlibTypeProvider extends PyTypeProviderBase {
private Properties myStdlibTypes = null;
private Project myProject = null;
private Map<String, PyType> myTypeCache = new HashMap<String, PyType>();
// TODO: Different databases for various Python versions (2.6, 2.7, 3.2, etc.)
@Override
public PyType getReturnType(PyFunction function, @Nullable PyReferenceExpression callSite, TypeEvalContext context) {
final String qname = getQualifiedName(function, callSite);
final String key = String.format("%s.return", qname);
final PyType cached = getCachedType(function.getProject(), key);
if (cached != null) {
return cached;
}
final StructuredDocString docString = getStructuredDocString(qname);
if (docString == null) {
return null;
}
final String s = docString.getReturnType();
final PyType result = PyTypeParser.getTypeByName(function, s);
myTypeCache.put(key, result);
return result;
}
@Override
public PyType getParameterType(PyNamedParameter param, PyFunction func, TypeEvalContext context) {
final String name = param.getName();
final String qname = getQualifiedName(func, param);
final String key = String.format("%s.%s", qname, name);
final PyType cached = getCachedType(param.getProject(), key);
if (cached != null) {
return cached;
}
final StructuredDocString docString = getStructuredDocString(qname);
if (docString == null) {
return null;
}
final String s = docString.getParamType(name);
final PyType result = PyTypeParser.getTypeByName(func, s);
myTypeCache.put(key, result);
return result;
}
@Override
public PyType getIterationType(PyClass iterable) {
final PyBuiltinCache builtinCache = PyBuiltinCache.getInstance(iterable);
@@ -19,4 +76,45 @@ public class PyStdlibTypeProvider extends PyTypeProviderBase {
}
return null;
}
private StructuredDocString getStructuredDocString(String qualifiedName) {
final Properties db = getStdlibTypes();
final String docString = db.getProperty(qualifiedName);
return StructuredDocString.parse(docString);
}
private static String getQualifiedName(PyFunction f, PsiElement callSite) {
String result = f.getName();
final PyClass c = f.getContainingClass();
final VirtualFile vfile = f.getContainingFile().getVirtualFile();
if (vfile != null) {
final String module = ResolveImportUtil.findShortestImportableName(callSite != null ? callSite : f, vfile);
result = String.format("%s.%s%s",
module,
c != null ? c.getName() + "." : "",
result);
}
return result;
}
@Nullable
private PyType getCachedType(Project project, String key) {
if (project != myProject) {
myProject = project;
myTypeCache.clear();
}
return myTypeCache.get(key);
}
private Properties getStdlibTypes() {
if (myStdlibTypes == null) {
myStdlibTypes = new Properties();
InputStream s = getClass().getResourceAsStream("StdlibTypes.properties");
try {
myStdlibTypes.load(s);
}
catch (IOException ignored) {}
}
return myStdlibTypes;
}
}
@@ -0,0 +1,410 @@
# Python 2.7 stdlib
## 5.4. Numeric types
__builtin__.abs = \
:type number: bool or int or long or float or complex \n\
:rtype: int \n\
__builtin__.int.__init__ = \
:type x: object \n\
:type base: bool or int or long or float or complex or None \n\
:rtype: int \n\
__builtin__.long.__init__ = \
:type x: object \n\
:type base: bool or int or long or float or complex or None \n\
:rtype: long \n\
__builtin__.float.__init__ = \
:type x: object \n\
:rtype: float \n\
__builtin__.complex.__init__ = \
:type real: object \n\
:type imag: object or None \n\
:rtype: complex \n\
__builtin__.divmod = \
:type x: bool or int or long or float or complex \n\
:type y: bool or int or long or float or complex \n\
:rtype: (int or long or float or complex, int or long or float or complex) \n\
__builtin__.pow = \
:type x: bool or int or long or float or complex \n\
:type y: bool or int or long or float or complex \n\
:rtype: int or long or float or complex \n\
__builtin__.round = \
:type number: int or long or float \n\
:type ndigits: bool or int or long or float or None \n\
:rtype: float \n\
## 5.6. Sequence types
__builtin__.str.capitalize = \
:rtype: bytes \n\
__builtin__.str.center = \
:type width: int or long \n\
:type fillchar: bytes or None \n\
:rtype: bytes \n\
__builtin__.str.count = \
:type sub: bytes \n\
:type start: int or long or None \n\
:type end: int or long or None \n\
:rtype: int or long \n\
__builtin__.str.decode = \
:type encoding: unicode or bytes \n\
:type errors: unicode or bytes or None \n\
:rtype: unicode \n\
__builtin__.str.encode = \
:type encoding: unicode or bytes \n\
:type errors: unicode or bytes or None \n\
:rtype: bytes \n\
__builtin__.str.endswith = \
:type suffix: bytes \n\
:type start: int or long or None \n\
:type end: int or long or None \n\
:rtype: bool \n\
__builtin__.str.find = \
:type sub: bytes \n\
:type start: int or long or None \n\
:type end: int or long or None \n\
:rtype: int or long \n\
__builtin__.str.index = \
:type sub: bytes \n\
:type start: int or long or None \n\
:type end: int or long or None \n\
:rtype: int or long \n\
__builtin__.str.isalnum = \
:rtype: bool \n\
__builtin__.str.isalpha = \
:rtype: bool \n\
__builtin__.str.isdigit = \
:rtype: bool \n\
__builtin__.str.islower = \
:rtype: bool \n\
__builtin__.str.isspace = \
:rtype: bool \n\
__builtin__.str.istitle = \
:rtype: bool \n\
__builtin__.str.isupper = \
:rtype: bool \n\
__builtin__.str.join = \
:type iterable: object \n\
:rtype: bytes \n\
__builtin__.str.ljust = \
:type width: int or long \n\
:type fillchar: bytes or None \n\
:rtype: bytes \n\
__builtin__.str.lower = \
:rtype: bytes \n\
__builtin__.str.lstrip = \
:type chars: bytes or None \n\
:rtype: bytes \n\
__builtin__.str.partition = \
:type sep: bytes \n\
:rtype: (bytes, bytes, bytes) \n\
__builtin__.str.replace = \
:type old: bytes \n\
:type new: bytes \n\
:type count: int or long \n\
:rtype: bytes \n\
__builtin__.str.rfind = \
:type sub: bytes \n\
:type start: int or long or None \n\
:type end: int or long or None \n\
:rtype: int or long \n\
__builtin__.str.rindex = \
:type sub: bytes \n\
:type start: int or long or None \n\
:type end: int or long or None \n\
:rtype: int or long \n\
__builtin__.str.rpartition = \
:type sep: bytes \n\
:rtype: (bytes, bytes, bytes) \n\
__builtin__.str.rsplit = \
:type sep: bytes or None \n\
:type maxsplit: int or long or None \n\
:rtype: list of bytes \n\
__builtin__.str.rstrip = \
:type chars: bytes or None \n\
:rtype: bytes \n\
__builtin__.str.split = \
:type sep: bytes or None \n\
:type maxsplit: int or long or None \n\
:rtype: list of bytes \n\
__builtin__.str.splitlines = \
:type keepends: bool or int or long or None \n\
:rtype: list of bytes \n\
__builtin__.str.startswith = \
:type prefix: bytes \n\
:type start: int or long or None \n\
:type end: int or long or None \n\
:rtype: bool \n\
__builtin__.str.strip = \
:type chars: bytes or None \n\
:rtype: bytes \n\
__builtin__.str.swapcase = \
:rtype: bytes \n\
__builtin__.str.title = \
:rtype: bytes \n\
__builtin__.str.translate = \
:type table: object \n\
:type deletechars: bytes \n\
:rtype: bytes \n\
__builtin__.str.upper = \
:rtype: bytes \n\
__builtin__.str.zfill = \
:type width: int or long \n\
:rtype: bytes \n\
__builtin__.unicode.capitalize = \
:rtype: unicode \n\
__builtin__.unicode.center = \
:type width: int or long \n\
:type fillchar: unicode or None \n\
:rtype: unicode \n\
__builtin__.unicode.count = \
:type sub: unicode \n\
:type start: int or long or None \n\
:type end: int or long or None \n\
:rtype: int or long \n\
__builtin__.unicode.decode = \
:type encoding: unicode or bytes \n\
:type errors: unicode or bytes or None \n\
:rtype: unicode \n\
__builtin__.unicode.encode = \
:type encoding: unicode or bytes \n\
:type errors: unicode or bytes or None \n\
:rtype: bytes \n\
__builtin__.unicode.endswith = \
:type suffix: unicode \n\
:type start: int or long or None \n\
:type end: int or long or None \n\
:rtype: bool \n\
__builtin__.unicode.find = \
:type sub: unicode \n\
:type start: int or long or None \n\
:type end: int or long or None \n\
:rtype: int or long \n\
__builtin__.unicode.index = \
:type sub: unicode \n\
:type start: int or long or None \n\
:type end: int or long or None \n\
:rtype: int or long \n\
__builtin__.unicode.isalnum = \
:rtype: bool \n\
__builtin__.unicode.isalpha = \
:rtype: bool \n\
__builtin__.unicode.isdecimal = \
:rtype: bool \n\
__builtin__.unicode.isdigit = \
:rtype: bool \n\
__builtin__.unicode.islower = \
:rtype: bool \n\
__builtin__.unicode.isnumeric = \
:rtype: bool \n\
__builtin__.unicode.isspace = \
:rtype: bool \n\
__builtin__.unicode.istitle = \
:rtype: bool \n\
__builtin__.unicode.isupper = \
:rtype: bool \n\
__builtin__.unicode.join = \
:type iterable: object \n\
:rtype: unicode \n\
__builtin__.unicode.ljust = \
:type width: int or long \n\
:type fillchar: unicode or None \n\
:rtype: unicode \n\
__builtin__.unicode.lower = \
:rtype: unicode \n\
__builtin__.unicode.lstrip = \
:type chars: unicode or None \n\
:rtype: unicode \n\
__builtin__.unicode.partition = \
:type sep: unicode \n\
:rtype: (unicode, unicode, unicode) \n\
__builtin__.unicode.replace = \
:type old: unicode \n\
:type new: unicode \n\
:type count: int or long \n\
:rtype: unicode \n\
__builtin__.unicode.rfind = \
:type sub: unicode \n\
:type start: int or long or None \n\
:type end: int or long or None \n\
:rtype: int or long \n\
__builtin__.unicode.rindex = \
:type sub: unicode \n\
:type start: int or long or None \n\
:type end: int or long or None \n\
:rtype: int or long \n\
__builtin__.unicode.rpartition = \
:type sep: unicode \n\
:rtype: (unicode, unicode, unicode) \n\
__builtin__.unicode.rsplit = \
:type sep: unicode or None \n\
:type maxsplit: int or long or None \n\
:rtype: list of unicode \n\
__builtin__.unicode.rstrip = \
:type chars: unicode or None \n\
:rtype: unicode \n\
__builtin__.unicode.split = \
:type sep: unicode or None \n\
:type maxsplit: int or long or None \n\
:rtype: list of unicode \n\
__builtin__.unicode.splitlines = \
:type keepends: bool or int or long or None \n\
:rtype: list of unicode \n\
__builtin__.unicode.startswith = \
:type prefix: unicode \n\
:type start: int or long or None \n\
:type end: int or long or None \n\
:rtype: bool \n\
__builtin__.unicode.strip = \
:type chars: unicode or None \n\
:rtype: unicode \n\
__builtin__.unicode.swapcase = \
:rtype: unicode \n\
__builtin__.unicode.title = \
:rtype: unicode \n\
__builtin__.unicode.translate = \
:type table: object \n\
:type deletechars: unicode \n\
:rtype: unicode \n\
__builtin__.unicode.upper = \
:rtype: unicode \n\
__builtin__.unicode.zfill = \
:type width: int or long \n\
:rtype: unicode \n\
## 15.2. io
__builtin__.open = \
:type name: unicode or bytes \n\
:type mode: unicode or bytes \n\
:type buffering: int \n\
:type encoding: unicode or None \n\
:type errors: unicode or None \n\
:rtype: file \n\
__builtin__.file.read = \
:type size: int or None \n\
:rtype: unknown \n\
__builtin__.file.write = \
:type p_str: unicode or bytes \n\
:rtype: None \n\
## 18.2. json
json.loads = \
:type s: unicode or bytes \n\
:type encoding: unicode or bytes \n\
:rtype: object \n\
## 18.12. base64
base64.b64encode = \
:type s: bytes \n\
:rtype: bytes \n\
base64.b64decode = \
:type s: bytes \n\
:rtype: bytes \n\
# DEBUG: Experimental
__builtin__._open_bytes = \
:type name: unicode or bytes \n\
:type mode: unicode or bytes \n\
:type buffering: int \n\
:type encoding: unicode or None \n\
:type errors: unicode or None \n\
:rtype: _io.FileIO \n\
__builtin__._open_unicode = \
:type name: unicode or bytes \n\
:type mode: unicode or bytes \n\
:type buffering: int \n\
:type encoding: unicode or None \n\
:type errors: unicode or None \n\
:rtype: _io.TextIOWrapper \n\
@@ -10,9 +10,11 @@ import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.Function;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.console.PydevConsoleRunner;
import com.jetbrains.python.console.PydevDocumentationProvider;
@@ -20,6 +22,7 @@ import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.PyBuiltinCache;
import com.jetbrains.python.psi.impl.PyQualifiedName;
import com.jetbrains.python.psi.resolve.ResolveImportUtil;
import com.jetbrains.python.psi.types.PyCollectionType;
import com.jetbrains.python.psi.types.PyType;
import com.jetbrains.python.psi.types.TypeEvalContext;
import com.jetbrains.python.toolbox.ChainIterable;
@@ -27,6 +30,7 @@ import com.jetbrains.python.toolbox.FP;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.methods.HeadMethod;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
@@ -85,15 +89,48 @@ public class PythonDocumentationProvider extends AbstractDocumentationProvider i
final String name = fun.getName();
cat.add("def ").addWith(func_name_wrapper, $(name));
cat.add(escaper.apply(PyUtil.getReadableRepr(fun.getParameterList(), false)));
if (!PyNames.INIT.equals(name) && !specifiesReturnType(fun.getDocStringExpression())) {
final PyType returnType = fun.getReturnType(TypeEvalContext.slow(), null);
cat.add(escaper.apply("\nInferred return type: "));
if (returnType == null) cat.add("unknown");
else cat.add(returnType.getName());
if (!PyNames.INIT.equals(name)) {
cat.add(escaper.apply("\nInferred type: "));
cat.add(escaper.apply(getTypeDescription(fun)));
}
return cat;
}
public static String getTypeDescription(@NotNull PyFunction fun) {
final String UNKNOWN = "unknown";
final TypeEvalContext context = TypeEvalContext.slow();
final PyType returnType = fun.getReturnType(context, null);
return String.format("(%s) -> %s\n",
StringUtil.join(fun.getParameterList().getParameters(),
new Function<PyParameter, String>() {
@Override
public String fun(PyParameter p) {
final PyNamedParameter np = p.getAsNamed();
if (np != null) {
String name = UNKNOWN;
final PyType t = np.getType(context);
if (t != null) {
name = getTypeName(t, context);
}
return String.format("%s: %s", np.getName(), name);
}
return p.toString();
}
}, ", "),
returnType != null ? getTypeName(returnType, context) : UNKNOWN);
}
public static String getTypeName(@NotNull PyType type, @NotNull TypeEvalContext context) {
final String name = type.getName();
if (type instanceof PyCollectionType) {
final PyType elementType = ((PyCollectionType)type).getElementType(context);
if (elementType != null) {
return String.format("%s of %s", name, elementType.getName());
}
}
return name;
}
static ChainIterable<String> describeDecorators(
PyDecoratable what, FP.Lambda1<Iterable<String>, Iterable<String>> deco_name_wrapper,
String deco_separator, FP.Lambda1<String, String> escaper
@@ -23,7 +23,7 @@ public abstract class StructuredDocString {
if (text == null) {
return null;
}
if (text.contains(":param ") || text.contains(":rtype ") || text.contains(":type ")) {
if (text.contains(":param ") || text.contains(":rtype") || text.contains(":type ")) {
return new SphinxDocString(text);
}
return new EpydocString(text);
@@ -0,0 +1,183 @@
package com.jetbrains.python.inspections;
import com.intellij.codeInspection.LocalInspectionToolSession;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Key;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiElementVisitor;
import com.intellij.psi.ResolveResult;
import com.jetbrains.python.documentation.PythonDocumentationProvider;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.resolve.ImplicitResolveResult;
import com.jetbrains.python.psi.types.*;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* @author vlan
*/
public class PyTypeCheckerInspection extends PyInspection {
private static final Logger LOG = Logger.getInstance(PyTypeCheckerInspection.class.getName());
private static Key<Long> TIME_KEY = Key.create("PyTypeCheckerInspection.StartTime");
@NotNull
@Override
public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly, @NotNull LocalInspectionToolSession session) {
if (LOG.isDebugEnabled()) {
session.putUserData(TIME_KEY, System.nanoTime());
}
return new PyInspectionVisitor(holder) {
// TODO: Show types in tooltips for variables
// TODO: Visit decorators with arguments
// TODO: Visit operators (requires resolve() for operators)
@Override
public void visitPyCallExpression(PyCallExpression node) {
List<PyFunction> functions = new ArrayList<PyFunction>();
final PyExpression callee = node.getCallee();
if (callee instanceof PyReferenceExpression) {
ResolveResult[] results = ((PyReferenceExpression)callee).getReference().multiResolve(false);
for (ResolveResult result : results) {
if (!(result instanceof ImplicitResolveResult)) {
PsiElement e = result.getElement();
if (e instanceof PyFunction) {
functions.add((PyFunction)e);
}
}
}
}
if (!functions.isEmpty()) {
PyFunction fun = functions.get(0);
final TypeEvalContext fastContext = TypeEvalContext.fast();
final TypeEvalContext slowContext = TypeEvalContext.slow();
final TypeEvalContext context = fun.getContainingFile() == node.getContainingFile() ?
slowContext : fastContext;
final PyArgumentList args = node.getArgumentList();
if (args != null) {
final PyArgumentList.AnalysisResult res = args.analyzeCall(context);
final Map<PyExpression, PyNamedParameter> mapped = res.getPlainMappedParams();
for (Map.Entry<PyExpression, PyNamedParameter> entry : mapped.entrySet()) {
final PyNamedParameter p = entry.getValue();
if (p.isPositionalContainer() || p.isKeywordContainer()) {
// TODO: Support *args, **kwargs
continue;
}
final PyType argType = entry.getKey().getType(slowContext);
final PyType paramType = p.getType(context);
if (argType != null && paramType != null) {
if (!match(paramType, argType, context)) {
registerProblem(entry.getKey(), String.format("Expected type '%s', got '%s' instead",
PythonDocumentationProvider.getTypeName(paramType, context),
PythonDocumentationProvider.getTypeName(argType, slowContext)));
}
}
}
}
}
}
};
}
public static boolean match(PyType superType, PyType subType, TypeEvalContext context) {
// TODO: subscriptable types?, module types?, etc.
if (superType == null || subType == null) {
return true;
}
if (superType instanceof PyUnionType) {
for (PyType t : ((PyUnionType)superType).getMembers()) {
if (match(t, subType, context)) {
return true;
}
}
return false;
}
if (subType instanceof PyUnionType) {
for (PyType t : ((PyUnionType)subType).getMembers()) {
if (!match(superType, t, context)) {
return false;
}
}
return true;
}
if (superType instanceof PyClassType && subType instanceof PyClassType) {
final PyClass superClass = ((PyClassType)superType).getPyClass();
final PyClass subClass = ((PyClassType)subType).getPyClass();
if (superType instanceof PyCollectionType && subType instanceof PyCollectionType) {
if (!matchClasses(superClass, subClass)) {
return false;
}
final PyType superElementType = ((PyCollectionType)superType).getElementType(context);
final PyType subElementType = ((PyCollectionType)subType).getElementType(context);
return match(superElementType, subElementType, context);
}
else if (superType instanceof PyTupleType && subType instanceof PyTupleType) {
final PyTupleType superTupleType = (PyTupleType)superType;
final PyTupleType subTupleType = (PyTupleType)subType;
if (superTupleType.getElementCount() != subTupleType.getElementCount()) {
return false;
}
else {
for (int i = 0; i < superTupleType.getElementCount(); i++) {
if (!match(superTupleType.getElementType(i), subTupleType.getElementType(i), context)) {
return false;
}
}
return true;
}
}
else if (matchClasses(superClass, subClass)) {
return true;
}
}
if (superType.equals(subType)) {
return true;
}
final String superName = superType.getName();
final String subName = subType.getName();
// TODO: No inheritance check for builtin numerics at this moment
final boolean subIsBool = "bool".equals(subName);
final boolean subIsInt = "int".equals(subName);
final boolean subIsLong = "long".equals(subName);
final boolean subIsFloat = "float".equals(subName);
if (superName == null || subName == null ||
superName.equals(subName) ||
("int".equals(superName) && subIsBool) ||
("long".equals(superName) && (subIsBool || subIsInt)) ||
("float".equals(superName) && (subIsBool || subIsInt || subIsLong)) ||
("complex".equals(superName) && (subIsBool || subIsInt || subIsLong || subIsFloat))) {
return true;
}
return false;
}
@Override
public void inspectionFinished(LocalInspectionToolSession session, ProblemsHolder problemsHolder) {
if (LOG.isDebugEnabled()) {
final Long startTime = session.getUserData(TIME_KEY);
if (startTime != null) {
LOG.debug(String.format("[%d] elapsed time: %d ms\n",
Thread.currentThread().getId(),
(System.nanoTime() - startTime) / 1000000));
}
}
}
@Nls
@NotNull
public String getDisplayName() {
return "Type checker";
}
private static boolean matchClasses(@Nullable PyClass superClass, @Nullable PyClass subClass) {
if (superClass == null || subClass == null || subClass.isSubclass(superClass)) {
return true;
}
else {
final String superName = superClass.getName();
return superName != null && superName.equals(subClass.getName());
}
}
}
@@ -60,6 +60,7 @@ public class PythonInspectionToolProvider implements InspectionToolProvider {
PyArgumentEqualDefaultInspection.class,
PySetFunctionToLiteralInspection.class,
PyDecoratorInspection.class,
PyTypeCheckerInspection.class,
};
}
}
@@ -1,7 +1,11 @@
package com.jetbrains.python.psi;
import org.jetbrains.annotations.Nullable;
/**
* @author yole
*/
public interface PyYieldExpression extends PyExpression {
@Nullable
PyExpression getExpression();
}
@@ -18,6 +18,7 @@ import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiFileSystemItem;
import com.intellij.psi.PsiManager;
import com.jetbrains.python.psi.LanguageLevel;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.PyFile;
import com.jetbrains.python.psi.PySequenceExpression;
@@ -164,7 +165,6 @@ public class PyBuiltinCache {
/**
* Looks for a top-level named item. (Package builtins does not contain any sensible nested names anyway.)
* @param name to look for
* @param type to look for and cast to (most often, PyFunction or PyClass)
* @return found element, or null.
*/
@Nullable
@@ -187,7 +187,7 @@ public class PyBuiltinCache {
* Stores the most often used types, returned by getNNNType().
*/
private final Map<String,PyClassType> myTypeCache = new HashMap<String, PyClassType>();
/**
@return
*/
@@ -249,6 +249,26 @@ public class PyBuiltinCache {
return getObjectType("str");
}
@Nullable
public PyClassType getBytesType(LanguageLevel level) {
if (level.isPy3K()) {
return getObjectType("bytes");
}
else {
return getObjectType("str");
}
}
@Nullable
public PyType getStringType(LanguageLevel level) {
if (level.isPy3K()) {
return getObjectType("str");
}
else {
return getObjectType("unicode");
}
}
@Nullable
public PyClassType getBoolType() {
return getObjectType("bool");
@@ -30,8 +30,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.Collections;
import java.util.List;
import java.util.*;
/**
* Implements PyFunction.
@@ -127,27 +126,63 @@ public class PyFunctionImpl extends PyPresentableElementImpl<PyFunctionStub> imp
return new PyClassType(pyClass, false);
}
}
for(PyTypeProvider typeProvider: Extensions.getExtensions(PyTypeProvider.EP_NAME)) {
final PyType returnType = typeProvider.getReturnType(this, callSite, typeEvalContext);
if (returnType != null) {
return returnType;
}
}
final PyType docStringType = getReturnTypeFromDocString();
if (docStringType != null) {
return docStringType;
}
}
if (typeEvalContext.allowReturnTypes()) {
final PyType yieldType = getYieldStatementType(typeEvalContext);
if (yieldType != null) {
return yieldType;
}
return getReturnStatementType(typeEvalContext);
}
return null;
}
@Nullable
private PyType getYieldStatementType(@NotNull final TypeEvalContext context) {
PyType elementType = null;
final PyBuiltinCache cache = PyBuiltinCache.getInstance(this);
final PyClass listClass = cache.getClass("list");
final PyStatementList statements = getStatementList();
final Set<PyType> types = new HashSet<PyType>();
if (statements != null && listClass != null) {
statements.accept(new PyRecursiveElementVisitor() {
@Override
public void visitPyYieldExpression(PyYieldExpression node) {
PyType t = node.getType(context);
if (t != null) {
types.add(t);
}
else {
types.add(cache.getObjectType());
}
}
});
final int n = types.size();
if (n == 1) {
elementType = types.iterator().next();
}
else if (n > 0) {
elementType = new PyUnionType(types);
}
}
if (elementType != null) {
return new PyCollectionTypeImpl(listClass, false, elementType);
}
return null;
}
@Nullable
public PyType getReturnStatementType(TypeEvalContext typeEvalContext) {
ReturnVisitor visitor = new ReturnVisitor(typeEvalContext);
ReturnVisitor visitor = new ReturnVisitor(this, typeEvalContext);
final PyStatementList statements = getStatementList();
if (statements != null) {
statements.accept(visitor);
@@ -224,30 +259,34 @@ public class PyFunctionImpl extends PyPresentableElementImpl<PyFunctionStub> imp
}
private static class ReturnVisitor extends PyRecursiveElementVisitor {
private final PyFunction myFunction;
private final TypeEvalContext myContext;
private PyType myResult = null;
private boolean myHasReturns = false;
public ReturnVisitor(final TypeEvalContext context) {
public ReturnVisitor(PyFunction function, final TypeEvalContext context) {
myFunction = function;
myContext = context;
}
@Override
public void visitPyReturnStatement(PyReturnStatement node) {
final PyExpression expr = node.getExpression();
PyType returnType;
returnType = expr == null ? PyNoneType.INSTANCE : myContext.getType(expr);
if (!myHasReturns) {
myResult = returnType;
myHasReturns = true;
}
else {
if (myResult == null) {
if (PsiTreeUtil.getParentOfType(node, ScopeOwner.class, true) == myFunction) {
final PyExpression expr = node.getExpression();
PyType returnType;
returnType = expr == null ? PyNoneType.INSTANCE : myContext.getType(expr);
if (!myHasReturns) {
myResult = returnType;
myHasReturns = true;
}
else {
if (returnType != null) {
myResult = PyUnionType.union(myResult, returnType);
if (myResult == null) {
myResult = returnType;
}
else {
if (returnType != null) {
myResult = PyUnionType.union(myResult, returnType);
}
}
}
}
@@ -24,10 +24,12 @@ public class PyKeywordArgumentImpl extends PyElementImpl implements PyKeywordArg
return node != null ? node.getText() : null;
}
@Override
public ASTNode getKeywordNode() {
return getNode().findChildByType(PyTokenTypes.IDENTIFIER);
}
@Override
public PyExpression getValueExpression() {
return PsiTreeUtil.getChildOfType(this, PyExpression.class);
}
@@ -38,6 +40,7 @@ public class PyKeywordArgumentImpl extends PyElementImpl implements PyKeywordArg
}
public PyType getType(@NotNull TypeEvalContext context) {
return null;
final PyExpression e = getValueExpression();
return e != null ? e.getType(context) : null;
}
}
@@ -1,7 +1,9 @@
package com.jetbrains.python.psi.impl;
import com.intellij.lang.ASTNode;
import com.jetbrains.python.PyElementTypes;
import com.jetbrains.python.psi.PyElementVisitor;
import com.jetbrains.python.psi.PyExpression;
import com.jetbrains.python.psi.PyYieldExpression;
import com.jetbrains.python.psi.types.PyType;
import com.jetbrains.python.psi.types.TypeEvalContext;
@@ -19,7 +21,14 @@ public class PyYieldExpressionImpl extends PyElementImpl implements PyYieldExpre
pyVisitor.visitPyYieldExpression(this);
}
@Override
public PyExpression getExpression() {
return childToPsi(PyElementTypes.EXPRESSIONS, 0);
}
@Override
public PyType getType(@NotNull TypeEvalContext context) {
return null;
final PyExpression e = getExpression();
return e != null ? e.getType(context) : null;
}
}
@@ -3,6 +3,7 @@ package com.jetbrains.python.psi.types;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.PyExpression;
import com.jetbrains.python.psi.PySequenceExpression;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
@@ -17,7 +18,7 @@ public class PyLiteralCollectionType extends PyClassType implements PyCollection
}
@Override
public PyType getElementType(TypeEvalContext context) {
public PyType getElementType(@NotNull TypeEvalContext context) {
final PyExpression[] elements = mySequence.getElements();
if (elements.length == 0 || elements.length > 10 /* performance */) {
return null;
@@ -5,6 +5,7 @@ import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.jetbrains.python.psi.LanguageLevel;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.PyFile;
import com.jetbrains.python.psi.impl.PyBuiltinCache;
@@ -43,9 +44,18 @@ public class PyTypeParser {
final PyBuiltinCache builtinCache = PyBuiltinCache.getInstance(anchor);
if (type.equals("unknown")) {
return null;
}
if (type.equals("string")) {
return builtinCache.getStringType(LanguageLevel.forElement(anchor));
}
if (type.equals("str")) {
return builtinCache.getStrType();
}
if (type.equals("bytes")) {
return builtinCache.getBytesType(LanguageLevel.forElement(anchor));
}
if (type.equals("boolean")) {
return builtinCache.getBoolType();
}
@@ -83,7 +93,7 @@ public class PyTypeParser {
return new PyClassType(classes.iterator().next(), false);
}
}
if (CharMatcher.JAVA_LETTER_OR_DIGIT.or(CharMatcher.is('.')).matchesAllOf(type)) {
if (CharMatcher.JAVA_LETTER_OR_DIGIT.or(CharMatcher.is('.')).or(CharMatcher.is('_')).matchesAllOf(type)) {
int pos = type.lastIndexOf('.');
if (pos > 0) {
String shortName = type.substring(pos+1);
@@ -93,6 +103,11 @@ public class PyTypeParser {
return new PyClassType(aClass, false);
}
}
for (PyClass aClass : classes) {
if (shortName.equals(aClass.getName())) {
return new PyClassType(aClass, false);
}
}
}
}
@@ -0,0 +1,116 @@
def f1(p1, p2, p3, p4, p5, p6, p7, p8, p9, p10=10, p11='11'):
"""
:type p1: integer
:type p2: integer
:type p3: float
:type p4: float
:type p5: int
:type p6: integer
:type p7: integer
:type p8: int
:type p9: int
:type p10: int
:type p11: string
"""
return p1 + p2 + p3 + p4 + p5 + p6 + p7 + p8 + p9 + p10 + int(p11)
def test_1():
p7 = int('7')
f1(1,
<warning descr="Expected type 'int', got 'str' instead">'2'</warning>,
3.0, 4, 5, int('6'), p7, p8=-8,
<warning descr="Expected type 'int', got 'str' instead">p9='foo'</warning>,
<warning descr="Expected type 'int', got 'str' instead">p10='foo'</warning>)
def str_to_none(b):
"""
:type b: str
"""
pass
def unicode_to_none(s):
"""
:type s: unicode
"""
pass
def string_to_none(s):
"""
:type s: string
"""
pass
def str_or_unicode_to_none(s):
"""
:type s: str or unicode
"""
pass
def test_str_unicode():
b1 = 'hello'
s1 = u'привет'
b2 = str(-1)
s2 = unicode(3.14)
ENC = 'utf-8'
str_to_none(<warning descr="Expected type 'str', got 'unicode' instead">b1.decode(ENC)</warning>)
unicode_to_none(b1.decode(ENC))
string_to_none(b1.decode(ENC))
str_or_unicode_to_none(b1.decode(ENC))
b1.encode(ENC)
s1.decode(ENC)
str_to_none(s1.encode(ENC))
unicode_to_none(<warning descr="Expected type 'unicode', got 'str' instead">s1.encode(ENC)</warning>)
string_to_none(<warning descr="Expected type 'unicode', got 'str' instead">s1.encode(ENC)</warning>)
str_or_unicode_to_none(s1.encode(ENC))
b2.decode(ENC)
b2.encode(ENC)
s2.decode(ENC)
s2.encode(ENC)
def f_list_tuple(spam, eggs):
"""
:type spam: list of string
:type eggs: (bool, int, string)
"""
return spam, eggs
def test_list_tuple():
f_list_tuple(<warning descr="Expected type 'list of unicode', got 'list of int' instead">[1, 2, 3]</warning>,
(<warning descr="Expected type 'tuple(bool,int,unicode)', got 'tuple(bool,int,str)' instead">False, 2, ''</warning>))
def test_builtin_numerics():
abs(False)
int(10)
long(False)
float(False)
complex(False)
divmod(False, False)
divmod(<warning descr="Expected type 'one of (bool, int, long, float, complex)', got 'str' instead">'foo'</warning>,
<warning descr="Expected type 'one of (bool, int, long, float, complex)', got 'unicode' instead">u'bar'</warning>)
pow(False, True)
round(False,
<warning descr="Expected type 'one of (bool, int, long, float, None)', got 'str' instead">'foo'</warning>)
def test_generator():
def gen(n):
for x in xrange(n):
yield str(x)
def f(xs):
"""
:type xs: list of int
"""
return xs
return (''.join(gen(10)),
f(<warning descr="Expected type 'list of int', got 'list of str' instead">gen(11)</warning>))
+1 -1
View File
@@ -1 +1 @@
<html><body><code>def <b>foo</b>()<br>Inferred&nbsp;return&nbsp;type:&nbsp;None<br>Doc&nbsp;of&nbsp;foo.</code></body></html>
<html><body><code>def <b>foo</b>()<br>Inferred&nbsp;type:&nbsp;()&nbsp;-&gt;&nbsp;None<br><br>Doc&nbsp;of&nbsp;foo.</code></body></html>
+1 -1
View File
@@ -1 +1 @@
<html><body><code>def <b>foo</b>()<br>Inferred&nbsp;return&nbsp;type:&nbsp;None<br>Doc&nbsp;of&nbsp;foo.</code></body></html>
<html><body><code>def <b>foo</b>()<br>Inferred&nbsp;type:&nbsp;()&nbsp;-&gt;&nbsp;None<br><br>Doc&nbsp;of&nbsp;foo.</code></body></html>
@@ -1,2 +1,2 @@
def foo(arg)
Inferred return type: int
Inferred type: (arg: unknown) -> int
@@ -1,3 +1,3 @@
class A
def f(self)
Inferred return type: int
Inferred type: (self: A) -> int
+1 -1
View File
@@ -1 +1 @@
<html><body><code>def <b>foo</b>()<br>Inferred&nbsp;return&nbsp;type:&nbsp;None<br>Doc&nbsp;of&nbsp;foo.<br>It&nbsp;has&nbsp;two&nbsp;lines.</code></body></html>
<html><body><code>def <b>foo</b>()<br>Inferred&nbsp;type:&nbsp;()&nbsp;-&gt;&nbsp;None<br><br>Doc&nbsp;of&nbsp;foo.<br>It&nbsp;has&nbsp;two&nbsp;lines.</code></body></html>
@@ -1 +1 @@
<html><body><code><small>class <a href="psi_element://#class#">B</a>(<a href="psi_element://#parent#A">A</a>)</small><br><br>def <b>foo</b>(self)<br>Inferred&nbsp;return&nbsp;type:&nbsp;None</code><br><br><i>Documentation is missing.</i> The following is copied from <code><a href="psi_element://#parent#A">A</a>.foo</code>.<br><br><code>Doc&nbsp;from&nbsp;A.foo.</code></body></html>
<html><body><code><small>class <a href="psi_element://#class#">B</a>(<a href="psi_element://#parent#A">A</a>)</small><br><br>def <b>foo</b>(self)<br>Inferred&nbsp;type:&nbsp;(self:&nbsp;B)&nbsp;-&gt;&nbsp;None<br></code><br><br><i>Documentation is missing.</i> The following is copied from <code><a href="psi_element://#parent#A">A</a>.foo</code>.<br><br><code>Doc&nbsp;from&nbsp;A.foo.</code></body></html>
+1 -1
View File
@@ -1 +1 @@
<html><body><code><small>class <a href="psi_element://#class#">Foo</a></small><br><br>@<i>deco</i><br>def <b>meth</b>(self)<br>Inferred&nbsp;return&nbsp;type:&nbsp;None<br><br>Doc&nbsp;of&nbsp;meth.<br></code></body></html>
<html><body><code><small>class <a href="psi_element://#class#">Foo</a></small><br><br>@<i>deco</i><br>def <b>meth</b>(self)<br>Inferred&nbsp;type:&nbsp;(self:&nbsp;Foo)&nbsp;-&gt;&nbsp;None<br><br><br>Doc&nbsp;of&nbsp;meth.<br></code></body></html>
+1 -1
View File
@@ -1 +1 @@
<html><body>property <b><code>x</code></b> of class <a href="psi_element://#class#">A</a>(<a href="psi_element://#parent#object">object</a>)<br><i>Copied from getter:</i><br>Does things to X<code><br><br>@<i>x.deleter</i><br>def <b>x</b>(self,&nbsp;v)<br>Inferred&nbsp;return&nbsp;type:&nbsp;None<br>Deletes&nbsp;X</code><small><br><br>Deleter of property</small><br></body></html>
<html><body>property <b><code>x</code></b> of class <a href="psi_element://#class#">A</a>(<a href="psi_element://#parent#object">object</a>)<br><i>Copied from getter:</i><br>Does things to X<code><br><br>@<i>x.deleter</i><br>def <b>x</b>(self,&nbsp;v)<br>Inferred&nbsp;type:&nbsp;(self:&nbsp;A,&nbsp;v:&nbsp;unknown)&nbsp;-&gt;&nbsp;None<br><br>Deletes&nbsp;X</code><small><br><br>Deleter of property</small><br></body></html>
+1 -1
View File
@@ -1 +1 @@
<html><body>property <b><code>x</code></b> of class <a href="psi_element://#class#">A</a>(<a href="psi_element://#parent#object">object</a>)<code><br><br>@<i>property</i><br>def <b>x</b>(self)<br>Inferred&nbsp;return&nbsp;type:&nbsp;int<br>Does&nbsp;things&nbsp;to&nbsp;X</code><small><br><br>Getter of property</small><br></body></html>
<html><body>property <b><code>x</code></b> of class <a href="psi_element://#class#">A</a>(<a href="psi_element://#parent#object">object</a>)<code><br><br>@<i>property</i><br>def <b>x</b>(self)<br>Inferred&nbsp;type:&nbsp;(self:&nbsp;A)&nbsp;-&gt;&nbsp;int<br><br>Does&nbsp;things&nbsp;to&nbsp;X</code><small><br><br>Getter of property</small><br></body></html>
+1 -1
View File
@@ -1 +1 @@
<html><body>property <b><code>x</code></b> of class <a href="psi_element://#class#">A</a>(<a href="psi_element://#parent#object">object</a>)<br><i>Copied from getter:</i><br>Does things to X<code><br><br>@<i>x.setter</i><br>def <b>x</b>(self,&nbsp;v)<br>Inferred&nbsp;return&nbsp;type:&nbsp;None<br>Sets&nbsp;X</code><small><br><br>Setter of property</small><br></body></html>
<html><body>property <b><code>x</code></b> of class <a href="psi_element://#class#">A</a>(<a href="psi_element://#parent#object">object</a>)<br><i>Copied from getter:</i><br>Does things to X<code><br><br>@<i>x.setter</i><br>def <b>x</b>(self,&nbsp;v)<br>Inferred&nbsp;type:&nbsp;(self:&nbsp;A,&nbsp;v:&nbsp;unknown)&nbsp;-&gt;&nbsp;None<br><br>Sets&nbsp;X</code><small><br><br>Setter of property</small><br></body></html>
+1 -1
View File
@@ -1 +1 @@
<html><body>property <b><code>x</code></b> of class <a href="psi_element://#class#">A</a>(<a href="psi_element://#parent#object">object</a>)<code><br><br>def <b>__getX</b>(self)<br>Inferred&nbsp;return&nbsp;type:&nbsp;unknown<br>Doc&nbsp;of&nbsp;getter</code><small><br><br>Deleter of property</small><br></body></html>
<html><body>property <b><code>x</code></b> of class <a href="psi_element://#class#">A</a>(<a href="psi_element://#parent#object">object</a>)<code><br><br>def <b>__getX</b>(self)<br>Inferred&nbsp;type:&nbsp;(self:&nbsp;A)&nbsp;-&gt;&nbsp;unknown<br><br>Doc&nbsp;of&nbsp;getter</code><small><br><br>Deleter of property</small><br></body></html>
+1 -1
View File
@@ -1 +1 @@
<html><body>property <b><code>x</code></b> of class <a href="psi_element://#class#">A</a>(<a href="psi_element://#parent#object">object</a>)<code><br><br>def <b>__getX</b>(self)<br>Inferred&nbsp;return&nbsp;type:&nbsp;unknown<br>Doc&nbsp;of&nbsp;getter</code><small><br><br>Getter of property</small><br></body></html>
<html><body>property <b><code>x</code></b> of class <a href="psi_element://#class#">A</a>(<a href="psi_element://#parent#object">object</a>)<code><br><br>def <b>__getX</b>(self)<br>Inferred&nbsp;type:&nbsp;(self:&nbsp;A)&nbsp;-&gt;&nbsp;unknown<br><br>Doc&nbsp;of&nbsp;getter</code><small><br><br>Getter of property</small><br></body></html>
+1 -1
View File
@@ -1 +1 @@
<html><body>property <b><code>x</code></b> of class <a href="psi_element://#class#">A</a>(<a href="psi_element://#parent#object">object</a>)<code><br><br>def <b>__getX</b>(self,&nbsp;x)<br>Inferred&nbsp;return&nbsp;type:&nbsp;None<br>Doc&nbsp;of&nbsp;getter</code><small><br><br>Setter of property</small><br></body></html>
<html><body>property <b><code>x</code></b> of class <a href="psi_element://#class#">A</a>(<a href="psi_element://#parent#object">object</a>)<code><br><br>def <b>__getX</b>(self,&nbsp;x)<br>Inferred&nbsp;type:&nbsp;(self:&nbsp;A,&nbsp;x:&nbsp;unknown)&nbsp;-&gt;&nbsp;None<br><br>Doc&nbsp;of&nbsp;getter</code><small><br><br>Setter of property</small><br></body></html>
@@ -343,4 +343,10 @@ public class PythonInspectionsTest extends PyLightFixtureTestCase {
public void testPyDecoratorInspection() { //PY-3348
doHighlightingTest(PyDecoratorInspection.class);
}
public void testPyTypeCheckerInspection() {
// TODO: Add a Py3K test
setLanguageLevel(LanguageLevel.PYTHON27);
doHighlightingTest(PyTypeCheckerInspection.class);
}
}