File: Lowering\LocalRewriter\LocalRewriter_ConditionalAccess.cs
Web Access
Project: src\src\Compilers\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.csproj (Microsoft.CodeAnalysis.CSharp)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System.Diagnostics;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CSharp
{
    internal sealed partial class LocalRewriter
    {
        public override BoundNode VisitConditionalAccess(BoundConditionalAccess node)
        {
            // Never returns null when used is true.
            return RewriteConditionalAccess(node, used: true)!;
        }
 
        public override BoundNode VisitLoweredConditionalAccess(BoundLoweredConditionalAccess node)
        {
            throw ExceptionUtilities.Unreachable();
        }
 
        // null when currently enclosing conditional access node
        // is not supposed to be lowered.
        private BoundExpression? _currentConditionalAccessTarget;
        private int _currentConditionalAccessID;
 
        private enum ConditionalAccessLoweringKind
        {
            LoweredConditionalAccess,
            Conditional,
            ConditionalCaptureReceiverByVal
        }
 
        // IL gen can generate more compact code for certain conditional accesses 
        // by utilizing stack dup/pop instructions 
        internal BoundExpression? RewriteConditionalAccess(BoundConditionalAccess node, bool used)
        {
            Debug.Assert(!_inExpressionLambda);
            Debug.Assert(node.AccessExpression.Type is { });
 
            var loweredReceiver = this.VisitExpression(node.Receiver);
            Debug.Assert(loweredReceiver.Type is { });
            var receiverType = loweredReceiver.Type;
 
            // Check trivial case
            if (loweredReceiver.IsDefaultValue() && receiverType.IsReferenceType)
            {
                return _factory.Default(node.Type);
            }
 
            ConditionalAccessLoweringKind loweringKind;
            // dynamic receivers are not directly supported in codegen and need to be lowered to a conditional
            var lowerToConditional = node.AccessExpression.Type.IsDynamic();
 
            if (!lowerToConditional)
            {
                // trivial cases are directly supported in IL gen
                loweringKind = ConditionalAccessLoweringKind.LoweredConditionalAccess;
            }
            else if (CanChangeValueBetweenReads(loweredReceiver))
            {
                // NOTE: dynamic operations historically do not propagate mutations
                // to the receiver if that happens to be a value type
                // so we can capture receiver by value in dynamic case regardless of 
                // the type of receiver
                // Nullable receivers are immutable so should be captured by value as well.
                loweringKind = ConditionalAccessLoweringKind.ConditionalCaptureReceiverByVal;
            }
            else
            {
                loweringKind = ConditionalAccessLoweringKind.Conditional;
            }
 
            var previousConditionalAccessTarget = _currentConditionalAccessTarget;
            var currentConditionalAccessID = ++_currentConditionalAccessID;
 
            LocalSymbol? temp = null;
 
            switch (loweringKind)
            {
                case ConditionalAccessLoweringKind.LoweredConditionalAccess:
                    _currentConditionalAccessTarget = new BoundConditionalReceiver(
                        loweredReceiver.Syntax,
                        currentConditionalAccessID,
                        receiverType);
 
                    break;
 
                case ConditionalAccessLoweringKind.Conditional:
                    _currentConditionalAccessTarget = loweredReceiver;
                    break;
 
                case ConditionalAccessLoweringKind.ConditionalCaptureReceiverByVal:
                    temp = _factory.SynthesizedLocal(receiverType);
                    _currentConditionalAccessTarget = _factory.Local(temp);
                    break;
 
                default:
                    throw ExceptionUtilities.UnexpectedValue(loweringKind);
            }
 
            BoundExpression? loweredAccessExpression;
 
            if (used)
            {
                loweredAccessExpression = this.VisitExpression(node.AccessExpression);
            }
            else
            {
                loweredAccessExpression = this.VisitUnusedExpression(node.AccessExpression);
                if (loweredAccessExpression == null)
                {
                    return null;
                }
            }
 
            Debug.Assert(loweredAccessExpression != null);
            Debug.Assert(loweredAccessExpression.Type is { });
            _currentConditionalAccessTarget = previousConditionalAccessTarget;
 
            TypeSymbol type = this.VisitType(node.Type);
 
            TypeSymbol nodeType = node.Type;
            TypeSymbol accessExpressionType = loweredAccessExpression.Type;
 
            if (accessExpressionType.IsVoidType())
            {
                type = nodeType = accessExpressionType;
            }
 
            if (!TypeSymbol.Equals(accessExpressionType, nodeType, TypeCompareKind.ConsiderEverything2) && nodeType.IsNullableType())
            {
                Debug.Assert(TypeSymbol.Equals(accessExpressionType, nodeType.GetNullableUnderlyingType(), TypeCompareKind.ConsiderEverything2));
                loweredAccessExpression = _factory.New((NamedTypeSymbol)nodeType, loweredAccessExpression);
            }
            else
            {
                Debug.Assert(TypeSymbol.Equals(accessExpressionType, nodeType, TypeCompareKind.ConsiderEverything2) ||
                    (nodeType.IsVoidType() && !used));
            }
 
            BoundExpression result;
 
            switch (loweringKind)
            {
                case ConditionalAccessLoweringKind.LoweredConditionalAccess:
                    Debug.Assert(loweredReceiver.Type is { });
                    result = new BoundLoweredConditionalAccess(
                        node.Syntax,
                        loweredReceiver,
                        receiverType.IsNullableType() ?
                                 UnsafeGetNullableMethod(node.Syntax, loweredReceiver.Type, SpecialMember.System_Nullable_T_get_HasValue) :
                                 null,
                        loweredAccessExpression,
                        null,
                        currentConditionalAccessID,
                        forceCopyOfNullableValueType: true,
                        type);
 
                    break;
 
                case ConditionalAccessLoweringKind.ConditionalCaptureReceiverByVal:
                    // capture the receiver into a temp
                    Debug.Assert(temp is { });
                    loweredReceiver = _factory.MakeSequence(
                                            _factory.AssignmentExpression(_factory.Local(temp), loweredReceiver),
                                            _factory.Local(temp));
 
                    goto case ConditionalAccessLoweringKind.Conditional;
 
                case ConditionalAccessLoweringKind.Conditional:
                    {
                        // (object)r != null ? access : default(T)
                        var condition = _factory.IsNotNullReference(loweredReceiver);
 
                        var consequence = loweredAccessExpression;
 
                        result = RewriteConditionalOperator(node.Syntax,
                            condition,
                            consequence,
                            _factory.Default(nodeType),
                            null,
                            nodeType,
                            isRef: false);
 
                        if (temp != null)
                        {
                            result = _factory.MakeSequence(temp, result);
                        }
                    }
                    break;
 
                default:
                    throw ExceptionUtilities.UnexpectedValue(loweringKind);
            }
 
            return result;
        }
 
        public override BoundNode VisitConditionalReceiver(BoundConditionalReceiver node)
        {
            var newtarget = _currentConditionalAccessTarget;
            Debug.Assert(newtarget is { Type: { } });
 
            if (newtarget.Type.IsNullableType())
            {
                newtarget = MakeOptimizedGetValueOrDefault(node.Syntax, newtarget);
            }
 
            return newtarget;
        }
    }
}