public static boolean areTypesConvertible(@NotNull PsiType fromType, @NotNull PsiType toType, @Nullable LanguageLevel languageLevel) {
    if (fromType == toType) return true;
    final boolean fromIsPrimitive = isPrimitiveAndNotNull(fromType);
    final boolean toIsPrimitive = isPrimitiveAndNotNull(toType);
    if (fromIsPrimitive || toIsPrimitive) {
      if (isVoidType(fromType) || isVoidType(toType)) return false;
      final int fromTypeRank = getTypeRank(fromType);
      final int toTypeRank = getTypeRank(toType);
      if (!toIsPrimitive) {
        if (fromTypeRank == toTypeRank) return true;
        if (toType instanceof PsiIntersectionType) {
          for (PsiType type : ((PsiIntersectionType)toType).getConjuncts()) {
            if (!areTypesConvertible(fromType, type)) return false;
          }
          return true;
        }
        // JLS 5.5: A value of a primitive type can be cast to a reference type by boxing conversion(see 5.1.7)
        if (!(toType instanceof PsiClassType)) return false;
        PsiClass toClass = ((PsiClassType)toType).resolve();
        if (toClass == null || toClass instanceof PsiTypeParameter) return false;
        PsiClassType boxedType = ((PsiPrimitiveType)fromType).getBoxedType(toClass.getManager(), toType.getResolveScope());
        return boxedType != null && areTypesConvertible(boxedType, toType);
      }
      if (!fromIsPrimitive) {
        // 5.5. Casting Contexts
        if ((fromTypeRank == SHORT_RANK || fromTypeRank == BYTE_RANK) && toTypeRank == CHAR_RANK) return false;

        fromType = uncapture(fromType); //starting from javac 9+
        if (fromType instanceof PsiClassType) {
          if (languageLevel == null) {
            languageLevel = ((PsiClassType)fromType).getLanguageLevel();
          }

          if (languageLevel.isAtLeast(LanguageLevel.JDK_1_7)) {
            final PsiClassType classType = (PsiClassType)fromType;
            final PsiClass psiClass = classType.resolve();
            if (psiClass == null) return false;
            final PsiClassType boxedType = ((PsiPrimitiveType)toType).getBoxedType(psiClass.getManager(), psiClass.getResolveScope());
            if (boxedType != null && isNarrowingReferenceConversionAllowed(fromType, boxedType)) {
              return true;
            }
          }
          //unboxing and widening with type parameters
          if (fromTypeRank == UNKNOWN_RANK &&
              isUnboxable((PsiPrimitiveType)toType, (PsiClassType)fromType, new HashSet<>())) {
            return true;
          }
        }
        return fromTypeRank == toTypeRank ||
               fromTypeRank < toTypeRank && toTypeRank <= MAX_NUMERIC_RANK;
      }
      return fromTypeRank == toTypeRank ||
             fromTypeRank <= MAX_NUMERIC_RANK && toTypeRank <= MAX_NUMERIC_RANK;
    }

    //type can be casted via widening reference conversion
    if (isAssignable(toType, fromType)) return true;

    if (isNullType(fromType) || isNullType(toType)) return true;

    // or narrowing reference conversion
    return isNarrowingReferenceConversionAllowed(fromType, toType);
  }
public static boolean areTypesConvertible(@NotNull PsiType fromType, @NotNull PsiType toType, @Nullable LanguageLevel languageLevel)