private BoundExpression RewritePointerElementAccess(BoundPointerElementAccess node, BoundExpression rewrittenExpression, BoundExpression rewrittenIndex) { // Optimization: p[0] == *p if (rewrittenIndex.IsDefaultValue()) { return(new BoundPointerIndirectionOperator( node.Syntax, rewrittenExpression, node.Type)); } BinaryOperatorKind additionKind = BinaryOperatorKind.Addition; switch (rewrittenIndex.Type.SpecialType) { case SpecialType.System_Int32: additionKind |= BinaryOperatorKind.PointerAndIntAddition; break; case SpecialType.System_UInt32: additionKind |= BinaryOperatorKind.PointerAndUIntAddition; break; case SpecialType.System_Int64: additionKind |= BinaryOperatorKind.PointerAndLongAddition; break; case SpecialType.System_UInt64: additionKind |= BinaryOperatorKind.PointerAndULongAddition; break; default: throw ExceptionUtilities.UnexpectedValue(rewrittenIndex.Type.SpecialType); } if (node.Checked) { additionKind |= BinaryOperatorKind.Checked; } return(new BoundPointerIndirectionOperator( node.Syntax, MakeBinaryOperator( node.Syntax, additionKind, rewrittenExpression, rewrittenIndex, rewrittenExpression.Type, method: null, isPointerElementAccess: true), //see RewriterPointerNumericOperator node.Type)); }
private BoundExpression RewritePointerElementAccess(BoundPointerElementAccess node, BoundExpression rewrittenExpression, BoundExpression rewrittenIndex) { // Optimization: p[0] == *p if (rewrittenIndex.IsDefaultValue()) { return(new BoundPointerIndirectionOperator( node.Syntax, rewrittenExpression, node.Type)); } BinaryOperatorKind additionKind = BinaryOperatorKind.Addition; Debug.Assert(rewrittenExpression.Type is { });
private BoundExpression RewritePointerElementAccess(BoundPointerElementAccess node, BoundExpression rewrittenExpression, BoundExpression rewrittenIndex) { // Optimization: p[0] == *p if (rewrittenIndex.IsDefaultValue()) { return new BoundPointerIndirectionOperator( node.Syntax, rewrittenExpression, node.Type); } BinaryOperatorKind additionKind = BinaryOperatorKind.Addition; switch (rewrittenIndex.Type.SpecialType) { case SpecialType.System_Int32: additionKind |= BinaryOperatorKind.PointerAndIntAddition; break; case SpecialType.System_UInt32: additionKind |= BinaryOperatorKind.PointerAndUIntAddition; break; case SpecialType.System_Int64: additionKind |= BinaryOperatorKind.PointerAndLongAddition; break; case SpecialType.System_UInt64: additionKind |= BinaryOperatorKind.PointerAndULongAddition; break; default: throw ExceptionUtilities.UnexpectedValue(rewrittenIndex.Type.SpecialType); } if (node.Checked) { additionKind |= BinaryOperatorKind.Checked; } return new BoundPointerIndirectionOperator( node.Syntax, MakeBinaryOperator( node.Syntax, additionKind, rewrittenExpression, rewrittenIndex, rewrittenExpression.Type, method: null, isPointerElementAccess: true), //see RewriterPointerNumericOperator node.Type); }
// a simple check for common non-side-effecting expressions internal static bool ReadIsSideeffecting( BoundExpression expression) { if (expression.ConstantValue != null) { return(false); } if (expression.IsDefaultValue()) { return(false); } switch (expression.Kind) { case BoundKind.ThisReference: case BoundKind.BaseReference: case BoundKind.Literal: case BoundKind.Parameter: case BoundKind.Local: case BoundKind.Lambda: return(false); case BoundKind.Conversion: var conv = (BoundConversion)expression; return(conv.ConversionHasSideEffects() || ReadIsSideeffecting(conv.Operand)); case BoundKind.ObjectCreationExpression: // common production of lowered conversions to nullable // new S?(arg) if (expression.Type.IsNullableType()) { var objCreation = (BoundObjectCreationExpression)expression; return(objCreation.Arguments.Length == 1 && ReadIsSideeffecting(objCreation.Arguments[0])); } return(false); default: return(true); } }
/// <summary> /// Variables local to current frame do not need temps when re-read multiple times /// as long as there is no code that may write to locals in between accesses and they /// are not captured. /// /// Example: /// l += goo(ref l); /// /// even though l is a local, we must access it via a temp since "goo(ref l)" may change it /// on between accesses. /// /// Note: In <c>this.x++</c>, <c>this</c> cannot change between reads. But in <c>(this, ...) == (..., this.Mutate())</c> it can. /// </summary> internal static bool CanChangeValueBetweenReads( BoundExpression expression, bool localsMayBeAssignedOrCaptured = true, bool structThisCanChangeValueBetweenReads = false) { if (expression.IsDefaultValue()) { return(false); } if (expression.ConstantValue != null) { var type = expression.Type; return(!ConstantValueIsTrivial(type)); } switch (expression.Kind) { case BoundKind.ThisReference: return(structThisCanChangeValueBetweenReads && ((BoundThisReference)expression).Type.IsStructType()); case BoundKind.BaseReference: return(false); case BoundKind.Literal: var type = expression.Type; return(!ConstantValueIsTrivial(type)); case BoundKind.Parameter: return(localsMayBeAssignedOrCaptured || ((BoundParameter)expression).ParameterSymbol.RefKind != RefKind.None); case BoundKind.Local: return(localsMayBeAssignedOrCaptured || ((BoundLocal)expression).LocalSymbol.RefKind != RefKind.None); case BoundKind.TypeExpression: return(false); default: return(true); } }
/// <summary> /// Returns true if the initializer is a field initializer which should be optimized out /// </summary> private static bool ShouldOptimizeOutInitializer(BoundStatement initializer) { BoundStatement statement = initializer; if (initializer.Kind == BoundKind.SequencePointWithSpan) { statement = ((BoundSequencePointWithSpan)initializer).StatementOpt; } else if (initializer.Kind == BoundKind.SequencePoint) { statement = ((BoundSequencePoint)initializer).StatementOpt; } if (statement == null || statement.Kind != BoundKind.ExpressionStatement) { return(false); } BoundAssignmentOperator assignment = ((BoundExpressionStatement)statement).Expression as BoundAssignmentOperator; if (assignment == null) { return(false); } Debug.Assert(assignment.Left.Kind == BoundKind.FieldAccess); var lhsField = ((BoundFieldAccess)assignment.Left).FieldSymbol; if (!lhsField.IsStatic && lhsField.ContainingType.IsStructType()) { return(false); } BoundExpression rhs = assignment.Right; return(rhs.IsDefaultValue()); }
/// <summary> /// Helper method to generate a lowered conversion. /// </summary> private BoundExpression MakeConversion( BoundConversion oldNode, CSharpSyntaxNode syntax, BoundExpression rewrittenOperand, ConversionKind conversionKind, MethodSymbol symbolOpt, bool @checked, bool explicitCastInCode, bool isExtensionMethod, bool isArrayIndex, ConstantValue constantValueOpt, TypeSymbol rewrittenType) { Debug.Assert(oldNode == null || oldNode.Syntax == syntax); Debug.Assert((object)rewrittenType != null); @checked = @checked && (inExpressionLambda && (explicitCastInCode || DistinctSpecialTypes(rewrittenOperand.Type, rewrittenType)) || NeedsChecked(rewrittenOperand.Type, rewrittenType)); switch (conversionKind) { case ConversionKind.Identity: // Spec 6.1.1: // An identity conversion converts from any type to the same type. // This conversion exists such that an entity that already has a required type can be said to be convertible to that type. // Because object and dynamic are considered equivalent there is an identity conversion between object and dynamic, // and between constructed types that are the same when replacing all occurrences of dynamic with object. // Why ignoreDynamic: false? // Lowering phase treats object and dynamic as equivalent types. So we don't need to produce any conversion here, // but we need to change the Type property on the resulting BoundExpression to match the rewrittenType. // This is necessary so that subsequent lowering transformations see that the expression is dynamic. if (inExpressionLambda || !rewrittenOperand.Type.Equals(rewrittenType, ignoreCustomModifiers: false, ignoreDynamic: false)) { break; } // 4.1.6 C# spec: To force a value of a floating point type to the exact precision of its type, an explicit cast can be used. // If this is not an identity conversion of a float with unknown precision, strip away the identity conversion. if (!(explicitCastInCode && IsFloatPointExpressionOfUnknownPrecision(rewrittenOperand))) { return rewrittenOperand; } break; case ConversionKind.ExplicitUserDefined: case ConversionKind.ImplicitUserDefined: return RewriteUserDefinedConversion( syntax: syntax, rewrittenOperand: rewrittenOperand, method: symbolOpt, rewrittenType: rewrittenType, conversionKind: conversionKind); case ConversionKind.IntPtr: return RewriteIntPtrConversion(oldNode, syntax, rewrittenOperand, conversionKind, symbolOpt, @checked, explicitCastInCode, isExtensionMethod, isArrayIndex, constantValueOpt, rewrittenType); case ConversionKind.ImplicitNullable: case ConversionKind.ExplicitNullable: return RewriteNullableConversion( syntax: syntax, rewrittenOperand: rewrittenOperand, conversionKind: conversionKind, @checked: @checked, explicitCastInCode: explicitCastInCode, rewrittenType: rewrittenType); case ConversionKind.Boxing: if (!inExpressionLambda) { // We can perform some optimizations if we have a nullable value type // as the operand and we know its nullability: // * (object)new int?() is the same as (object)null // * (object)new int?(123) is the same as (object)123 if (NullableNeverHasValue(rewrittenOperand)) { return new BoundDefaultOperator(syntax, rewrittenType); } BoundExpression nullableValue = NullableAlwaysHasValue(rewrittenOperand); if (nullableValue != null) { // Recurse, eliminating the unnecessary ctor. return MakeConversion(oldNode, syntax, nullableValue, conversionKind, symbolOpt, @checked, explicitCastInCode, isExtensionMethod, isArrayIndex, constantValueOpt, rewrittenType); } } break; case ConversionKind.NullLiteral: if (!inExpressionLambda || !explicitCastInCode) { return new BoundDefaultOperator(syntax, rewrittenType); } break; case ConversionKind.ImplicitReference: case ConversionKind.ExplicitReference: if (rewrittenOperand.IsDefaultValue() && (!inExpressionLambda || !explicitCastInCode)) { return new BoundDefaultOperator(syntax, rewrittenType); } break; case ConversionKind.ImplicitNumeric: case ConversionKind.ExplicitNumeric: if (rewrittenOperand.IsDefaultValue() && (!inExpressionLambda || !explicitCastInCode)) { return new BoundDefaultOperator(syntax, rewrittenType); } if (rewrittenType.SpecialType == SpecialType.System_Decimal || rewrittenOperand.Type.SpecialType == SpecialType.System_Decimal) { return RewriteDecimalConversion(oldNode, syntax, rewrittenOperand, rewrittenOperand.Type, rewrittenType); } break; case ConversionKind.ImplicitEnumeration: // A conversion from constant zero to nullable is actually classified as an // implicit enumeration conversion, not an implicit nullable conversion. // Lower it to (E?)(E)0. if (rewrittenType.IsNullableType()) { var operand = MakeConversion( oldNode, syntax, rewrittenOperand, ConversionKind.ImplicitEnumeration, symbolOpt, @checked, explicitCastInCode, isExtensionMethod, false, constantValueOpt, rewrittenType.GetNullableUnderlyingType()); return MakeConversion( oldNode, syntax, operand, ConversionKind.ImplicitNullable, symbolOpt, @checked, explicitCastInCode, isExtensionMethod, isArrayIndex, constantValueOpt, rewrittenType); } goto case ConversionKind.ExplicitEnumeration; case ConversionKind.ExplicitEnumeration: if (!rewrittenType.IsNullableType() && rewrittenOperand.IsDefaultValue() && (!inExpressionLambda || !explicitCastInCode)) { return new BoundDefaultOperator(syntax, rewrittenType); } if (rewrittenType.SpecialType == SpecialType.System_Decimal) { Debug.Assert(rewrittenOperand.Type.IsEnumType()); var underlyingTypeFrom = rewrittenOperand.Type.GetEnumUnderlyingType(); rewrittenOperand = MakeConversion(rewrittenOperand, underlyingTypeFrom, false); return RewriteDecimalConversion(oldNode, syntax, rewrittenOperand, underlyingTypeFrom, rewrittenType); } else if (rewrittenOperand.Type.SpecialType == SpecialType.System_Decimal) { // This is where we handle conversion from Decimal to Enum: e.g., E e = (E) d; // where 'e' is of type Enum E and 'd' is of type Decimal. // Conversion can be simply done by applying its underlying numeric type to RewriteDecimalConversion(). Debug.Assert(rewrittenType.IsEnumType()); var underlyingTypeTo = rewrittenType.GetEnumUnderlyingType(); var rewrittenNode = RewriteDecimalConversion(oldNode, syntax, rewrittenOperand, rewrittenOperand.Type, underlyingTypeTo); // However, the type of the rewritten node becomes underlying numeric type, not Enum type, // which violates the overall constraint saying the type cannot be changed during rewriting (see LocalRewriter.cs). // Instead of loosening this constraint, we return BoundConversion from underlying numeric type to Enum type, // which will be eliminated during emitting (see EmitEnumConversion): e.g., E e = (E)(int) d; return new BoundConversion( syntax, rewrittenNode, conversionKind, LookupResultKind.Viable, isBaseConversion: false, symbolOpt: symbolOpt, @checked: false, explicitCastInCode: explicitCastInCode, isExtensionMethod: isExtensionMethod, isArrayIndex: false, constantValueOpt: constantValueOpt, type: rewrittenType); } break; case ConversionKind.ImplicitDynamic: case ConversionKind.ExplicitDynamic: Debug.Assert((object)symbolOpt == null); Debug.Assert(!isExtensionMethod); Debug.Assert(constantValueOpt == null); return dynamicFactory.MakeDynamicConversion(rewrittenOperand, explicitCastInCode || conversionKind == ConversionKind.ExplicitDynamic, isArrayIndex, @checked, rewrittenType).ToExpression(); default: break; } return oldNode != null ? oldNode.Update( rewrittenOperand, conversionKind, oldNode.ResultKind, isBaseConversion: oldNode.IsBaseConversion, symbolOpt: symbolOpt, @checked: @checked, explicitCastInCode: explicitCastInCode, isExtensionMethod: isExtensionMethod, isArrayIndex: isArrayIndex, constantValueOpt: constantValueOpt, type: rewrittenType) : new BoundConversion( syntax, rewrittenOperand, conversionKind, LookupResultKind.Viable, isBaseConversion: false, symbolOpt: symbolOpt, @checked: @checked, explicitCastInCode: explicitCastInCode, isExtensionMethod: isExtensionMethod, isArrayIndex: isArrayIndex, constantValueOpt: constantValueOpt, type: rewrittenType); }
private static bool IsNullOrEmptyStringConstant(BoundExpression operand) { return((operand.ConstantValue != null && string.IsNullOrEmpty(operand.ConstantValue.StringValue)) || operand.IsDefaultValue()); }
private static bool IsNullOrEmptyStringConstant(BoundExpression operand) { return (operand.ConstantValue != null && string.IsNullOrEmpty(operand.ConstantValue.StringValue)) || operand.IsDefaultValue(); }
private BoundExpression MakeNullCoalescingOperator( CSharpSyntaxNode syntax, BoundExpression rewrittenLeft, BoundExpression rewrittenRight, Conversion leftConversion, 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, ignoreDynamic: true)); if (_inExpressionLambda) { TypeSymbol strippedLeftType = rewrittenLeft.Type.StrippedType(); Conversion rewrittenConversion = MakeConversion(syntax, leftConversion, strippedLeftType, rewrittenResultType); return new BoundNullCoalescingOperator(syntax, rewrittenLeft, rewrittenRight, rewrittenConversion, rewrittenResultType); } // 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). if (rewrittenLeft.IsDefaultValue()) { return rewrittenRight; } if (rewrittenLeft.ConstantValue != null) { Debug.Assert(!rewrittenLeft.ConstantValue.IsNull); 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, 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.Type.SpecialType != SpecialType.System_Decimal) { whenNullOpt = null; } return conditionalAccess.Update( conditionalAccess.Receiver, conditionalAccess.HasValueMethodOpt, whenNotNull: notNullAccess, whenNullOpt: whenNullOpt, id: conditionalAccess.Id, type: rewrittenResultType ); } } } // 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.Type.Equals(rewrittenResultType, ignoreDynamic: true)); // (temp != null) ? MakeConversion(temp, LeftConversion) : RightOperand BoundExpression conditionalExpression = RewriteConditionalOperator( syntax: syntax, rewrittenCondition: nullCheck, rewrittenConsequence: convertedLeft, rewrittenAlternative: rewrittenRight, constantValueOpt: null, rewrittenType: rewrittenResultType); Debug.Assert(conditionalExpression.ConstantValue == null); // we shouldn't have hit this else case otherwise Debug.Assert(conditionalExpression.Type.Equals(rewrittenResultType, ignoreDynamic: true)); return new BoundSequence( syntax: syntax, locals: ImmutableArray.Create(boundTemp.LocalSymbol), sideEffects: ImmutableArray.Create<BoundExpression>(tempAssignment), value: conditionalExpression, type: rewrittenResultType); }
// a simple check for common non-side-effecting expressions internal static bool ReadIsSideeffecting( BoundExpression expression) { if (expression.ConstantValue != null) { return false; } if (expression.IsDefaultValue()) { return false; } switch (expression.Kind) { case BoundKind.ThisReference: case BoundKind.BaseReference: case BoundKind.Literal: case BoundKind.Parameter: case BoundKind.Local: case BoundKind.Lambda: return false; case BoundKind.Conversion: var conv = (BoundConversion)expression; return conv.ConversionHasSideEffects() || ReadIsSideeffecting(conv.Operand); case BoundKind.ObjectCreationExpression: // common production of lowered conversions to nullable // new S?(arg) if (expression.Type.IsNullableType()) { var objCreation = (BoundObjectCreationExpression)expression; return objCreation.Arguments.Length == 1 && ReadIsSideeffecting(objCreation.Arguments[0]); } return true; case BoundKind.Call: var call = (BoundCall)expression; var method = call.Method; // common production of lowered lifted operators // GetValueOrDefault is known to be not sideeffecting. if (method.ContainingType?.IsNullableType() == true) { if (IsSpecialMember(method, SpecialMember.System_Nullable_T_GetValueOrDefault) || IsSpecialMember(method, SpecialMember.System_Nullable_T_get_HasValue)) { return ReadIsSideeffecting(call.ReceiverOpt); } } return true; default: return true; } }
/// <summary> /// Variables local to current frame do not need temps when re-read multiple times /// as long as there is no code that may write to locals in between accesses and they /// are not captured. /// /// Example: /// l += foo(ref l); /// /// even though l is a local, we must access it via a temp since "foo(ref l)" may change it /// on between accesses. /// </summary> internal static bool CanChangeValueBetweenReads( BoundExpression expression, bool localsMayBeAssignedOrCaptured = true) { if (expression.IsDefaultValue()) { return false; } if (expression.ConstantValue != null) { var type = expression.Type; return !ConstantValueIsTrivial(type); } switch (expression.Kind) { case BoundKind.ThisReference: case BoundKind.BaseReference: return false; case BoundKind.Literal: var type = expression.Type; return !ConstantValueIsTrivial(type); case BoundKind.Parameter: return localsMayBeAssignedOrCaptured || ((BoundParameter)expression).ParameterSymbol.RefKind != RefKind.None; case BoundKind.Local: return localsMayBeAssignedOrCaptured || ((BoundLocal)expression).LocalSymbol.RefKind != RefKind.None; default: return true; } }
// a simple check for common nonsideeffecting expressions internal static bool ReadIsSideeffecting( BoundExpression expression) { if (expression.ConstantValue != null) { return false; } if (expression.IsDefaultValue()) { return false; } switch (expression.Kind) { case BoundKind.ThisReference: case BoundKind.BaseReference: case BoundKind.Literal: case BoundKind.Parameter: case BoundKind.Local: case BoundKind.Lambda: return false; case BoundKind.Conversion: var conv = (BoundConversion)expression; return conv.ConversionHasSideEffects() || ReadIsSideeffecting(conv.Operand); case BoundKind.ObjectCreationExpression: // common production of lowered conversions to nullable // new S?(arg) if (expression.Type.IsNullableType()) { var objCreation = (BoundObjectCreationExpression)expression; return objCreation.Arguments.Length == 1 && ReadIsSideeffecting(objCreation.Arguments[0]); } return false; default: return true; } }
// a simple check for common non-side-effecting expressions internal static bool ReadIsSideeffecting( BoundExpression expression) { if (expression.ConstantValue != null) { return(false); } if (expression.IsDefaultValue()) { return(false); } switch (expression.Kind) { case BoundKind.ThisReference: case BoundKind.BaseReference: case BoundKind.Literal: case BoundKind.Parameter: case BoundKind.Local: case BoundKind.Lambda: return(false); case BoundKind.Conversion: var conv = (BoundConversion)expression; return(conv.ConversionHasSideEffects() || ReadIsSideeffecting(conv.Operand)); case BoundKind.PassByCopy: return(ReadIsSideeffecting(((BoundPassByCopy)expression).Expression)); case BoundKind.ObjectCreationExpression: // common production of lowered conversions to nullable // new S?(arg) if (expression.Type.IsNullableType()) { var objCreation = (BoundObjectCreationExpression)expression; return(objCreation.Arguments.Length == 1 && ReadIsSideeffecting(objCreation.Arguments[0])); } return(true); case BoundKind.Call: var call = (BoundCall)expression; var method = call.Method; // common production of lowered lifted operators // GetValueOrDefault is known to be not sideeffecting. if (method.ContainingType?.IsNullableType() == true) { if (IsSpecialMember(method, SpecialMember.System_Nullable_T_GetValueOrDefault) || IsSpecialMember(method, SpecialMember.System_Nullable_T_get_HasValue)) { return(ReadIsSideeffecting(call.ReceiverOpt)); } } return(true); default: return(true); } }
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)); if (_inExpressionLambda) { TypeSymbol strippedLeftType = rewrittenLeft.Type.StrippedType(); Conversion rewrittenConversion = TryMakeConversion(syntax, leftConversion, strippedLeftType, rewrittenResultType); if (!rewrittenConversion.Exists) { return(BadExpression(syntax, rewrittenResultType, rewrittenLeft, rewrittenRight)); } return(new BoundNullCoalescingOperator(syntax, rewrittenLeft, rewrittenRight, rewrittenConversion, resultKind, rewrittenResultType)); } 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.Type.SpecialType != SpecialType.System_Decimal) { 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)); }
private BoundExpression MakeBinaryOperator( BoundBinaryOperator oldNode, CSharpSyntaxNode syntax, BinaryOperatorKind operatorKind, BoundExpression loweredLeft, BoundExpression loweredRight, TypeSymbol type, MethodSymbol method, bool isPointerElementAccess = false, bool isCompoundAssignment = false, BoundUnaryOperator applyParentUnaryOperator = null) { Debug.Assert(oldNode == null || (oldNode.Syntax == syntax)); if (_inExpressionLambda) { switch (operatorKind.Operator() | operatorKind.OperandTypes()) { case BinaryOperatorKind.ObjectAndStringConcatenation: case BinaryOperatorKind.StringAndObjectConcatenation: case BinaryOperatorKind.StringConcatenation: return RewriteStringConcatenation(syntax, operatorKind, loweredLeft, loweredRight, type); case BinaryOperatorKind.DelegateCombination: return RewriteDelegateOperation(syntax, operatorKind, loweredLeft, loweredRight, type, SpecialMember.System_Delegate__Combine); case BinaryOperatorKind.DelegateRemoval: return RewriteDelegateOperation(syntax, operatorKind, loweredLeft, loweredRight, type, SpecialMember.System_Delegate__Remove); case BinaryOperatorKind.DelegateEqual: return RewriteDelegateOperation(syntax, operatorKind, loweredLeft, loweredRight, type, SpecialMember.System_Delegate__op_Equality); case BinaryOperatorKind.DelegateNotEqual: return RewriteDelegateOperation(syntax, operatorKind, loweredLeft, loweredRight, type, SpecialMember.System_Delegate__op_Inequality); } } else // try to lower the expression. { if (operatorKind.IsDynamic()) { Debug.Assert(!isPointerElementAccess); if (operatorKind.IsLogical()) { return MakeDynamicLogicalBinaryOperator(syntax, operatorKind, loweredLeft, loweredRight, method, type, isCompoundAssignment, applyParentUnaryOperator); } else { Debug.Assert((object)method == null); return _dynamicFactory.MakeDynamicBinaryOperator(operatorKind, loweredLeft, loweredRight, isCompoundAssignment, type).ToExpression(); } } if (operatorKind.IsLifted()) { return RewriteLiftedBinaryOperator(syntax, operatorKind, loweredLeft, loweredRight, type, method); } if (operatorKind.IsUserDefined()) { return LowerUserDefinedBinaryOperator(syntax, operatorKind, loweredLeft, loweredRight, type, method); } switch (operatorKind.OperatorWithLogical() | operatorKind.OperandTypes()) { case BinaryOperatorKind.NullableNullEqual: case BinaryOperatorKind.NullableNullNotEqual: return RewriteNullableNullEquality(syntax, operatorKind, loweredLeft, loweredRight, type); case BinaryOperatorKind.ObjectAndStringConcatenation: case BinaryOperatorKind.StringAndObjectConcatenation: case BinaryOperatorKind.StringConcatenation: return RewriteStringConcatenation(syntax, operatorKind, loweredLeft, loweredRight, type); case BinaryOperatorKind.StringEqual: return RewriteStringEquality(oldNode, syntax, operatorKind, loweredLeft, loweredRight, type, SpecialMember.System_String__op_Equality); case BinaryOperatorKind.StringNotEqual: return RewriteStringEquality(oldNode, syntax, operatorKind, loweredLeft, loweredRight, type, SpecialMember.System_String__op_Inequality); case BinaryOperatorKind.DelegateCombination: return RewriteDelegateOperation(syntax, operatorKind, loweredLeft, loweredRight, type, SpecialMember.System_Delegate__Combine); case BinaryOperatorKind.DelegateRemoval: return RewriteDelegateOperation(syntax, operatorKind, loweredLeft, loweredRight, type, SpecialMember.System_Delegate__Remove); case BinaryOperatorKind.DelegateEqual: return RewriteDelegateOperation(syntax, operatorKind, loweredLeft, loweredRight, type, SpecialMember.System_Delegate__op_Equality); case BinaryOperatorKind.DelegateNotEqual: return RewriteDelegateOperation(syntax, operatorKind, loweredLeft, loweredRight, type, SpecialMember.System_Delegate__op_Inequality); case BinaryOperatorKind.LogicalBoolAnd: if (loweredRight.ConstantValue == ConstantValue.True) return loweredLeft; if (loweredLeft.ConstantValue == ConstantValue.True) return loweredRight; if (loweredLeft.ConstantValue == ConstantValue.False) return loweredLeft; if (loweredRight.Kind == BoundKind.Local || loweredRight.Kind == BoundKind.Parameter) { operatorKind &= ~BinaryOperatorKind.Logical; } goto default; case BinaryOperatorKind.LogicalBoolOr: if (loweredRight.ConstantValue == ConstantValue.False) return loweredLeft; if (loweredLeft.ConstantValue == ConstantValue.False) return loweredRight; if (loweredLeft.ConstantValue == ConstantValue.True) return loweredLeft; if (loweredRight.Kind == BoundKind.Local || loweredRight.Kind == BoundKind.Parameter) { operatorKind &= ~BinaryOperatorKind.Logical; } goto default; case BinaryOperatorKind.BoolAnd: if (loweredRight.ConstantValue == ConstantValue.True) return loweredLeft; if (loweredLeft.ConstantValue == ConstantValue.True) return loweredRight; goto default; case BinaryOperatorKind.BoolOr: if (loweredRight.ConstantValue == ConstantValue.False) return loweredLeft; if (loweredLeft.ConstantValue == ConstantValue.False) return loweredRight; goto default; case BinaryOperatorKind.BoolEqual: if (loweredLeft.ConstantValue == ConstantValue.True) return loweredRight; if (loweredRight.ConstantValue == ConstantValue.True) return loweredLeft; if (loweredLeft.ConstantValue == ConstantValue.False) return MakeUnaryOperator(UnaryOperatorKind.BoolLogicalNegation, syntax, null, loweredRight, loweredRight.Type); if (loweredRight.ConstantValue == ConstantValue.False) return MakeUnaryOperator(UnaryOperatorKind.BoolLogicalNegation, syntax, null, loweredLeft, loweredLeft.Type); goto default; case BinaryOperatorKind.BoolNotEqual: if (loweredLeft.ConstantValue == ConstantValue.False) return loweredRight; if (loweredRight.ConstantValue == ConstantValue.False) return loweredLeft; if (loweredLeft.ConstantValue == ConstantValue.True) return MakeUnaryOperator(UnaryOperatorKind.BoolLogicalNegation, syntax, null, loweredRight, loweredRight.Type); if (loweredRight.ConstantValue == ConstantValue.True) return MakeUnaryOperator(UnaryOperatorKind.BoolLogicalNegation, syntax, null, loweredLeft, loweredLeft.Type); goto default; case BinaryOperatorKind.BoolXor: if (loweredLeft.ConstantValue == ConstantValue.False) return loweredRight; if (loweredRight.ConstantValue == ConstantValue.False) return loweredLeft; if (loweredLeft.ConstantValue == ConstantValue.True) return MakeUnaryOperator(UnaryOperatorKind.BoolLogicalNegation, syntax, null, loweredRight, loweredRight.Type); if (loweredRight.ConstantValue == ConstantValue.True) return MakeUnaryOperator(UnaryOperatorKind.BoolLogicalNegation, syntax, null, loweredLeft, loweredLeft.Type); goto default; case BinaryOperatorKind.IntLeftShift: case BinaryOperatorKind.UIntLeftShift: case BinaryOperatorKind.IntRightShift: case BinaryOperatorKind.UIntRightShift: return RewriteBuiltInShiftOperation(oldNode, syntax, operatorKind, loweredLeft, loweredRight, type, 0x1F); case BinaryOperatorKind.LongLeftShift: case BinaryOperatorKind.ULongLeftShift: case BinaryOperatorKind.LongRightShift: case BinaryOperatorKind.ULongRightShift: return RewriteBuiltInShiftOperation(oldNode, syntax, operatorKind, loweredLeft, loweredRight, type, 0x3F); case BinaryOperatorKind.DecimalAddition: case BinaryOperatorKind.DecimalSubtraction: case BinaryOperatorKind.DecimalMultiplication: case BinaryOperatorKind.DecimalDivision: case BinaryOperatorKind.DecimalRemainder: case BinaryOperatorKind.DecimalEqual: case BinaryOperatorKind.DecimalNotEqual: case BinaryOperatorKind.DecimalLessThan: case BinaryOperatorKind.DecimalLessThanOrEqual: case BinaryOperatorKind.DecimalGreaterThan: case BinaryOperatorKind.DecimalGreaterThanOrEqual: return RewriteDecimalBinaryOperation(syntax, loweredLeft, loweredRight, operatorKind); case BinaryOperatorKind.PointerAndIntAddition: case BinaryOperatorKind.PointerAndUIntAddition: case BinaryOperatorKind.PointerAndLongAddition: case BinaryOperatorKind.PointerAndULongAddition: case BinaryOperatorKind.PointerAndIntSubtraction: case BinaryOperatorKind.PointerAndUIntSubtraction: case BinaryOperatorKind.PointerAndLongSubtraction: case BinaryOperatorKind.PointerAndULongSubtraction: if (loweredRight.IsDefaultValue()) { return loweredLeft; } return RewritePointerNumericOperator(syntax, operatorKind, loweredLeft, loweredRight, type, isPointerElementAccess, isLeftPointer: true); case BinaryOperatorKind.IntAndPointerAddition: case BinaryOperatorKind.UIntAndPointerAddition: case BinaryOperatorKind.LongAndPointerAddition: case BinaryOperatorKind.ULongAndPointerAddition: if (loweredLeft.IsDefaultValue()) { return loweredRight; } return RewritePointerNumericOperator(syntax, operatorKind, loweredLeft, loweredRight, type, isPointerElementAccess, isLeftPointer: false); case BinaryOperatorKind.PointerSubtraction: return RewritePointerSubtraction(operatorKind, loweredLeft, loweredRight, type); case BinaryOperatorKind.IntAddition: case BinaryOperatorKind.UIntAddition: case BinaryOperatorKind.LongAddition: case BinaryOperatorKind.ULongAddition: if (loweredLeft.IsDefaultValue()) { return loweredRight; } if (loweredRight.IsDefaultValue()) { return loweredLeft; } goto default; case BinaryOperatorKind.IntSubtraction: case BinaryOperatorKind.LongSubtraction: case BinaryOperatorKind.UIntSubtraction: case BinaryOperatorKind.ULongSubtraction: if (loweredRight.IsDefaultValue()) { return loweredLeft; } goto default; case BinaryOperatorKind.IntMultiplication: case BinaryOperatorKind.LongMultiplication: case BinaryOperatorKind.UIntMultiplication: case BinaryOperatorKind.ULongMultiplication: if (loweredLeft.IsDefaultValue()) { return loweredLeft; } if (loweredRight.IsDefaultValue()) { return loweredRight; } if (loweredLeft.ConstantValue?.UInt64Value == 1) { return loweredRight; } if (loweredRight.ConstantValue?.UInt64Value == 1) { return loweredLeft; } goto default; case BinaryOperatorKind.IntGreaterThan: case BinaryOperatorKind.IntLessThanOrEqual: if (loweredLeft.Kind == BoundKind.ArrayLength && loweredRight.IsDefaultValue()) { //array length is never negative var newOp = operatorKind == BinaryOperatorKind.IntGreaterThan ? BinaryOperatorKind.NotEqual : BinaryOperatorKind.Equal; operatorKind &= ~BinaryOperatorKind.OpMask; operatorKind |= newOp; loweredLeft = UnconvertArrayLength((BoundArrayLength)loweredLeft); } goto default; case BinaryOperatorKind.IntLessThan: case BinaryOperatorKind.IntGreaterThanOrEqual: if (loweredRight.Kind == BoundKind.ArrayLength && loweredLeft.IsDefaultValue()) { //array length is never negative var newOp = operatorKind == BinaryOperatorKind.IntLessThan ? BinaryOperatorKind.NotEqual : BinaryOperatorKind.Equal; operatorKind &= ~BinaryOperatorKind.OpMask; operatorKind |= newOp; loweredRight = UnconvertArrayLength((BoundArrayLength)loweredRight); } goto default; case BinaryOperatorKind.IntEqual: case BinaryOperatorKind.IntNotEqual: if (loweredLeft.Kind == BoundKind.ArrayLength && loweredRight.IsDefaultValue()) { loweredLeft = UnconvertArrayLength((BoundArrayLength)loweredLeft); } else if (loweredRight.Kind == BoundKind.ArrayLength && loweredLeft.IsDefaultValue()) { loweredRight = UnconvertArrayLength((BoundArrayLength)loweredRight); } goto default; default: break; } } return (oldNode != null) ? oldNode.Update(operatorKind, loweredLeft, loweredRight, oldNode.ConstantValueOpt, oldNode.MethodOpt, oldNode.ResultKind, type) : new BoundBinaryOperator(syntax, operatorKind, loweredLeft, loweredRight, null, null, LookupResultKind.Viable, type); }
private BoundExpression MakeNullCoalescingOperator( SyntaxNode syntax, BoundExpression rewrittenLeft, BoundExpression rewrittenRight, Conversion leftConversion, 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)); if (_inExpressionLambda) { TypeSymbol strippedLeftType = rewrittenLeft.Type.StrippedType(); Conversion rewrittenConversion = MakeConversion(syntax, leftConversion, strippedLeftType, rewrittenResultType); return(new BoundNullCoalescingOperator(syntax, rewrittenLeft, rewrittenRight, rewrittenConversion, rewrittenResultType)); } // 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). if (rewrittenLeft.IsDefaultValue()) { return(rewrittenRight); } if (rewrittenLeft.ConstantValue != null) { Debug.Assert(!rewrittenLeft.ConstantValue.IsNull); 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, 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.Type.SpecialType != SpecialType.System_Decimal) { whenNullOpt = null; } return(conditionalAccess.Update( conditionalAccess.Receiver, conditionalAccess.HasValueMethodOpt, whenNotNull: notNullAccess, whenNullOpt: whenNullOpt, id: conditionalAccess.Id, type: rewrittenResultType )); } } } // 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.Type.Equals(rewrittenResultType, TypeCompareKind.IgnoreDynamicAndTupleNames)); // (temp != null) ? MakeConversion(temp, LeftConversion) : RightOperand BoundExpression conditionalExpression = RewriteConditionalOperator( syntax: syntax, rewrittenCondition: nullCheck, rewrittenConsequence: convertedLeft, rewrittenAlternative: rewrittenRight, constantValueOpt: null, rewrittenType: rewrittenResultType); Debug.Assert(conditionalExpression.ConstantValue == null); // we shouldn't have hit this else case otherwise Debug.Assert(conditionalExpression.Type.Equals(rewrittenResultType, TypeCompareKind.IgnoreDynamicAndTupleNames)); return(new BoundSequence( syntax: syntax, locals: ImmutableArray.Create(boundTemp.LocalSymbol), sideEffects: ImmutableArray.Create <BoundExpression>(tempAssignment), value: conditionalExpression, type: rewrittenResultType)); }