private BoundExpression TrivialLiftedComparisonOperatorOptimizations(
            CSharpSyntaxNode syntax,
            BinaryOperatorKind kind,
            BoundExpression left,
            BoundExpression right,
            MethodSymbol method)
        {
            Debug.Assert(left != null);
            Debug.Assert(right != null);

            // Optimization #1: if both sides are null then the result 
            // is either true (for equality) or false (for everything else.)

            bool leftAlwaysNull = NullableNeverHasValue(left);
            bool rightAlwaysNull = NullableNeverHasValue(right);

            TypeSymbol boolType = _compilation.GetSpecialType(SpecialType.System_Boolean);

            if (leftAlwaysNull && rightAlwaysNull)
            {
                return MakeLiteral(syntax, ConstantValue.Create(kind.Operator() == BinaryOperatorKind.Equal), boolType);
            }

            // Optimization #2: If both sides are non-null then we can again eliminate the lifting entirely.

            BoundExpression leftNonNull = NullableAlwaysHasValue(left);
            BoundExpression rightNonNull = NullableAlwaysHasValue(right);

            if (leftNonNull != null && rightNonNull != null)
            {
                return MakeBinaryOperator(
                    syntax: syntax,
                    operatorKind: kind.Unlifted(),
                    loweredLeft: leftNonNull,
                    loweredRight: rightNonNull,
                    type: boolType,
                    method: method);
            }

            // Optimization #3: If one side is null and the other is definitely not, then we generate the side effects
            // of the non-null side and result in true (for not-equals) or false (for everything else.)

            BinaryOperatorKind operatorKind = kind.Operator();

            if (leftAlwaysNull && rightNonNull != null || rightAlwaysNull && leftNonNull != null)
            {
                BoundExpression result = MakeLiteral(syntax, ConstantValue.Create(operatorKind == BinaryOperatorKind.NotEqual), boolType);

                BoundExpression nonNull = leftAlwaysNull ? rightNonNull : leftNonNull;

                if (ReadIsSideeffecting(nonNull))
                {
                    result = new BoundSequence(
                                    syntax: syntax,
                                    locals: ImmutableArray<LocalSymbol>.Empty,
                                    sideEffects: ImmutableArray.Create<BoundExpression>(nonNull),
                                    value: result,
                                    type: boolType);
                }

                return result;
            }

            // Optimization #4: If one side is null and the other is unknown, then we have three cases:
            // #4a: If we have x == null then that becomes !x.HasValue.
            // #4b: If we have x != null then that becomes x.HasValue.
            // #4c: If we have x OP null then that becomes side effects of x, result in false.

            if (leftAlwaysNull || rightAlwaysNull)
            {
                BoundExpression maybeNull = leftAlwaysNull ? right : left;

                if (operatorKind == BinaryOperatorKind.Equal || operatorKind == BinaryOperatorKind.NotEqual)
                {
                    BoundExpression callHasValue = MakeNullableHasValue(syntax, maybeNull);
                    BoundExpression result = operatorKind == BinaryOperatorKind.Equal ?
                        MakeUnaryOperator(UnaryOperatorKind.BoolLogicalNegation, syntax, null, callHasValue, boolType) :
                        callHasValue;
                    return result;
                }
                else
                {
                    BoundExpression falseExpr = MakeBooleanConstant(syntax, operatorKind == BinaryOperatorKind.NotEqual);
                    return _factory.Sequence(maybeNull, falseExpr);
                }
            }

            return null;
        }
        private BoundExpression LowerLiftedUserDefinedComparisonOperator(
            CSharpSyntaxNode syntax,
            BinaryOperatorKind kind,
            BoundExpression loweredLeft,
            BoundExpression loweredRight,
            MethodSymbol method)
        {
            // If both sides are null, or neither side is null, then we can do some simple optimizations.

            BoundExpression optimized = TrivialLiftedComparisonOperatorOptimizations(syntax, kind, loweredLeft, loweredRight, method);
            if (optimized != null)
            {
                return optimized;
            }

            // Otherwise, the expression
            //
            // x == y 
            //
            // becomes
            //
            // tempX = x;
            // tempY = y;
            // result = tempX.HasValue == tempY.HasValue ? 
            //            (tempX.HasValue ? 
            //              tempX.GetValueOrDefault() == tempY.GetValueOrDefault() : 
            //              true) : 
            //          false;
            //
            //
            // the expression
            //
            // x != y 
            //
            // becomes
            //
            // tempX = x;
            // tempY = y;
            // result = tempX.HasValue == tempY.HasValue ? 
            //            (tempX.HasValue ? 
            //              tempX.GetValueOrDefault() != tempY.GetValueOrDefault() : 
            //              false) : 
            //            true;
            //
            //
            // For the other comparison operators <, <=, >, >=,
            //
            // x OP y 
            //
            // becomes
            //
            // tempX = x;
            // tempY = y;
            // result = tempX.HasValue & tempY.HasValue ? 
            //              tempX.GetValueOrDefault() OP tempY.GetValueOrDefault() : 
            //              false;
            //
            // We have not yet optimized the case where we have a known-not-null value on one side, 
            // and an unknown value on the other. In those cases we will still generate a temp, but
            // we will not generate the call to the unnecessary nullable ctor or to GetValueOrDefault.
            // Rather, we will generate the value's temp instead of a call to GetValueOrDefault, and generate
            // literal true for HasValue. The tree construction methods we call will use those constants
            // to eliminate unnecessary branches.

            BoundExpression xNonNull = NullableAlwaysHasValue(loweredLeft);
            BoundExpression yNonNull = NullableAlwaysHasValue(loweredRight);

            // TODO: (This TODO applies throughout this file, not just to this method.)
            // TODO: We might be storing a constant to this temporary that we could simply inline.
            // TODO: (There are other expressions that can be safely moved around other than constants
            // TODO: as well -- for example a boxing conversion of a constant int to object.)
            // TODO: Build a better temporary-storage management system that decides whether or not
            // TODO: to store a temporary.

            BoundAssignmentOperator tempAssignmentX;
            BoundLocal boundTempX = _factory.StoreToTemp(xNonNull ?? loweredLeft, out tempAssignmentX);
            BoundAssignmentOperator tempAssignmentY;
            BoundLocal boundTempY = _factory.StoreToTemp(yNonNull ?? loweredRight, out tempAssignmentY);

            BoundExpression callX_GetValueOrDefault = MakeOptimizedGetValueOrDefault(syntax, boundTempX);
            BoundExpression callY_GetValueOrDefault = MakeOptimizedGetValueOrDefault(syntax, boundTempY);
            BoundExpression callX_HasValue = MakeOptimizedHasValue(syntax, boundTempX);
            BoundExpression callY_HasValue = MakeOptimizedHasValue(syntax, boundTempY);

            // tempx.HasValue == tempy.HasValue
            BinaryOperatorKind conditionOperator;
            BinaryOperatorKind operatorKind = kind.Operator();
            switch (operatorKind)
            {
                case BinaryOperatorKind.Equal:
                case BinaryOperatorKind.NotEqual:
                    conditionOperator = BinaryOperatorKind.BoolEqual;
                    break;
                default:
                    conditionOperator = BinaryOperatorKind.BoolAnd;
                    break;
            }

            TypeSymbol boolType = _compilation.GetSpecialType(SpecialType.System_Boolean);

            BoundExpression condition = MakeBinaryOperator(
                syntax: syntax,
                operatorKind: conditionOperator,
                loweredLeft: callX_HasValue,
                loweredRight: callY_HasValue,
                type: boolType,
                method: null);

            // tempX.GetValueOrDefault() OP tempY.GetValueOrDefault()
            BoundExpression unliftedOp = MakeBinaryOperator(
                syntax: syntax,
                operatorKind: kind.Unlifted(),
                loweredLeft: callX_GetValueOrDefault,
                loweredRight: callY_GetValueOrDefault,
                type: boolType,
                method: method);

            BoundExpression consequence;
            if (operatorKind == BinaryOperatorKind.Equal || operatorKind == BinaryOperatorKind.NotEqual)
            {
                // tempx.HasValue ? tempX.GetValueOrDefault() == tempY.GetValueOrDefault() : true
                consequence = RewriteConditionalOperator(
                    syntax: syntax,
                    rewrittenCondition: callX_HasValue,
                    rewrittenConsequence: unliftedOp,
                    rewrittenAlternative: MakeLiteral(syntax, ConstantValue.Create(operatorKind == BinaryOperatorKind.Equal), boolType),
                    constantValueOpt: null,
                    rewrittenType: boolType);
            }
            else
            {
                // tempX.GetValueOrDefault() OP tempY.GetValueOrDefault()
                consequence = unliftedOp;
            }

            // false
            BoundExpression alternative = MakeBooleanConstant(syntax, operatorKind == BinaryOperatorKind.NotEqual);

            BoundExpression conditionalExpression = RewriteConditionalOperator(
                syntax: syntax,
                rewrittenCondition: condition,
                rewrittenConsequence: consequence,
                rewrittenAlternative: alternative,
                constantValueOpt: null,
                rewrittenType: boolType);

            return new BoundSequence(
                syntax: syntax,
                locals: ImmutableArray.Create<LocalSymbol>(boundTempX.LocalSymbol, boundTempY.LocalSymbol),
                sideEffects: ImmutableArray.Create<BoundExpression>(tempAssignmentX, tempAssignmentY),
                value: conditionalExpression,
                type: boolType);
        }
        private BoundExpression MakeLiftedBinaryOperatorConsequence(
            CSharpSyntaxNode syntax,
            BinaryOperatorKind kind,
            BoundExpression left,
            BoundExpression right,
            TypeSymbol type,
            MethodSymbol method)
        {
            // tempX.GetValueOrDefault() OP tempY.GetValueOrDefault()
            BoundExpression unliftedOp = MakeBinaryOperator(
                syntax: syntax,
                operatorKind: kind.Unlifted(),
                loweredLeft: left,
                loweredRight: right,
                type: type.GetNullableUnderlyingType(),
                method: method);

            // new R?(tempX.GetValueOrDefault() OP tempY.GetValueOrDefault)
            return new BoundObjectCreationExpression(
                syntax,
                GetNullableMethod(syntax, type, SpecialMember.System_Nullable_T__ctor),
                unliftedOp);
        }