@Nullable
  public static PsiType getLeastUpperBound(@NotNull PsiType type1, @NotNull PsiType type2, @NotNull PsiManager manager) {
    if (type1 == PsiTypes.nullType()) return type2;
    if (type2 == PsiTypes.nullType()) return type1;
    {
      PsiType numericLUB = getNumericLUB(type1, type2);
      if (numericLUB != null) return numericLUB;
    }
    if (type1 instanceof GrTupleType tuple1 && type2 instanceof GrTupleType tuple2) {
      List<PsiType> components1 = tuple1.getComponentTypes();
      List<PsiType> components2 = tuple2.getComponentTypes();

      if (components1.isEmpty()) return genNewListBy(type2, manager);
      if (components2.isEmpty()) return genNewListBy(type1, manager);

      PsiType[] components3 = PsiType.createArray(Math.min(components1.size(), components2.size()));
      for (int i = 0; i < components3.length; i++) {
        PsiType c1 = components1.get(i);
        PsiType c2 = components2.get(i);
        if (c1 == null || c2 == null) {
          components3[i] = null;
        }
        else {
          components3[i] = getLeastUpperBound(c1, c2, manager);
        }
      }
      return new GrImmediateTupleType(Arrays.asList(components3), JavaPsiFacade.getInstance(manager.getProject()),
                                      tuple1.getResolveScope().intersectWith(tuple2.getResolveScope()));
    }
    else if (checkEmptyListAndList(type1, type2)) {
      return genNewListBy(type2, manager);
    }
    else if (checkEmptyListAndList(type2, type1)) {
      return genNewListBy(type1, manager);
    }
    else if (type1 instanceof GrMapType && type2 instanceof GrMapType) {
      return GrMapType.merge(((GrMapType)type1), ((GrMapType)type2));
    }
    else if (checkEmptyMapAndMap(type1, type2)) {
      return genNewMapBy(type2, manager);
    }
    else if (checkEmptyMapAndMap(type2, type1)) {
      return genNewMapBy(type1, manager);
    }
    else if (type1 instanceof GrClosureType clType1 && type2 instanceof GrClosureType clType2) {
      List<GrSignature> signatures1 = clType1.getSignatures();
      List<GrSignature> signatures2 = clType2.getSignatures();

      if (signatures1.size() == 1 && signatures2.size() == 1) {
        final GrSignature signature1 = signatures1.get(0);
        final GrSignature signature2 = signatures2.get(0);
        if (signature1.getParameterCount() == signature2.getParameterCount()) {
          final GrSignature signature = GrImmediateClosureSignatureImpl.getLeastUpperBound(signature1, signature2, manager);
          if (signature != null) {
            GlobalSearchScope scope = clType1.getResolveScope().intersectWith(clType2.getResolveScope());
            final LanguageLevel languageLevel = ComparatorUtil.max(clType1.getLanguageLevel(), clType2.getLanguageLevel());
            return GrClosureType.create(Collections.singletonList(signature), scope, JavaPsiFacade.getInstance(manager.getProject()),
                                        languageLevel, true);
          }
        }
      }
    }
    else if (GroovyCommonClassNames.GROOVY_LANG_GSTRING.equals(getQualifiedName(type1)) &&
             CommonClassNames.JAVA_LANG_STRING.equals(getQualifiedName(type2))) {
      return type2;
    }
    else if (GroovyCommonClassNames.GROOVY_LANG_GSTRING.equals(getQualifiedName(type2)) &&
             CommonClassNames.JAVA_LANG_STRING.equals(getQualifiedName(type1))) {
      return type1;
    }
    return GenericsUtil.getLeastUpperBound(type1, type2, manager);
  }
@Nullable
  public static PsiType getLeastUpperBound(@NotNull PsiType type1, @NotNull PsiType type2, @NotNull PsiManager manager)