private static PsiType handleBoundComposition(
     PsiWildcardType wildcardType, PsiWildcardType bound) {
   if (bound.isExtends() == wildcardType.isExtends()) {
     final PsiType newBoundBound = bound.getBound();
     if (newBoundBound != null) {
       return PsiWildcardType.changeBound(wildcardType, newBoundBound);
     }
   }
   return PsiWildcardType.createUnbounded(wildcardType.getManager());
 }
  private PsiType addBounds(PsiType substituted, final PsiTypeParameter typeParameter) {
    PsiElement captureContext = null;
    if (substituted instanceof PsiCapturedWildcardType) {
      final PsiCapturedWildcardType captured = (PsiCapturedWildcardType) substituted;
      substituted = captured.getWildcard();
      captureContext = captured.getContext();
    }
    if (substituted instanceof PsiWildcardType && !((PsiWildcardType) substituted).isSuper()) {
      PsiType originalBound = ((PsiWildcardType) substituted).getBound();
      PsiManager manager = typeParameter.getManager();
      final PsiType[] boundTypes = typeParameter.getExtendsListTypes();
      for (PsiType boundType : boundTypes) {
        PsiType substitutedBoundType = boundType.accept(mySimpleSubstitutionVisitor);
        PsiWildcardType wildcardType = (PsiWildcardType) substituted;
        if (substitutedBoundType != null
            && !(substitutedBoundType instanceof PsiWildcardType)
            && !substitutedBoundType.equalsToText("java.lang.Object")) {
          if (originalBound == null
              || (!TypeConversionUtil.erasure(substitutedBoundType)
                      .isAssignableFrom(TypeConversionUtil.erasure(originalBound))
                  && !TypeConversionUtil.erasure(substitutedBoundType)
                      .isAssignableFrom(
                          originalBound))) { // erasure is essential to avoid infinite recursion
            if (wildcardType.isExtends()) {
              final PsiType glb =
                  GenericsUtil.getGreatestLowerBound(wildcardType.getBound(), substitutedBoundType);
              if (glb != null) {
                substituted = PsiWildcardType.createExtends(manager, glb);
              }
            } else {
              // unbounded
              substituted = PsiWildcardType.createExtends(manager, substitutedBoundType);
            }
          }
        }
      }
    }

    if (captureContext != null) {
      LOG.assertTrue(substituted instanceof PsiWildcardType);
      substituted = PsiCapturedWildcardType.create((PsiWildcardType) substituted, captureContext);
    }
    return substituted;
  }
    @Override
    public PsiType visitWildcardType(PsiWildcardType wildcardType) {
      final PsiType bound = wildcardType.getBound();
      if (bound == null) {
        return wildcardType;
      } else {
        final PsiType newBound = bound.accept(this);
        if (newBound == null) {
          return null;
        }
        if (newBound instanceof PsiWildcardType) {
          return handleBoundComposition(wildcardType, (PsiWildcardType) newBound);
        }
        if (newBound instanceof PsiCapturedWildcardType
            && wildcardType.isExtends()
                != ((PsiCapturedWildcardType) newBound).getWildcard().isExtends()) {
          return handleBoundComposition(
              wildcardType, ((PsiCapturedWildcardType) newBound).getWildcard());
        }

        return PsiWildcardType.changeBound(wildcardType, newBound);
      }
    }
  private static PsiType getLeastContainingTypeArgument(
      PsiType type1,
      PsiType type2,
      Set<Pair<PsiType, PsiType>> compared,
      PsiManager manager,
      PsiClass nestedLayer,
      PsiTypeParameter parameter) {
    Pair<PsiType, PsiType> types = new Pair<PsiType, PsiType>(type1, type2);
    if (compared.contains(types)) {
      if (nestedLayer != null) {
        PsiSubstitutor subst = PsiSubstitutor.EMPTY;
        for (PsiTypeParameter param : PsiUtil.typeParametersIterable(nestedLayer)) {
          subst = subst.put(param, PsiWildcardType.createUnbounded(manager));
        }
        subst =
            subst.put(
                parameter,
                getLeastContainingTypeArgument(type1, type2, compared, manager, null, null));

        final PsiClassType boundType =
            JavaPsiFacade.getInstance(manager.getProject())
                .getElementFactory()
                .createType(nestedLayer, subst);
        return PsiWildcardType.createExtends(manager, boundType);
      }
      return PsiWildcardType.createUnbounded(manager);
    }
    compared.add(types);

    try {
      if (type1 instanceof PsiWildcardType) {
        PsiWildcardType wild1 = (PsiWildcardType) type1;
        final PsiType bound1 = wild1.getBound();
        if (bound1 == null) return type1;
        if (type2 instanceof PsiWildcardType) {
          PsiWildcardType wild2 = (PsiWildcardType) type2;
          final PsiType bound2 = wild2.getBound();
          if (bound2 == null) return type2;
          if (wild1.isExtends() == wild2.isExtends()) {
            return wild1.isExtends()
                ? PsiWildcardType.createExtends(
                    manager, getLeastUpperBound(bound1, bound2, compared, manager))
                : PsiWildcardType.createSuper(manager, getGreatestLowerBound(bound1, bound2));
          } else {
            return bound1.equals(bound2) ? bound1 : PsiWildcardType.createUnbounded(manager);
          }
        } else {
          return wild1.isExtends()
              ? PsiWildcardType.createExtends(
                  manager, getLeastUpperBound(bound1, type2, compared, manager))
              : wild1.isSuper()
                  ? PsiWildcardType.createSuper(manager, getGreatestLowerBound(bound1, type2))
                  : wild1;
        }
      } else if (type2 instanceof PsiWildcardType) {
        return getLeastContainingTypeArgument(type2, type1, compared, manager, null, null);
      }
      // Done with wildcards

      if (type1.equals(type2)) return type1;
      return PsiWildcardType.createExtends(
          manager, getLeastUpperBound(type1, type2, compared, manager));
    } finally {
      compared.remove(types);
    }
  }