From e19a3783eaa80a5cf0d8ed66503ca491d4668291 Mon Sep 17 00:00:00 2001 From: Semyon Proshev Date: Mon, 24 Dec 2018 15:33:37 +0300 Subject: [PATCH] Consider pyi file located not in stub package or typeshed as having more priority than them (PY-32963) It is assumed to be provided with a distribution. As a result stub package should not replace any other package because such package could contain pyi stubs. --- .../codeInsight/typing/PyStubPackages.kt | 26 ++++++++--------- .../python/psi/resolve/ResolveImportUtil.java | 18 +++--------- .../lib/pkg-stubs/__init__.pyi | 0 .../lib/pkg-stubs/foo.pyi | 0 .../lib/pkg/__init__.py | 0 .../lib/pkg/__init__.pyi} | 0 .../lib/pkg/foo.py | 0 .../lib/pkg/foo.pyi | 1 + .../main.py | 0 .../lib}/pkg/__init__.py | 0 .../lib/pkg/__init__.pyi | 0 .../UserPyInsteadProvidedPyi/lib/pkg/foo.py | 2 ++ .../UserPyInsteadProvidedPyi/lib/pkg/foo.pyi | 1 + .../resolve/UserPyInsteadProvidedPyi/main.py | 4 +++ .../UserPyInsteadProvidedPyi/pkg/__init__.py | 0 .../pkg/foo.py | 0 .../com/jetbrains/python/Py3ResolveTest.java | 28 +++++++++++++++++-- 17 files changed, 50 insertions(+), 30 deletions(-) rename python/testData/resolve/{UserPyInsteadStubPackage => ProvidedPyiInsteadStubPackage}/lib/pkg-stubs/__init__.pyi (100%) rename python/testData/resolve/{UserPyInsteadStubPackage => ProvidedPyiInsteadStubPackage}/lib/pkg-stubs/foo.pyi (100%) rename python/testData/resolve/{UserPyInsteadStubPackage => ProvidedPyiInsteadStubPackage}/lib/pkg/__init__.py (100%) rename python/testData/resolve/{UserPyInsteadStubPackage/lib/pkg/py.typed => ProvidedPyiInsteadStubPackage/lib/pkg/__init__.pyi} (100%) rename python/testData/resolve/{UserPyInsteadStubPackage => ProvidedPyiInsteadStubPackage}/lib/pkg/foo.py (100%) create mode 100644 python/testData/resolve/ProvidedPyiInsteadStubPackage/lib/pkg/foo.pyi rename python/testData/resolve/{UserPyInsteadStubPackage => ProvidedPyiInsteadStubPackage}/main.py (100%) rename python/testData/resolve/{UserPyInsteadStubPackage => UserPyInsteadProvidedPyi/lib}/pkg/__init__.py (100%) create mode 100644 python/testData/resolve/UserPyInsteadProvidedPyi/lib/pkg/__init__.pyi create mode 100644 python/testData/resolve/UserPyInsteadProvidedPyi/lib/pkg/foo.py create mode 100644 python/testData/resolve/UserPyInsteadProvidedPyi/lib/pkg/foo.pyi create mode 100644 python/testData/resolve/UserPyInsteadProvidedPyi/main.py create mode 100644 python/testData/resolve/UserPyInsteadProvidedPyi/pkg/__init__.py rename python/testData/resolve/{UserPyInsteadStubPackage => UserPyInsteadProvidedPyi}/pkg/foo.py (100%) diff --git a/python/src/com/jetbrains/python/codeInsight/typing/PyStubPackages.kt b/python/src/com/jetbrains/python/codeInsight/typing/PyStubPackages.kt index 4e3bf9cb5648..464d5f14dc82 100644 --- a/python/src/com/jetbrains/python/codeInsight/typing/PyStubPackages.kt +++ b/python/src/com/jetbrains/python/codeInsight/typing/PyStubPackages.kt @@ -8,7 +8,6 @@ import com.intellij.openapi.module.ModuleUtilCore import com.intellij.openapi.project.Project import com.intellij.openapi.roots.ProjectFileIndex import com.intellij.openapi.util.Key -import com.intellij.openapi.util.text.StringUtil import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.PsiDirectory import com.intellij.psi.PsiElement @@ -136,14 +135,7 @@ private fun getClassOrContentOrSourceRoot(project: Project, file: VirtualFile): return null } -fun stubPackageIsPartial(stubPackageDirectory: PsiDirectory): Boolean { - val pyTyped = stubPackageDirectory.findFile("py.typed") ?: return false - - return pyTyped.textLength < "partial".length + 5 && - pyTyped.text.let { it.startsWith("partial") && it.substring("partial".length).all(StringUtil::isLineBreak) } -} - -private fun pyi(element: PsiElement) = element is PyiFile || PyUtil.turnDirIntoInit(element) is PyiFile +private fun isPyi(element: PsiElement) = element is PyiFile || PyUtil.turnDirIntoInit(element) is PyiFile private fun isNamespacePackage(element: PsiElement): Boolean { if (element is PsiDirectory) { @@ -160,9 +152,10 @@ private fun isNamespacePackage(element: PsiElement): Boolean { */ private fun resolvedElementPriority(element: PsiElement, module: Module?) = when { isNamespacePackage(element) -> Priority.NAMESPACE_PACKAGE - isUserFile(element, module) -> if (pyi(element)) Priority.USER_STUB else Priority.USER_CODE + isUserFile(element, module) -> if (isPyi(element)) Priority.USER_STUB else Priority.USER_CODE isInStubPackage(element) -> Priority.STUB_PACKAGE isInTypeShed(element) -> Priority.TYPESHED + isPyi(element) -> Priority.PROVIDED_STUB isInInlinePackage(element, module) -> Priority.INLINE_PACKAGE else -> Priority.OTHER } @@ -178,7 +171,7 @@ private fun isUserFile(element: PsiElement, module: Module?) = fun isInStubPackage(element: PsiElement) = element.getUserData(STUB_PACKAGE_KEY) == true private fun isInTypeShed(element: PsiElement) = - pyi(element) && (element as? PsiFileSystemItem)?.virtualFile.let { it != null && PyTypeShed.isInside(it) } + isPyi(element) && (element as? PsiFileSystemItem)?.virtualFile.let { it != null && PyTypeShed.isInside(it) } /** * See [https://www.python.org/dev/peps/pep-0561/#packaging-type-information]. @@ -190,7 +183,7 @@ private fun isInInlinePackage(element: PsiElement, module: Module?): Boolean { val cached = element.getUserData(INLINE_PACKAGE_KEY) if (cached != null) return cached - val result = !pyi(element) && (element is PyFile || PyUtil.turnDirIntoInit(element) is PyFile) && getPyTyped(element) != null + val result = !isPyi(element) && (element is PyFile || PyUtil.turnDirIntoInit(element) is PyFile) && getPyTyped(element) != null element.putUserData(INLINE_PACKAGE_KEY, result) return result @@ -222,5 +215,12 @@ private fun getPyTyped(element: PsiElement?): VirtualFile? { * Order is important, see [filterTopPriorityResults]. */ private enum class Priority { - USER_STUB, USER_CODE, STUB_PACKAGE, INLINE_PACKAGE, TYPESHED, OTHER, NAMESPACE_PACKAGE + USER_STUB, // pyi file located in user's project + USER_CODE, // py file located in user's project + PROVIDED_STUB, // pyi file provided with installed lib and located inside it + STUB_PACKAGE, // pyi file located in some stub package + INLINE_PACKAGE, // py file located in some inline package + TYPESHED, // pyi file located in typeshed + OTHER, // other cases, e.g. py file located inside installed lib + NAMESPACE_PACKAGE // namespace package has the lowest priority } \ No newline at end of file diff --git a/python/src/com/jetbrains/python/psi/resolve/ResolveImportUtil.java b/python/src/com/jetbrains/python/psi/resolve/ResolveImportUtil.java index 3a58d7d81f2a..edf3c2da5c1f 100644 --- a/python/src/com/jetbrains/python/psi/resolve/ResolveImportUtil.java +++ b/python/src/com/jetbrains/python/psi/resolve/ResolveImportUtil.java @@ -382,22 +382,12 @@ public class ResolveImportUtil { final PsiDirectory subdir = dir.findSubdirectory(referencedName); // VFS may be case insensitive on Windows, but resolve is always case sensitive (PEP 235, PY-18958), so we check name here if (subdir != null && subdir.getName().equals(referencedName) && (!checkForPackage || PyUtil.isPackage(subdir, containingFile))) { - final PsiDirectory stubPackage = PyStubPackages.findStubPackage(dir, referencedName, checkForPackage, withoutStubs); - - if (stubPackage == null || PyStubPackages.stubPackageIsPartial(stubPackage)) { - result.add(new RatedResolveResult(RatedResolveResult.RATE_NORMAL, PyStubPackages.transferStubPackageMarker(dir, subdir))); - } - - if (stubPackage != null) { - result.add(new RatedResolveResult(RatedResolveResult.RATE_NORMAL, stubPackage)); - } + result.add(new RatedResolveResult(RatedResolveResult.RATE_NORMAL, PyStubPackages.transferStubPackageMarker(dir, subdir))); } - if (subdir == null) { - final PsiDirectory stubPackage = PyStubPackages.findStubPackage(dir, referencedName, checkForPackage, withoutStubs); - if (stubPackage != null) { - result.add(new RatedResolveResult(RatedResolveResult.RATE_NORMAL, stubPackage)); - } + final PsiDirectory stubPackage = PyStubPackages.findStubPackage(dir, referencedName, checkForPackage, withoutStubs); + if (stubPackage != null) { + result.add(new RatedResolveResult(RatedResolveResult.RATE_NORMAL, stubPackage)); } final PsiFile module = findPyFileInDir(dir, referencedName, withoutStubs); diff --git a/python/testData/resolve/UserPyInsteadStubPackage/lib/pkg-stubs/__init__.pyi b/python/testData/resolve/ProvidedPyiInsteadStubPackage/lib/pkg-stubs/__init__.pyi similarity index 100% rename from python/testData/resolve/UserPyInsteadStubPackage/lib/pkg-stubs/__init__.pyi rename to python/testData/resolve/ProvidedPyiInsteadStubPackage/lib/pkg-stubs/__init__.pyi diff --git a/python/testData/resolve/UserPyInsteadStubPackage/lib/pkg-stubs/foo.pyi b/python/testData/resolve/ProvidedPyiInsteadStubPackage/lib/pkg-stubs/foo.pyi similarity index 100% rename from python/testData/resolve/UserPyInsteadStubPackage/lib/pkg-stubs/foo.pyi rename to python/testData/resolve/ProvidedPyiInsteadStubPackage/lib/pkg-stubs/foo.pyi diff --git a/python/testData/resolve/UserPyInsteadStubPackage/lib/pkg/__init__.py b/python/testData/resolve/ProvidedPyiInsteadStubPackage/lib/pkg/__init__.py similarity index 100% rename from python/testData/resolve/UserPyInsteadStubPackage/lib/pkg/__init__.py rename to python/testData/resolve/ProvidedPyiInsteadStubPackage/lib/pkg/__init__.py diff --git a/python/testData/resolve/UserPyInsteadStubPackage/lib/pkg/py.typed b/python/testData/resolve/ProvidedPyiInsteadStubPackage/lib/pkg/__init__.pyi similarity index 100% rename from python/testData/resolve/UserPyInsteadStubPackage/lib/pkg/py.typed rename to python/testData/resolve/ProvidedPyiInsteadStubPackage/lib/pkg/__init__.pyi diff --git a/python/testData/resolve/UserPyInsteadStubPackage/lib/pkg/foo.py b/python/testData/resolve/ProvidedPyiInsteadStubPackage/lib/pkg/foo.py similarity index 100% rename from python/testData/resolve/UserPyInsteadStubPackage/lib/pkg/foo.py rename to python/testData/resolve/ProvidedPyiInsteadStubPackage/lib/pkg/foo.py diff --git a/python/testData/resolve/ProvidedPyiInsteadStubPackage/lib/pkg/foo.pyi b/python/testData/resolve/ProvidedPyiInsteadStubPackage/lib/pkg/foo.pyi new file mode 100644 index 000000000000..36a173b06914 --- /dev/null +++ b/python/testData/resolve/ProvidedPyiInsteadStubPackage/lib/pkg/foo.pyi @@ -0,0 +1 @@ +def bar(a: str, b: str) -> str: ... \ No newline at end of file diff --git a/python/testData/resolve/UserPyInsteadStubPackage/main.py b/python/testData/resolve/ProvidedPyiInsteadStubPackage/main.py similarity index 100% rename from python/testData/resolve/UserPyInsteadStubPackage/main.py rename to python/testData/resolve/ProvidedPyiInsteadStubPackage/main.py diff --git a/python/testData/resolve/UserPyInsteadStubPackage/pkg/__init__.py b/python/testData/resolve/UserPyInsteadProvidedPyi/lib/pkg/__init__.py similarity index 100% rename from python/testData/resolve/UserPyInsteadStubPackage/pkg/__init__.py rename to python/testData/resolve/UserPyInsteadProvidedPyi/lib/pkg/__init__.py diff --git a/python/testData/resolve/UserPyInsteadProvidedPyi/lib/pkg/__init__.pyi b/python/testData/resolve/UserPyInsteadProvidedPyi/lib/pkg/__init__.pyi new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testData/resolve/UserPyInsteadProvidedPyi/lib/pkg/foo.py b/python/testData/resolve/UserPyInsteadProvidedPyi/lib/pkg/foo.py new file mode 100644 index 000000000000..3262c3a1922f --- /dev/null +++ b/python/testData/resolve/UserPyInsteadProvidedPyi/lib/pkg/foo.py @@ -0,0 +1,2 @@ +def bar(a: str, b: str): + return a + b \ No newline at end of file diff --git a/python/testData/resolve/UserPyInsteadProvidedPyi/lib/pkg/foo.pyi b/python/testData/resolve/UserPyInsteadProvidedPyi/lib/pkg/foo.pyi new file mode 100644 index 000000000000..36a173b06914 --- /dev/null +++ b/python/testData/resolve/UserPyInsteadProvidedPyi/lib/pkg/foo.pyi @@ -0,0 +1 @@ +def bar(a: str, b: str) -> str: ... \ No newline at end of file diff --git a/python/testData/resolve/UserPyInsteadProvidedPyi/main.py b/python/testData/resolve/UserPyInsteadProvidedPyi/main.py new file mode 100644 index 000000000000..60e25e4ed882 --- /dev/null +++ b/python/testData/resolve/UserPyInsteadProvidedPyi/main.py @@ -0,0 +1,4 @@ +from pkg import foo + +foo.bar("a", "b") +# \ No newline at end of file diff --git a/python/testData/resolve/UserPyInsteadProvidedPyi/pkg/__init__.py b/python/testData/resolve/UserPyInsteadProvidedPyi/pkg/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testData/resolve/UserPyInsteadStubPackage/pkg/foo.py b/python/testData/resolve/UserPyInsteadProvidedPyi/pkg/foo.py similarity index 100% rename from python/testData/resolve/UserPyInsteadStubPackage/pkg/foo.py rename to python/testData/resolve/UserPyInsteadProvidedPyi/pkg/foo.py diff --git a/python/testSrc/com/jetbrains/python/Py3ResolveTest.java b/python/testSrc/com/jetbrains/python/Py3ResolveTest.java index 54304ec32ae7..951c7d0824e2 100644 --- a/python/testSrc/com/jetbrains/python/Py3ResolveTest.java +++ b/python/testSrc/com/jetbrains/python/Py3ResolveTest.java @@ -674,7 +674,7 @@ public class Py3ResolveTest extends PyResolveTestCase { } // PY-30942 - public void testUserPyInsteadStubPackage() { + public void testUserPyInsteadProvidedPyi() { final String path = "resolve/" + getTestName(false); myFixture.copyDirectoryToProject(path + "/pkg", "pkg"); myFixture.configureByFile(path + "/main.py"); @@ -695,6 +695,26 @@ public class Py3ResolveTest extends PyResolveTestCase { ); } + // PY-32963 + public void testProvidedPyiInsteadStubPackage() { + final String path = "resolve/" + getTestName(false); + myFixture.configureByFile(path + "/main.py"); + + final VirtualFile libDir = StandardFileSystems.local().findFileByPath(getTestDataPath() + "/" + path + "/lib"); + assertNotNull(libDir); + + runWithAdditionalClassEntryInSdkRoots( + libDir, + () -> { + final PsiElement element = PyResolveTestCase.findReferenceByMarker(myFixture.getFile()).resolve(); + + final PsiFile file = element.getContainingFile(); + assertEquals("foo.pyi", file.getName()); + assertEquals("pkg", file.getParent().getName()); + } + ); + } + // PY-30942 public void testStubPackageInsteadInlinePackage() { final String path = "resolve/" + getTestName(false); @@ -801,8 +821,9 @@ public class Py3ResolveTest extends PyResolveTestCase { ); } + // TODO: this should be fixed after introducing an ability to check visited paths while resolving some qualified name // PY-30942 - public void testNoInlinePackageInsteadStubPackage() { + public void _testNoInlinePackageInsteadStubPackage() { final String path = "resolve/" + getTestName(false); myFixture.configureByFile(path + "/main.py"); @@ -815,8 +836,9 @@ public class Py3ResolveTest extends PyResolveTestCase { ); } + // TODO: this should be fixed after introducing an ability to check visited paths while resolving some qualified name // PY-30942 - public void testNoInlinePackageInsteadStubPackageAnotherImport() { + public void _testNoInlinePackageInsteadStubPackageAnotherImport() { final String path = "resolve/" + getTestName(false); myFixture.configureByFile(path + "/main.py");