@Override
  public boolean reduce(InferenceSession session, List<? super ConstraintFormula> constraints) {
    if (!LambdaUtil.isFunctionalType(myT)) {
      session.registerIncompatibleErrorMessage(
        JavaPsiBundle.message("error.incompatible.type.not.a.functional.interface", session.getPresentableText(myT)));
      return false;
    }

    final PsiType groundTargetType = myExpression.getGroundTargetType(myT);
    final PsiClassType.ClassResolveResult classResolveResult = PsiUtil.resolveGenericsClassInType(groundTargetType);
    final PsiMethod interfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(classResolveResult);
    if (interfaceMethod == null) {
      session.registerIncompatibleErrorMessage(
        JavaPsiBundle.message("error.incompatible.type.no.valid.function.type.found", session.getPresentableText(myT)));
      return false;
    }

    final PsiSubstitutor substitutor = LambdaUtil.getSubstitutor(interfaceMethod, classResolveResult);
    final MethodSignature signature = interfaceMethod.getSignature(substitutor);
    final PsiParameter[] targetParameters = interfaceMethod.getParameterList().getParameters();
    final PsiType interfaceMethodReturnType = interfaceMethod.getReturnType();
    final PsiType returnType = substitutor.substitute(interfaceMethodReturnType);
    final PsiType[] typeParameters = myExpression.getTypeParameters();

    final PsiMethodReferenceUtil.QualifierResolveResult qualifierResolveResult = PsiMethodReferenceUtil.getQualifierResolveResult(myExpression);

    if (myExpression.isExact()) {
      final PsiMember applicableMember = myExpression.getPotentiallyApplicableMember();
      LOG.assertTrue(applicableMember != null);

      final PsiClass applicableMemberContainingClass = applicableMember.getContainingClass();
      final PsiClass containingClass = qualifierResolveResult.getContainingClass();

      PsiSubstitutor psiSubstitutor = getSubstitutor(signature, qualifierResolveResult, applicableMember, applicableMemberContainingClass, myExpression);

      int idx = 0;
      for (PsiTypeParameter param : ((PsiTypeParameterListOwner)applicableMember).getTypeParameters()) {
        if (idx < typeParameters.length) {
          psiSubstitutor = psiSubstitutor.put(param, typeParameters[idx++]);
        }
      }
      final PsiParameter[] parameters = applicableMember instanceof PsiMethod ? ((PsiMethod)applicableMember).getParameterList().getParameters() : PsiParameter.EMPTY_ARRAY;
      if (targetParameters.length == parameters.length + 1) {
        final PsiType qualifierType = PsiMethodReferenceUtil.getQualifierType(myExpression);
        final PsiClass qualifierClass = PsiUtil.resolveClassInType(qualifierType);
        if (qualifierClass != null) {
          final PsiType pType = signature.getParameterTypes()[0];
          constraints.add(new StrictSubtypingConstraint(session.substituteWithInferenceVariables(qualifierType), pType));
        }
        for (int i = 1; i < targetParameters.length; i++) {
          constraints.add(new TypeCompatibilityConstraint(session.substituteWithInferenceVariables(psiSubstitutor.substitute(parameters[i - 1].getType())),
                                                          PsiUtil.captureToplevelWildcards(signature.getParameterTypes()[i], myExpression)));
        }
      }
      else if (targetParameters.length == parameters.length) {
        for (int i = 0; i < targetParameters.length; i++) {
          constraints.add(new TypeCompatibilityConstraint(session.substituteWithInferenceVariables(psiSubstitutor.substitute(parameters[i].getType())),
                                                          PsiUtil.captureToplevelWildcards(signature.getParameterTypes()[i], myExpression)));
        }
      }
      else {
        session.registerIncompatibleErrorMessage(
          JavaPsiBundle.message("error.incompatible.type.incompatible.parameter.types.in.method.reference"));
        return false;
      }
      if (!PsiTypes.voidType().equals(returnType) && returnType != null) {
        PsiType applicableMethodReturnType = null;
        if (applicableMember instanceof PsiMethod) {
          final PsiType getClassReturnType = PsiTypesUtil.patchMethodGetClassReturnType(myExpression, (PsiMethod)applicableMember);
          applicableMethodReturnType = getClassReturnType != null ? getClassReturnType : ((PsiMethod)applicableMember).getReturnType();
        }

        if (PsiTypes.voidType().equals(applicableMethodReturnType)) {
          session.registerIncompatibleErrorMessage(
            JavaPsiBundle.message("error.incompatible.type.incompatible.types.expected.not.void.got.void.method.reference"));
          return false;
        }

        if (applicableMethodReturnType == null && 
            (applicableMember instanceof PsiClass || applicableMember instanceof PsiMethod && ((PsiMethod)applicableMember).isConstructor())) {
          if (containingClass != null) {
            applicableMethodReturnType = JavaPsiFacade.getElementFactory(applicableMember.getProject()).createType(containingClass, PsiSubstitutor.EMPTY);
          } 
        }

        if (applicableMethodReturnType != null) {
          applicableMethodReturnType = psiSubstitutor.substitute(applicableMethodReturnType);
          applicableMethodReturnType = Registry.is("unsound.capture.conversion.java.spec.change") ? applicableMethodReturnType
                                                                                                  : PsiUtil.captureToplevelWildcards(applicableMethodReturnType, myExpression);
          constraints.add(new TypeCompatibilityConstraint(returnType, session.substituteWithInferenceVariables(applicableMethodReturnType)));
        }
      }
      return true;
    }

    //------   non exact method references   --------------------

    for (PsiType paramType : signature.getParameterTypes()) {
      if (!session.isProperType(paramType)) {
        //session.registerIncompatibleErrorMessage("Parameter type in not yet inferred: " + session.getPresentableText(paramType));
        return false;
      }
    }

    JavaResolveResult resolve = myExpression.advancedResolve(false);
    final PsiElement element = resolve.getElement();
    if (element == null || resolve instanceof MethodCandidateInfo && !((MethodCandidateInfo)resolve).isApplicable()) {
      session.registerIncompatibleErrorMessage(
        JavaPsiBundle.message("error.incompatible.type.declaration.for.the.method.reference.not.found"));
      return false;
    }

    if (PsiTypes.voidType().equals(returnType) || returnType == null) {
      return true;
    }

    if (element instanceof PsiMethod) {
      final PsiMethod method = (PsiMethod)element;
      PsiType referencedMethodReturnType;
      final PsiClass containingClass = method.getContainingClass();
      LOG.assertTrue(containingClass != null, method);
      PsiSubstitutor psiSubstitutor = getSubstitutor(signature, qualifierResolveResult, method, containingClass, myExpression);

      if (method.isConstructor()) {
        referencedMethodReturnType = JavaPsiFacade.getElementFactory(method.getProject()).createType(containingClass, PsiSubstitutor.EMPTY);
      }
      else {
        final PsiType getClassReturnType = PsiTypesUtil.patchMethodGetClassReturnType(myExpression, method);
        referencedMethodReturnType = getClassReturnType != null ? getClassReturnType : method.getReturnType();
      }
      LOG.assertTrue(referencedMethodReturnType != null, method);

      //if i) the method reference elides NonWildTypeArguments, 
      //  ii) the compile-time declaration is a generic method, and 
      // iii) the return type of the compile-time declaration mentions at least one of the method's type parameters;
      if (typeParameters.length == 0) {
        PsiTypeParameter[] methodTypeParameters = method.isConstructor() ? containingClass.getTypeParameters() : method.getTypeParameters();
        if (methodTypeParameters.length > 0) {
          final PsiClass interfaceClass = classResolveResult.getElement();
          LOG.assertTrue(interfaceClass != null);
          if (PsiTypesUtil.mentionsTypeParameters(referencedMethodReturnType,
                                                  ContainerUtil.newHashSet(methodTypeParameters))) {
            session.initBounds(myExpression, psiSubstitutor, methodTypeParameters);
            //the constraint reduces to the bound set B3 which would be used to determine the method reference's invocation type 
            //when targeting the return type of the function type, as defined in 18.5.2.
            session.collectApplicabilityConstraints(myExpression, ((MethodCandidateInfo)resolve), groundTargetType);
            session.registerReturnTypeConstraints(psiSubstitutor.substitute(referencedMethodReturnType), returnType, myExpression);
            return true;
          }
        }
      }

      if (PsiTypes.voidType().equals(referencedMethodReturnType)) {
        session.registerIncompatibleErrorMessage(
          JavaPsiBundle.message("error.incompatible.type.expected.non.void.got.void.method.reference"));
        return false;
      }
 
      int idx = 0;
      for (PsiTypeParameter param : method.getTypeParameters()) {
        if (idx < typeParameters.length) {
          psiSubstitutor = psiSubstitutor.put(param, typeParameters[idx++]);
        }
      }

      PsiClass qContainingClass = qualifierResolveResult.getContainingClass();
      if (qContainingClass != null && PsiUtil.isRawSubstitutor(qContainingClass, qualifierResolveResult.getSubstitutor())) {
        //15.13.1 If there exist a parameterization, then it would be used to search, the *raw type* would be used otherwise
        if (getParameterization(signature, qualifierResolveResult, method, myExpression, qContainingClass) == null) {
          if (!PsiMethodReferenceUtil.isSecondSearchPossible(signature.getParameterTypes(), qualifierResolveResult, myExpression)) {
            session.initBounds(myExpression, qContainingClass.getTypeParameters());
          }
          else {
            referencedMethodReturnType = TypeConversionUtil.erasure(referencedMethodReturnType);
          }
        }
      }

      referencedMethodReturnType = psiSubstitutor.substitute(referencedMethodReturnType);
      referencedMethodReturnType = Registry.is("unsound.capture.conversion.java.spec.change") ? referencedMethodReturnType
                                                                                              : PsiUtil.captureToplevelWildcards(referencedMethodReturnType, myExpression);
      constraints.add(new TypeCompatibilityConstraint(returnType, session.substituteWithInferenceVariables(referencedMethodReturnType)));
    }
    
    return true;
  }
@Override
  public boolean reduce(InferenceSession session, List<? super ConstraintFormula> constraints)