public static void processMatchingExportedNames(@NotNull QualifiedName qualifiedNamePattern,
                                                  @NotNull PsiFile currentFile,
                                                  @NotNull GlobalSearchScope scope,
                                                  @NotNull Processor<? super ExportedName> processor) {
    if (qualifiedNamePattern.getComponentCount() < 2) return;
    QualifiedNameMatcher matcher = new QualifiedNameMatcher(qualifiedNamePattern);
    StubIndex stubIndex = StubIndex.getInstance();
    Project project = Objects.requireNonNull(scope.getProject());
    PsiManager psiManager = PsiManager.getInstance(project);

    GlobalSearchScope moduleMatchingScope = scope.intersectWith(new ModuleQualifiedNameMatchingScope(matcher, project));
    Set<QualifiedName> alreadySuggestedAttributes = new HashSet<>();
    IndexLookupStats stats = new IndexLookupStats();
    try {
      List<String> matchingAttributeNames = new ArrayList<>();
      stubIndex.processAllKeys(PyExportedModuleAttributeIndex.KEY, attributeName -> {
        ProgressManager.checkCanceled();
        stats.scannedKeys++;
        if (!matcher.attributeMatches(attributeName)) return true;
        stats.matchingKeys++;
        matchingAttributeNames.add(attributeName);
        return true;
      }, moduleMatchingScope);

      for (String attributeName : matchingAttributeNames) {
        stubIndex.processElements(PyExportedModuleAttributeIndex.KEY,
                                  attributeName, project, moduleMatchingScope, null, PyElement.class, element -> {
            ProgressManager.checkCanceled();
            VirtualFile vFile = element.getContainingFile().getVirtualFile();
            QualifiedName moduleQualifiedName = findQualifiedNameInClosestRoot(vFile, project);
            assert moduleQualifiedName != null : vFile;
            QualifiedName canonicalImportPath = findCanonicalImportPath(element, moduleQualifiedName, currentFile);
            QualifiedName importPath;
            if (canonicalImportPath != null && matcher.qualifierMatches(canonicalImportPath)) {
              importPath = canonicalImportPath;
            }
            else {
              importPath = moduleQualifiedName;
            }
            if (ContainerUtil.exists(importPath.getComponents(), c -> c.startsWith("_")) && !psiManager.isInProject(element)) {
              return true;
            }
            QualifiedName attributeQualifiedName = importPath.append(attributeName);
            if (alreadySuggestedAttributes.add(attributeQualifiedName)) {
              if (!processor.process(new ExportedName(attributeQualifiedName, element))) {
                return false;
              }
            }
            return true;
          });
      }
    }
    catch (ProcessCanceledException e) {
      stats.cancelled = true;
      throw e;
    }
    finally {
      if (LOG.isDebugEnabled()) {
        LOG.debug("Index lookup stats for '" + qualifiedNamePattern + "':\n" +
                  "Scanned keys: " + stats.scannedKeys + "\n" +
                  "Matched keys: " + stats.matchingKeys + "\n" +
                  (stats.cancelled ? "Cancelled in " : "Completed in ") + stats.getDuration() + " ms");
      }
    }
  }
public static void processMatchingExportedNames(@NotNull QualifiedName qualifiedNamePattern,
                                                  @NotNull PsiFile currentFile,
                                                  @NotNull GlobalSearchScope scope,
                                                  @NotNull Processor<? super ExportedName> processor)