@NotNull
  public static Collection<PsiClass> calculateWeakestClassesNecessary(@NotNull PsiElement variableOrMethod,
                                                                      boolean useRighthandTypeAsWeakestTypeInAssignments) {
    final PsiType variableOrMethodType;
    if (variableOrMethod instanceof PsiVariable variable) {
      variableOrMethodType = variable.getType();
    }
    else if (variableOrMethod instanceof PsiMethod method) {
      variableOrMethodType = method.getReturnType();
      if (PsiTypes.voidType().equals(variableOrMethodType)) {
        return Collections.emptyList();
      }
    }
    else {
      throw new IllegalArgumentException("PsiMethod or PsiVariable expected: " + variableOrMethod);
    }
    final PsiClass variableOrMethodClass = PsiUtil.resolveClassInClassTypeOnly(variableOrMethodType);
    if (variableOrMethodClass == null || variableOrMethodClass instanceof PsiTypeParameter) {
      return Collections.emptyList();
    }
    final Set<PsiClass> weakestTypeClasses = new HashSet<>();
    final GlobalSearchScope scope = variableOrMethod.getResolveScope();
    final JavaPsiFacade facade = JavaPsiFacade.getInstance(variableOrMethod.getProject());
    final PsiClass lowerBoundClass;
    if (variableOrMethod instanceof PsiResourceVariable resourceVariable) {
      lowerBoundClass = facade.findClass(CommonClassNames.JAVA_LANG_AUTO_CLOSEABLE, scope);
      if (lowerBoundClass == null || variableOrMethodClass.equals(lowerBoundClass)) {
        return Collections.emptyList();
      }
      weakestTypeClasses.add(lowerBoundClass);
      @NonNls final String methodCallText = resourceVariable.getName() + ".close()";
      final PsiMethodCallExpression methodCallExpression =
        (PsiMethodCallExpression)facade.getElementFactory().createExpressionFromText(methodCallText, resourceVariable.getParent());
      if (!findWeakestType(methodCallExpression, weakestTypeClasses)) {
        return Collections.emptyList();
      }
      if (weakestTypeClasses.isEmpty()) {
        weakestTypeClasses.add(lowerBoundClass);
      }
    }
    else {
      lowerBoundClass = facade.findClass(CommonClassNames.JAVA_LANG_OBJECT, scope);
      if (lowerBoundClass == null || variableOrMethodClass.equals(lowerBoundClass)) {
        return Collections.emptyList();
      }
      weakestTypeClasses.add(lowerBoundClass);
    }

    final Query<PsiReference> query = ReferencesSearch.search(variableOrMethod, variableOrMethod.getUseScope());
    boolean hasUsages = false;
    for (PsiReference reference : query) {
      ProgressManager.checkCanceled();
      hasUsages = true;
      PsiElement referenceElement = reference.getElement();
      PsiElement referenceParent = PsiUtil.skipParenthesizedExprUp(referenceElement.getParent());
      if (referenceParent instanceof PsiMethodCallExpression) {
        referenceElement = referenceParent;
        referenceParent = PsiUtil.skipParenthesizedExprUp(referenceElement.getParent());
      }
      final PsiElement referenceGrandParent = referenceParent.getParent();
      if (referenceElement instanceof PsiMethodReferenceExpression methodReferenceExpression) {
        final PsiType type = methodReferenceExpression.getFunctionalInterfaceType();
        final PsiType returnType = LambdaUtil.getFunctionalInterfaceReturnType(type);
        if (!PsiTypes.voidType().equals(returnType) && !checkType(returnType, weakestTypeClasses)) {
          return Collections.emptyList();
        }
      } else if (referenceParent instanceof PsiExpressionList) {
        if (!(referenceGrandParent instanceof PsiMethodCallExpression methodCallExpression)) {
          return Collections.emptyList();
        }
        if (!findWeakestType(referenceElement, methodCallExpression, weakestTypeClasses)) {
          return Collections.emptyList();
        }
      }
      else if (referenceParent instanceof PsiLambdaExpression lambda) {
        final PsiClassType returnType = ObjectUtils.tryCast(LambdaUtil.getFunctionalInterfaceReturnType(lambda), PsiClassType.class);
        checkType(returnType, weakestTypeClasses);
      }
      else if (referenceGrandParent instanceof PsiMethodCallExpression methodCallExpression) {
        if (PsiUtil.skipParenthesizedExprUp(methodCallExpression.getParent()) instanceof PsiTypeCastExpression ||
            !findWeakestType(methodCallExpression, weakestTypeClasses)) {
          return Collections.emptyList();
        }
      }
      else if (referenceParent instanceof PsiResourceExpression) {
        final PsiClass closeable = facade.findClass(CommonClassNames.JAVA_LANG_AUTO_CLOSEABLE, scope);
        checkClass(closeable, weakestTypeClasses);
      }
      else if (referenceParent instanceof PsiAssignmentExpression assignmentExpression) {
        if (!findWeakestType(referenceElement, assignmentExpression, useRighthandTypeAsWeakestTypeInAssignments, weakestTypeClasses)) {
          return Collections.emptyList();
        }
      }
      else if (referenceParent instanceof PsiNameValuePair) {
        final PsiType type = ExpectedTypeUtils.findExpectedType((PsiExpression)referenceElement, true);
        if (!checkType(type, weakestTypeClasses)) {
          return Collections.emptyList();
        }
      }
      else if (referenceParent instanceof PsiVariable variable) {
        final PsiType type = variable.getType();
        if (!type.isAssignableFrom(variableOrMethodType) || !checkType(type, weakestTypeClasses)) {
          return Collections.emptyList();
        }
      }
      else if (referenceParent instanceof PsiForeachStatement foreachStatement) {
        if (!Comparing.equal(foreachStatement.getIteratedValue(), referenceElement)) {
          return Collections.emptyList();
        }
        final PsiClass javaLangIterableClass = facade.findClass(CommonClassNames.JAVA_LANG_ITERABLE, scope);
        if (javaLangIterableClass == null) {
          return Collections.emptyList();
        }
        checkClass(javaLangIterableClass, weakestTypeClasses);
      }
      else if (referenceParent instanceof PsiReturnStatement) {
        final PsiType type = PsiTypesUtil.getMethodReturnType(referenceParent);
        if (!checkType(type, weakestTypeClasses)) {
          return Collections.emptyList();
        }
      }
      else if (referenceParent instanceof PsiReferenceExpression referenceExpression) {
        // field access, method call is handled above.
        final PsiElement target = referenceExpression.resolve();
        if (!(target instanceof PsiField field)) {
          return Collections.emptyList();
        }
        final PsiClass containingClass = field.getContainingClass();
        checkClass(containingClass, weakestTypeClasses);
      }
      else if (referenceParent instanceof PsiArrayInitializerExpression arrayInitializerExpression) {
        if (!findWeakestType(arrayInitializerExpression, weakestTypeClasses)) {
          return Collections.emptyList();
        }
      }
      else if (referenceParent instanceof PsiThrowStatement throwStatement) {
        if (!findWeakestType(throwStatement, variableOrMethodClass, weakestTypeClasses)) {
          return Collections.emptyList();
        }
      }
      else if (referenceParent instanceof PsiConditionalExpression conditionalExpression) {
        final PsiExpression condition = conditionalExpression.getCondition();
        if (referenceElement.equals(condition)) {
          return Collections.emptyList();
        }
        final PsiType type = ExpectedTypeUtils.findExpectedType(conditionalExpression, true);
        if (!checkType(type, weakestTypeClasses)) {
          return Collections.emptyList();
        }
      }
      else if (referenceParent instanceof PsiPolyadicExpression polyadicExpression) {
        final PsiType type = polyadicExpression.getType();
        if (TypeUtils.isJavaLangString(type)) {
          if (ExpressionUtils.hasStringType((PsiExpression)referenceElement)) {
            final PsiExpression[] operands = polyadicExpression.getOperands();
            boolean first = true;
            boolean stringSeen = false;
            for (PsiExpression operand : operands) {
              if (operand == reference) {
                break;
              }
              first = false;
              if (ExpressionUtils.hasStringType(operand)) {
                stringSeen = true;
                break;
              }
            }
            if (!stringSeen && (!first || !ExpressionUtils.hasStringType(operands[1]))) {
              if (!checkType(type, weakestTypeClasses)) {
                return Collections.emptyList();
              }
            }
          }
        }
        else if (!ComparisonUtils.isEqualityComparison(polyadicExpression)) {
          return Collections.emptyList();
        }
      }
      else if (referenceParent instanceof PsiSwitchBlock) {
        // only enums, Strings and primitives can be a switch expression
        // pattern matching not yet supported
        return Collections.emptyList();
      }
      else if (referenceParent instanceof PsiUnaryExpression) {
        // only primitives and boxed types are the target of a unary
        // expression
        return Collections.emptyList();
      }
      else if (referenceParent instanceof PsiIfStatement) {
        // only booleans and boxed Booleans are the condition of an if
        // statement
        return Collections.emptyList();
      }
      else if (referenceParent instanceof PsiForStatement) {
        // only booleans and boxed Booleans are the condition of an
        // for statement
        return Collections.emptyList();
      }
      else if (referenceParent instanceof PsiNewExpression newExpression) {
        final PsiExpression qualifier = newExpression.getQualifier();
        if (qualifier != null) {
          final PsiClass innerClass = PsiUtil.resolveClassInClassTypeOnly(newExpression.getType());
          if (innerClass == null) {
            return Collections.emptyList();
          }
          final PsiClass outerClass = innerClass.getContainingClass();
          if (outerClass != null) {
            checkClass(outerClass, weakestTypeClasses);
          }
        }
      }
      if (weakestTypeClasses.contains(variableOrMethodClass) || weakestTypeClasses.isEmpty()) {
        return Collections.emptyList();
      }
    }
    if (!hasUsages) {
      return Collections.emptyList();
    }
    return filterAccessibleClasses(weakestTypeClasses, variableOrMethodClass, variableOrMethod);
  }
@NotNull
  public static Collection<PsiClass> calculateWeakestClassesNecessary(@NotNull PsiElement variableOrMethod,
                                                                      boolean useRighthandTypeAsWeakestTypeInAssignments)