private static boolean isNarrowingReferenceConversionAllowed(@NotNull PsiType fromType, @NotNull PsiType toType) {
    toType = uncapture(toType);
    fromType = uncapture(fromType);

    if (toType instanceof PsiPrimitiveType || fromType instanceof PsiPrimitiveType) return fromType.equals(toType);
    //Done with primitives
    if (toType instanceof PsiDiamondType || fromType instanceof PsiDiamondType) return false;
    if (toType instanceof PsiArrayType && !(fromType instanceof PsiArrayType)) {
      if (fromType instanceof PsiClassType) {
        final PsiClass resolved = ((PsiClassType)fromType).resolve();
        if (resolved instanceof PsiTypeParameter) {
          for (final PsiClassType boundType : resolved.getExtendsListTypes()) {
            if (!isNarrowingReferenceConversionAllowed(boundType, toType)) return false;
          }
          return true;
        }
      }
      return isAssignable(fromType, toType);
    }
    if (fromType instanceof PsiArrayType) {
      if (toType instanceof PsiClassType) {
        final PsiClass resolved = ((PsiClassType)toType).resolve();
        if (resolved instanceof PsiTypeParameter) {
          for (final PsiClassType boundType : resolved.getExtendsListTypes()) {
            if (!areTypesConvertible(fromType, boundType)) return false;
          }
          return true;
        }
      }
      return toType instanceof PsiArrayType
             && isNarrowingReferenceConversionAllowed(((PsiArrayType)fromType).getComponentType(),
                                                      ((PsiArrayType)toType).getComponentType());
    }
    //Done with array types

    if (fromType instanceof PsiIntersectionType) {
      final PsiType[] conjuncts = ((PsiIntersectionType)fromType).getConjuncts();
      for (PsiType conjunct : conjuncts) {
        if (isNarrowingReferenceConversionAllowed(conjunct, toType)) return true;
      }
      return false;
    }
    if (toType instanceof PsiIntersectionType) {
      if (fromType instanceof PsiClassType && ((PsiClassType)fromType).getLanguageLevel().isAtLeast(LanguageLevel.JDK_1_8)) {
        for (PsiType conjunct : ((PsiIntersectionType)toType).getConjuncts()) {
          if (!isNarrowingReferenceConversionAllowed(fromType, conjunct)) return false;
        }
        return true;
      }
      return false;
    }

    if (fromType instanceof PsiDisjunctionType) {
      return isNarrowingReferenceConversionAllowed(((PsiDisjunctionType)fromType).getLeastUpperBound(), toType);
    }
    if (toType instanceof PsiDisjunctionType) {
      return false;
    }

    if (fromType instanceof PsiWildcardType) {
      final PsiWildcardType fromWildcard = (PsiWildcardType)fromType;
      final PsiType bound = fromWildcard.getBound();
      if (bound == null) return true;
      if (fromWildcard.isSuper()) {
        return isAssignable(toType, bound);
      }
      return isNarrowingReferenceConversionAllowed(bound, toType);
    }
    if (toType instanceof PsiWildcardType) {
      final PsiWildcardType toWildcard = (PsiWildcardType)toType;
      if (toWildcard.isSuper()) return false;
      final PsiType bound = toWildcard.getBound();
      return bound == null || isNarrowingReferenceConversionAllowed(fromType, bound);
    }

    if (isAssignable(fromType, toType)) return true;

    if (!(fromType instanceof PsiClassType) || !(toType instanceof PsiClassType)) return false;
    PsiClassType fromClassType = (PsiClassType)fromType;
    PsiClassType toClassType = (PsiClassType)toType;

    PsiClassType.ClassResolveResult fromResult = fromClassType.resolveGenerics();
    final PsiClass fromClass = fromResult.getElement();
    if (fromClass == null) return false;
    if (fromClass instanceof PsiTypeParameter) {
      return isNarrowingReferenceConversionAllowed(obtainSafeSuperType((PsiTypeParameter)fromClass), toType);
    }

    PsiClassType.ClassResolveResult toResult = toClassType.resolveGenerics();
    final PsiClass toClass = toResult.getElement();
    if (toClass == null) return false;
    if (toClass instanceof PsiTypeParameter) {
      return isNarrowingReferenceConversionAllowed(fromType, obtainSafeSuperType((PsiTypeParameter)toClass));
    }
    //Done with type parameters

    PsiManager manager = fromClass.getManager();
    final LanguageLevel languageLevel = toClassType.getLanguageLevel();
    //  jep-397
    if (JavaFeature.SEALED_CLASSES.isSufficient(languageLevel)) {
      if (fromClass.isInterface() || toClass.isInterface()) {
        if (fromClass.hasModifierProperty(PsiModifier.SEALED)) {
          if (!canConvertSealedTo(fromClass, toClass)) return false;
        }
        else if (toClass.hasModifierProperty(PsiModifier.SEALED)) {
          if (!canConvertSealedTo(toClass, fromClass)) return false;
        }
      }
    }
    if (!fromClass.isInterface()) {
      if (toClass.isInterface()) {
        return (!fromClass.hasModifierProperty(PsiModifier.FINAL) || fromClass.isInheritor(toClass, true)) &&
               checkSuperTypesWithDifferentTypeArguments(toResult, fromClass, manager, fromResult.getSubstitutor(), null, languageLevel);
      }
      else {
        if (manager.areElementsEquivalent(fromClass, toClass)) {
          return areSameParameterTypes(fromClassType, toClassType);
        }

        if (toClass.isInheritor(fromClass, true)) {
          return checkSuperTypesWithDifferentTypeArguments(fromResult, toClass, manager, toResult.getSubstitutor(), null, languageLevel);
        }
        if (fromClass.isInheritor(toClass, true)) {
          return checkSuperTypesWithDifferentTypeArguments(toResult, fromClass, manager, fromResult.getSubstitutor(), null, languageLevel);
        }

        return false;
      }
    }
    else if (!toClass.isInterface()) {
      if (!toClass.hasModifierProperty(PsiModifier.FINAL)) {
        return checkSuperTypesWithDifferentTypeArguments(fromResult, toClass, manager, toResult.getSubstitutor(), null, languageLevel);
      }
      else {
        PsiSubstitutor toSubstitutor = getMaybeSuperClassSubstitutor(fromClass, toClass, toResult.getSubstitutor());
        return toSubstitutor != null && areSameArgumentTypes(fromClass, fromResult.getSubstitutor(), toSubstitutor);
      }
    }
    else if (languageLevel.compareTo(LanguageLevel.JDK_1_5) < 0) {
      //In jls2 check for method in both interfaces with the same signature but different return types.
      Collection<HierarchicalMethodSignature> fromClassMethodSignatures = fromClass.getVisibleSignatures();
      Collection<HierarchicalMethodSignature> toClassMethodSignatures = toClass.getVisibleSignatures();

      for (HierarchicalMethodSignature fromMethodSignature : fromClassMethodSignatures) {
        for (HierarchicalMethodSignature toMethodSignature : toClassMethodSignatures) {
          if (fromMethodSignature.equals(toMethodSignature)) {
            final PsiType fromClassReturnType = fromMethodSignature.getMethod().getReturnType();
            final PsiType toClassReturnType = toMethodSignature.getMethod().getReturnType();
            if (fromClassReturnType != null
                && toClassReturnType != null
                && !fromClassReturnType.equals(toClassReturnType)) {
              return false;
            }
          }
        }
      }
      return true;
    }
    else {
      //In jls3 check for super interface with distinct type arguments
      PsiClassType.ClassResolveResult baseResult;
      PsiClass derived;
      PsiSubstitutor derivedSubstitutor;
      if (toClass.isInheritor(fromClass, true)) {
        baseResult = fromResult;
        derived = toClass;
        derivedSubstitutor = toResult.getSubstitutor();
      }
      else {
        baseResult = toResult;
        derived = fromClass;
        derivedSubstitutor = fromResult.getSubstitutor();
      }
      return checkSuperTypesWithDifferentTypeArguments(baseResult, derived, manager, derivedSubstitutor, null, languageLevel);
    }
  }
private static boolean isNarrowingReferenceConversionAllowed(@NotNull PsiType fromType, @NotNull PsiType toType)