@RequiredReadAction
  private static boolean processInheritors(
      @NotNull final Processor<DotNetTypeDeclaration> consumer,
      @NotNull final String baseVmQName,
      @NotNull final SearchScope searchScope,
      @NotNull final SearchParameters parameters) {

    if (DotNetTypes.System.Object.equals(baseVmQName)) {
      return AllTypesSearch.search(
              searchScope, parameters.getProject(), parameters.getNameCondition())
          .forEach(
              new Processor<DotNetTypeDeclaration>() {
                @Override
                public boolean process(final DotNetTypeDeclaration aClass) {
                  ProgressIndicatorProvider.checkCanceled();
                  final String qname1 =
                      ApplicationManager.getApplication()
                          .runReadAction(
                              new Computable<String>() {
                                @Override
                                @Nullable
                                public String compute() {
                                  return aClass.getVmQName();
                                }
                              });
                  return DotNetTypes.System.Object.equals(qname1)
                      || consumer.process(parameters.myTransformer.fun(aClass));
                }
              });
    }

    final Ref<String> currentBase = Ref.create(null);
    final Stack<String> stack = new Stack<String>();
    // there are two sets for memory optimization: it's cheaper to hold FQN than PsiClass
    final Set<String> processedFqns =
        new THashSet<String>(); // FQN of processed classes if the class has one

    final Processor<DotNetTypeDeclaration> processor =
        new Processor<DotNetTypeDeclaration>() {
          @Override
          public boolean process(final DotNetTypeDeclaration candidate) {
            ProgressIndicatorProvider.checkCanceled();

            final Ref<Boolean> result = new Ref<Boolean>();
            final Ref<String> vmQNameRef = new Ref<String>();
            ApplicationManager.getApplication()
                .runReadAction(
                    new Runnable() {
                      @Override
                      public void run() {
                        vmQNameRef.set(candidate.getVmQName());
                        if (parameters.isCheckInheritance() || parameters.isCheckDeep()) {
                          if (!candidate.isInheritor(currentBase.get(), false)) {
                            result.set(true);
                            return;
                          }
                        }

                        if (PsiSearchScopeUtil.isInScope(searchScope, candidate)) {
                          final String name = candidate.getName();
                          if (name != null
                              && parameters.getNameCondition().value(name)
                              && !consumer.process(parameters.myTransformer.fun(candidate))) {
                            result.set(false);
                          }
                        }
                      }
                    });
            if (!result.isNull()) {
              return result.get();
            }

            if (parameters.isCheckDeep() && !isSealed(candidate)) {
              stack.push(vmQNameRef.get());
            }

            return true;
          }
        };
    stack.push(baseVmQName);

    final GlobalSearchScope projectScope = GlobalSearchScope.allScope(parameters.getProject());
    while (!stack.isEmpty()) {
      ProgressIndicatorProvider.checkCanceled();

      String vmQName = stack.pop();

      if (!processedFqns.add(vmQName)) {
        continue;
      }

      currentBase.set(vmQName);
      if (!DirectTypeInheritorsSearch.search(parameters.getProject(), vmQName, projectScope, false)
          .forEach(processor)) {
        return false;
      }
    }
    return true;
  }