private static @Nullable TypeMismatch deepCheck(@NotNull PsiType leftType,
                                                  @NotNull PsiType rightType,
                                                  @NotNull Set<Couple<PsiType>> checked,
                                                  @NotNull LookForMutualSubclass lookForMutualSubclass) {
    if (leftType instanceof PsiCapturedWildcardType) {
      leftType = ((PsiCapturedWildcardType)leftType).getUpperBound();
    }
    if (rightType instanceof PsiCapturedWildcardType) {
      rightType = ((PsiCapturedWildcardType)rightType).getUpperBound();
    }
    if (!checked.add(Couple.of(leftType, rightType))) {
      return null;
    }
    if (leftType.isAssignableFrom(rightType) || rightType.isAssignableFrom(leftType)) return null;
    PsiClass leftClass = PsiUtil.resolveClassInClassTypeOnly(leftType);
    PsiClass rightClass = PsiUtil.resolveClassInClassTypeOnly(rightType);
    if (leftClass == null || rightClass == null) return null;
    if (!rightClass.isInterface()) {
      PsiClass tmp = leftClass;
      leftClass = rightClass;
      rightClass = tmp;
    }
    if (leftClass == rightClass || TypeUtils.mayBeEqualByContract(leftType, rightType)) {
      // check type parameters
      if (leftType instanceof PsiClassType && rightType instanceof PsiClassType) {
        final PsiType[] leftParameters = ((PsiClassType)leftType).getParameters();
        final PsiType[] rightParameters = ((PsiClassType)rightType).getParameters();
        if (leftParameters.length == rightParameters.length) {
          for (int i = 0, length = leftParameters.length; i < length; i++) {
            final PsiType leftParameter = leftParameters[i];
            final PsiType rightParameter = rightParameters[i];
            if (!TypeUtils.areConvertible(leftParameter, rightParameter) &&
                !TypeUtils.mayBeEqualByContract(leftParameter, rightParameter)) {
              return new TypeMismatch(leftType, rightType, Convertible.NOT_CONVERTIBLE);
            }
            TypeMismatch mismatch = deepCheck(leftParameter, rightParameter, checked, lookForMutualSubclass);
            if (mismatch != null) {
              return mismatch;
            }
          }
        }
      }
    }
    else if (TypeUtils.cannotBeEqualByContract(leftType, rightType)) {
      return new TypeMismatch(leftType, rightType, Convertible.NOT_CONVERTIBLE);
    }
    else if (lookForMutualSubclass != LookForMutualSubclass.NEVER) {
      ThreeState mutualSubclass =
        InheritanceUtil.existsMutualSubclass(leftClass, rightClass, lookForMutualSubclass == LookForMutualSubclass.IF_CHEAP);
      if (mutualSubclass != ThreeState.YES) {
        Convertible convertible = mutualSubclass == ThreeState.NO
                                  ? Convertible.CONVERTIBLE_BUT_NO_MUTUAL_SUBCLASS
                                  : Convertible.CONVERTIBLE_MUTUAL_SUBCLASS_UNKNOWN;
        return new TypeMismatch(leftType, rightType, convertible);
      }
    }
    return null;
  }
private static @Nullable TypeMismatch deepCheck(@NotNull PsiType leftType,
                                                  @NotNull PsiType rightType,
                                                  @NotNull Set<Couple<PsiType>> checked,
                                                  @NotNull LookForMutualSubclass lookForMutualSubclass)