diff --git a/python/pluginResources/intellij.python.community.impl.xml b/python/pluginResources/intellij.python.community.impl.xml index fe4075ee3091..174dbbf1becc 100644 --- a/python/pluginResources/intellij.python.community.impl.xml +++ b/python/pluginResources/intellij.python.community.impl.xml @@ -160,6 +160,7 @@ + diff --git a/python/python-psi-impl/BUILD.bazel b/python/python-psi-impl/BUILD.bazel index f2f2dfa1c7bf..2b23fe039cd6 100644 --- a/python/python-psi-impl/BUILD.bazel +++ b/python/python-psi-impl/BUILD.bazel @@ -66,6 +66,8 @@ jvm_library( "//libraries/hdr-histogram", "//libraries/kotlinx/collections-immutable:libraries-kotlinx-collections-immutable", "//python/python-requirements:requirements", + "//plugins/toml", + "//plugins/toml/core", ], ) @@ -128,6 +130,8 @@ jvm_library( "//libraries/hdr-histogram:hdr-histogram_test_lib", "//libraries/kotlinx/collections-immutable:libraries-kotlinx-collections-immutable_test_lib", "//python/python-requirements:requirements_test_lib", + "//plugins/toml:toml_test_lib", + "//plugins/toml/core:core_test_lib", ], ) ### auto-generated section `build intellij.python.psi.impl` end diff --git a/python/python-psi-impl/intellij.python.psi.impl.iml b/python/python-psi-impl/intellij.python.psi.impl.iml index 0d5e3e9217d9..a94700d1a92c 100644 --- a/python/python-psi-impl/intellij.python.psi.impl.iml +++ b/python/python-psi-impl/intellij.python.psi.impl.iml @@ -52,5 +52,7 @@ + + \ No newline at end of file diff --git a/python/python-psi-impl/resources/intellij.python.psi.impl.xml b/python/python-psi-impl/resources/intellij.python.psi.impl.xml index e74a1e7d03c5..3e8540ce8148 100644 --- a/python/python-psi-impl/resources/intellij.python.psi.impl.xml +++ b/python/python-psi-impl/resources/intellij.python.psi.impl.xml @@ -1,10 +1,14 @@ - - + + + + + + diff --git a/python/python-psi-impl/src/com/jetbrains/python/codeInsight/typing/PyStubPackages.kt b/python/python-psi-impl/src/com/jetbrains/python/codeInsight/typing/PyStubPackages.kt index d6bf5af6a73a..d1d7ee3f5b83 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/codeInsight/typing/PyStubPackages.kt +++ b/python/python-psi-impl/src/com/jetbrains/python/codeInsight/typing/PyStubPackages.kt @@ -3,19 +3,25 @@ package com.jetbrains.python.codeInsight.typing +import com.intellij.openapi.fileEditor.FileDocumentManager import com.intellij.openapi.module.Module import com.intellij.openapi.project.Project import com.intellij.openapi.roots.ProjectFileIndex import com.intellij.openapi.util.Key +import com.intellij.openapi.vfs.VfsUtilCore 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.PsiFileSystemItem +import com.intellij.psi.PsiManager +import com.intellij.psi.util.CachedValueProvider +import com.intellij.psi.util.CachedValuesManager import com.intellij.psi.util.QualifiedName import com.jetbrains.python.psi.PyFile import com.jetbrains.python.psi.PyUtil import com.jetbrains.python.psi.resolve.RatedResolveResult +import com.jetbrains.python.psi.resolve.isNamespacePackage import com.jetbrains.python.pyi.PyiFile import com.jetbrains.python.pyi.PyiUtil @@ -137,6 +143,26 @@ private fun getClassOrContentOrSourceRoot(project: Project, file: VirtualFile): */ fun isInStubPackage(element: PsiElement) = element.getUserData(STUB_PACKAGE_KEY) == true +internal fun isInPartialStubPackage(element: PsiElement): Boolean { + // The enclosing stub package (the element itself, its package directory, or any ancestor up to the root) is explicitly + // marked as partial via its `py.typed` marker. Works both for package directories and for plain stub modules + // (e.g. `foo-stubs/bar.pyi`), so that submodules of a partial stub package still fall back to the runtime package. + val pyTyped = getPyTyped(element) ?: return false + return isPartialPyTypedMarker(element.project, pyTyped) +} + +/** + * Returns `true` if the stub package [element] directly contains a sub-package named [name] that is a namespace package. + * + * Namespace packages cannot ship complete type information (PEP 561), so submodules resolved underneath them must still + * fall back to the runtime package even when the enclosing stub package itself is complete. + */ +internal fun hasNamespaceSubPackage(element: PsiElement, name: String): Boolean { + val packageDir = getPackageDir(element) ?: return false + val subPackage = packageDir.findSubdirectory(name) ?: return false + return isNamespacePackage(subPackage) +} + /** * See [https://www.python.org/dev/peps/pep-0561/#packaging-type-information]. * Value is cached in element's user data. @@ -165,12 +191,34 @@ private fun getPyTyped(element: PsiElement?): VirtualFile? { val root = getClassOrContentOrSourceRoot(element.project, file) ?: return null var current = if (file.isDirectory) file else file.parent - while (current != null && current != root && current.isDirectory) { + while (current != null && current.isDirectory) { val pyTyped = current.findChild("py.typed") if (pyTyped != null && !pyTyped.isDirectory) return pyTyped + if (current == root) break current = current.parent } return null } + +private fun getPackageDir(element: PsiElement): PsiDirectory? { + return when (val packageDir = PyUtil.turnInitIntoDir(element)) { + is PsiDirectory -> packageDir + else -> null + } +} + +private fun isPartialPyTypedMarker(project: Project, pyTyped: VirtualFile): Boolean { + val psiFile = PsiManager.getInstance(project).findFile(pyTyped) ?: return false + return CachedValuesManager.getCachedValue(psiFile) { + CachedValueProvider.Result.create(readPyTypedMarker(pyTyped), psiFile) + } +} + +private fun readPyTypedMarker(pyTyped: VirtualFile): Boolean { + return runCatching { + val text = FileDocumentManager.getInstance().getCachedDocument(pyTyped)?.text ?: VfsUtilCore.loadText(pyTyped) + "partial\n" in text + }.getOrDefault(false) +} diff --git a/python/python-psi-impl/src/com/jetbrains/python/codeInsight/typing/PyTypeShed.kt b/python/python-psi-impl/src/com/jetbrains/python/codeInsight/typing/PyTypeShed.kt index 7a4d50e3c652..9959da979f40 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/codeInsight/typing/PyTypeShed.kt +++ b/python/python-psi-impl/src/com/jetbrains/python/codeInsight/typing/PyTypeShed.kt @@ -16,14 +16,31 @@ package com.jetbrains.python.codeInsight.typing import com.intellij.openapi.application.PathManager +import com.intellij.openapi.fileEditor.FileDocumentManager +import com.intellij.openapi.project.Project import com.intellij.openapi.projectRoots.Sdk +import com.intellij.openapi.util.Key import com.intellij.openapi.vfs.StandardFileSystems import com.intellij.openapi.vfs.VfsUtilCore import com.intellij.openapi.vfs.VirtualFile +import com.intellij.openapi.vfs.VirtualFileManager +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiFileFactory +import com.intellij.psi.PsiFileSystemItem +import com.intellij.psi.PsiManager +import com.intellij.psi.util.CachedValue +import com.intellij.psi.util.CachedValueProvider +import com.intellij.psi.util.CachedValuesManager import com.intellij.psi.util.QualifiedName import com.intellij.python.community.helpersLocator.PythonHelpersLocator import com.jetbrains.python.PythonRuntimeService import com.jetbrains.python.psi.LanguageLevel +import org.toml.lang.psi.TomlFile +import org.toml.lang.psi.TomlFileType +import org.toml.lang.psi.TomlKeyValue +import org.toml.lang.psi.TomlLiteral +import org.toml.lang.psi.ext.TomlLiteralKind +import org.toml.lang.psi.ext.kind import java.io.File /** @@ -33,6 +50,10 @@ import java.io.File * */ object PyTypeShed { + private const val METADATA_TOML = "METADATA.toml" + private const val PARTIAL_STUB_KEY = "partial_stub" + private val PARTIAL_THIRD_PARTY_STUB_PACKAGE_KEY = + Key.create>("PyTypeShed.partialThirdPartyStubPackage") private val stdlibNamesAvailableOnlyInSubsetOfSupportedLanguageLevels = mapOf( // name to python versions when this name was introduced and removed @@ -230,12 +251,65 @@ object PyTypeShed { fun isInStandardLibrary(file: VirtualFile): Boolean = stdlibRoot?.let { VfsUtilCore.isAncestor(it, file, false) } == true + fun isInPartialThirdPartyStubPackage(element: PsiElement): Boolean { + val file = (element as? PsiFileSystemItem)?.virtualFile ?: element.containingFile?.virtualFile ?: return false + val distributionRoot = getThirdPartyDistributionRoot(file) ?: return false + return isPartialThirdPartyStubPackage(element.project, distributionRoot) + } + + private fun getThirdPartyDistributionRoot(file: VirtualFile): VirtualFile? { + val stubsRoot = thirdPartyStubRoot ?: return null + if (!VfsUtilCore.isAncestor(stubsRoot, file, false)) return null + + var current = if (file.isDirectory) file else file.parent + while (current != null && current.parent != stubsRoot) { + current = current.parent + } + + return current?.takeIf { it.parent == stubsRoot } + } + /** * Find the directory containing .pyi stubs for the package [packageName] under `typeshed/stubs`. * * [packageName] should match the name of the package on PyPI. */ fun getStubRootForPackage(packageName: String): VirtualFile? { - return thirdPartyStubRoot?.findChild(packageName) + val stubsRoot = thirdPartyStubRoot ?: return null + return stubsRoot.findChild(packageName) } -} \ No newline at end of file + + private fun isPartialThirdPartyStubPackage(project: Project, distributionRoot: VirtualFile): Boolean { + val psiDirectory = PsiManager.getInstance(project).findDirectory(distributionRoot) ?: return false + val metadataFile = distributionRoot.findChild(METADATA_TOML) + return CachedValuesManager.getManager(project).getCachedValue(psiDirectory, PARTIAL_THIRD_PARTY_STUB_PACKAGE_KEY, { + val metadataPsiFile = metadataFile?.let { PsiManager.getInstance(project).findFile(it) as? TomlFile } + val isPartial = when { + metadataPsiFile != null -> hasPartialStubMarker(metadataPsiFile) + metadataFile != null -> readPartialStubMarker(project, metadataFile) + else -> false + } + CachedValueProvider.Result.create( + isPartial, + metadataPsiFile ?: metadataFile ?: psiDirectory, + VirtualFileManager.VFS_STRUCTURE_MODIFICATIONS, + ) + }, false) + } + + private fun readPartialStubMarker(project: Project, metadataFile: VirtualFile): Boolean { + val metadataText = FileDocumentManager.getInstance().getCachedDocument(metadataFile)?.text ?: VfsUtilCore.loadText(metadataFile) + val psiFile = + PsiFileFactory.getInstance(project).createFileFromText(METADATA_TOML, TomlFileType, metadataText) as? TomlFile ?: return false + return hasPartialStubMarker(psiFile) + } + + private fun hasPartialStubMarker(metadataFile: TomlFile): Boolean { + val partialStubEntry = metadataFile.children + .filterIsInstance() + .firstOrNull { it.key.text == PARTIAL_STUB_KEY } ?: return false + val literal = partialStubEntry.value as? TomlLiteral ?: return false + + return literal.kind is TomlLiteralKind.Boolean && literal.text == "true" + } +} diff --git a/python/python-psi-impl/src/com/jetbrains/python/psi/resolve/PyPartialStubMarkersInvalidator.kt b/python/python-psi-impl/src/com/jetbrains/python/psi/resolve/PyPartialStubMarkersInvalidator.kt new file mode 100644 index 000000000000..72f6d5a46b7f --- /dev/null +++ b/python/python-psi-impl/src/com/jetbrains/python/psi/resolve/PyPartialStubMarkersInvalidator.kt @@ -0,0 +1,130 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.jetbrains.python.psi.resolve + +import com.intellij.openapi.components.Service +import com.intellij.openapi.components.service +import com.intellij.openapi.editor.EditorFactory +import com.intellij.openapi.editor.event.DocumentEvent +import com.intellij.openapi.editor.event.DocumentListener +import com.intellij.openapi.fileEditor.FileDocumentManager +import com.intellij.openapi.module.ModuleManager +import com.intellij.openapi.project.Project +import com.intellij.openapi.projectRoots.Sdk +import com.intellij.openapi.roots.ProjectFileIndex +import com.intellij.openapi.roots.ProjectRootManager +import com.intellij.openapi.startup.ProjectActivity +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.openapi.vfs.VirtualFileManager +import com.intellij.openapi.vfs.newvfs.BulkFileListener +import com.intellij.openapi.vfs.newvfs.events.VFileCopyEvent +import com.intellij.openapi.vfs.newvfs.events.VFileEvent +import com.intellij.openapi.vfs.newvfs.events.VFilePropertyChangeEvent +import com.intellij.util.asDisposable +import com.jetbrains.python.sdk.legacy.PythonSdkUtil +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.launch +import kotlin.time.Duration.Companion.milliseconds + +@OptIn(FlowPreview::class) +@Service(Service.Level.PROJECT) +class PyPartialStubMarkersInvalidator( + private val project: Project, + private val coroutineScope: CoroutineScope, +) { + fun subscribe() { + val invalidationRequests = MutableSharedFlow(extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST) + val parentDisposable = coroutineScope.asDisposable() + + coroutineScope.launch { + invalidationRequests + .debounce(INVALIDATION_DELAY) + .collectLatest { + clearPathCaches(project) + } + } + + project.messageBus.connect(parentDisposable).subscribe(VirtualFileManager.VFS_CHANGES, object : BulkFileListener { + override fun after(events: MutableList) { + if (events.any { affectsPartialStubMarkers(project, it) }) { + invalidationRequests.tryEmit(Unit) + } + } + }) + + EditorFactory.getInstance().eventMulticaster.addDocumentListener(object : DocumentListener { + override fun documentChanged(event: DocumentEvent) { + val file = FileDocumentManager.getInstance().getFile(event.document) ?: return + if (isProjectPartialStubMarkerFile(project, file)) { + invalidationRequests.tryEmit(Unit) + } + } + }, parentDisposable) + } + + private fun clearPathCaches(project: Project) { + if (project.isDisposed) return + val sdks = linkedSetOf() + + for (module in ModuleManager.getInstance(project).modules) { + PythonModulePathCache.getInstance(module).clearCache() + PythonSdkUtil.findPythonSdk(module)?.let(sdks::add) + } + + ProjectRootManager.getInstance(project).projectSdk + ?.takeIf(PythonSdkUtil::isPythonSdk) + ?.let(sdks::add) + + for (sdk in sdks) { + PythonSdkPathCache.getInstance(project, sdk).clearCache() + } + } + + private fun isPartialStubMarkerFileName(fileName: String?): Boolean { + return fileName == "py.typed" + } + + private fun affectsPartialStubMarkers(project: Project, event: VFileEvent): Boolean { + if (event.file?.let { isProjectPartialStubMarkerFile(project, it) } == true) { + return true + } + + return when (event) { + is VFileCopyEvent -> + isPartialStubMarkerFileName(event.newChildName) && + isProjectRelevantLocation(project, event.newParent) + is VFilePropertyChangeEvent -> + event.isRename && + (isPartialStubMarkerFileName(event.oldValue as? String) || isPartialStubMarkerFileName(event.newValue as? String)) && + event.file.parent?.let { isProjectRelevantLocation(project, it) } == true + else -> false + } + } + + private fun isProjectPartialStubMarkerFile(project: Project, file: VirtualFile): Boolean { + return isPartialStubMarkerFileName(file.name) && isProjectRelevantLocation(project, file) + } + + private fun isProjectRelevantLocation(project: Project, file: VirtualFile): Boolean { + if (!project.isInitialized || !file.isValid) { + return false + } + + val index = ProjectFileIndex.getInstance(project) + return index.isInProject(file) + } + + companion object { + private val INVALIDATION_DELAY = 300.milliseconds + } +} + +internal class PyPartialStubMarkersInvalidatorActivity : ProjectActivity { + override suspend fun execute(project: Project) { + project.service().subscribe() + } +} diff --git a/python/python-psi-impl/src/com/jetbrains/python/psi/resolve/PyResolveImportUtil.kt b/python/python-psi-impl/src/com/jetbrains/python/psi/resolve/PyResolveImportUtil.kt index 8b8b75dd59ec..0b5bd981875f 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/psi/resolve/PyResolveImportUtil.kt +++ b/python/python-psi-impl/src/com/jetbrains/python/psi/resolve/PyResolveImportUtil.kt @@ -23,7 +23,9 @@ import com.intellij.psi.util.QualifiedName import com.intellij.util.concurrency.annotations.RequiresBackgroundThread import com.intellij.util.concurrency.annotations.RequiresReadLock import com.jetbrains.python.codeInsight.typing.PyTypeShed +import com.jetbrains.python.codeInsight.typing.hasNamespaceSubPackage import com.jetbrains.python.codeInsight.typing.isInInlinePackage +import com.jetbrains.python.codeInsight.typing.isInPartialStubPackage import com.jetbrains.python.codeInsight.typing.isInStubPackage import com.jetbrains.python.facet.PythonPathContributingFacet import com.jetbrains.python.module.PyModuleService @@ -113,7 +115,7 @@ private fun resolveModuleFromRoots(name: QualifiedName, context: PyQualifiedName val head = name.removeTail(name.componentCount - 1) val nameNoHead = name.removeHead(1) return nameNoHead.components.fold(resultsFromRoots(head, context).distinct()) { results, component -> - filterTopPriorityResults(results, context.module) + filterTopPriorityResults(results, context.module, component) .asSequence() .filterIsInstance() .flatMap { resolveModuleAt(QualifiedName.fromComponents(component), it, context).asSequence() } @@ -408,7 +410,7 @@ private fun filterTopPriorityResultsWithFallback( * Filters resolved elements according to their import priority in sys.path and * [PEP 561](https://www.python.org/dev/peps/pep-0561/#type-checker-module-resolution-order) rules. */ -private fun filterTopPriorityResults(resolved: List, module: Module?): List { +private fun filterTopPriorityResults(resolved: List, module: Module?, nextComponent: String? = null): List { if (resolved.isEmpty()) return emptyList() val groupedResults = resolved.groupByTo(sortedMapOf>()) { resolvedElementPriority(it, module) } @@ -417,12 +419,12 @@ private fun filterTopPriorityResults(resolved: List, module: Module? if (groupedResults.topResultIs(Priority.NAMESPACE_PACKAGE)) return groupedResults[Priority.NAMESPACE_PACKAGE]!! + skeletons groupedResults.remove(Priority.NAMESPACE_PACKAGE) + val topPriority = groupedResults.keys.firstOrNull() + val topResult = topPriority?.let { groupedResults[it]?.firstOrNull() } val priorityResults = when { groupedResults.isEmpty() -> emptyList() - // stub packages can be partial - groupedResults.topResultIs(Priority.STUB_PACKAGE) -> firstResultWithFallback(groupedResults, Priority.STUB_PACKAGE) - // third party sdk should not overwrite packages from the same vendor - groupedResults.topResultIs(Priority.THIRD_PARTY_SDK) -> firstResultWithFallback(groupedResults, Priority.THIRD_PARTY_SDK) + topPriority != null && topResult != null && shouldFallbackToNextPriority(topPriority, topResult, module, nextComponent) -> + firstResultWithFallback(groupedResults, topPriority) else -> listOf(groupedResults.values.first().first()) } return priorityResults + skeletons @@ -439,6 +441,25 @@ private fun firstResultWithFallback(results: SortedMap isUserFile(element, module) && isInPartialStub(element, nextComponent) + Priority.STUB_PACKAGE -> isInPartialStub(element, nextComponent) + Priority.TYPESHED -> PyTypeShed.isInPartialThirdPartyStubPackage(element) + Priority.THIRD_PARTY_SDK -> true + else -> false + } +} + +/** + * A stub package result must keep the runtime package as a fallback when the stub does not authoritatively cover the + * path being resolved: either the enclosing stub package is marked partial, or the next component to resolve is a + * namespace package inside the stub (namespace packages are inherently incomplete, see PEP 561). + */ +private fun isInPartialStub(element: PsiElement, nextComponent: String?): Boolean { + return isInPartialStubPackage(element) || (nextComponent != null && hasNamespaceSubPackage(element, nextComponent)) +} + /** * See [https://www.python.org/dev/peps/pep-0561/#type-checker-module-resolution-order]. */ @@ -491,5 +512,3 @@ private enum class Priority { NAMESPACE_PACKAGE, // namespace package but may contain several entries in resolve result SKELETON // generated skeletons have lowest priority but are always included in the resolve result as a fallback } - - diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUser/__init__.py b/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUser/__init__.py new file mode 100644 index 000000000000..8b137891791f --- /dev/null +++ b/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUser/__init__.py @@ -0,0 +1 @@ + diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUser/a.py b/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUser/a.py new file mode 100644 index 000000000000..4034126c4b4d --- /dev/null +++ b/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUser/a.py @@ -0,0 +1,3 @@ +from mypackage.module2 import d + +print(d) diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUser/mypackage-stubs/__init__.pyi b/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUser/mypackage-stubs/__init__.pyi new file mode 100644 index 000000000000..8b137891791f --- /dev/null +++ b/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUser/mypackage-stubs/__init__.pyi @@ -0,0 +1 @@ + diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUser/mypackage-stubs/mymodule.pyi b/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUser/mypackage-stubs/mymodule.pyi new file mode 100644 index 000000000000..efd1039da217 --- /dev/null +++ b/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUser/mypackage-stubs/mymodule.pyi @@ -0,0 +1 @@ +b: int diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUser/mypackage-stubs/py.typed b/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUser/mypackage-stubs/py.typed new file mode 100644 index 000000000000..b648ac923333 --- /dev/null +++ b/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUser/mypackage-stubs/py.typed @@ -0,0 +1 @@ +partial diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUser/mypackage/__init__.py b/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUser/mypackage/__init__.py new file mode 100644 index 000000000000..8b137891791f --- /dev/null +++ b/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUser/mypackage/__init__.py @@ -0,0 +1 @@ + diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUser/mypackage/module2.py b/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUser/mypackage/module2.py new file mode 100644 index 000000000000..6e6d28cebd12 --- /dev/null +++ b/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUser/mypackage/module2.py @@ -0,0 +1 @@ +d = ["hello"] diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUser/mypackage/mymodule.py b/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUser/mypackage/mymodule.py new file mode 100644 index 000000000000..8f1cdfd5d44b --- /dev/null +++ b/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUser/mypackage/mymodule.py @@ -0,0 +1,2 @@ +b = 1 +c = "" diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUserNested/__init__.py b/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUserNested/__init__.py new file mode 100644 index 000000000000..8b137891791f --- /dev/null +++ b/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUserNested/__init__.py @@ -0,0 +1 @@ + diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUserNested/a/b.py b/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUserNested/a/b.py new file mode 100644 index 000000000000..4034126c4b4d --- /dev/null +++ b/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUserNested/a/b.py @@ -0,0 +1,3 @@ +from mypackage.module2 import d + +print(d) diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUserNested/mypackage-stubs/__init__.pyi b/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUserNested/mypackage-stubs/__init__.pyi new file mode 100644 index 000000000000..8b137891791f --- /dev/null +++ b/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUserNested/mypackage-stubs/__init__.pyi @@ -0,0 +1 @@ + diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUserNested/mypackage-stubs/mymodule.pyi b/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUserNested/mypackage-stubs/mymodule.pyi new file mode 100644 index 000000000000..efd1039da217 --- /dev/null +++ b/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUserNested/mypackage-stubs/mymodule.pyi @@ -0,0 +1 @@ +b: int diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUserNested/mypackage-stubs/py.typed b/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUserNested/mypackage-stubs/py.typed new file mode 100644 index 000000000000..b648ac923333 --- /dev/null +++ b/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUserNested/mypackage-stubs/py.typed @@ -0,0 +1 @@ +partial diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUserNested/mypackage/__init__.py b/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUserNested/mypackage/__init__.py new file mode 100644 index 000000000000..8b137891791f --- /dev/null +++ b/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUserNested/mypackage/__init__.py @@ -0,0 +1 @@ + diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUserNested/mypackage/module2.py b/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUserNested/mypackage/module2.py new file mode 100644 index 000000000000..6e6d28cebd12 --- /dev/null +++ b/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUserNested/mypackage/module2.py @@ -0,0 +1 @@ +d = ["hello"] diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUserNested/mypackage/mymodule.py b/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUserNested/mypackage/mymodule.py new file mode 100644 index 000000000000..8f1cdfd5d44b --- /dev/null +++ b/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUserNested/mypackage/mymodule.py @@ -0,0 +1,2 @@ +b = 1 +c = "" diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUserNestedWithSiblingStub/__init__.py b/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUserNestedWithSiblingStub/__init__.py new file mode 100644 index 000000000000..8b137891791f --- /dev/null +++ b/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUserNestedWithSiblingStub/__init__.py @@ -0,0 +1 @@ + diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUserNestedWithSiblingStub/a.pyi b/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUserNestedWithSiblingStub/a.pyi new file mode 100644 index 000000000000..8b137891791f --- /dev/null +++ b/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUserNestedWithSiblingStub/a.pyi @@ -0,0 +1 @@ + diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUserNestedWithSiblingStub/a/b.py b/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUserNestedWithSiblingStub/a/b.py new file mode 100644 index 000000000000..4034126c4b4d --- /dev/null +++ b/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUserNestedWithSiblingStub/a/b.py @@ -0,0 +1,3 @@ +from mypackage.module2 import d + +print(d) diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUserNestedWithSiblingStub/mypackage-stubs/__init__.pyi b/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUserNestedWithSiblingStub/mypackage-stubs/__init__.pyi new file mode 100644 index 000000000000..8b137891791f --- /dev/null +++ b/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUserNestedWithSiblingStub/mypackage-stubs/__init__.pyi @@ -0,0 +1 @@ + diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUserNestedWithSiblingStub/mypackage-stubs/mymodule.pyi b/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUserNestedWithSiblingStub/mypackage-stubs/mymodule.pyi new file mode 100644 index 000000000000..efd1039da217 --- /dev/null +++ b/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUserNestedWithSiblingStub/mypackage-stubs/mymodule.pyi @@ -0,0 +1 @@ +b: int diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUserNestedWithSiblingStub/mypackage-stubs/py.typed b/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUserNestedWithSiblingStub/mypackage-stubs/py.typed new file mode 100644 index 000000000000..b648ac923333 --- /dev/null +++ b/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUserNestedWithSiblingStub/mypackage-stubs/py.typed @@ -0,0 +1 @@ +partial diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUserNestedWithSiblingStub/mypackage/__init__.py b/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUserNestedWithSiblingStub/mypackage/__init__.py new file mode 100644 index 000000000000..8b137891791f --- /dev/null +++ b/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUserNestedWithSiblingStub/mypackage/__init__.py @@ -0,0 +1 @@ + diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUserNestedWithSiblingStub/mypackage/module2.py b/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUserNestedWithSiblingStub/mypackage/module2.py new file mode 100644 index 000000000000..6e6d28cebd12 --- /dev/null +++ b/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUserNestedWithSiblingStub/mypackage/module2.py @@ -0,0 +1 @@ +d = ["hello"] diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUserNestedWithSiblingStub/mypackage/mymodule.py b/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUserNestedWithSiblingStub/mypackage/mymodule.py new file mode 100644 index 000000000000..8f1cdfd5d44b --- /dev/null +++ b/python/testData/inspections/PyUnresolvedReferencesInspection3K/PartialStubPackageUserNestedWithSiblingStub/mypackage/mymodule.py @@ -0,0 +1,2 @@ +b = 1 +c = "" diff --git a/python/testData/resolve/InlinePackageInsteadStubPackage/lib/pkg-stubs/__init__.pyi b/python/testData/resolve/InlinePackageInsteadStubPackage/lib/pkg-stubs/__init__.pyi new file mode 100644 index 000000000000..ea4c9c8b4e1a --- /dev/null +++ b/python/testData/resolve/InlinePackageInsteadStubPackage/lib/pkg-stubs/__init__.pyi @@ -0,0 +1 @@ +# Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. diff --git a/python/testData/resolve/InlinePackageInsteadStubPackage/lib/pkg/__init__.py b/python/testData/resolve/InlinePackageInsteadStubPackage/lib/pkg/__init__.py new file mode 100644 index 000000000000..ea4c9c8b4e1a --- /dev/null +++ b/python/testData/resolve/InlinePackageInsteadStubPackage/lib/pkg/__init__.py @@ -0,0 +1 @@ +# Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. diff --git a/python/testData/resolve/InlinePackageInsteadStubPackage/lib/pkg/foo.py b/python/testData/resolve/InlinePackageInsteadStubPackage/lib/pkg/foo.py new file mode 100644 index 000000000000..340ba28864f0 --- /dev/null +++ b/python/testData/resolve/InlinePackageInsteadStubPackage/lib/pkg/foo.py @@ -0,0 +1,4 @@ +# Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. + +def bar(a: str, b: str): + return a + b diff --git a/python/testData/resolve/InlinePackageInsteadStubPackage/lib/pkg/py.typed b/python/testData/resolve/InlinePackageInsteadStubPackage/lib/pkg/py.typed new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testData/resolve/InlinePackageInsteadStubPackage/main.py b/python/testData/resolve/InlinePackageInsteadStubPackage/main.py new file mode 100644 index 000000000000..9cea504d3521 --- /dev/null +++ b/python/testData/resolve/InlinePackageInsteadStubPackage/main.py @@ -0,0 +1,6 @@ +# Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. + +from pkg import foo + +foo.bar("a", "b") +# diff --git a/python/testData/resolve/NoPyInsteadCompleteTypeShed/lib/synthetic_complete_typeshed/__init__.py b/python/testData/resolve/NoPyInsteadCompleteTypeShed/lib/synthetic_complete_typeshed/__init__.py new file mode 100644 index 000000000000..ea4c9c8b4e1a --- /dev/null +++ b/python/testData/resolve/NoPyInsteadCompleteTypeShed/lib/synthetic_complete_typeshed/__init__.py @@ -0,0 +1 @@ +# Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. diff --git a/python/testData/resolve/NoPyInsteadCompleteTypeShed/lib/synthetic_complete_typeshed/shortcuts.py b/python/testData/resolve/NoPyInsteadCompleteTypeShed/lib/synthetic_complete_typeshed/shortcuts.py new file mode 100644 index 000000000000..40b25115f6d6 --- /dev/null +++ b/python/testData/resolve/NoPyInsteadCompleteTypeShed/lib/synthetic_complete_typeshed/shortcuts.py @@ -0,0 +1,4 @@ +# Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. + +def foo(): + return 42 diff --git a/python/testData/resolve/NoPyInsteadCompleteTypeShed/main.py b/python/testData/resolve/NoPyInsteadCompleteTypeShed/main.py new file mode 100644 index 000000000000..7d18fd483e48 --- /dev/null +++ b/python/testData/resolve/NoPyInsteadCompleteTypeShed/main.py @@ -0,0 +1,6 @@ +# Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. + +from synthetic_complete_typeshed import shortcuts + +shortcuts.foo() +# diff --git a/python/testData/resolve/NoPyInsteadCompleteUserStubPackage/lib/pkg/__init__.py b/python/testData/resolve/NoPyInsteadCompleteUserStubPackage/lib/pkg/__init__.py new file mode 100644 index 000000000000..ea4c9c8b4e1a --- /dev/null +++ b/python/testData/resolve/NoPyInsteadCompleteUserStubPackage/lib/pkg/__init__.py @@ -0,0 +1 @@ +# Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. diff --git a/python/testData/resolve/NoPyInsteadCompleteUserStubPackage/lib/pkg/foo.py b/python/testData/resolve/NoPyInsteadCompleteUserStubPackage/lib/pkg/foo.py new file mode 100644 index 000000000000..bd56fbf3357f --- /dev/null +++ b/python/testData/resolve/NoPyInsteadCompleteUserStubPackage/lib/pkg/foo.py @@ -0,0 +1,4 @@ +# Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. + +def bar(a, b): + return a + b diff --git a/python/testData/resolve/NoPyInsteadCompleteUserStubPackage/project/main.py b/python/testData/resolve/NoPyInsteadCompleteUserStubPackage/project/main.py new file mode 100644 index 000000000000..d727a5f6e6e3 --- /dev/null +++ b/python/testData/resolve/NoPyInsteadCompleteUserStubPackage/project/main.py @@ -0,0 +1,6 @@ +# Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. + +from pkg import foo + +foo.bar("a", "b") +# diff --git a/python/testData/resolve/NoPyInsteadCompleteUserStubPackage/project/pkg/__init__.pyi b/python/testData/resolve/NoPyInsteadCompleteUserStubPackage/project/pkg/__init__.pyi new file mode 100644 index 000000000000..ea4c9c8b4e1a --- /dev/null +++ b/python/testData/resolve/NoPyInsteadCompleteUserStubPackage/project/pkg/__init__.pyi @@ -0,0 +1 @@ +# Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. diff --git a/python/testData/resolve/NoPyInsteadFalsePartialTypeShed/lib/synthetic_false_partial_typeshed/__init__.py b/python/testData/resolve/NoPyInsteadFalsePartialTypeShed/lib/synthetic_false_partial_typeshed/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testData/resolve/NoPyInsteadFalsePartialTypeShed/lib/synthetic_false_partial_typeshed/shortcuts.py b/python/testData/resolve/NoPyInsteadFalsePartialTypeShed/lib/synthetic_false_partial_typeshed/shortcuts.py new file mode 100644 index 000000000000..9e67bdd291cb --- /dev/null +++ b/python/testData/resolve/NoPyInsteadFalsePartialTypeShed/lib/synthetic_false_partial_typeshed/shortcuts.py @@ -0,0 +1,2 @@ +def foo(): + return 42 diff --git a/python/testData/resolve/NoPyInsteadFalsePartialTypeShed/main.py b/python/testData/resolve/NoPyInsteadFalsePartialTypeShed/main.py new file mode 100644 index 000000000000..7ab583da2837 --- /dev/null +++ b/python/testData/resolve/NoPyInsteadFalsePartialTypeShed/main.py @@ -0,0 +1,4 @@ +from synthetic_false_partial_typeshed import shortcuts + +shortcuts.foo() +# diff --git a/python/testData/resolve/NoPyInsteadNonPartialStubPackageMarker/lib/pkg-stubs/__init__.pyi b/python/testData/resolve/NoPyInsteadNonPartialStubPackageMarker/lib/pkg-stubs/__init__.pyi new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testData/resolve/NoPyInsteadNonPartialStubPackageMarker/lib/pkg-stubs/py.typed b/python/testData/resolve/NoPyInsteadNonPartialStubPackageMarker/lib/pkg-stubs/py.typed new file mode 100644 index 000000000000..a44b4700a63a --- /dev/null +++ b/python/testData/resolve/NoPyInsteadNonPartialStubPackageMarker/lib/pkg-stubs/py.typed @@ -0,0 +1 @@ +not-partial-no-no diff --git a/python/testData/resolve/NoPyInsteadNonPartialStubPackageMarker/lib/pkg/__init__.py b/python/testData/resolve/NoPyInsteadNonPartialStubPackageMarker/lib/pkg/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testData/resolve/NoPyInsteadNonPartialStubPackageMarker/lib/pkg/foo.py b/python/testData/resolve/NoPyInsteadNonPartialStubPackageMarker/lib/pkg/foo.py new file mode 100644 index 000000000000..c3fbc1014d0a --- /dev/null +++ b/python/testData/resolve/NoPyInsteadNonPartialStubPackageMarker/lib/pkg/foo.py @@ -0,0 +1,2 @@ +def bar(a, b): + return a + b diff --git a/python/testData/resolve/NoPyInsteadNonPartialStubPackageMarker/main.py b/python/testData/resolve/NoPyInsteadNonPartialStubPackageMarker/main.py new file mode 100644 index 000000000000..cc84d4e5f2b2 --- /dev/null +++ b/python/testData/resolve/NoPyInsteadNonPartialStubPackageMarker/main.py @@ -0,0 +1,4 @@ +from pkg import foo + +foo.bar("a", "b") +# diff --git a/python/testData/resolve/NoRuntimeMembersInsteadPartialStubPackageModule/lib/pkg-stubs/__init__.pyi b/python/testData/resolve/NoRuntimeMembersInsteadPartialStubPackageModule/lib/pkg-stubs/__init__.pyi new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testData/resolve/NoRuntimeMembersInsteadPartialStubPackageModule/lib/pkg-stubs/foo.pyi b/python/testData/resolve/NoRuntimeMembersInsteadPartialStubPackageModule/lib/pkg-stubs/foo.pyi new file mode 100644 index 000000000000..3532afeb86e2 --- /dev/null +++ b/python/testData/resolve/NoRuntimeMembersInsteadPartialStubPackageModule/lib/pkg-stubs/foo.pyi @@ -0,0 +1 @@ +def declared(a: str, b: str) -> str: ... diff --git a/python/testData/resolve/NoRuntimeMembersInsteadPartialStubPackageModule/lib/pkg-stubs/py.typed b/python/testData/resolve/NoRuntimeMembersInsteadPartialStubPackageModule/lib/pkg-stubs/py.typed new file mode 100644 index 000000000000..b648ac923333 --- /dev/null +++ b/python/testData/resolve/NoRuntimeMembersInsteadPartialStubPackageModule/lib/pkg-stubs/py.typed @@ -0,0 +1 @@ +partial diff --git a/python/testData/resolve/NoRuntimeMembersInsteadPartialStubPackageModule/lib/pkg/__init__.py b/python/testData/resolve/NoRuntimeMembersInsteadPartialStubPackageModule/lib/pkg/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testData/resolve/NoRuntimeMembersInsteadPartialStubPackageModule/lib/pkg/foo.py b/python/testData/resolve/NoRuntimeMembersInsteadPartialStubPackageModule/lib/pkg/foo.py new file mode 100644 index 000000000000..b48b06f19775 --- /dev/null +++ b/python/testData/resolve/NoRuntimeMembersInsteadPartialStubPackageModule/lib/pkg/foo.py @@ -0,0 +1,6 @@ +def declared(a, b): + return a + b + + +def runtime_only(a, b): + return a + b diff --git a/python/testData/resolve/NoRuntimeMembersInsteadPartialStubPackageModule/main.py b/python/testData/resolve/NoRuntimeMembersInsteadPartialStubPackageModule/main.py new file mode 100644 index 000000000000..90ff9fa2a102 --- /dev/null +++ b/python/testData/resolve/NoRuntimeMembersInsteadPartialStubPackageModule/main.py @@ -0,0 +1,4 @@ +from pkg import foo + +foo.runtime_only("a", "b") +# diff --git a/python/testData/resolve/PyInsteadNamespaceStubPackage/lib/pkg-stubs/__init__.pyi b/python/testData/resolve/PyInsteadNamespaceStubPackage/lib/pkg-stubs/__init__.pyi new file mode 100644 index 000000000000..2ae28399f5fd --- /dev/null +++ b/python/testData/resolve/PyInsteadNamespaceStubPackage/lib/pkg-stubs/__init__.pyi @@ -0,0 +1 @@ +pass diff --git a/python/testData/resolve/PyInsteadNamespaceStubPackage/lib/pkg-stubs/nspkg/bar.pyi b/python/testData/resolve/PyInsteadNamespaceStubPackage/lib/pkg-stubs/nspkg/bar.pyi new file mode 100644 index 000000000000..7a7dbb4eb832 --- /dev/null +++ b/python/testData/resolve/PyInsteadNamespaceStubPackage/lib/pkg-stubs/nspkg/bar.pyi @@ -0,0 +1 @@ +def stub_only() -> None: ... diff --git a/python/testData/resolve/PyInsteadNamespaceStubPackage/lib/pkg/__init__.py b/python/testData/resolve/PyInsteadNamespaceStubPackage/lib/pkg/__init__.py new file mode 100644 index 000000000000..2ae28399f5fd --- /dev/null +++ b/python/testData/resolve/PyInsteadNamespaceStubPackage/lib/pkg/__init__.py @@ -0,0 +1 @@ +pass diff --git a/python/testData/resolve/PyInsteadNamespaceStubPackage/lib/pkg/nspkg/foo.py b/python/testData/resolve/PyInsteadNamespaceStubPackage/lib/pkg/nspkg/foo.py new file mode 100644 index 000000000000..c3fbc1014d0a --- /dev/null +++ b/python/testData/resolve/PyInsteadNamespaceStubPackage/lib/pkg/nspkg/foo.py @@ -0,0 +1,2 @@ +def bar(a, b): + return a + b diff --git a/python/testData/resolve/PyInsteadNamespaceStubPackage/main.py b/python/testData/resolve/PyInsteadNamespaceStubPackage/main.py new file mode 100644 index 000000000000..9b62fdc5ce86 --- /dev/null +++ b/python/testData/resolve/PyInsteadNamespaceStubPackage/main.py @@ -0,0 +1,4 @@ +from pkg.nspkg import foo + +foo.bar("a", "b") +# diff --git a/python/testData/resolve/PyInsteadNestedPartialUserStubPackage/main.py b/python/testData/resolve/PyInsteadNestedPartialUserStubPackage/main.py new file mode 100644 index 000000000000..de6846df228a --- /dev/null +++ b/python/testData/resolve/PyInsteadNestedPartialUserStubPackage/main.py @@ -0,0 +1,4 @@ +from mypackage.package2 import mymodule + +mymodule.bar("a", "b") +# diff --git a/python/testData/resolve/PyInsteadNestedPartialUserStubPackage/mypackage/__init__.py b/python/testData/resolve/PyInsteadNestedPartialUserStubPackage/mypackage/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testData/resolve/PyInsteadNestedPartialUserStubPackage/mypackage/mymodule.py b/python/testData/resolve/PyInsteadNestedPartialUserStubPackage/mypackage/mymodule.py new file mode 100644 index 000000000000..c3fbc1014d0a --- /dev/null +++ b/python/testData/resolve/PyInsteadNestedPartialUserStubPackage/mypackage/mymodule.py @@ -0,0 +1,2 @@ +def bar(a, b): + return a + b diff --git a/python/testData/resolve/PyInsteadNestedPartialUserStubPackage/mypackage/package2/__init__.py b/python/testData/resolve/PyInsteadNestedPartialUserStubPackage/mypackage/package2/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testData/resolve/PyInsteadNestedPartialUserStubPackage/mypackage/package2/mymodule.py b/python/testData/resolve/PyInsteadNestedPartialUserStubPackage/mypackage/package2/mymodule.py new file mode 100644 index 000000000000..c3fbc1014d0a --- /dev/null +++ b/python/testData/resolve/PyInsteadNestedPartialUserStubPackage/mypackage/package2/mymodule.py @@ -0,0 +1,2 @@ +def bar(a, b): + return a + b diff --git a/python/testData/resolve/PyInsteadNestedPartialUserStubPackage/stubs/mypackage/__init__.pyi b/python/testData/resolve/PyInsteadNestedPartialUserStubPackage/stubs/mypackage/__init__.pyi new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testData/resolve/PyInsteadNestedPartialUserStubPackage/stubs/mypackage/package2/__init__.pyi b/python/testData/resolve/PyInsteadNestedPartialUserStubPackage/stubs/mypackage/package2/__init__.pyi new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testData/resolve/PyInsteadNestedPartialUserStubPackage/stubs/mypackage/package2/py.typed b/python/testData/resolve/PyInsteadNestedPartialUserStubPackage/stubs/mypackage/package2/py.typed new file mode 100644 index 000000000000..b648ac923333 --- /dev/null +++ b/python/testData/resolve/PyInsteadNestedPartialUserStubPackage/stubs/mypackage/package2/py.typed @@ -0,0 +1 @@ +partial diff --git a/python/testData/resolve/PyInsteadNestedPartialUserStubPackage/stubs/mypackage/py.typed b/python/testData/resolve/PyInsteadNestedPartialUserStubPackage/stubs/mypackage/py.typed new file mode 100644 index 000000000000..7ecb56eb3fa3 --- /dev/null +++ b/python/testData/resolve/PyInsteadNestedPartialUserStubPackage/stubs/mypackage/py.typed @@ -0,0 +1 @@ +no diff --git a/python/testData/resolve/PyInsteadPartialStubPackageAnotherImport/lib/pkg-stubs/__init__.pyi b/python/testData/resolve/PyInsteadPartialStubPackageAnotherImport/lib/pkg-stubs/__init__.pyi new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testData/resolve/PyInsteadPartialStubPackageAnotherImport/lib/pkg-stubs/py.typed b/python/testData/resolve/PyInsteadPartialStubPackageAnotherImport/lib/pkg-stubs/py.typed new file mode 100644 index 000000000000..b648ac923333 --- /dev/null +++ b/python/testData/resolve/PyInsteadPartialStubPackageAnotherImport/lib/pkg-stubs/py.typed @@ -0,0 +1 @@ +partial diff --git a/python/testData/resolve/PyInsteadPartialStubPackageAnotherImport/lib/pkg/__init__.py b/python/testData/resolve/PyInsteadPartialStubPackageAnotherImport/lib/pkg/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testData/resolve/PyInsteadPartialStubPackageAnotherImport/lib/pkg/foo.py b/python/testData/resolve/PyInsteadPartialStubPackageAnotherImport/lib/pkg/foo.py new file mode 100644 index 000000000000..c3fbc1014d0a --- /dev/null +++ b/python/testData/resolve/PyInsteadPartialStubPackageAnotherImport/lib/pkg/foo.py @@ -0,0 +1,2 @@ +def bar(a, b): + return a + b diff --git a/python/testData/resolve/PyInsteadPartialStubPackageAnotherImport/main.py b/python/testData/resolve/PyInsteadPartialStubPackageAnotherImport/main.py new file mode 100644 index 000000000000..3642a7adb99d --- /dev/null +++ b/python/testData/resolve/PyInsteadPartialStubPackageAnotherImport/main.py @@ -0,0 +1,4 @@ +from pkg.foo import bar + +bar("a", "b") +# diff --git a/python/testData/resolve/PyInsteadPartialTypeShed/lib/synthetic_partial_typeshed/__init__.py b/python/testData/resolve/PyInsteadPartialTypeShed/lib/synthetic_partial_typeshed/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testData/resolve/PyInsteadPartialTypeShed/lib/synthetic_partial_typeshed/shortcuts.py b/python/testData/resolve/PyInsteadPartialTypeShed/lib/synthetic_partial_typeshed/shortcuts.py new file mode 100644 index 000000000000..9e67bdd291cb --- /dev/null +++ b/python/testData/resolve/PyInsteadPartialTypeShed/lib/synthetic_partial_typeshed/shortcuts.py @@ -0,0 +1,2 @@ +def foo(): + return 42 diff --git a/python/testData/resolve/PyInsteadPartialTypeShed/main.py b/python/testData/resolve/PyInsteadPartialTypeShed/main.py new file mode 100644 index 000000000000..917ce7a5d1b9 --- /dev/null +++ b/python/testData/resolve/PyInsteadPartialTypeShed/main.py @@ -0,0 +1,4 @@ +from synthetic_partial_typeshed import shortcuts + +shortcuts.foo() +# diff --git a/python/testData/resolve/PyInsteadPartialUserStubPackage/lib/pkg/__init__.py b/python/testData/resolve/PyInsteadPartialUserStubPackage/lib/pkg/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testData/resolve/PyInsteadPartialUserStubPackage/lib/pkg/foo.py b/python/testData/resolve/PyInsteadPartialUserStubPackage/lib/pkg/foo.py new file mode 100644 index 000000000000..c3fbc1014d0a --- /dev/null +++ b/python/testData/resolve/PyInsteadPartialUserStubPackage/lib/pkg/foo.py @@ -0,0 +1,2 @@ +def bar(a, b): + return a + b diff --git a/python/testData/resolve/PyInsteadPartialUserStubPackage/project/main.py b/python/testData/resolve/PyInsteadPartialUserStubPackage/project/main.py new file mode 100644 index 000000000000..8f6cba0985a8 --- /dev/null +++ b/python/testData/resolve/PyInsteadPartialUserStubPackage/project/main.py @@ -0,0 +1,4 @@ +from pkg import foo + +foo.bar("a", "b") +# diff --git a/python/testData/resolve/PyInsteadPartialUserStubPackage/project/pkg/__init__.pyi b/python/testData/resolve/PyInsteadPartialUserStubPackage/project/pkg/__init__.pyi new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testData/resolve/PyInsteadPartialUserStubPackage/project/pkg/py.typed b/python/testData/resolve/PyInsteadPartialUserStubPackage/project/pkg/py.typed new file mode 100644 index 000000000000..b648ac923333 --- /dev/null +++ b/python/testData/resolve/PyInsteadPartialUserStubPackage/project/pkg/py.typed @@ -0,0 +1 @@ +partial diff --git a/python/testData/resolve/PyInsteadPartialUserStubPackageSourceRoot/main.py b/python/testData/resolve/PyInsteadPartialUserStubPackageSourceRoot/main.py new file mode 100644 index 000000000000..01631fb5872b --- /dev/null +++ b/python/testData/resolve/PyInsteadPartialUserStubPackageSourceRoot/main.py @@ -0,0 +1,4 @@ +from mypackage import mymodule + +mymodule.bar("a", "b") +# diff --git a/python/testData/resolve/PyInsteadPartialUserStubPackageSourceRoot/mypackage/__init__.py b/python/testData/resolve/PyInsteadPartialUserStubPackageSourceRoot/mypackage/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testData/resolve/PyInsteadPartialUserStubPackageSourceRoot/mypackage/mymodule.py b/python/testData/resolve/PyInsteadPartialUserStubPackageSourceRoot/mypackage/mymodule.py new file mode 100644 index 000000000000..c3fbc1014d0a --- /dev/null +++ b/python/testData/resolve/PyInsteadPartialUserStubPackageSourceRoot/mypackage/mymodule.py @@ -0,0 +1,2 @@ +def bar(a, b): + return a + b diff --git a/python/testData/resolve/PyInsteadPartialUserStubPackageSourceRoot/stubs/mypackage/__init__.pyi b/python/testData/resolve/PyInsteadPartialUserStubPackageSourceRoot/stubs/mypackage/__init__.pyi new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testData/resolve/PyInsteadPartialUserStubPackageSourceRoot/stubs/mypackage/py.typed b/python/testData/resolve/PyInsteadPartialUserStubPackageSourceRoot/stubs/mypackage/py.typed new file mode 100644 index 000000000000..b648ac923333 --- /dev/null +++ b/python/testData/resolve/PyInsteadPartialUserStubPackageSourceRoot/stubs/mypackage/py.typed @@ -0,0 +1 @@ +partial diff --git a/python/testData/resolve/typeshed/stubs/pycharm-test-complete-typeshed/METADATA.toml b/python/testData/resolve/typeshed/stubs/pycharm-test-complete-typeshed/METADATA.toml new file mode 100644 index 000000000000..48508735a58e --- /dev/null +++ b/python/testData/resolve/typeshed/stubs/pycharm-test-complete-typeshed/METADATA.toml @@ -0,0 +1,2 @@ +version = "1.0.*" +upstream_repository = "https://example.invalid/pycharm-test-complete-typeshed" diff --git a/python/testData/resolve/typeshed/stubs/pycharm-test-complete-typeshed/synthetic_complete_typeshed/__init__.pyi b/python/testData/resolve/typeshed/stubs/pycharm-test-complete-typeshed/synthetic_complete_typeshed/__init__.pyi new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testData/resolve/typeshed/stubs/pycharm-test-complete-typeshed/synthetic_complete_typeshed/existing_module.pyi b/python/testData/resolve/typeshed/stubs/pycharm-test-complete-typeshed/synthetic_complete_typeshed/existing_module.pyi new file mode 100644 index 000000000000..89aab3f5245d --- /dev/null +++ b/python/testData/resolve/typeshed/stubs/pycharm-test-complete-typeshed/synthetic_complete_typeshed/existing_module.pyi @@ -0,0 +1 @@ +def existing() -> int: ... diff --git a/python/testData/resolve/typeshed/stubs/pycharm-test-false-partial-typeshed/METADATA.toml b/python/testData/resolve/typeshed/stubs/pycharm-test-false-partial-typeshed/METADATA.toml new file mode 100644 index 000000000000..437472b815a7 --- /dev/null +++ b/python/testData/resolve/typeshed/stubs/pycharm-test-false-partial-typeshed/METADATA.toml @@ -0,0 +1,3 @@ +version = "1.0.*" +upstream_repository = "https://example.invalid/pycharm-test-false-partial-typeshed" +partial_stub = false diff --git a/python/testData/resolve/typeshed/stubs/pycharm-test-false-partial-typeshed/synthetic_false_partial_typeshed/__init__.pyi b/python/testData/resolve/typeshed/stubs/pycharm-test-false-partial-typeshed/synthetic_false_partial_typeshed/__init__.pyi new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testData/resolve/typeshed/stubs/pycharm-test-partial-typeshed/METADATA.toml b/python/testData/resolve/typeshed/stubs/pycharm-test-partial-typeshed/METADATA.toml new file mode 100644 index 000000000000..72344e01a22b --- /dev/null +++ b/python/testData/resolve/typeshed/stubs/pycharm-test-partial-typeshed/METADATA.toml @@ -0,0 +1,3 @@ +version = "1.0.*" +upstream_repository = "https://example.invalid/pycharm-test-partial-typeshed" +partial_stub = true diff --git a/python/testData/resolve/typeshed/stubs/pycharm-test-partial-typeshed/synthetic_partial_typeshed/__init__.pyi b/python/testData/resolve/typeshed/stubs/pycharm-test-partial-typeshed/synthetic_partial_typeshed/__init__.pyi new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testData/resolve/typeshed/stubs/pycharm-test-partial-typeshed/synthetic_partial_typeshed/existing_module.pyi b/python/testData/resolve/typeshed/stubs/pycharm-test-partial-typeshed/synthetic_partial_typeshed/existing_module.pyi new file mode 100644 index 000000000000..89aab3f5245d --- /dev/null +++ b/python/testData/resolve/typeshed/stubs/pycharm-test-partial-typeshed/synthetic_partial_typeshed/existing_module.pyi @@ -0,0 +1 @@ +def existing() -> int: ... diff --git a/python/testSrc/com/jetbrains/python/Py3ResolveTest.java b/python/testSrc/com/jetbrains/python/Py3ResolveTest.java index 225d3c9a10ae..2d4104b73fac 100644 --- a/python/testSrc/com/jetbrains/python/Py3ResolveTest.java +++ b/python/testSrc/com/jetbrains/python/Py3ResolveTest.java @@ -1,14 +1,22 @@ // Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.jetbrains.python; +import com.intellij.openapi.application.WriteAction; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.fileEditor.FileDocumentManager; +import com.intellij.openapi.module.Module; import com.intellij.openapi.util.RecursionManager; import com.intellij.openapi.util.StackOverflowPreventedException; +import com.intellij.openapi.vfs.VfsUtil; +import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiPolyVariantReference; import com.intellij.psi.PsiReference; import com.intellij.psi.ResolveResult; import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.testFramework.PlatformTestUtil; +import com.intellij.testFramework.PsiTestUtil; import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil; import com.jetbrains.python.fixtures.PyResolveTestCase; import com.jetbrains.python.fixtures.PyTestCase; @@ -24,6 +32,8 @@ import com.jetbrains.python.psi.impl.PyPsiUtils; import com.jetbrains.python.psi.types.TypeEvalContext; import com.jetbrains.python.pyi.PyiUtil; +import java.io.IOException; + public class Py3ResolveTest extends PyResolveTestCase { @@ -732,9 +742,23 @@ 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() { + // PY-55589 + public void testPyInsteadPartialStubPackageAnotherImport() { + final String path = "resolve/" + getTestName(false); + myFixture.configureByFile(path + "/main.py"); + + runWithAdditionalClassEntryInSdkRoots( + path + "/lib", + () -> { + final PsiElement element = PyResolveTestCase.findReferenceByMarker(myFixture.getFile()).resolve(); + assertInstanceOf(element, PyFunction.class); + assertEquals("foo.py", element.getContainingFile().getName()); + } + ); + } + + // PY-55589 + public void testNoRuntimeMembersInsteadPartialStubPackageModule() { final String path = "resolve/" + getTestName(false); myFixture.configureByFile(path + "/main.py"); @@ -744,9 +768,8 @@ 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 testNoInlinePackageInsteadStubPackage() { final String path = "resolve/" + getTestName(false); myFixture.configureByFile(path + "/main.py"); @@ -756,6 +779,212 @@ public class Py3ResolveTest extends PyResolveTestCase { ); } + // PY-30942 + public void testNoInlinePackageInsteadStubPackageAnotherImport() { + final String path = "resolve/" + getTestName(false); + myFixture.configureByFile(path + "/main.py"); + + runWithAdditionalClassEntryInSdkRoots( + path + "/lib", + () -> assertNull(PyResolveTestCase.findReferenceByMarker(myFixture.getFile()).resolve()) + ); + } + + // PY-55589 + public void testNoPyInsteadNonPartialStubPackageMarker() { + final String path = "resolve/" + getTestName(false); + myFixture.configureByFile(path + "/main.py"); + + runWithAdditionalClassEntryInSdkRoots( + path + "/lib", + () -> assertNull(PyResolveTestCase.findReferenceByMarker(myFixture.getFile()).resolve()) + ); + } + + // PY-55589 + public void testPyInsteadNamespaceStubPackage() { + final String path = "resolve/" + getTestName(false); + myFixture.configureByFile(path + "/main.py"); + + runWithAdditionalClassEntryInSdkRoots( + path + "/lib", + () -> { + final PsiElement element = PyResolveTestCase.findReferenceByMarker(myFixture.getFile()).resolve(); + assertInstanceOf(element, PyFunction.class); + assertEquals("foo.py", element.getContainingFile().getName()); + } + ); + } + + // PY-55589 + public void testPyInsteadPartialUserStubPackage() { + final String path = "resolve/" + getTestName(false); + myFixture.copyDirectoryToProject(path + "/project", ""); + myFixture.configureByFile("main.py"); + + runWithAdditionalClassEntryInSdkRoots( + path + "/lib", + () -> { + final PsiElement element = PyResolveTestCase.findReferenceByMarker(myFixture.getFile()).resolve(); + assertInstanceOf(element, PyFunction.class); + assertEquals("foo.py", element.getContainingFile().getName()); + } + ); + } + + // PY-55589 + public void testPyInsteadPartialUserStubPackageSourceRoot() { + final String path = "resolve/" + getTestName(false); + myFixture.copyDirectoryToProject(path, ""); + myFixture.configureByFile("main.py"); + + final Module module = myFixture.getModule(); + final VirtualFile sourceRoot = myFixture.findFileInTempDir("stubs"); + assertNotNull(sourceRoot); + + PsiTestUtil.addSourceRoot(module, sourceRoot); + try { + final PsiElement element = PyResolveTestCase.findReferenceByMarker(myFixture.getFile()).resolve(); + assertInstanceOf(element, PyFunction.class); + assertEquals("mymodule.py", element.getContainingFile().getName()); + } + finally { + PsiTestUtil.removeSourceRoot(module, sourceRoot); + } + } + + // PY-55589 + public void testPyInsteadNestedPartialUserStubPackage() { + final String path = "resolve/" + getTestName(false); + myFixture.copyDirectoryToProject(path, ""); + myFixture.configureByFile("main.py"); + + final Module module = myFixture.getModule(); + final VirtualFile sourceRoot = myFixture.findFileInTempDir("stubs"); + assertNotNull(sourceRoot); + + PsiTestUtil.addSourceRoot(module, sourceRoot); + try { + final PsiElement element = PyResolveTestCase.findReferenceByMarker(myFixture.getFile()).resolve(); + assertInstanceOf(element, PyFunction.class); + assertEquals("mymodule.py", element.getContainingFile().getName()); + } + finally { + PsiTestUtil.removeSourceRoot(module, sourceRoot); + } + } + + // PY-55589 + public void testEditingPyTypedInvalidatesPartialUserStubPackageResolveCache() { + final String path = "resolve/PyInsteadPartialUserStubPackage"; + myFixture.copyDirectoryToProject(path + "/project", ""); + myFixture.configureByFile("main.py"); + + runWithAdditionalClassEntryInSdkRoots( + path + "/lib", + () -> { + final PsiElement initial = PyResolveTestCase.findReferenceByMarker(myFixture.getFile()).resolve(); + assertInstanceOf(initial, PyFunction.class); + assertEquals("foo.py", initial.getContainingFile().getName()); + + final VirtualFile pyTyped = myFixture.findFileInTempDir("pkg/py.typed"); + assertNotNull(pyTyped); + + WriteAction.run(() -> { + try { + VfsUtil.saveText(pyTyped, ""); + } + catch (IOException e) { + throw new RuntimeException(e); + } + }); + PlatformTestUtil.waitForAlarm(300); + + assertNull(PyResolveTestCase.findReferenceByMarker(myFixture.getFile()).resolve()); + } + ); + } + + // PY-55589 + public void testEditingUnsavedPyTypedInvalidatesPartialUserStubPackageResolveCache() { + final String path = "resolve/PyInsteadPartialUserStubPackage"; + myFixture.copyDirectoryToProject(path + "/project", ""); + myFixture.configureByFile("main.py"); + + runWithAdditionalClassEntryInSdkRoots( + path + "/lib", + () -> { + final PsiElement initial = PyResolveTestCase.findReferenceByMarker(myFixture.getFile()).resolve(); + assertInstanceOf(initial, PyFunction.class); + assertEquals("foo.py", initial.getContainingFile().getName()); + + final VirtualFile pyTyped = myFixture.findFileInTempDir("pkg/py.typed"); + assertNotNull(pyTyped); + + final Document pyTypedDocument = FileDocumentManager.getInstance().getDocument(pyTyped); + assertNotNull(pyTypedDocument); + + WriteAction.run(() -> pyTypedDocument.setText("")); + assertTrue(FileDocumentManager.getInstance().isDocumentUnsaved(pyTypedDocument)); + PlatformTestUtil.waitForAlarm(300); + + assertNull(PyResolveTestCase.findReferenceByMarker(myFixture.getFile()).resolve()); + } + ); + } + + // PY-55589 + public void testNoPyInsteadCompleteUserStubPackage() { + final String path = "resolve/" + getTestName(false); + myFixture.copyDirectoryToProject(path + "/project", ""); + myFixture.configureByFile("main.py"); + + runWithAdditionalClassEntryInSdkRoots( + path + "/lib", + () -> assertNull(PyResolveTestCase.findReferenceByMarker(myFixture.getFile()).resolve()) + ); + } + + // PY-65967 + public void testPyInsteadPartialTypeShed() throws IOException { + final String path = "resolve/" + getTestName(false); + myFixture.configureByFile(path + "/main.py"); + enableTestDataTypeshedStubsForPackages("pycharm-test-partial-typeshed"); + + runWithAdditionalClassEntryInSdkRoots( + path + "/lib", + () -> { + final PsiElement element = PyResolveTestCase.findReferenceByMarker(myFixture.getFile()).resolve(); + assertInstanceOf(element, PyFunction.class); + assertEquals("shortcuts.py", element.getContainingFile().getName()); + } + ); + } + + // PY-65967 + public void testNoPyInsteadCompleteTypeShed() throws IOException { + final String path = "resolve/" + getTestName(false); + myFixture.configureByFile(path + "/main.py"); + enableTestDataTypeshedStubsForPackages("pycharm-test-complete-typeshed"); + + runWithAdditionalClassEntryInSdkRoots( + path + "/lib", + () -> assertNull(PyResolveTestCase.findReferenceByMarker(myFixture.getFile()).resolve()) + ); + } + + // PY-65967 + public void testNoPyInsteadFalsePartialTypeShed() throws IOException { + final String path = "resolve/" + getTestName(false); + myFixture.configureByFile(path + "/main.py"); + enableTestDataTypeshedStubsForPackages("pycharm-test-false-partial-typeshed"); + + runWithAdditionalClassEntryInSdkRoots( + path + "/lib", + () -> assertNull(PyResolveTestCase.findReferenceByMarker(myFixture.getFile()).resolve()) + ); + } + // PY-31354 public void testStubPackageInOtherRoot() { final String path = "resolve/" + getTestName(false); diff --git a/python/testSrc/com/jetbrains/python/fixtures/PyTestCase.java b/python/testSrc/com/jetbrains/python/fixtures/PyTestCase.java index b34784d0b61a..fce5f869ec22 100644 --- a/python/testSrc/com/jetbrains/python/fixtures/PyTestCase.java +++ b/python/testSrc/com/jetbrains/python/fixtures/PyTestCase.java @@ -85,6 +85,7 @@ import org.jetbrains.annotations.Nullable; import org.junit.Assert; import java.io.File; +import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -311,6 +312,35 @@ public abstract class PyTestCase extends UsefulTestCase { IndexingTestUtil.waitUntilIndexesAreReadyInAllOpenedProjects(); } + protected void enableTestDataTypeshedStubsForPackages(String @NotNull ... packageNames) throws IOException { + final String absPath = getTestDataPath() + "/resolve/typeshed/stubs"; + final VirtualFile sourceThirdPartyStubRoot = LocalFileSystem.getInstance().refreshAndFindFileByPath(absPath); + assertNotNull("Third-party typeshed root '" + absPath + "' not found", sourceThirdPartyStubRoot); + + final VirtualFile targetThirdPartyStubRoot = PyTypeShed.INSTANCE.getThirdPartyStubRoot(); + assertNotNull("Bundled third-party typeshed root not found", targetThirdPartyStubRoot); + + final List copiedStubRoots = new ArrayList<>(); + WriteAction.run(() -> { + for (String packageName : packageNames) { + final VirtualFile sourceStubRoot = sourceThirdPartyStubRoot.findChild(packageName); + assertNotNull("Stub package root for " + packageName + " not found under " + absPath, sourceStubRoot); + + final VirtualFile existingStubRoot = targetThirdPartyStubRoot.findChild(packageName); + if (existingStubRoot != null) { + VfsTestUtil.deleteFile(existingStubRoot); + } + + final VirtualFile targetStubRoot = VfsTestUtil.createDir(targetThirdPartyStubRoot, packageName); + VfsUtil.copyDirectory(this, sourceStubRoot, targetStubRoot, null); + copiedStubRoots.add(targetStubRoot); + } + }); + + Disposer.register(getTestRootDisposable(), () -> WriteAction.run(() -> copiedStubRoots.forEach(VfsTestUtil::deleteFile))); + enablePyiStubsForPackages(packageNames); + } + protected void enablePyiStubsForPackages(String @NotNull ... packageNames) { Sdk sdk = PythonSdkUtil.findPythonSdk(myFixture.getModule()); assertNotNull(sdk); diff --git a/python/testSrc/com/jetbrains/python/inspections/Py3UnresolvedReferencesInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/Py3UnresolvedReferencesInspectionTest.java index 67b8c6ff797b..f62152674fc8 100644 --- a/python/testSrc/com/jetbrains/python/inspections/Py3UnresolvedReferencesInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/Py3UnresolvedReferencesInspectionTest.java @@ -37,7 +37,11 @@ public class Py3UnresolvedReferencesInspectionTest extends PyInspectionTestCase } protected void doMultiFileTest(@NotNull String filename, @NotNull List sourceRoots) { - myFixture.copyDirectoryToProject(getTestDirectoryPath(), ""); + doMultiFileTest(getTestName(false), filename, sourceRoots); + } + + protected void doMultiFileTest(@NotNull String testDirectory, @NotNull String filename, @NotNull List sourceRoots) { + myFixture.copyDirectoryToProject(TEST_DIRECTORY + testDirectory, ""); final Module module = myFixture.getModule(); for (String root : sourceRoots) { PsiTestUtil.addSourceRoot(module, myFixture.findFileInTempDir(root)); @@ -511,6 +515,26 @@ public class Py3UnresolvedReferencesInspectionTest extends PyInspectionTestCase }); } + // PY-55589 + public void testPartialStubPackageUser() { + doMultiFileTest("a.py"); + } + + // PY-55589 + public void testPartialStubPackageUserNested() { + doMultiFileTest("a/b.py"); + } + + // PY-55589 + public void testPartialStubPackageUserNestedWithSiblingStub() { + doMultiFileTest("a/b.py"); + } + + // PY-55589 + public void testPartialStubPackageUserNestedWithSiblingStubSourceRoot() { + doMultiFileTest("PartialStubPackageUserNestedWithSiblingStub", "a/b.py", Collections.singletonList("mypackage-stubs")); + } + // PY-85880 public void testLiteralUnionInTuple() { doTestByText("""