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); }
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)); }
// 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); }
// 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; }
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; }