class CoercedExpression {

    public CoercedExpressionResult coerce() {
        final TypedExpression coercedRight;

        final Class<?> leftClass = left.getRawClass();
        final Class<?> rightClass = right.getRawClass();

        boolean sameClass = leftClass == rightClass;
        boolean isUnificationExpression = left instanceof UnificationTypedExpression || right instanceof UnificationTypedExpression;

        if (sameClass || isUnificationExpression) {
            return new CoercedExpressionResult(left, right);
        }

        if (!canCoerce()) {
            throw new CoercedExpressionException(new InvalidExpressionErrorResult("Comparison operation requires compatible types. Found " + leftClass + " and " + rightClass));
        }

        final Expression rightExpression = right.getExpression();

        final boolean leftIsPrimitive = leftClass.isPrimitive() || Number.class.isAssignableFrom( leftClass );
        final boolean canCoerceLiteralNumberExpr = canCoerceLiteralNumberExpr(leftClass);

        if (leftIsPrimitive && canCoerceLiteralNumberExpr && rightExpression instanceof LiteralStringValueExpr) {
            final Expression coercedLiteralNumberExprToType = coerceLiteralNumberExprToType((LiteralStringValueExpr) right.getExpression(), leftClass);
            coercedRight = right.cloneWithNewExpression(coercedLiteralNumberExprToType);
        } else if (shouldCoerceBToString(left, right)) {
            coercedRight = coerceToString(right);
        } else if (isNotBinaryExpression(right) && canBeNarrowed(leftClass, rightClass) && right.isNumberLiteral()) {
            coercedRight = castToClass(leftClass);
        } else if (leftClass == long.class && rightClass == int.class) {
            coercedRight = right.cloneWithNewExpression(new CastExpr(PrimitiveType.longType(), right.getExpression()));
        } else if (leftClass == Date.class && rightClass == String.class) {
            coercedRight = coerceToDate(right);
        } else if (shouldCoerceBToMap()) {
            coercedRight = castToClass(toNonPrimitiveType(leftClass));
        } else if (isBoolean(leftClass) && !isBoolean(rightClass)) {
            coercedRight = coerceBoolean(right);
        } else {
            coercedRight = right;
        }

        final TypedExpression coercedLeft;
        if (toNonPrimitiveType(leftClass) == Character.class && shouldCoerceBToString(right, left)) {
            coercedLeft = coerceToString(left);
        } else {
            coercedLeft = left;
        }

        return new CoercedExpressionResult(coercedLeft, coercedRight);
    }

    public  CoercedExpression(TypedExpression left, TypedExpression right, boolean equalityExpr);

}