private BoundExpression MakeNullCoalescingOperator( SyntaxNode syntax, BoundExpression rewrittenLeft, BoundExpression rewrittenRight, Conversion leftConversion, BoundNullCoalescingOperatorResultKind resultKind, TypeSymbol?rewrittenResultType) { Debug.Assert(rewrittenLeft != null); Debug.Assert(rewrittenRight != null); Debug.Assert(leftConversion.IsValid); Debug.Assert(rewrittenResultType is { });
private BoundExpression MakeNullCoalescingOperator( SyntaxNode syntax, BoundExpression rewrittenLeft, BoundExpression rewrittenRight, BoundValuePlaceholder?leftPlaceholder, BoundExpression?leftConversion, BoundNullCoalescingOperatorResultKind resultKind, TypeSymbol?rewrittenResultType) { Debug.Assert(rewrittenLeft != null); Debug.Assert(rewrittenRight != null); Debug.Assert(BoundNode.GetConversion(leftConversion, leftPlaceholder).IsValid); Debug.Assert(rewrittenResultType is { });
private BoundExpression MakeNullCoalescingOperator( SyntaxNode syntax, BoundExpression rewrittenLeft, BoundExpression rewrittenRight, Conversion leftConversion, BoundNullCoalescingOperatorResultKind resultKind, TypeSymbol rewrittenResultType) { Debug.Assert(rewrittenLeft != null); Debug.Assert(rewrittenRight != null); Debug.Assert(leftConversion.IsValid); Debug.Assert((object)rewrittenResultType != null); Debug.Assert(rewrittenRight.Type.Equals(rewrittenResultType, TypeCompareKind.IgnoreDynamicAndTupleNames | TypeCompareKind.IgnoreNullableModifiersForReferenceTypes)); var isUnconstrainedTypeParameter = (object)rewrittenLeft.Type != null && !rewrittenLeft.Type.IsReferenceType && !rewrittenLeft.Type.IsValueType; // first we can make a small optimization: // If left is a constant then we already know whether it is null or not. If it is null then we // can simply generate "right". If it is not null then we can simply generate // MakeConversion(left). This does not hold when the left is an unconstrained type parameter: at runtime, // it can be either left or right depending on the runtime type of T if (!isUnconstrainedTypeParameter) { if (rewrittenLeft.IsDefaultValue()) { return(rewrittenRight); } if (rewrittenLeft.ConstantValue != null) { Debug.Assert(!rewrittenLeft.ConstantValue.IsNull); return(GetConvertedLeftForNullCoalescingOperator(rewrittenLeft, leftConversion, rewrittenResultType)); } } // string concatenation is never null. // interpolated string lowering may introduce redundant null coalescing, which we have to remove. if (IsStringConcat(rewrittenLeft)) { return(GetConvertedLeftForNullCoalescingOperator(rewrittenLeft, leftConversion, rewrittenResultType)); } // if left conversion is intrinsic implicit (always succeeds) and results in a reference type // we can apply conversion before doing the null check that allows for a more efficient IL emit. if (rewrittenLeft.Type.IsReferenceType && leftConversion.IsImplicit && !leftConversion.IsUserDefined) { if (!leftConversion.IsIdentity) { rewrittenLeft = MakeConversionNode(rewrittenLeft.Syntax, rewrittenLeft, leftConversion, rewrittenResultType, @checked: false); } return(new BoundNullCoalescingOperator(syntax, rewrittenLeft, rewrittenRight, Conversion.Identity, resultKind, rewrittenResultType)); } if (leftConversion.IsIdentity || leftConversion.Kind == ConversionKind.ExplicitNullable) { var conditionalAccess = rewrittenLeft as BoundLoweredConditionalAccess; if (conditionalAccess != null && (conditionalAccess.WhenNullOpt == null || NullableNeverHasValue(conditionalAccess.WhenNullOpt))) { var notNullAccess = NullableAlwaysHasValue(conditionalAccess.WhenNotNull); if (notNullAccess != null) { var whenNullOpt = rewrittenRight; if (whenNullOpt.Type.IsNullableType()) { notNullAccess = conditionalAccess.WhenNotNull; } if (whenNullOpt.IsDefaultValue()) { whenNullOpt = null; } return(conditionalAccess.Update( conditionalAccess.Receiver, conditionalAccess.HasValueMethodOpt, whenNotNull: notNullAccess, whenNullOpt: whenNullOpt, id: conditionalAccess.Id, type: rewrittenResultType )); } } } // Optimize left ?? right to left.GetValueOrDefault() when left is T? and right is the default value of T if (rewrittenLeft.Type.IsNullableType() && RemoveIdentityConversions(rewrittenRight).IsDefaultValue() && rewrittenRight.Type.Equals(rewrittenLeft.Type.GetNullableUnderlyingType(), TypeCompareKind.AllIgnoreOptions) && TryGetNullableMethod(rewrittenLeft.Syntax, rewrittenLeft.Type, SpecialMember.System_Nullable_T_GetValueOrDefault, out MethodSymbol getValueOrDefault)) { return(BoundCall.Synthesized(rewrittenLeft.Syntax, rewrittenLeft, getValueOrDefault)); } // We lower left ?? right to // // var temp = left; // (temp != null) ? MakeConversion(temp) : right // BoundAssignmentOperator tempAssignment; BoundLocal boundTemp = _factory.StoreToTemp(rewrittenLeft, out tempAssignment); // temp != null BoundExpression nullCheck = MakeNullCheck(syntax, boundTemp, BinaryOperatorKind.NotEqual); // MakeConversion(temp, rewrittenResultType) BoundExpression convertedLeft = GetConvertedLeftForNullCoalescingOperator(boundTemp, leftConversion, rewrittenResultType); Debug.Assert(convertedLeft.HasErrors || convertedLeft.Type.Equals(rewrittenResultType, TypeCompareKind.IgnoreDynamicAndTupleNames | TypeCompareKind.IgnoreNullableModifiersForReferenceTypes)); // (temp != null) ? MakeConversion(temp, LeftConversion) : RightOperand BoundExpression conditionalExpression = RewriteConditionalOperator( syntax: syntax, rewrittenCondition: nullCheck, rewrittenConsequence: convertedLeft, rewrittenAlternative: rewrittenRight, constantValueOpt: null, rewrittenType: rewrittenResultType, isRef: false); Debug.Assert(conditionalExpression.ConstantValue == null); // we shouldn't have hit this else case otherwise Debug.Assert(conditionalExpression.Type.Equals(rewrittenResultType, TypeCompareKind.IgnoreDynamicAndTupleNames | TypeCompareKind.IgnoreNullableModifiersForReferenceTypes)); return(new BoundSequence( syntax: syntax, locals: ImmutableArray.Create(boundTemp.LocalSymbol), sideEffects: ImmutableArray.Create <BoundExpression>(tempAssignment), value: conditionalExpression, type: rewrittenResultType)); }