@Override
  public boolean reduce(InferenceSession session, List<? super ConstraintFormula> constraints) {
    if (myT instanceof PsiWildcardType && myS instanceof PsiWildcardType) {
      final PsiType tBound = ((PsiWildcardType)myT).getBound();
      final PsiType sBound = ((PsiWildcardType)myS).getBound();

      if (tBound == null && sBound == null) return true;

      if (sBound == null && ((PsiWildcardType)myT).isExtends()) {
        //extends bound of "?" (Object)
        constraints.add(new TypeEqualityConstraint(((PsiWildcardType)myS).getExtendsBound(), tBound));
        return true;
      }

      if (tBound == null && ((PsiWildcardType)myS).isExtends()) {
        //extends bound of "?" (Object)
        constraints.add(new TypeEqualityConstraint(((PsiWildcardType)myT).getExtendsBound(), sBound));
        return true;
      }

      if (((PsiWildcardType)myT).isExtends() && ((PsiWildcardType)myS).isExtends() ||
          ((PsiWildcardType)myT).isSuper() && ((PsiWildcardType)myS).isSuper()) {

        LOG.assertTrue(tBound != null);
        LOG.assertTrue(sBound != null);
        constraints.add(new TypeEqualityConstraint(tBound, sBound));
        return true;
      }
    }

    if (myT instanceof PsiWildcardType || myS instanceof PsiWildcardType) {
      session.registerIncompatibleErrorMessage(
        JavaPsiBundle.message("error.incompatible.type.incompatible.equality.constraint", session.getPresentableText(myT), session.getPresentableText(myS)));
      return false;
    }

    if (session.isProperType(myT) && session.isProperType(myS)) {
      final boolean equal = Comparing.equal(myT, myS);
      if (!equal) {
        session.registerIncompatibleErrorMessage(
          JavaPsiBundle.message("error.incompatible.type.incompatible.equality.constraint", session.getPresentableText(myT), session.getPresentableText(myS)));
      }
      return equal;
    }

    if (myT == null || myT == PsiTypes.nullType()) return false;
    if (myS == null || myS == PsiTypes.nullType()) return false;

    InferenceVariable inferenceVariable = session.getInferenceVariable(myS);
    if (inferenceVariable != null && !(myT instanceof PsiPrimitiveType)) {
      InferenceVariable.addBound(myS, myT, InferenceBound.EQ, session);
      return true;
    }
    inferenceVariable = session.getInferenceVariable(myT);
    if (inferenceVariable != null && !(myS instanceof PsiPrimitiveType)) {
      InferenceVariable.addBound(myT, myS, InferenceBound.EQ, session);
      return true;
    }
    if (myT instanceof PsiClassType && myS instanceof PsiClassType) {
      final PsiClassType.ClassResolveResult tResult = ((PsiClassType)myT).resolveGenerics();
      final PsiClassType.ClassResolveResult sResult = ((PsiClassType)myS).resolveGenerics();
      final PsiClass tClass = tResult.getElement();
      //equal erasure
      if (tClass != null && tClass.getManager().areElementsEquivalent(tClass, sResult.getElement())) {
        final PsiSubstitutor tSubstitutor = tResult.getSubstitutor();
        final PsiSubstitutor sSubstitutor = sResult.getSubstitutor();
        for (PsiTypeParameter typeParameter : PsiUtil.typeParametersIterable(tClass)) {
          final PsiType tSubstituted = tSubstitutor.substitute(typeParameter);
          final PsiType sSubstituted = sSubstitutor.substitute(typeParameter);
          if (tSubstituted != null && sSubstituted != null) {
            constraints.add(new TypeEqualityConstraint(tSubstituted, sSubstituted));
          }
          if (tSubstituted == null ^ sSubstituted == null) {
            session.registerIncompatibleErrorMessage(
              JavaPsiBundle.message("error.incompatible.type.incompatible.equality.constraint", session.getPresentableText(myT), session.getPresentableText(myS)));
            return false;
          }
        }
        return true;
      }
    }
    if (myT instanceof PsiArrayType && myS instanceof PsiArrayType) {
      constraints.add(new TypeEqualityConstraint(((PsiArrayType)myT).getComponentType(), ((PsiArrayType)myS).getComponentType()));
      return true;
    }

    session.registerIncompatibleErrorMessage(session.getInferenceVariables(),
                                             JavaPsiBundle.message("type.conforms.to.constraint", session.getPresentableText(myS), session.getPresentableText(myT)));
    return false;
  }
@Override
  public boolean reduce(InferenceSession session, List<? super ConstraintFormula> constraints)