Example #1
0
        public override BoundNode VisitConditionalAccess(BoundConditionalAccess node)
        {
            if (_inExpressionLambda)
            {
                Error(ErrorCode.ERR_NullPropagatingOpInExpressionTree, node);
            }

            return(base.VisitConditionalAccess(node));
        }
        private bool TryGetOptimizableNullableConditionalAccess(BoundExpression operand, out BoundConditionalAccess conditionalAccess)
        {
            if (operand.Kind != BoundKind.ConditionalAccess ||
                            _inExpressionLambda ||
                            _factory.CurrentMethod.IsAsync)
            {
                conditionalAccess = null;
                return false;
            }

            conditionalAccess = (BoundConditionalAccess)operand;
            return conditionalAccess.Type.IsNullableType() && !conditionalAccess.AccessExpression.Type.IsNullableType();
        }
        private BoundNode FuseNodes(BoundConditionalAccess conditionalAccess, BoundNullCoalescingOperator node)
        {
            var type = node.RightOperand.Type;
            conditionalAccess = conditionalAccess.Update(conditionalAccess.Receiver, conditionalAccess.AccessExpression, type);

            var whenNull = (BoundExpression)Visit(node.RightOperand);
            if (whenNull.IsDefaultValue() && whenNull.Type.SpecialType != SpecialType.System_Decimal)
            {
                whenNull = null;
            }

            return RewriteConditionalAccess(conditionalAccess, used: true, rewrittenWhenNull: whenNull);
        }
Example #4
0
        private BoundNode FuseNodes(BoundConditionalAccess conditionalAccess, BoundNullCoalescingOperator node)
        {
            var type = node.RightOperand.Type;

            conditionalAccess = conditionalAccess.Update(conditionalAccess.Receiver, conditionalAccess.AccessExpression, type);

            var whenNull = (BoundExpression)Visit(node.RightOperand);

            if (whenNull.IsDefaultValue() && whenNull.Type.SpecialType != SpecialType.System_Decimal)
            {
                whenNull = null;
            }

            return(RewriteConditionalAccess(conditionalAccess, used: true, rewrittenWhenNull: whenNull));
        }
 public override BoundNode VisitConditionalAccess(BoundConditionalAccess node)
 {
     return RewriteConditionalAccess(node, used: true);
 }
        // in simple cases could be left unlowered.
        // IL gen can generate more compact code for unlowered conditional accesses 
        // by utilizing stack dup/pop instructions 
        public override BoundNode VisitConditionalAccess(BoundConditionalAccess node)
        {
            BoundExpression loweredReceiver = (BoundExpression)this.Visit(node.Receiver);

            var receiverType = loweredReceiver.Type;

            //TODO: if AccessExpression does not contain awaits, the node could be left unlowered (saves a temp),
            //      but there seem to be no way of knowing that without walking AccessExpression.
            var needToLower = receiverType.IsNullableType() || 
                this.inExpressionLambda || 
                this.factory.CurrentMethod.IsAsync || 
                node.Type.IsDynamic();

            var previousConditionalAccesTarget = currentConditionalAccessTarget;
            LocalSymbol temp = null;

            if (needToLower)
            {
                if (NeedsTemp(loweredReceiver, localsMayBeAssigned: false))
                {
                    temp = factory.SynthesizedLocal(receiverType);
                    currentConditionalAccessTarget = factory.Local(temp);
                    loweredReceiver = factory.AssignmentExpression(factory.Local(temp), loweredReceiver);
                }
                else
                {
                    currentConditionalAccessTarget = loweredReceiver;
                }
            }
            else
            {
                currentConditionalAccessTarget = null;
            }

            BoundExpression loweredAccessExpression = (BoundExpression)this.Visit(node.AccessExpression);

            currentConditionalAccessTarget = previousConditionalAccesTarget;

            TypeSymbol type = this.VisitType(node.Type);

            TypeSymbol nodeType = node.Type;
            TypeSymbol accessExpressionType = loweredAccessExpression.Type;

            if (accessExpressionType != nodeType)
            {
                Debug.Assert(nodeType.IsNullableType() && accessExpressionType == nodeType.GetNullableUnderlyingType());
                loweredAccessExpression = factory.New((NamedTypeSymbol)nodeType, loweredAccessExpression);
            }

            BoundExpression result;
            if (!needToLower)
            {
                Debug.Assert(receiverType.IsReferenceType);
                result = node.Update(loweredReceiver, loweredAccessExpression, type);
            }
            else
            {
                var condition = receiverType.IsReferenceType ?
                    factory.ObjectNotEqual(loweredReceiver, factory.Null(receiverType)) :
                    MakeOptimizedHasValue(loweredReceiver.Syntax, loweredReceiver);

                var consequence = loweredAccessExpression;
                var alternative = factory.Default(nodeType);

                result = RewriteConditionalOperator(node.Syntax,
                    condition,
                    consequence,
                    alternative,
                    null,
                    alternative.Type);

                if (temp!=null)
                {
                    result = factory.Sequence(temp, result);
                }
            }

            return result;
        }
 // 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 { });
 public override BoundNode VisitConditionalAccess(BoundConditionalAccess node)
 {
     // Never returns null when used is true.
     return(RewriteConditionalAccess(node, used: true) !);
 }
        // 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);

            var loweredReceiver = this.VisitExpression(node.Receiver);
            var receiverType    = loweredReceiver.Type;

            // Check trivial case
            if (loweredReceiver.IsDefaultValue())
            {
                return(_factory.Default(node.Type));
            }

            ConditionalAccessLoweringKind loweringKind;
            // dynamic receivers are not directly supported in codegen and need to be lowered to a ternary
            var lowerToTernary = node.AccessExpression.Type.IsDynamic();

            if (!lowerToTernary)
            {
                // 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.TernaryCaptureReceiverByVal;
            }
            else
            {
                loweringKind = ConditionalAccessLoweringKind.Ternary;
            }


            var previousConditionalAccessTarget = _currentConditionalAccessTarget;
            var currentConditionalAccessID      = ++_currentConditionalAccessID;

            LocalSymbol     temp = null;
            BoundExpression unconditionalAccess = null;

            switch (loweringKind)
            {
            case ConditionalAccessLoweringKind.LoweredConditionalAccess:
                _currentConditionalAccessTarget = new BoundConditionalReceiver(
                    loweredReceiver.Syntax,
                    currentConditionalAccessID,
                    receiverType);

                break;

            case ConditionalAccessLoweringKind.Ternary:
                _currentConditionalAccessTarget = loweredReceiver;
                break;

            case ConditionalAccessLoweringKind.TernaryCaptureReceiverByVal:
                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);
            _currentConditionalAccessTarget = previousConditionalAccessTarget;

            TypeSymbol type = this.VisitType(node.Type);

            TypeSymbol nodeType             = node.Type;
            TypeSymbol accessExpressionType = loweredAccessExpression.Type;

            if (accessExpressionType.SpecialType == SpecialType.System_Void)
            {
                type = nodeType = accessExpressionType;
            }

            if (accessExpressionType != nodeType && nodeType.IsNullableType())
            {
                Debug.Assert(accessExpressionType == nodeType.GetNullableUnderlyingType());
                loweredAccessExpression = _factory.New((NamedTypeSymbol)nodeType, loweredAccessExpression);

                if (unconditionalAccess != null)
                {
                    unconditionalAccess = _factory.New((NamedTypeSymbol)nodeType, unconditionalAccess);
                }
            }
            else
            {
                Debug.Assert(accessExpressionType == nodeType ||
                             (nodeType.SpecialType == SpecialType.System_Void && !used));
            }

            BoundExpression result;
            var             objectType = _compilation.GetSpecialType(SpecialType.System_Object);

            switch (loweringKind)
            {
            case ConditionalAccessLoweringKind.LoweredConditionalAccess:
                result = new BoundLoweredConditionalAccess(
                    node.Syntax,
                    loweredReceiver,
                    receiverType.IsNullableType() ?
                    GetNullableMethod(node.Syntax, loweredReceiver.Type, SpecialMember.System_Nullable_T_get_HasValue) :
                    null,
                    loweredAccessExpression,
                    null,
                    currentConditionalAccessID,
                    type);

                break;

            case ConditionalAccessLoweringKind.TernaryCaptureReceiverByVal:
                // capture the receiver into a temp
                loweredReceiver = _factory.Sequence(
                    _factory.AssignmentExpression(_factory.Local(temp), loweredReceiver),
                    _factory.Local(temp));

                goto case ConditionalAccessLoweringKind.Ternary;

            case ConditionalAccessLoweringKind.Ternary:
            {
                // (object)r != null ? access : default(T)
                var condition = _factory.ObjectNotEqual(
                    _factory.Convert(objectType, loweredReceiver),
                    _factory.Null(objectType));

                var consequence = loweredAccessExpression;

                result = RewriteConditionalOperator(node.Syntax,
                                                    condition,
                                                    consequence,
                                                    _factory.Default(nodeType),
                                                    null,
                                                    nodeType);

                if (temp != null)
                {
                    result = _factory.Sequence(temp, result);
                }
            }
            break;

            default:
                throw ExceptionUtilities.UnexpectedValue(loweringKind);
            }

            return(result);
        }
 public override BoundNode VisitConditionalAccess(BoundConditionalAccess node)
 {
     return(RewriteConditionalAccess(node, used: true));
 }
Example #11
0
        // in simple cases could be left unlowered.
        // IL gen can generate more compact code for unlowered conditional accesses
        // by utilizing stack dup/pop instructions
        public override BoundNode VisitConditionalAccess(BoundConditionalAccess node)
        {
            BoundExpression loweredReceiver = (BoundExpression)this.Visit(node.Receiver);

            var receiverType = loweredReceiver.Type;

            //TODO: if AccessExpression does not contain awaits, the node could be left unlowered (saves a temp),
            //      but there seem to be no way of knowing that without walking AccessExpression.
            var needToLower = receiverType.IsNullableType() || this.inExpressionLambda || this.factory.CurrentMethod.IsAsync;

            var         previousConditionalAccesTarget = currentConditionalAccessTarget;
            LocalSymbol temp = null;

            if (needToLower)
            {
                if (NeedsTemp(loweredReceiver, localsMayBeAssigned: false))
                {
                    temp = factory.SynthesizedLocal(receiverType);
                    currentConditionalAccessTarget = factory.Local(temp);
                    loweredReceiver = factory.AssignmentExpression(factory.Local(temp), loweredReceiver);
                }
                else
                {
                    currentConditionalAccessTarget = loweredReceiver;
                }
            }
            else
            {
                currentConditionalAccessTarget = null;
            }

            BoundExpression loweredAccessExpression = (BoundExpression)this.Visit(node.AccessExpression);

            currentConditionalAccessTarget = previousConditionalAccesTarget;

            TypeSymbol type = this.VisitType(node.Type);

            TypeSymbol nodeType             = node.Type;
            TypeSymbol accessExpressionType = loweredAccessExpression.Type;

            if (accessExpressionType != nodeType)
            {
                Debug.Assert(nodeType.IsNullableType() && accessExpressionType == nodeType.GetNullableUnderlyingType());
                loweredAccessExpression = factory.New((NamedTypeSymbol)nodeType, loweredAccessExpression);
            }

            BoundExpression result;

            if (!needToLower)
            {
                Debug.Assert(receiverType.IsReferenceType);
                result = node.Update(loweredReceiver, loweredAccessExpression, type);
            }
            else
            {
                var condition = receiverType.IsReferenceType ?
                                factory.ObjectNotEqual(loweredReceiver, factory.Null(receiverType)) :
                                MakeOptimizedHasValue(loweredReceiver.Syntax, loweredReceiver);

                var consequence = loweredAccessExpression;
                var alternative = factory.Default(nodeType);

                result = RewriteConditionalOperator(node.Syntax,
                                                    condition,
                                                    consequence,
                                                    alternative,
                                                    null,
                                                    alternative.Type);

                if (temp != null)
                {
                    result = factory.Sequence(temp, result);
                }
            }

            return(result);
        }
Example #12
0
        // in simple cases could be left unlowered.
        // IL gen can generate more compact code for unlowered conditional accesses
        // by utilizing stack dup/pop instructions
        internal BoundExpression RewriteConditionalAccess(BoundConditionalAccess node, bool used, BoundExpression rewrittenWhenNull = null)
        {
            Debug.Assert(!_inExpressionLambda);

            var loweredReceiver = this.VisitExpression(node.Receiver);
            var receiverType    = loweredReceiver.Type;

            //TODO: if AccessExpression does not contain awaits, the node could be left unlowered (saves a temp),
            //      but there seem to be no way of knowing that without walking AccessExpression.
            //      For now we will just check that we are in an async method, but it would be nice
            //      to have something more precise.
            var isAsync = _factory.CurrentMethod.IsAsync;

            ConditionalAccessLoweringKind loweringKind;
            // CONSIDER: If we knew that loweredReceiver is not a captured local
            //       we could pass "false" for localsMayBeAssignedOrCaptured
            //       otherwise not capturing receiver into a temp
            //       could introduce additional races into the code if receiver is captured
            //       into a closure and is modified between null check of the receiver
            //       and the actual access.
            //
            //       Nullable is special since we are not going to read any part of it twice
            //       we will read "HasValue" and then, conditionally will read "ValueOrDefault"
            //       that is no different than just reading both values unconditionally.
            //       As a result in the case of nullable, not reading captured local through a temp
            //       does not introduce any additional races so it is irrelevant whether
            //       the local is captured or not.
            var localsMayBeAssignedOrCaptured = !receiverType.IsNullableType();
            var needTemp = IntroducingReadCanBeObservable(loweredReceiver, localsMayBeAssignedOrCaptured);

            if (!isAsync && !node.AccessExpression.Type.IsDynamic() && rewrittenWhenNull == null &&
                (receiverType.IsReferenceType || receiverType.IsTypeParameter() && needTemp))
            {
                // trivial cases can be handled more efficiently in IL gen
                loweringKind = ConditionalAccessLoweringKind.None;
            }
            else if (needTemp)
            {
                if (receiverType.IsReferenceType || receiverType.IsNullableType())
                {
                    loweringKind = ConditionalAccessLoweringKind.CaptureReceiverByVal;
                }
                else
                {
                    loweringKind = isAsync ?
                                   ConditionalAccessLoweringKind.DuplicateCode :
                                   ConditionalAccessLoweringKind.CaptureReceiverByRef;
                }
            }
            else
            {
                // locals do not need to be captured
                loweringKind = ConditionalAccessLoweringKind.NoCapture;
            }


            var             previousConditionalAccesTarget = _currentConditionalAccessTarget;
            LocalSymbol     temp = null;
            BoundExpression unconditionalAccess = null;

            switch (loweringKind)
            {
            case ConditionalAccessLoweringKind.None:
                _currentConditionalAccessTarget = null;
                break;

            case ConditionalAccessLoweringKind.NoCapture:
                _currentConditionalAccessTarget = loweredReceiver;
                break;

            case ConditionalAccessLoweringKind.DuplicateCode:
                _currentConditionalAccessTarget = loweredReceiver;
                unconditionalAccess             = used ?
                                                  this.VisitExpression(node.AccessExpression) :
                                                  this.VisitUnusedExpression(node.AccessExpression);

                goto case ConditionalAccessLoweringKind.CaptureReceiverByVal;

            case ConditionalAccessLoweringKind.CaptureReceiverByVal:
                temp = _factory.SynthesizedLocal(receiverType);
                _currentConditionalAccessTarget = _factory.Local(temp);
                break;

            case ConditionalAccessLoweringKind.CaptureReceiverByRef:
                temp = _factory.SynthesizedLocal(receiverType, refKind: RefKind.Ref);
                _currentConditionalAccessTarget = _factory.Local(temp);
                break;

            default:
                throw ExceptionUtilities.UnexpectedValue(loweringKind);
            }

            BoundExpression loweredAccessExpression = used ?
                                                      this.VisitExpression(node.AccessExpression) :
                                                      this.VisitUnusedExpression(node.AccessExpression);

            _currentConditionalAccessTarget = previousConditionalAccesTarget;

            TypeSymbol type = this.VisitType(node.Type);

            TypeSymbol nodeType             = node.Type;
            TypeSymbol accessExpressionType = loweredAccessExpression.Type;

            if (accessExpressionType.SpecialType == SpecialType.System_Void)
            {
                type = nodeType = accessExpressionType;
            }

            if (accessExpressionType != nodeType && nodeType.IsNullableType())
            {
                Debug.Assert(accessExpressionType == nodeType.GetNullableUnderlyingType());
                loweredAccessExpression = _factory.New((NamedTypeSymbol)nodeType, loweredAccessExpression);

                if (unconditionalAccess != null)
                {
                    unconditionalAccess = _factory.New((NamedTypeSymbol)nodeType, unconditionalAccess);
                }
            }
            else
            {
                Debug.Assert(accessExpressionType == nodeType ||
                             (nodeType.SpecialType == SpecialType.System_Void && !used));
            }

            BoundExpression result;
            var             objectType = _compilation.GetSpecialType(SpecialType.System_Object);

            rewrittenWhenNull = rewrittenWhenNull ?? _factory.Default(nodeType);

            switch (loweringKind)
            {
            case ConditionalAccessLoweringKind.None:
                Debug.Assert(!receiverType.IsValueType);
                result = node.Update(loweredReceiver, loweredAccessExpression, type);
                break;

            case ConditionalAccessLoweringKind.CaptureReceiverByVal:
                // capture the receiver into a temp
                loweredReceiver = _factory.Sequence(
                    _factory.AssignmentExpression(_factory.Local(temp), loweredReceiver),
                    _factory.Local(temp));

                goto case ConditionalAccessLoweringKind.NoCapture;

            case ConditionalAccessLoweringKind.NoCapture:
            {
                // (object)r != null ? access : default(T)
                var condition = receiverType.IsNullableType() ?
                                MakeOptimizedHasValue(loweredReceiver.Syntax, loweredReceiver) :
                                _factory.ObjectNotEqual(
                    _factory.Convert(objectType, loweredReceiver),
                    _factory.Null(objectType));

                var consequence = loweredAccessExpression;

                result = RewriteConditionalOperator(node.Syntax,
                                                    condition,
                                                    consequence,
                                                    rewrittenWhenNull,
                                                    null,
                                                    nodeType);

                if (temp != null)
                {
                    result = _factory.Sequence(temp, result);
                }
            }
            break;

            case ConditionalAccessLoweringKind.CaptureReceiverByRef:
                // {ref T r; T v;
                //    r = ref receiver;
                //    (isClass && { v = r; r = ref v; v == null } ) ?
                //                                          null;
                //                                          r.Foo()}

            {
                var v = _factory.SynthesizedLocal(receiverType);

                BoundExpression captureRef = _factory.AssignmentExpression(_factory.Local(temp), loweredReceiver, refKind: RefKind.Ref);
                BoundExpression isNull     = _factory.LogicalAnd(
                    IsClass(receiverType, objectType),
                    _factory.Sequence(
                        _factory.AssignmentExpression(_factory.Local(v), _factory.Local(temp)),
                        _factory.AssignmentExpression(_factory.Local(temp), _factory.Local(v), RefKind.Ref),
                        _factory.ObjectEqual(_factory.Convert(objectType, _factory.Local(v)), _factory.Null(objectType)))
                    );

                result = RewriteConditionalOperator(node.Syntax,
                                                    isNull,
                                                    rewrittenWhenNull,
                                                    loweredAccessExpression,
                                                    null,
                                                    nodeType);

                result = _factory.Sequence(
                    ImmutableArray.Create(temp, v),
                    captureRef,
                    result
                    );
            }
            break;

            case ConditionalAccessLoweringKind.DuplicateCode:
            {
                Debug.Assert(!receiverType.IsNullableType());

                // if we have a class, do regular conditional access via a val temp
                loweredReceiver = _factory.AssignmentExpression(_factory.Local(temp), loweredReceiver);
                BoundExpression ifClass = RewriteConditionalOperator(node.Syntax,
                                                                     _factory.ObjectNotEqual(
                                                                         _factory.Convert(objectType, loweredReceiver),
                                                                         _factory.Null(objectType)),
                                                                     loweredAccessExpression,
                                                                     rewrittenWhenNull,
                                                                     null,
                                                                     nodeType);

                if (temp != null)
                {
                    ifClass = _factory.Sequence(temp, ifClass);
                }

                // if we have a struct, then just access unconditionally
                BoundExpression ifStruct = unconditionalAccess;

                // (object)(default(T)) != null ? ifStruct: ifClass
                result = RewriteConditionalOperator(node.Syntax,
                                                    IsClass(receiverType, objectType),
                                                    ifClass,
                                                    ifStruct,
                                                    null,
                                                    nodeType);
            }
            break;

            default:
                throw ExceptionUtilities.Unreachable;
            }

            return(result);
        }
        // in simple cases could be left unlowered.
        // IL gen can generate more compact code for unlowered conditional accesses 
        // by utilizing stack dup/pop instructions 
        internal BoundExpression RewriteConditionalAccess(BoundConditionalAccess node, bool used, BoundExpression rewrittenWhenNull = null)
        {
            Debug.Assert(!_inExpressionLambda);

            var loweredReceiver = this.VisitExpression(node.Receiver);
            var receiverType = loweredReceiver.Type;

            //TODO: if AccessExpression does not contain awaits, the node could be left unlowered (saves a temp),
            //      but there seem to be no way of knowing that without walking AccessExpression.
            //      For now we will just check that we are in an async method, but it would be nice
            //      to have something more precise.
            var isAsync = _factory.CurrentMethod.IsAsync;

            ConditionalAccessLoweringKind loweringKind;
            // CONSIDER: If we knew that loweredReceiver is not a captured local
            //       we could pass "false" for localsMayBeAssignedOrCaptured
            //       otherwise not capturing receiver into a temp
            //       could introduce additional races into the code if receiver is captured
            //       into a closure and is modified between null check of the receiver 
            //       and the actual access.
            //
            //       Nullable is special since we are not going to read any part of it twice
            //       we will read "HasValue" and then, conditionally will read "ValueOrDefault"
            //       that is no different than just reading both values unconditionally.
            //       As a result in the case of nullable, not reading captured local through a temp 
            //       does not introduce any additional races so it is irrelevant whether 
            //       the local is captured or not.
            var localsMayBeAssignedOrCaptured = !receiverType.IsNullableType();
            var needTemp = IntroducingReadCanBeObservable(loweredReceiver, localsMayBeAssignedOrCaptured);

            if (!isAsync && !node.AccessExpression.Type.IsDynamic() && rewrittenWhenNull == null &&
                (receiverType.IsReferenceType || receiverType.IsTypeParameter() && needTemp))
            {
                // trivial cases can be handled more efficiently in IL gen
                loweringKind = ConditionalAccessLoweringKind.None;
            }
            else if (needTemp)
            {
                if (receiverType.IsReferenceType || receiverType.IsNullableType())
                {
                    loweringKind = ConditionalAccessLoweringKind.CaptureReceiverByVal;
                }
                else
                {
                    loweringKind = isAsync ?
                        ConditionalAccessLoweringKind.DuplicateCode :
                        ConditionalAccessLoweringKind.CaptureReceiverByRef;
                }
            }
            else
            {
                // locals do not need to be captured
                loweringKind = ConditionalAccessLoweringKind.NoCapture;
            }


            var previousConditionalAccesTarget = _currentConditionalAccessTarget;
            LocalSymbol temp = null;
            BoundExpression unconditionalAccess = null;

            switch (loweringKind)
            {
                case ConditionalAccessLoweringKind.None:
                    _currentConditionalAccessTarget = null;
                    break;

                case ConditionalAccessLoweringKind.NoCapture:
                    _currentConditionalAccessTarget = loweredReceiver;
                    break;

                case ConditionalAccessLoweringKind.DuplicateCode:
                    _currentConditionalAccessTarget = loweredReceiver;
                    unconditionalAccess = used ?
                        this.VisitExpression(node.AccessExpression) :
                        this.VisitUnusedExpression(node.AccessExpression);

                    goto case ConditionalAccessLoweringKind.CaptureReceiverByVal;

                case ConditionalAccessLoweringKind.CaptureReceiverByVal:
                    temp = _factory.SynthesizedLocal(receiverType);
                    _currentConditionalAccessTarget = _factory.Local(temp);
                    break;

                case ConditionalAccessLoweringKind.CaptureReceiverByRef:
                    temp = _factory.SynthesizedLocal(receiverType, refKind: RefKind.Ref);
                    _currentConditionalAccessTarget = _factory.Local(temp);
                    break;

                default:
                    throw ExceptionUtilities.UnexpectedValue(loweringKind);
            }

            BoundExpression loweredAccessExpression = used ?
                        this.VisitExpression(node.AccessExpression) :
                        this.VisitUnusedExpression(node.AccessExpression);

            _currentConditionalAccessTarget = previousConditionalAccesTarget;

            TypeSymbol type = this.VisitType(node.Type);

            TypeSymbol nodeType = node.Type;
            TypeSymbol accessExpressionType = loweredAccessExpression.Type;

            if (accessExpressionType.SpecialType == SpecialType.System_Void)
            {
                type = nodeType = accessExpressionType;
            }

            if (accessExpressionType != nodeType && nodeType.IsNullableType())
            {
                Debug.Assert(accessExpressionType == nodeType.GetNullableUnderlyingType());
                loweredAccessExpression = _factory.New((NamedTypeSymbol)nodeType, loweredAccessExpression);

                if (unconditionalAccess != null)
                {
                    unconditionalAccess = _factory.New((NamedTypeSymbol)nodeType, unconditionalAccess);
                }
            }
            else
            {
                Debug.Assert(accessExpressionType == nodeType ||
                    (nodeType.SpecialType == SpecialType.System_Void && !used));
            }

            BoundExpression result;
            var objectType = _compilation.GetSpecialType(SpecialType.System_Object);

            rewrittenWhenNull = rewrittenWhenNull ?? _factory.Default(nodeType);

            switch (loweringKind)
            {
                case ConditionalAccessLoweringKind.None:
                    Debug.Assert(!receiverType.IsValueType);
                    result = node.Update(loweredReceiver, loweredAccessExpression, type);
                    break;

                case ConditionalAccessLoweringKind.CaptureReceiverByVal:
                    // capture the receiver into a temp
                    loweredReceiver = _factory.Sequence(
                                            _factory.AssignmentExpression(_factory.Local(temp), loweredReceiver),
                                            _factory.Local(temp));

                    goto case ConditionalAccessLoweringKind.NoCapture;

                case ConditionalAccessLoweringKind.NoCapture:
                    {
                        // (object)r != null ? access : default(T)
                        var condition = receiverType.IsNullableType() ?
                            MakeOptimizedHasValue(loweredReceiver.Syntax, loweredReceiver) :
                            _factory.ObjectNotEqual(
                                _factory.Convert(objectType, loweredReceiver),
                                _factory.Null(objectType));

                        var consequence = loweredAccessExpression;

                        result = RewriteConditionalOperator(node.Syntax,
                            condition,
                            consequence,
                            rewrittenWhenNull,
                            null,
                            nodeType);

                        if (temp != null)
                        {
                            result = _factory.Sequence(temp, result);
                        }
                    }
                    break;

                case ConditionalAccessLoweringKind.CaptureReceiverByRef:
                    // {ref T r; T v; 
                    //    r = ref receiver; 
                    //    (isClass && { v = r; r = ref v; v == null } ) ? 
                    //                                          null;
                    //                                          r.Foo()}

                    {
                        var v = _factory.SynthesizedLocal(receiverType);

                        BoundExpression captureRef = _factory.AssignmentExpression(_factory.Local(temp), loweredReceiver, refKind: RefKind.Ref);
                        BoundExpression isNull = _factory.LogicalAnd(
                                                IsClass(receiverType, objectType),
                                                _factory.Sequence(
                                                    _factory.AssignmentExpression(_factory.Local(v), _factory.Local(temp)),
                                                    _factory.AssignmentExpression(_factory.Local(temp), _factory.Local(v), RefKind.Ref),
                                                    _factory.ObjectEqual(_factory.Convert(objectType, _factory.Local(v)), _factory.Null(objectType)))
                                                );

                        result = RewriteConditionalOperator(node.Syntax,
                           isNull,
                           rewrittenWhenNull,
                           loweredAccessExpression,
                           null,
                           nodeType);

                        result = _factory.Sequence(
                                ImmutableArray.Create(temp, v),
                                captureRef,
                                result
                            );
                    }
                    break;

                case ConditionalAccessLoweringKind.DuplicateCode:
                    {
                        Debug.Assert(!receiverType.IsNullableType());

                        // if we have a class, do regular conditional access via a val temp
                        loweredReceiver = _factory.AssignmentExpression(_factory.Local(temp), loweredReceiver);
                        BoundExpression ifClass = RewriteConditionalOperator(node.Syntax,
                            _factory.ObjectNotEqual(
                                                    _factory.Convert(objectType, loweredReceiver),
                                                    _factory.Null(objectType)),
                            loweredAccessExpression,
                            rewrittenWhenNull,
                            null,
                            nodeType);

                        if (temp != null)
                        {
                            ifClass = _factory.Sequence(temp, ifClass);
                        }

                        // if we have a struct, then just access unconditionally
                        BoundExpression ifStruct = unconditionalAccess;

                        // (object)(default(T)) != null ? ifStruct: ifClass
                        result = RewriteConditionalOperator(node.Syntax,
                            IsClass(receiverType, objectType),
                            ifClass,
                            ifStruct,
                            null,
                            nodeType);
                    }
                    break;

                default:
                    throw ExceptionUtilities.Unreachable;
            }

            return result;
        }
Example #14
0
        private bool TryGetOptimizableNullableConditionalAccess(BoundExpression operand, out BoundConditionalAccess conditionalAccess)
        {
            if (operand.Kind != BoundKind.ConditionalAccess ||
                _inExpressionLambda ||
                _factory.CurrentMethod.IsAsync)
            {
                conditionalAccess = null;
                return(false);
            }

            conditionalAccess = (BoundConditionalAccess)operand;
            return(conditionalAccess.Type.IsNullableType() && !conditionalAccess.AccessExpression.Type.IsNullableType());
        }
        // IL gen can generate more compact code for certain conditional accesses 
        // by utilizing stack dup/pop instructions 
        internal BoundExpression RewriteConditionalAccess(BoundConditionalAccess node, bool used, BoundExpression rewrittenWhenNull = null)
        {
            Debug.Assert(!_inExpressionLambda);

            var loweredReceiver = this.VisitExpression(node.Receiver);
            var receiverType = loweredReceiver.Type;

            // Check trivial case
            if (loweredReceiver.IsDefaultValue())
            {
                return rewrittenWhenNull ?? _factory.Default(node.Type);
            }

            ConditionalAccessLoweringKind loweringKind;
            // dynamic receivers are not directly supported in codegen and need to be lowered to a ternary
            var lowerToTernary = node.AccessExpression.Type.IsDynamic();

            if (!lowerToTernary)
            {
                // 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 hapens 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.TernaryCaptureReceiverByVal;
            }
            else
            {
                loweringKind = ConditionalAccessLoweringKind.Ternary;
            }


            var previousConditionalAccesTarget = _currentConditionalAccessTarget;
            var currentConditionalAccessID = ++this._currentConditionalAccessID;

            LocalSymbol temp = null;
            BoundExpression unconditionalAccess = null;

            switch (loweringKind)
            {
                case ConditionalAccessLoweringKind.LoweredConditionalAccess:
                    _currentConditionalAccessTarget = new BoundConditionalReceiver(
                        loweredReceiver.Syntax, 
                        currentConditionalAccessID, 
                        receiverType);

                    break;

                case ConditionalAccessLoweringKind.Ternary:
                    _currentConditionalAccessTarget = loweredReceiver;
                    break;

                case ConditionalAccessLoweringKind.TernaryCaptureReceiverByVal:
                    temp = _factory.SynthesizedLocal(receiverType);
                    _currentConditionalAccessTarget = _factory.Local(temp);
                    break;

                default:
                    throw ExceptionUtilities.UnexpectedValue(loweringKind);
            }

            BoundExpression loweredAccessExpression = used ?
                        this.VisitExpression(node.AccessExpression) :
                        this.VisitUnusedExpression(node.AccessExpression);

            _currentConditionalAccessTarget = previousConditionalAccesTarget;

            TypeSymbol type = this.VisitType(node.Type);

            TypeSymbol nodeType = node.Type;
            TypeSymbol accessExpressionType = loweredAccessExpression.Type;

            if (accessExpressionType.SpecialType == SpecialType.System_Void)
            {
                type = nodeType = accessExpressionType;
            }

            if (accessExpressionType != nodeType && nodeType.IsNullableType())
            {
                Debug.Assert(accessExpressionType == nodeType.GetNullableUnderlyingType());
                loweredAccessExpression = _factory.New((NamedTypeSymbol)nodeType, loweredAccessExpression);

                if (unconditionalAccess != null)
                {
                    unconditionalAccess = _factory.New((NamedTypeSymbol)nodeType, unconditionalAccess);
                }
            }
            else
            {
                Debug.Assert(accessExpressionType == nodeType ||
                    (nodeType.SpecialType == SpecialType.System_Void && !used));
            }

            BoundExpression result;
            var objectType = _compilation.GetSpecialType(SpecialType.System_Object);

            switch (loweringKind)
            {
                case ConditionalAccessLoweringKind.LoweredConditionalAccess:
                    result = new BoundLoweredConditionalAccess(
                        node.Syntax, 
                        loweredReceiver,
                        receiverType.IsNullableType() ?
                                 GetNullableMethod(node.Syntax, loweredReceiver.Type, SpecialMember.System_Nullable_T_get_HasValue):
                                 null,
                        loweredAccessExpression, 
                        rewrittenWhenNull,
                        currentConditionalAccessID,
                        type);

                    break;

                case ConditionalAccessLoweringKind.TernaryCaptureReceiverByVal:
                    // capture the receiver into a temp
                    loweredReceiver = _factory.Sequence(
                                            _factory.AssignmentExpression(_factory.Local(temp), loweredReceiver),
                                            _factory.Local(temp));

                    goto case ConditionalAccessLoweringKind.Ternary;

                case ConditionalAccessLoweringKind.Ternary:
                    {
                        // (object)r != null ? access : default(T)
                        var condition = _factory.ObjectNotEqual(
                                _factory.Convert(objectType, loweredReceiver),
                                _factory.Null(objectType));

                        var consequence = loweredAccessExpression;

                        result = RewriteConditionalOperator(node.Syntax,
                            condition,
                            consequence,
                            rewrittenWhenNull ?? _factory.Default(nodeType),
                            null,
                            nodeType);

                        if (temp != null)
                        {
                            result = _factory.Sequence(temp, result);
                        }
                    }
                    break;

                default:
                    throw ExceptionUtilities.Unreachable;
            }

            return result;
        }
        public override BoundNode VisitConditionalAccess(BoundConditionalAccess node)
        {
            if (_inExpressionLambda)
            {
                Error(ErrorCode.ERR_NullPropagatingOpInExpressionTree, node);
            }

            return base.VisitConditionalAccess(node);
        }
        // in simple cases could be left unlowered.
        // IL gen can generate more compact code for unlowered conditional accesses 
        // by utilizing stack dup/pop instructions 
        internal BoundExpression VisitConditionalAccess(BoundConditionalAccess node, bool used)
        {
            Debug.Assert(!this.inExpressionLambda);

            var loweredReceiver = this.VisitExpression(node.Receiver);
            var receiverType = loweredReceiver.Type;

            //TODO: if AccessExpression does not contain awaits, the node could be left unlowered (saves a temp),
            //      but there seem to be no way of knowing that without walking AccessExpression.
            //      For now we will just check that we are in an async method, but it would be nice
            //      to have something more precise.
            var isAsync = this.factory.CurrentMethod.IsAsync;

            ConditionalAccessLoweringKind loweringKind;

            if (!receiverType.IsValueType && !isAsync && !node.Type.IsDynamic())
            {
                // trivial cases can be handled more efficiently in IL gen
                loweringKind = ConditionalAccessLoweringKind.None;
            }
            else if(NeedsTemp(loweredReceiver, localsMayBeAssigned: false))
            {
                if (receiverType.IsReferenceType || receiverType.IsNullableType())
                {
                    loweringKind = ConditionalAccessLoweringKind.CaptureReceiverByVal;
                }
                else
                {
                    loweringKind = isAsync ?
                        ConditionalAccessLoweringKind.DuplicateCode :
                        ConditionalAccessLoweringKind.CaptureReceiverByRef;
                }
            }
            else
            {
                // locals do not need to be captured
                loweringKind = ConditionalAccessLoweringKind.NoCapture;
            }


            var previousConditionalAccesTarget = currentConditionalAccessTarget;
            LocalSymbol temp = null;
            BoundExpression unconditionalAccess = null;

            switch (loweringKind)
            {
                case ConditionalAccessLoweringKind.None:
                    currentConditionalAccessTarget = null;
                    break;

                case ConditionalAccessLoweringKind.NoCapture:
                    currentConditionalAccessTarget = loweredReceiver;
                    break;

                case ConditionalAccessLoweringKind.DuplicateCode:
                    currentConditionalAccessTarget = loweredReceiver;
                    unconditionalAccess = used? 
                        this.VisitExpression(node.AccessExpression) :
                        this.VisitUnusedExpression(node.AccessExpression);

                    goto case ConditionalAccessLoweringKind.CaptureReceiverByVal;

                case ConditionalAccessLoweringKind.CaptureReceiverByVal:
                    temp = factory.SynthesizedLocal(receiverType);
                    currentConditionalAccessTarget = factory.Local(temp);
                    break;

                case ConditionalAccessLoweringKind.CaptureReceiverByRef:
                    temp = factory.SynthesizedLocal(receiverType, refKind: RefKind.Ref);
                    currentConditionalAccessTarget = factory.Local(temp);
                    break;
            }

            BoundExpression loweredAccessExpression = used ?
                        this.VisitExpression(node.AccessExpression) :
                        this.VisitUnusedExpression(node.AccessExpression);

            currentConditionalAccessTarget = previousConditionalAccesTarget;

            TypeSymbol type = this.VisitType(node.Type);

            TypeSymbol nodeType = node.Type;
            TypeSymbol accessExpressionType = loweredAccessExpression.Type;


            if (accessExpressionType != nodeType && nodeType.IsNullableType())
            {
                Debug.Assert(accessExpressionType == nodeType.GetNullableUnderlyingType());
                loweredAccessExpression = factory.New((NamedTypeSymbol)nodeType, loweredAccessExpression);
            }
            else
            {
                Debug.Assert(accessExpressionType == nodeType ||
                    (accessExpressionType.SpecialType == SpecialType.System_Void && !used));
            }

            BoundExpression result;
                     var objectType = compilation.GetSpecialType(SpecialType.System_Object);

            switch (loweringKind)
            {
                case ConditionalAccessLoweringKind.None:
                    Debug.Assert(!receiverType.IsValueType);
                    result = node.Update(loweredReceiver, loweredAccessExpression, type);
                    break;

                case ConditionalAccessLoweringKind.CaptureReceiverByVal:
                    // capture the receiver into a temp
                    loweredReceiver = factory.AssignmentExpression(factory.Local(temp), loweredReceiver);
                    goto case ConditionalAccessLoweringKind.NoCapture;

                case ConditionalAccessLoweringKind.NoCapture:
                    {

                        // (object)r != null ? access : default(T)
                        var condition = receiverType.IsNullableType() ?
                            MakeOptimizedHasValue(loweredReceiver.Syntax, loweredReceiver) :
                            factory.ObjectNotEqual(
                                factory.Convert(objectType, loweredReceiver),
                                factory.Null(objectType));

                        var consequence = loweredAccessExpression;
                        var alternative = factory.Default(nodeType);

                        result = RewriteConditionalOperator(node.Syntax,
                            condition,
                            consequence,
                            alternative,
                            null,
                            nodeType);

                        if (temp != null)
                        {
                            result = factory.Sequence(temp, result);
                        }
                    }
                    break;

                case ConditionalAccessLoweringKind.CaptureReceiverByRef:
                    // {ref T r; T v; 
                    //    r = ref receiver; 
                    //    (isClass && { v = r; r = ref v; v == null } ) ? 
                    //                                          null;
                    //                                          r.Foo()}

                    {
                        var v = factory.SynthesizedLocal(receiverType);

                        BoundExpression captureRef = factory.AssignmentExpression(factory.Local(temp), loweredReceiver, refKind: RefKind.Ref);
                        BoundExpression isNull = factory.LogicalAnd(
                                                IsClass(receiverType, objectType),
                                                factory.Sequence(
                                                    factory.AssignmentExpression(factory.Local(v), factory.Local(temp)),
                                                    factory.AssignmentExpression(factory.Local(temp), factory.Local(v), RefKind.Ref),
                                                    factory.ObjectEqual(factory.Convert(objectType, factory.Local(v)), factory.Null(objectType)))
                                                );

                        result = RewriteConditionalOperator(node.Syntax,
                           isNull,
                           factory.Default(nodeType),
                           loweredAccessExpression,
                           null,
                           nodeType);

                        result = factory.Sequence(
                                ImmutableArray.Create(temp, v),
                                captureRef,
                                result
                            );

                    }
                    break;

                case ConditionalAccessLoweringKind.DuplicateCode:
                    {
                        Debug.Assert(!receiverType.IsNullableType());

                        // if we have a class, do regular conditional access via a val temp
                        loweredReceiver = factory.AssignmentExpression(factory.Local(temp), loweredReceiver);
                        BoundExpression ifClass = RewriteConditionalOperator(node.Syntax,
                            factory.ObjectNotEqual(
                                                    factory.Convert(objectType, loweredReceiver),
                                                    factory.Null(objectType)),
                            loweredAccessExpression,
                            factory.Default(nodeType),
                            null,
                            nodeType);

                        if (temp != null)
                        {
                            ifClass = factory.Sequence(temp, ifClass);
                        }

                        // if we have a struct, then just access unconditionally
                        BoundExpression ifStruct = unconditionalAccess;

                        // (object)(default(T)) != null ? ifStruct: ifClass
                        result = RewriteConditionalOperator(node.Syntax,
                            IsClass(receiverType, objectType),
                            ifClass,
                            ifStruct,
                            null,
                            nodeType);
                    }
                    break;

                default:
                    throw ExceptionUtilities.Unreachable;
            }

            return result;
        }