diff --git a/python/python-psi-api/src/com/jetbrains/python/psi/resolve/PyCanonicalPathProvider.java b/python/python-psi-api/src/com/jetbrains/python/psi/resolve/PyCanonicalPathProvider.java index 0f929392e89c..28d3e9b59a7c 100644 --- a/python/python-psi-api/src/com/jetbrains/python/psi/resolve/PyCanonicalPathProvider.java +++ b/python/python-psi-api/src/com/jetbrains/python/psi/resolve/PyCanonicalPathProvider.java @@ -18,6 +18,7 @@ package com.jetbrains.python.psi.resolve; import com.intellij.openapi.extensions.ExtensionPointName; import com.intellij.psi.PsiElement; import com.intellij.psi.util.QualifiedName; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -32,10 +33,25 @@ public interface PyCanonicalPathProvider { /** * Allows to provide a custom qualified name when a specific symbol is going to be imported into a specific file. * + * @param symbol the symbol being imported * @param qName the real qualified name of the symbol being imported. * @param foothold the location where the symbol is being imported. * @return the qualified name to use in the import statement, or null if no replacement is necessary. + * @apiNote Method will become abstract in 2021.2. */ @Nullable - QualifiedName getCanonicalPath(@NotNull QualifiedName qName, @Nullable PsiElement foothold); + default QualifiedName getCanonicalPath(@Nullable PsiElement symbol, @NotNull QualifiedName qName, @Nullable PsiElement foothold) { + return getCanonicalPath(qName, foothold); + } + + /** + * @deprecated Please implement {@link PyCanonicalPathProvider#getCanonicalPath(QualifiedName, PsiElement, PsiElement)} instead, + * this method is no longer called directly, new method calls it by default as a workaround. + */ + @Deprecated + @Nullable + @ApiStatus.ScheduledForRemoval(inVersion = "2021.2") + default QualifiedName getCanonicalPath(@NotNull QualifiedName qName, @Nullable PsiElement foothold) { + return null; + } } diff --git a/python/python-psi-impl/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibCanonicalPathProvider.kt b/python/python-psi-impl/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibCanonicalPathProvider.kt index 8eea83c055b6..620ed9082b7f 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibCanonicalPathProvider.kt +++ b/python/python-psi-impl/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibCanonicalPathProvider.kt @@ -16,16 +16,22 @@ package com.jetbrains.python.codeInsight.stdlib import com.intellij.psi.PsiElement +import com.intellij.psi.util.PsiUtilCore import com.intellij.psi.util.QualifiedName import com.jetbrains.python.psi.resolve.PyCanonicalPathProvider +import com.jetbrains.python.sdk.PythonSdkUtil import java.util.* /** * @author yole */ class PyStdlibCanonicalPathProvider : PyCanonicalPathProvider { - override fun getCanonicalPath(qName: QualifiedName, foothold: PsiElement?): QualifiedName? { - return restoreStdlibCanonicalPath(qName) + override fun getCanonicalPath(symbol: PsiElement?, qName: QualifiedName, foothold: PsiElement?): QualifiedName? { + val virtualFile = PsiUtilCore.getVirtualFile(symbol) + if (virtualFile != null && foothold != null && PythonSdkUtil.isStdLib(virtualFile, PythonSdkUtil.findPythonSdk(foothold))) { + return restoreStdlibCanonicalPath(qName) + } + return null } } diff --git a/python/python-psi-impl/src/com/jetbrains/python/codeInsight/userSkeletons/PyUserSkeletonsUtil.java b/python/python-psi-impl/src/com/jetbrains/python/codeInsight/userSkeletons/PyUserSkeletonsUtil.java index 69ca602a4b6a..c5cbfcdf824c 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/codeInsight/userSkeletons/PyUserSkeletonsUtil.java +++ b/python/python-psi-impl/src/com/jetbrains/python/codeInsight/userSkeletons/PyUserSkeletonsUtil.java @@ -227,7 +227,7 @@ public class PyUserSkeletonsUtil { String moduleName = QualifiedNameFinder.findShortestImportableName(file, moduleVirtualFile); if (moduleName != null) { final QualifiedName qName = QualifiedName.fromDottedString(moduleName); - final QualifiedName restored = QualifiedNameFinder.canonizeQualifiedName(qName, null); + final QualifiedName restored = QualifiedNameFinder.canonizeQualifiedName(file, qName, null); if (restored != null) { moduleName = restored.toString(); } diff --git a/python/python-psi-impl/src/com/jetbrains/python/documentation/PyDocumentationBuilder.java b/python/python-psi-impl/src/com/jetbrains/python/documentation/PyDocumentationBuilder.java index 07dc9b77f260..a0057aa8a721 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/documentation/PyDocumentationBuilder.java +++ b/python/python-psi-impl/src/com/jetbrains/python/documentation/PyDocumentationBuilder.java @@ -535,7 +535,7 @@ public class PyDocumentationBuilder { final QualifiedName name = QualifiedNameFinder.findShortestImportableQName(followed); if (name != null) { myProlog.add($(PyUtil.isPackage(followed) ? "Package " : "Module ")) - .addWith(TagBold, $(ObjectUtils.chooseNotNull(QualifiedNameFinder.canonizeQualifiedName(name, null), name).toString())); + .addWith(TagBold, $(ObjectUtils.chooseNotNull(QualifiedNameFinder.canonizeQualifiedName(followed, name, null), name).toString())); } else { final String path = file.getPath(); diff --git a/python/python-psi-impl/src/com/jetbrains/python/psi/resolve/CompletionVariantsProcessor.java b/python/python-psi-impl/src/com/jetbrains/python/psi/resolve/CompletionVariantsProcessor.java index e0a236c23f30..079ea79f4acf 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/psi/resolve/CompletionVariantsProcessor.java +++ b/python/python-psi-impl/src/com/jetbrains/python/psi/resolve/CompletionVariantsProcessor.java @@ -8,6 +8,7 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFileSystemItem; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.QualifiedName; import com.intellij.util.ObjectUtils; @@ -105,9 +106,10 @@ public class CompletionVariantsProcessor extends VariantsProcessor { source = cls.getName(); } else if (myContext == null || !PyUtil.inSameFile(myContext, element)) { - final QualifiedName path = QualifiedNameFinder.findShortestImportableQName(PyPsiUtils.getFileSystemItem(element)); + final PsiFileSystemItem fileSystemItem = PyPsiUtils.getFileSystemItem(element); + final QualifiedName path = QualifiedNameFinder.findShortestImportableQName(fileSystemItem); if (path != null) { - source = ObjectUtils.chooseNotNull(QualifiedNameFinder.canonizeQualifiedName(path, null), path).toString(); + source = ObjectUtils.chooseNotNull(QualifiedNameFinder.canonizeQualifiedName(fileSystemItem, path, null), path).toString(); } } } diff --git a/python/python-psi-impl/src/com/jetbrains/python/psi/resolve/QualifiedNameFinder.java b/python/python-psi-impl/src/com/jetbrains/python/psi/resolve/QualifiedNameFinder.java index 0c0e717cd229..5f51a3e58472 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/psi/resolve/QualifiedNameFinder.java +++ b/python/python-psi-impl/src/com/jetbrains/python/psi/resolve/QualifiedNameFinder.java @@ -142,16 +142,16 @@ public class QualifiedNameFinder { } final QualifiedName qname = findShortestImportableQName(foothold != null ? foothold : symbol, virtualFile); if (qname != null) { - final QualifiedName restored = canonizeQualifiedName(qname, foothold); + final QualifiedName restored = canonizeQualifiedName(symbol, qname, foothold); if (restored != null) return restored; } return qname; } @Nullable - public static QualifiedName canonizeQualifiedName(QualifiedName qname, PsiElement foothold) { + public static QualifiedName canonizeQualifiedName(PsiElement symbol, QualifiedName qname, PsiElement foothold) { for (PyCanonicalPathProvider provider : PyCanonicalPathProvider.EP_NAME.getExtensionList()) { - final QualifiedName restored = provider.getCanonicalPath(qname, foothold); + final QualifiedName restored = provider.getCanonicalPath(symbol, qname, foothold); if (restored != null) { return restored; } diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection/ResolvePathImportToUserFile/path.py b/python/testData/inspections/PyUnresolvedReferencesInspection/ResolvePathImportToUserFile/path.py new file mode 100644 index 000000000000..9332a2735b6a --- /dev/null +++ b/python/testData/inspections/PyUnresolvedReferencesInspection/ResolvePathImportToUserFile/path.py @@ -0,0 +1,2 @@ +def foo(): + pass diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection/ResolvePathImportToUserFile/resolvePathImportToUserFile.py b/python/testData/inspections/PyUnresolvedReferencesInspection/ResolvePathImportToUserFile/resolvePathImportToUserFile.py new file mode 100644 index 000000000000..e7aa849552b6 --- /dev/null +++ b/python/testData/inspections/PyUnresolvedReferencesInspection/ResolvePathImportToUserFile/resolvePathImportToUserFile.py @@ -0,0 +1,3 @@ +import path + +path.foo() diff --git a/python/testData/quickFixes/PyAddImportQuickFixTest/osPathFunctions/ntpath.py b/python/testData/quickFixes/PyAddImportQuickFixTest/osPathFunctions/ntpath.py deleted file mode 100644 index ad485416a697..000000000000 --- a/python/testData/quickFixes/PyAddImportQuickFixTest/osPathFunctions/ntpath.py +++ /dev/null @@ -1,2 +0,0 @@ -def join(*args): - pass \ No newline at end of file diff --git a/python/testData/quickFixes/PyAddImportQuickFixTest/osPathFunctions/os.py b/python/testData/quickFixes/PyAddImportQuickFixTest/osPathFunctions/os.py deleted file mode 100644 index d87b27c36c39..000000000000 --- a/python/testData/quickFixes/PyAddImportQuickFixTest/osPathFunctions/os.py +++ /dev/null @@ -1,4 +0,0 @@ -if windows(): - import ntpath as path -else: - import posixpath as path diff --git a/python/testData/quickFixes/PyAddImportQuickFixTest/osPathFunctions/posixpath.py b/python/testData/quickFixes/PyAddImportQuickFixTest/osPathFunctions/posixpath.py deleted file mode 100644 index 947e6834b663..000000000000 --- a/python/testData/quickFixes/PyAddImportQuickFixTest/osPathFunctions/posixpath.py +++ /dev/null @@ -1,2 +0,0 @@ -def join(*args): - pass diff --git a/python/testSrc/com/jetbrains/python/inspections/PyUnresolvedReferencesInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyUnresolvedReferencesInspectionTest.java index 2bcf4f69a757..bad6f0423267 100644 --- a/python/testSrc/com/jetbrains/python/inspections/PyUnresolvedReferencesInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/PyUnresolvedReferencesInspectionTest.java @@ -857,6 +857,11 @@ public class PyUnresolvedReferencesInspectionTest extends PyInspectionTestCase { }); } + // PY-44918 + public void testResolvePathImportToUserFile() { + doMultiFileTest("resolvePathImportToUserFile.py"); + } + @NotNull @Override protected Class getInspectionClass() { diff --git a/python/testSrc/com/jetbrains/python/quickFixes/PyAddImportQuickFixTest.java b/python/testSrc/com/jetbrains/python/quickFixes/PyAddImportQuickFixTest.java index d30fd3db060d..bc2293dafe6d 100644 --- a/python/testSrc/com/jetbrains/python/quickFixes/PyAddImportQuickFixTest.java +++ b/python/testSrc/com/jetbrains/python/quickFixes/PyAddImportQuickFixTest.java @@ -16,6 +16,7 @@ package com.jetbrains.python.quickFixes; import com.intellij.lang.injection.InjectedLanguageManager; +import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiElement; import com.intellij.psi.codeStyle.CommonCodeStyleSettings; import com.intellij.psi.util.PsiTreeUtil; @@ -34,6 +35,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.List; +import java.util.function.Consumer; /** * @author Mikhail Golubev @@ -50,12 +52,32 @@ public class PyAddImportQuickFixTest extends PyQuickFixTestCase { } public void testOsPathFunctions() { - doMultiFileAutoImportTest("Import", fix -> { - final List candidates = fix.getCandidates(); - final List names = ContainerUtil.map(candidates, c -> c.getPresentableText("join")); - assertSameElements(names, "os.path.join()"); - return true; - }); + Consumer fileConsumer = file -> { + doMultiFileAutoImportTest("Import", fix -> { + final List candidates = fix.getCandidates(); + final List names = ContainerUtil.map(candidates, c -> c.getPresentableText("join")); + assertSameElements(names, "os.path.join()"); + return true; + }); + }; + runWithAdditionalFileInLibDir( + "ntpath.py", + "def join(*args):\n" + + " pass", + f -> runWithAdditionalFileInLibDir( + "os.py", + "if windows():\n" + + " import ntpath as path\n" + + "else:\n" + + " import posixpath as path", + f1 -> runWithAdditionalFileInLibDir( + "posixpath.py", + "def join(*args):\n" + + " pass", + fileConsumer + ) + ) + ); } // PY-19975 @@ -67,7 +89,7 @@ public class PyAddImportQuickFixTest extends PyQuickFixTestCase { return true; }); } - + // PY-22422 public void testAddParenthesesAndTrailingCommaToUpdatedFromImport() { getPythonCodeStyleSettings().FROM_IMPORT_WRAPPING = CommonCodeStyleSettings.WRAP_ALWAYS;