diff --git a/python/psi-api/src/com/jetbrains/python/psi/types/PyOverridingModuleMembersProvider.java b/python/psi-api/src/com/jetbrains/python/psi/types/PyOverridingModuleMembersProvider.java
new file mode 100644
index 000000000000..90869d3c7318
--- /dev/null
+++ b/python/psi-api/src/com/jetbrains/python/psi/types/PyOverridingModuleMembersProvider.java
@@ -0,0 +1,7 @@
+package com.jetbrains.python.psi.types;
+
+/**
+ * @author vlan
+ */
+public abstract class PyOverridingModuleMembersProvider extends PyModuleMembersProvider {
+}
diff --git a/python/src/META-INF/python-plugin-common.xml b/python/src/META-INF/python-plugin-common.xml
index cfa88840b3da..7d31ba833638 100644
--- a/python/src/META-INF/python-plugin-common.xml
+++ b/python/src/META-INF/python-plugin-common.xml
@@ -497,6 +497,9 @@
+
+
+
@@ -856,6 +859,10 @@
+
+
+
+
diff --git a/python/src/com/jetbrains/python/codeInsight/userSkeletons/PyUserSkeletonsLineMarkerProvider.java b/python/src/com/jetbrains/python/codeInsight/userSkeletons/PyUserSkeletonsLineMarkerProvider.java
new file mode 100644
index 000000000000..624c67e7ce7a
--- /dev/null
+++ b/python/src/com/jetbrains/python/codeInsight/userSkeletons/PyUserSkeletonsLineMarkerProvider.java
@@ -0,0 +1,70 @@
+package com.jetbrains.python.codeInsight.userSkeletons;
+
+import com.intellij.codeHighlighting.Pass;
+import com.intellij.codeInsight.daemon.GutterIconNavigationHandler;
+import com.intellij.codeInsight.daemon.LineMarkerInfo;
+import com.intellij.codeInsight.daemon.LineMarkerProvider;
+import com.intellij.icons.AllIcons;
+import com.intellij.openapi.editor.markup.GutterIconRenderer;
+import com.intellij.psi.PsiElement;
+import com.intellij.util.Function;
+import com.intellij.util.PsiNavigateUtil;
+import com.jetbrains.python.psi.PyElement;
+import com.jetbrains.python.psi.PyFunction;
+import com.jetbrains.python.psi.PyTargetExpression;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+import java.awt.event.MouseEvent;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * @author vlan
+ */
+public class PyUserSkeletonsLineMarkerProvider implements LineMarkerProvider {
+ // TODO: Create an icon for a related user skeleton
+ public static final Icon ICON = AllIcons.Gutter.Unique;
+
+ @Nullable
+ @Override
+ public LineMarkerInfo getLineMarkerInfo(@NotNull PsiElement element) {
+ return null;
+ }
+
+ @Override
+ public void collectSlowLineMarkers(@NotNull List elements, @NotNull Collection result) {
+ for (PsiElement element : elements) {
+ final PyElement skeleton = getUserSkeleton(element);
+ if (skeleton != null) {
+ result.add(new LineMarkerInfo(
+ element, element.getTextRange(), ICON, Pass.VISIBLE_LINE_MARKERS,
+ new Function() {
+ @Override
+ public String fun(PsiElement e) {
+ return "Has user skeleton";
+ }
+ },
+ new GutterIconNavigationHandler() {
+ @Override
+ public void navigate(MouseEvent e, PsiElement elt) {
+ final PyElement s = getUserSkeleton(elt);
+ if (s != null) {
+ PsiNavigateUtil.navigate(s);
+ }
+ }
+ },
+ GutterIconRenderer.Alignment.RIGHT));
+ }
+ }
+ }
+
+ @Nullable
+ private static PyElement getUserSkeleton(@NotNull PsiElement element) {
+ if (element instanceof PyFunction || element instanceof PyTargetExpression) {
+ return PyUserSkeletonsUtil.getUserSkeleton((PyElement)element);
+ }
+ return null;
+ }
+}
diff --git a/python/src/com/jetbrains/python/codeInsight/userSkeletons/PyUserSkeletonsModuleMembersProvider.java b/python/src/com/jetbrains/python/codeInsight/userSkeletons/PyUserSkeletonsModuleMembersProvider.java
new file mode 100644
index 000000000000..59c0a79954c4
--- /dev/null
+++ b/python/src/com/jetbrains/python/codeInsight/userSkeletons/PyUserSkeletonsModuleMembersProvider.java
@@ -0,0 +1,44 @@
+package com.jetbrains.python.codeInsight.userSkeletons;
+
+import com.intellij.psi.PsiElement;
+import com.jetbrains.python.codeInsight.PyDynamicMember;
+import com.jetbrains.python.psi.PyElement;
+import com.jetbrains.python.psi.PyFile;
+import com.jetbrains.python.psi.types.PyModuleMembersProvider;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author vlan
+ */
+public class PyUserSkeletonsModuleMembersProvider extends PyModuleMembersProvider {
+ @Nullable
+ @Override
+ public PsiElement resolveMember(PyFile module, String name) {
+ final PyFile moduleSkeleton = PyUserSkeletonsUtil.getUserSkeleton(module);
+ if (moduleSkeleton != null) {
+ return moduleSkeleton.getElementNamed(name);
+ }
+ return null;
+ }
+
+ @Override
+ protected Collection getMembersByQName(PyFile module, String qName) {
+ final PyFile moduleSkeleton = PyUserSkeletonsUtil.getUserSkeletonForModuleQName(qName, module);
+ if (moduleSkeleton != null) {
+ final List results = new ArrayList();
+ for (PyElement element : moduleSkeleton.iterateNames()) {
+ final String name = element.getName();
+ if (name != null) {
+ results.add(new PyDynamicMember(name, element));
+ }
+ }
+ return results;
+ }
+ return Collections.emptyList();
+ }
+}
diff --git a/python/src/com/jetbrains/python/codeInsight/userSkeletons/PyUserSkeletonsTypeProvider.java b/python/src/com/jetbrains/python/codeInsight/userSkeletons/PyUserSkeletonsTypeProvider.java
new file mode 100644
index 000000000000..f93b3b22978c
--- /dev/null
+++ b/python/src/com/jetbrains/python/codeInsight/userSkeletons/PyUserSkeletonsTypeProvider.java
@@ -0,0 +1,49 @@
+package com.jetbrains.python.codeInsight.userSkeletons;
+
+import com.intellij.psi.PsiElement;
+import com.jetbrains.python.psi.*;
+import com.jetbrains.python.psi.types.PyType;
+import com.jetbrains.python.psi.types.PyTypeProviderBase;
+import com.jetbrains.python.psi.types.TypeEvalContext;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * @author vlan
+ */
+public class PyUserSkeletonsTypeProvider extends PyTypeProviderBase {
+ @Override
+ public PyType getParameterType(@NotNull PyNamedParameter param, @NotNull PyFunction func, @NotNull TypeEvalContext context) {
+ final String name = param.getName();
+ if (name != null) {
+ final PyFunction functionSkeleton = PyUserSkeletonsUtil.getUserSkeleton(func);
+ if (functionSkeleton != null) {
+ final PyNamedParameter paramSkeleton = functionSkeleton.getParameterList().findParameterByName(name);
+ if (paramSkeleton != null) {
+ return context.getType(paramSkeleton);
+ }
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public PyType getReturnType(@NotNull PyFunction function, @Nullable PyQualifiedExpression callSite, @NotNull TypeEvalContext context) {
+ final PyFunction functionSkeleton = PyUserSkeletonsUtil.getUserSkeleton(function);
+ if (functionSkeleton != null) {
+ return functionSkeleton.getReturnType(context, callSite);
+ }
+ return null;
+ }
+
+ @Override
+ public PyType getReferenceType(@NotNull PsiElement target, TypeEvalContext context, @Nullable PsiElement anchor) {
+ if (target instanceof PyTargetExpression) {
+ final PyTargetExpression targetSkeleton = PyUserSkeletonsUtil.getUserSkeleton((PyTargetExpression)target);
+ if (targetSkeleton != null) {
+ return context.getType(targetSkeleton);
+ }
+ }
+ return null;
+ }
+}
diff --git a/python/src/com/jetbrains/python/codeInsight/userSkeletons/PyUserSkeletonsUtil.java b/python/src/com/jetbrains/python/codeInsight/userSkeletons/PyUserSkeletonsUtil.java
new file mode 100644
index 000000000000..48b2ccedd8d2
--- /dev/null
+++ b/python/src/com/jetbrains/python/codeInsight/userSkeletons/PyUserSkeletonsUtil.java
@@ -0,0 +1,146 @@
+package com.jetbrains.python.codeInsight.userSkeletons;
+
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.PathManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.projectRoots.Sdk;
+import com.intellij.openapi.projectRoots.SdkModificator;
+import com.intellij.openapi.roots.OrderRootType;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vfs.LocalFileSystem;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiDirectory;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.PsiManager;
+import com.jetbrains.python.PythonHelpersLocator;
+import com.jetbrains.python.codeInsight.controlflow.ScopeOwner;
+import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil;
+import com.jetbrains.python.psi.*;
+import com.jetbrains.python.psi.impl.PyQualifiedName;
+import com.jetbrains.python.psi.resolve.*;
+import com.jetbrains.python.psi.types.PyClassLikeType;
+import com.jetbrains.python.psi.types.PyType;
+import com.jetbrains.python.psi.types.TypeEvalContext;
+import com.jetbrains.python.sdk.PythonSdkType;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author vlan
+ */
+public class PyUserSkeletonsUtil {
+ @Nullable private static VirtualFile ourUserSkeletonsDirectory;
+
+ @NotNull
+ public static String getUserSkeletonsPath() {
+ if (ApplicationManager.getApplication().isInternal()) {
+ return StringUtil.join(new String[] {PathManager.getHomePath(), "python", "helpers", "user-skeletons"}, File.separator);
+ }
+ // TODO: Add the possibility to put skeletons into PathManager.getSystemPath() + "/user-skeletons"
+ return PythonHelpersLocator.getHelperPath("user-skeletons");
+ }
+
+ @Nullable
+ public static VirtualFile getUserSkeletonsDirectory() {
+ if (ourUserSkeletonsDirectory == null) {
+ ourUserSkeletonsDirectory = LocalFileSystem.getInstance().findFileByPath(getUserSkeletonsPath());
+ }
+ return ourUserSkeletonsDirectory;
+ }
+
+ @Nullable
+ public static T getUserSkeleton(@NotNull T element) {
+ final PsiFile file = element.getContainingFile();
+ if (file instanceof PyFile) {
+ final PyFile skeletonFile = getUserSkeletonForFile((PyFile)file);
+ if (skeletonFile != null && skeletonFile != file) {
+ final PsiElement skeletonElement = getUserSkeleton(element, skeletonFile);
+ if (element.getClass().isInstance(skeletonElement) && skeletonElement != element) {
+ //noinspection unchecked
+ return (T)skeletonElement;
+ }
+ }
+ }
+ return null;
+ }
+
+ @Nullable
+ public static PyFile getUserSkeletonForModuleQName(@NotNull String qName, @NotNull PsiElement foothold) {
+ final Sdk sdk = PythonSdkType.getSdk(foothold);
+ if (sdk != null) {
+ final Project project = foothold.getProject();
+ final PythonSdkPathCache cache = PythonSdkPathCache.getInstance(project, sdk);
+ final PyQualifiedName cacheQName = PyQualifiedName.fromDottedString("user-skeletons." + qName);
+ final List results = cache.get(cacheQName);
+ if (results != null) {
+ final PsiElement element = results.isEmpty() ? null : results.get(0);
+ if (element instanceof PyFile) {
+ return (PyFile)element;
+ }
+ }
+ final VirtualFile directory = getUserSkeletonsDirectory();
+ if (directory != null) {
+ final PsiDirectory psiDirectory = PsiManager.getInstance(project).findDirectory(directory);
+ final PsiElement fileSkeleton = new QualifiedNameResolverImpl(qName).resolveModuleAt(psiDirectory);
+ if (fileSkeleton instanceof PyFile) {
+ cache.put(cacheQName, Collections.singletonList(fileSkeleton));
+ return (PyFile)fileSkeleton;
+ }
+ }
+ cache.put(cacheQName, Collections.emptyList());
+ }
+ return null;
+ }
+
+ public static void addUserSkeletonsRoot(@NotNull SdkModificator sdkModificator) {
+ final VirtualFile root = LocalFileSystem.getInstance().refreshAndFindFileByPath(getUserSkeletonsPath());
+ if (root != null) {
+ sdkModificator.addRoot(root, OrderRootType.CLASSES);
+ }
+ }
+
+ @Nullable
+ private static PsiElement getUserSkeleton(@NotNull PyElement element, @NotNull PyFile skeletonFile) {
+ if (element instanceof PyFile) {
+ return skeletonFile;
+ }
+ final ScopeOwner owner = ScopeUtil.getScopeOwner(element);
+ final String name = element.getName();
+ if (owner != null && name != null) {
+ assert owner != element;
+ final PsiElement originalOwner = getUserSkeleton(owner, skeletonFile);
+ if (originalOwner instanceof PyClass) {
+ final PyType type = TypeEvalContext.codeInsightFallback().getType((PyClass)originalOwner);
+ if (type instanceof PyClassLikeType) {
+ final PyClassLikeType classType = (PyClassLikeType)type;
+ final List extends RatedResolveResult> resolveResults = classType.resolveMember(name, null, AccessDirection.READ,
+ PyResolveContext.noImplicits(), false);
+ if (resolveResults != null && !resolveResults.isEmpty()) {
+ return resolveResults.get(0).getElement();
+ }
+ }
+ }
+ else if (originalOwner instanceof NameDefiner) {
+ return ((NameDefiner)originalOwner).getElementNamed(name);
+ }
+ }
+ return null;
+ }
+
+ @Nullable
+ private static PyFile getUserSkeletonForFile(@NotNull PyFile file) {
+ final VirtualFile moduleVirtualFile = file.getVirtualFile();
+ if (moduleVirtualFile != null) {
+ final String moduleName = QualifiedNameFinder.findShortestImportableName(file, moduleVirtualFile);
+ if (moduleName != null) {
+ return getUserSkeletonForModuleQName(moduleName, file);
+ }
+ }
+ return null;
+ }
+}
diff --git a/python/src/com/jetbrains/python/psi/impl/PyTargetExpressionImpl.java b/python/src/com/jetbrains/python/psi/impl/PyTargetExpressionImpl.java
index 0b17deaaeecf..bf852b4d3a2f 100644
--- a/python/src/com/jetbrains/python/psi/impl/PyTargetExpressionImpl.java
+++ b/python/src/com/jetbrains/python/psi/impl/PyTargetExpressionImpl.java
@@ -120,7 +120,15 @@ public class PyTargetExpressionImpl extends PyPresentableElementImpl sysPath = PythonSdkType.getSysPath(sdk.getHomePath());
+ sysPath.add(PyUserSkeletonsUtil.getUserSkeletonsPath());
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
diff --git a/python/testData/completion/reCompileMatch.after.py b/python/testData/completion/reCompileMatch.after.py
new file mode 100644
index 000000000000..f795c16ae36a
--- /dev/null
+++ b/python/testData/completion/reCompileMatch.after.py
@@ -0,0 +1,3 @@
+import re
+p = re.compile('foo')
+p.match()
diff --git a/python/testData/completion/reCompileMatch.py b/python/testData/completion/reCompileMatch.py
new file mode 100644
index 000000000000..0142c04723f2
--- /dev/null
+++ b/python/testData/completion/reCompileMatch.py
@@ -0,0 +1,3 @@
+import re
+p = re.compile('foo')
+p.ma
diff --git a/python/testData/inspections/PyTypeCheckerInspection/FieldWithNoneInStub/a.py b/python/testData/inspections/PyTypeCheckerInspection/FieldWithNoneInStub/a.py
new file mode 100644
index 000000000000..63273117f513
--- /dev/null
+++ b/python/testData/inspections/PyTypeCheckerInspection/FieldWithNoneInStub/a.py
@@ -0,0 +1,12 @@
+from m1 import C
+
+
+def f(x):
+ """
+ :type x: int
+ """
+ pass
+
+
+def test():
+ f(C.foo)
diff --git a/python/testData/inspections/PyTypeCheckerInspection/FieldWithNoneInStub/m1.py b/python/testData/inspections/PyTypeCheckerInspection/FieldWithNoneInStub/m1.py
new file mode 100644
index 000000000000..f2625742cd02
--- /dev/null
+++ b/python/testData/inspections/PyTypeCheckerInspection/FieldWithNoneInStub/m1.py
@@ -0,0 +1,2 @@
+class C(object):
+ foo = None
diff --git a/python/testSrc/com/jetbrains/python/PythonCompletionTest.java b/python/testSrc/com/jetbrains/python/PythonCompletionTest.java
index 6f72a5eb87c1..8a235a590666 100644
--- a/python/testSrc/com/jetbrains/python/PythonCompletionTest.java
+++ b/python/testSrc/com/jetbrains/python/PythonCompletionTest.java
@@ -644,4 +644,9 @@ public class PythonCompletionTest extends PyTestCase {
public void testParameterFromUsages() {
doTest();
}
+
+ // PY-1219
+ public void testReCompileMatch() {
+ doTest();
+ }
}
diff --git a/python/testSrc/com/jetbrains/python/PythonMockSdk.java b/python/testSrc/com/jetbrains/python/PythonMockSdk.java
index 438d73ac4947..a1e1809d2c4a 100644
--- a/python/testSrc/com/jetbrains/python/PythonMockSdk.java
+++ b/python/testSrc/com/jetbrains/python/PythonMockSdk.java
@@ -7,6 +7,7 @@ import com.intellij.openapi.projectRoots.SdkType;
import com.intellij.openapi.projectRoots.impl.ProjectJdkImpl;
import com.intellij.openapi.roots.OrderRootType;
import com.intellij.openapi.vfs.LocalFileSystem;
+import com.jetbrains.python.codeInsight.userSkeletons.PyUserSkeletonsUtil;
import com.jetbrains.python.sdk.PythonSdkType;
import org.jetbrains.annotations.NonNls;
@@ -53,6 +54,8 @@ public class PythonMockSdk {
sdkModificator.addRoot(LocalFileSystem.getInstance().refreshAndFindFileByIoFile(libPath), OrderRootType.CLASSES);
}
+ PyUserSkeletonsUtil.addUserSkeletonsRoot(sdkModificator);
+
String mock_stubs_path = mock_path + PythonSdkType.SKELETON_DIR_NAME;
sdkModificator.addRoot(LocalFileSystem.getInstance().refreshAndFindFileByPath(mock_stubs_path), PythonSdkType.BUILTIN_ROOT_TYPE);
diff --git a/python/testSrc/com/jetbrains/python/inspections/PyTypeCheckerInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyTypeCheckerInspectionTest.java
index f561664f9e4b..d84e8f9bb0a5 100644
--- a/python/testSrc/com/jetbrains/python/inspections/PyTypeCheckerInspectionTest.java
+++ b/python/testSrc/com/jetbrains/python/inspections/PyTypeCheckerInspectionTest.java
@@ -6,12 +6,22 @@ import com.jetbrains.python.fixtures.PyTestCase;
* @author vlan
*/
public class PyTypeCheckerInspectionTest extends PyTestCase {
+ public static final String TEST_DIRECTORY = "inspections/PyTypeCheckerInspection/";
+
private void doTest() {
- myFixture.configureByFile("inspections/PyTypeCheckerInspection/" + getTestName(false) + ".py");
+ myFixture.configureByFile(TEST_DIRECTORY + getTestName(false) + ".py");
myFixture.enableInspections(PyTypeCheckerInspection.class);
myFixture.checkHighlighting(true, false, true);
}
+ private void doMultiFileTest() {
+ final String testName = getTestName(false);
+ myFixture.copyDirectoryToProject(TEST_DIRECTORY + testName, "");
+ myFixture.configureFromTempProjectFile("a.py");
+ myFixture.enableInspections(PyTypeCheckerInspection.class);
+ myFixture.checkHighlighting(true, false, false);
+ }
+
public void testSimple() {
doTest();
}
@@ -165,4 +175,9 @@ public class PyTypeCheckerInspectionTest extends PyTestCase {
public void testNegativeIsInstance() {
doTest();
}
+
+ // PY-7340
+ public void testFieldWithNoneInStub() {
+ doMultiFileTest();
+ }
}