private BoundExpression MakeNullCoalescingOperator(
     SyntaxNode syntax,
     BoundExpression rewrittenLeft,
     BoundExpression rewrittenRight,
     Conversion leftConversion,
     BoundNullCoalescingOperatorResultKind resultKind,
     TypeSymbol?rewrittenResultType)
 {
     Debug.Assert(rewrittenLeft != null);
     Debug.Assert(rewrittenRight != null);
     Debug.Assert(leftConversion.IsValid);
     Debug.Assert(rewrittenResultType is { });
 private BoundExpression MakeNullCoalescingOperator(
     SyntaxNode syntax,
     BoundExpression rewrittenLeft,
     BoundExpression rewrittenRight,
     BoundValuePlaceholder?leftPlaceholder,
     BoundExpression?leftConversion,
     BoundNullCoalescingOperatorResultKind resultKind,
     TypeSymbol?rewrittenResultType)
 {
     Debug.Assert(rewrittenLeft != null);
     Debug.Assert(rewrittenRight != null);
     Debug.Assert(BoundNode.GetConversion(leftConversion, leftPlaceholder).IsValid);
     Debug.Assert(rewrittenResultType is { });
        private BoundExpression MakeNullCoalescingOperator(
            SyntaxNode syntax,
            BoundExpression rewrittenLeft,
            BoundExpression rewrittenRight,
            Conversion leftConversion,
            BoundNullCoalescingOperatorResultKind resultKind,
            TypeSymbol rewrittenResultType)
        {
            Debug.Assert(rewrittenLeft != null);
            Debug.Assert(rewrittenRight != null);
            Debug.Assert(leftConversion.IsValid);
            Debug.Assert((object)rewrittenResultType != null);
            Debug.Assert(rewrittenRight.Type.Equals(rewrittenResultType, TypeCompareKind.IgnoreDynamicAndTupleNames | TypeCompareKind.IgnoreNullableModifiersForReferenceTypes));

            var isUnconstrainedTypeParameter = (object)rewrittenLeft.Type != null && !rewrittenLeft.Type.IsReferenceType && !rewrittenLeft.Type.IsValueType;

            // first we can make a small optimization:
            // If left is a constant then we already know whether it is null or not. If it is null then we
            // can simply generate "right". If it is not null then we can simply generate
            // MakeConversion(left). This does not hold when the left is an unconstrained type parameter: at runtime,
            // it can be either left or right depending on the runtime type of T
            if (!isUnconstrainedTypeParameter)
            {
                if (rewrittenLeft.IsDefaultValue())
                {
                    return(rewrittenRight);
                }

                if (rewrittenLeft.ConstantValue != null)
                {
                    Debug.Assert(!rewrittenLeft.ConstantValue.IsNull);

                    return(GetConvertedLeftForNullCoalescingOperator(rewrittenLeft, leftConversion, rewrittenResultType));
                }
            }

            // string concatenation is never null.
            // interpolated string lowering may introduce redundant null coalescing, which we have to remove.
            if (IsStringConcat(rewrittenLeft))
            {
                return(GetConvertedLeftForNullCoalescingOperator(rewrittenLeft, leftConversion, rewrittenResultType));
            }

            // if left conversion is intrinsic implicit (always succeeds) and results in a reference type
            // we can apply conversion before doing the null check that allows for a more efficient IL emit.
            if (rewrittenLeft.Type.IsReferenceType &&
                leftConversion.IsImplicit &&
                !leftConversion.IsUserDefined)
            {
                if (!leftConversion.IsIdentity)
                {
                    rewrittenLeft = MakeConversionNode(rewrittenLeft.Syntax, rewrittenLeft, leftConversion, rewrittenResultType, @checked: false);
                }
                return(new BoundNullCoalescingOperator(syntax, rewrittenLeft, rewrittenRight, Conversion.Identity, resultKind, rewrittenResultType));
            }

            if (leftConversion.IsIdentity || leftConversion.Kind == ConversionKind.ExplicitNullable)
            {
                var conditionalAccess = rewrittenLeft as BoundLoweredConditionalAccess;
                if (conditionalAccess != null &&
                    (conditionalAccess.WhenNullOpt == null || NullableNeverHasValue(conditionalAccess.WhenNullOpt)))
                {
                    var notNullAccess = NullableAlwaysHasValue(conditionalAccess.WhenNotNull);
                    if (notNullAccess != null)
                    {
                        var whenNullOpt = rewrittenRight;

                        if (whenNullOpt.Type.IsNullableType())
                        {
                            notNullAccess = conditionalAccess.WhenNotNull;
                        }

                        if (whenNullOpt.IsDefaultValue())
                        {
                            whenNullOpt = null;
                        }

                        return(conditionalAccess.Update(
                                   conditionalAccess.Receiver,
                                   conditionalAccess.HasValueMethodOpt,
                                   whenNotNull: notNullAccess,
                                   whenNullOpt: whenNullOpt,
                                   id: conditionalAccess.Id,
                                   type: rewrittenResultType
                                   ));
                    }
                }
            }

            // Optimize left ?? right to left.GetValueOrDefault() when left is T? and right is the default value of T
            if (rewrittenLeft.Type.IsNullableType() &&
                RemoveIdentityConversions(rewrittenRight).IsDefaultValue() &&
                rewrittenRight.Type.Equals(rewrittenLeft.Type.GetNullableUnderlyingType(), TypeCompareKind.AllIgnoreOptions) &&
                TryGetNullableMethod(rewrittenLeft.Syntax, rewrittenLeft.Type, SpecialMember.System_Nullable_T_GetValueOrDefault, out MethodSymbol getValueOrDefault))
            {
                return(BoundCall.Synthesized(rewrittenLeft.Syntax, rewrittenLeft, getValueOrDefault));
            }

            // We lower left ?? right to
            //
            // var temp = left;
            // (temp != null) ? MakeConversion(temp) : right
            //

            BoundAssignmentOperator tempAssignment;
            BoundLocal boundTemp = _factory.StoreToTemp(rewrittenLeft, out tempAssignment);

            // temp != null
            BoundExpression nullCheck = MakeNullCheck(syntax, boundTemp, BinaryOperatorKind.NotEqual);

            // MakeConversion(temp, rewrittenResultType)
            BoundExpression convertedLeft = GetConvertedLeftForNullCoalescingOperator(boundTemp, leftConversion, rewrittenResultType);

            Debug.Assert(convertedLeft.HasErrors || convertedLeft.Type.Equals(rewrittenResultType, TypeCompareKind.IgnoreDynamicAndTupleNames | TypeCompareKind.IgnoreNullableModifiersForReferenceTypes));

            // (temp != null) ? MakeConversion(temp, LeftConversion) : RightOperand
            BoundExpression conditionalExpression = RewriteConditionalOperator(
                syntax: syntax,
                rewrittenCondition: nullCheck,
                rewrittenConsequence: convertedLeft,
                rewrittenAlternative: rewrittenRight,
                constantValueOpt: null,
                rewrittenType: rewrittenResultType,
                isRef: false);

            Debug.Assert(conditionalExpression.ConstantValue == null); // we shouldn't have hit this else case otherwise
            Debug.Assert(conditionalExpression.Type.Equals(rewrittenResultType, TypeCompareKind.IgnoreDynamicAndTupleNames | TypeCompareKind.IgnoreNullableModifiersForReferenceTypes));

            return(new BoundSequence(
                       syntax: syntax,
                       locals: ImmutableArray.Create(boundTemp.LocalSymbol),
                       sideEffects: ImmutableArray.Create <BoundExpression>(tempAssignment),
                       value: conditionalExpression,
                       type: rewrittenResultType));
        }