diff --git a/python/python-common-tests/com/jetbrains/python/PythonCommonCompletionTest.java b/python/python-common-tests/com/jetbrains/python/PythonCommonCompletionTest.java index 0b381753fc24..7158e34ba4fc 100644 --- a/python/python-common-tests/com/jetbrains/python/PythonCommonCompletionTest.java +++ b/python/python-common-tests/com/jetbrains/python/PythonCommonCompletionTest.java @@ -12,6 +12,7 @@ import com.intellij.openapi.vfs.VirtualFile; import com.jetbrains.python.documentation.docstrings.DocStringFormat; import com.jetbrains.python.fixture.PythonCommonTestCase; import com.jetbrains.python.psi.LanguageLevel; +import com.jetbrains.python.sdk.PythonSdkUtil; import one.util.streamex.StreamEx; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -1711,6 +1712,20 @@ public abstract class PythonCommonCompletionTest extends PythonCommonTestCase { doTestHasattrContributor(inList, null); } + // PY-39956 + public void testDuplicatesFromProvider() { + final String testName = getTestName(true); + + final VirtualFile skeletonsDir = + StandardFileSystems.local().findFileByPath(getTestDataPath() + "/" + testName + "/" + PythonSdkUtil.SKELETON_DIR_NAME); + + assertNotNull(skeletonsDir); + runWithAdditionalClassEntryInSdkRoots( + skeletonsDir, + () -> assertNull(doTestByText("from itertools import prod")) + ); + } + private void doTestHasattrContributor(String[] inList, String[] notInList) { doTestHasattrContributor("hasattrCompletion/" + getTestName(true) + ".py", inList, notInList); } diff --git a/python/python-psi-impl/src/com/jetbrains/python/psi/types/PyModuleType.java b/python/python-psi-impl/src/com/jetbrains/python/psi/types/PyModuleType.java index c9a6d9257189..e68c453e14e5 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/psi/types/PyModuleType.java +++ b/python/python-psi-impl/src/com/jetbrains/python/psi/types/PyModuleType.java @@ -21,10 +21,10 @@ import com.intellij.util.containers.ContainerUtil; import com.jetbrains.python.PyNames; import com.jetbrains.python.codeInsight.PyCustomMember; import com.jetbrains.python.codeInsight.completion.PyCompletionUtilsKt; -import com.jetbrains.python.codeInsight.mlcompletion.PyCompletionMlElementInfo; -import com.jetbrains.python.codeInsight.mlcompletion.PyCompletionMlElementKind; import com.jetbrains.python.codeInsight.controlflow.ScopeOwner; import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil; +import com.jetbrains.python.codeInsight.mlcompletion.PyCompletionMlElementInfo; +import com.jetbrains.python.codeInsight.mlcompletion.PyCompletionMlElementKind; import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.impl.PyImportedModule; import com.jetbrains.python.psi.impl.ResolveResultList; @@ -419,8 +419,8 @@ public class PyModuleType implements PyType { // Modules don't descend from obje for (PyModuleMembersProvider provider : PyModuleMembersProvider.EP_NAME.getExtensionList()) { for (PyCustomMember member : provider.getMembers(myModule, point, typeEvalContext)) { final String name = member.getName(); - if (namesAlready != null) { - namesAlready.add(name); + if (namesAlready != null && !namesAlready.add(name)) { + continue; } if (PyUtil.isClassPrivateName(name)) { continue; diff --git a/python/testData/completion/duplicatesFromProvider/python_stubs/itertools.py b/python/testData/completion/duplicatesFromProvider/python_stubs/itertools.py new file mode 100644 index 000000000000..d7003139cdc2 --- /dev/null +++ b/python/testData/completion/duplicatesFromProvider/python_stubs/itertools.py @@ -0,0 +1,2 @@ +def product(): + pass \ No newline at end of file