|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp
{
internal sealed partial class LocalRewriter
{
/// <summary>
/// This rewriter lowers pre-/post- increment/decrement operations (initially represented as
/// unary operators). We use BoundSequenceExpressions because we need to capture the RHS of the
/// assignment in a temp variable.
/// </summary>
/// <remarks>
/// This rewriter assumes that it will be run before decimal rewriting (so that it does not have
/// to lower decimal constants and operations) and call rewriting (so that it does not have to
/// lower property accesses).
/// </remarks>
public override BoundNode VisitUnaryOperator(BoundUnaryOperator node)
{
switch (node.OperatorKind.Operator())
{
case UnaryOperatorKind.PrefixDecrement:
case UnaryOperatorKind.PrefixIncrement:
case UnaryOperatorKind.PostfixDecrement:
case UnaryOperatorKind.PostfixIncrement:
Debug.Assert(false); // these should have been represented as a BoundIncrementOperator
return base.VisitUnaryOperator(node);
}
// TODO(tomat): We need to pass a parent operator kind into binary operator visitor.
// We circumvent logic in VisitExpression. The extra logic doesn't apply under these conditions so we are ok.
// This is a bit fragile however. Consider refactoring VisitExpression.
if (node.Operand.Kind == BoundKind.BinaryOperator)
{
// Optimization:
// Binary operator lowering combines the binary operator with IsTrue/IsFalse more efficiently than we can do here.
var binaryOperator = (BoundBinaryOperator)node.Operand;
if (node.OperatorKind == UnaryOperatorKind.DynamicTrue && binaryOperator.OperatorKind == BinaryOperatorKind.DynamicLogicalOr ||
node.OperatorKind == UnaryOperatorKind.DynamicFalse && binaryOperator.OperatorKind == BinaryOperatorKind.DynamicLogicalAnd)
{
return VisitBinaryOperator(binaryOperator, applyParentUnaryOperator: node);
}
}
BoundExpression loweredOperand = VisitExpression(node.Operand);
return MakeUnaryOperator(node, node.OperatorKind, node.Syntax, node.MethodOpt, node.ConstrainedToTypeOpt, loweredOperand, node.Type);
}
private BoundExpression MakeUnaryOperator(
UnaryOperatorKind kind,
SyntaxNode syntax,
MethodSymbol? method,
TypeSymbol? constrainedToTypeOpt,
BoundExpression loweredOperand,
TypeSymbol type)
{
return MakeUnaryOperator(null, kind, syntax, method, constrainedToTypeOpt, loweredOperand, type);
}
private BoundExpression MakeUnaryOperator(
BoundUnaryOperator? oldNode,
UnaryOperatorKind kind,
SyntaxNode syntax,
MethodSymbol? method,
TypeSymbol? constrainedToTypeOpt,
BoundExpression loweredOperand,
TypeSymbol type)
{
if (kind.IsDynamic())
{
Debug.Assert((kind == UnaryOperatorKind.DynamicTrue || kind == UnaryOperatorKind.DynamicFalse) && type.SpecialType == SpecialType.System_Boolean
|| type.IsDynamic());
Debug.Assert(method is null);
// Logical operators on boxed Boolean constants:
var constant = UnboxConstant(loweredOperand);
if (constant == ConstantValue.True || constant == ConstantValue.False)
{
switch (kind)
{
case UnaryOperatorKind.DynamicTrue:
return _factory.Literal(constant.BooleanValue);
case UnaryOperatorKind.DynamicLogicalNegation:
return MakeConversionNode(_factory.Literal(!constant.BooleanValue), type, @checked: false);
}
}
return _dynamicFactory.MakeDynamicUnaryOperator(kind, loweredOperand, type).ToExpression();
}
else if (kind.IsLifted())
{
if (!_inExpressionLambda)
{
return LowerLiftedUnaryOperator(kind, syntax, method, constrainedToTypeOpt, loweredOperand, type);
}
}
else if (kind.IsUserDefined())
{
Debug.Assert(method is { });
Debug.Assert(TypeSymbol.Equals(type, method.ReturnType, TypeCompareKind.ConsiderEverything2));
if (!_inExpressionLambda || kind == UnaryOperatorKind.UserDefinedTrue || kind == UnaryOperatorKind.UserDefinedFalse)
{
return BoundCall.Synthesized(
syntax,
receiverOpt: constrainedToTypeOpt is null ? null : new BoundTypeExpression(syntax, aliasOpt: null, constrainedToTypeOpt),
initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown,
method,
loweredOperand);
}
}
else if (kind.Operator() == UnaryOperatorKind.UnaryPlus)
{
// We do not call the operator even for decimal; we simply optimize it away entirely.
return loweredOperand;
}
if (kind == UnaryOperatorKind.EnumBitwiseComplement)
{
var underlyingType = loweredOperand.Type.GetEnumUnderlyingType();
Debug.Assert(underlyingType is { });
var upconvertSpecialType = Binder.GetEnumPromotedType(underlyingType.SpecialType);
var upconvertType = upconvertSpecialType == underlyingType.SpecialType ?
underlyingType :
_compilation.GetSpecialType(upconvertSpecialType);
var newOperand = MakeConversionNode(loweredOperand, upconvertType, false);
UnaryOperatorKind newKind = kind.Operator().WithType(upconvertSpecialType);
var newNode = (oldNode != null) ?
oldNode.Update(
newKind,
newOperand,
oldNode.ConstantValueOpt,
methodOpt: method,
constrainedToTypeOpt: constrainedToTypeOpt,
newOperand.ResultKind,
upconvertType) :
new BoundUnaryOperator(
syntax,
newKind,
newOperand,
null,
methodOpt: method,
constrainedToTypeOpt: constrainedToTypeOpt,
LookupResultKind.Viable,
upconvertType);
return MakeConversionNode(newNode.Syntax, newNode, Conversion.ExplicitEnumeration, type, @checked: false);
}
if (kind == UnaryOperatorKind.DecimalUnaryMinus)
{
method = (MethodSymbol)_compilation.Assembly.GetSpecialTypeMember(SpecialMember.System_Decimal__op_UnaryNegation);
if (!_inExpressionLambda)
{
return BoundCall.Synthesized(syntax, receiverOpt: null, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, method, loweredOperand);
}
}
return (oldNode != null) ?
oldNode.Update(kind, loweredOperand, oldNode.ConstantValueOpt, methodOpt: method, constrainedToTypeOpt: constrainedToTypeOpt, oldNode.ResultKind, type) :
new BoundUnaryOperator(syntax, kind, loweredOperand, null, methodOpt: method, constrainedToTypeOpt: constrainedToTypeOpt, LookupResultKind.Viable, type);
}
private BoundExpression LowerLiftedUnaryOperator(
UnaryOperatorKind kind,
SyntaxNode syntax,
MethodSymbol? method,
TypeSymbol? constrainedToTypeOpt,
BoundExpression loweredOperand,
TypeSymbol type)
{
// First, an optimization. If we know that the operand is always null then
// we can simply lower to the alternative.
BoundExpression? optimized = OptimizeLiftedUnaryOperator(kind, syntax, method, constrainedToTypeOpt, loweredOperand, type);
if (optimized != null)
{
return optimized;
}
// We do not know whether the operand is null or non-null, so we generate:
//
// S? temp = operand;
// R? r = temp.HasValue ?
// new R?(OP(temp.GetValueOrDefault())) :
// default(R?);
BoundAssignmentOperator tempAssignment;
BoundLocal boundTemp = _factory.StoreToTemp(loweredOperand, out tempAssignment);
MethodSymbol getValueOrDefault = UnsafeGetNullableMethod(syntax, boundTemp.Type, SpecialMember.System_Nullable_T_GetValueOrDefault);
// temp.HasValue
BoundExpression condition = _factory.MakeNullableHasValue(syntax, boundTemp);
// temp.GetValueOrDefault()
BoundExpression call_GetValueOrDefault = BoundCall.Synthesized(syntax, boundTemp, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, getValueOrDefault);
// new R?(temp.GetValueOrDefault())
BoundExpression consequence = GetLiftedUnaryOperatorConsequence(kind, syntax, method, constrainedToTypeOpt, type, call_GetValueOrDefault);
// default(R?)
BoundExpression alternative = new BoundDefaultExpression(syntax, type);
// temp.HasValue ?
// new R?(OP(temp.GetValueOrDefault())) :
// default(R?);
BoundExpression conditionalExpression = RewriteConditionalOperator(
syntax: syntax,
rewrittenCondition: condition,
rewrittenConsequence: consequence,
rewrittenAlternative: alternative,
constantValueOpt: null,
rewrittenType: type,
isRef: false);
// temp = operand;
// temp.HasValue ?
// new R?(OP(temp.GetValueOrDefault())) :
// default(R?);
return new BoundSequence(
syntax: syntax,
locals: ImmutableArray.Create<LocalSymbol>(boundTemp.LocalSymbol),
sideEffects: ImmutableArray.Create<BoundExpression>(tempAssignment),
value: conditionalExpression,
type: type);
}
private BoundExpression? OptimizeLiftedUnaryOperator(
UnaryOperatorKind operatorKind,
SyntaxNode syntax,
MethodSymbol? method,
TypeSymbol? constrainedToTypeOpt,
BoundExpression loweredOperand,
TypeSymbol type)
{
if (NullableNeverHasValue(loweredOperand))
{
return new BoundDefaultExpression(syntax, type);
}
// Second, another simple optimization. If we know that the operand is never null
// then we can obtain the non-null value and skip generating the temporary. That is,
// "~(new int?(M()))" is the same as "new int?(~M())".
BoundExpression? neverNull = NullableAlwaysHasValue(loweredOperand);
if (neverNull != null)
{
return GetLiftedUnaryOperatorConsequence(operatorKind, syntax, method, constrainedToTypeOpt, type, neverNull);
}
var conditionalLeft = loweredOperand as BoundLoweredConditionalAccess;
// NOTE: we could in theory handle side-effecting loweredRight here too
// by including it as a part of whenNull, but there is a concern
// that it can lead to code duplication
var optimize = conditionalLeft != null &&
(conditionalLeft.WhenNullOpt == null || conditionalLeft.WhenNullOpt.IsDefaultValue());
if (optimize)
{
var result = LowerLiftedUnaryOperator(operatorKind, syntax, method, constrainedToTypeOpt, conditionalLeft!.WhenNotNull, type);
Debug.Assert(result.Type is { });
return conditionalLeft.Update(
conditionalLeft.Receiver,
conditionalLeft.HasValueMethodOpt,
whenNotNull: result,
whenNullOpt: null,
id: conditionalLeft.Id,
forceCopyOfNullableValueType: conditionalLeft.ForceCopyOfNullableValueType,
type: result.Type
);
}
// This optimization is analogous to DistributeLiftedConversionIntoLiftedOperand.
// Suppose we have a lifted unary conversion whose operand is itself a lifted operation.
// That is, we have something like:
//
// int? r = - (M() + N());
//
// where M() and N() return nullable ints. We would simply codegen this as first
// creating the nullable int result of M() + N(), then checking it for nullity,
// and then doing the unary minus. That is:
//
// int? m = M();
// int? n = N();
// int? t = m.HasValue && n.HasValue ? new int?(m.Value + n.Value) : new int?();
// int? r = t.HasValue ? new int?(-t.Value) : new int?();
//
// However, we also observe that we can distribute the unary minus into both branches of
// the conditional:
//
// int? m = M();
// int? n = N();
// int? r = m.HasValue && n.HasValue ? - (new int?(m.Value + n.Value))) : - new int?();
//
// And we already optimize those! So we could reduce this to:
//
// int? m = M();
// int? n = N();
// int? r = m.HasValue && n.HasValue ? new int?(- (m.Value + n.Value)) : new int?());
//
// which avoids entirely the creation of the unnecessary nullable int and the unnecessary
// extra null check.
if (loweredOperand.Kind == BoundKind.Sequence)
{
BoundSequence seq = (BoundSequence)loweredOperand;
if (seq.Value.Kind == BoundKind.ConditionalOperator)
{
BoundConditionalOperator conditional = (BoundConditionalOperator)seq.Value;
Debug.Assert(TypeSymbol.Equals(seq.Type, conditional.Type, TypeCompareKind.ConsiderEverything2));
Debug.Assert(TypeSymbol.Equals(conditional.Type, conditional.Consequence.Type, TypeCompareKind.ConsiderEverything2));
Debug.Assert(TypeSymbol.Equals(conditional.Type, conditional.Alternative.Type, TypeCompareKind.ConsiderEverything2));
if (NullableAlwaysHasValue(conditional.Consequence) != null && NullableNeverHasValue(conditional.Alternative))
{
return new BoundSequence(
syntax,
seq.Locals,
seq.SideEffects,
RewriteConditionalOperator(
syntax,
conditional.Condition,
MakeUnaryOperator(operatorKind, syntax, method, constrainedToTypeOpt, conditional.Consequence, type),
MakeUnaryOperator(operatorKind, syntax, method, constrainedToTypeOpt, conditional.Alternative, type),
ConstantValue.NotAvailable,
type,
isRef: false),
type);
}
}
}
return null;
}
private BoundExpression GetLiftedUnaryOperatorConsequence(UnaryOperatorKind kind, SyntaxNode syntax, MethodSymbol? method, TypeSymbol? constrainedToTypeOpt, TypeSymbol type, BoundExpression nonNullOperand)
{
MethodSymbol ctor = UnsafeGetNullableMethod(syntax, type, SpecialMember.System_Nullable_T__ctor);
// OP(temp.GetValueOrDefault())
BoundExpression unliftedOp = MakeUnaryOperator(
oldNode: null,
kind: kind.Unlifted(),
syntax: syntax,
method: method,
constrainedToTypeOpt: constrainedToTypeOpt,
loweredOperand: nonNullOperand,
type: type.GetNullableUnderlyingType());
// new R?(OP(temp.GetValueOrDefault()))
BoundExpression consequence = new BoundObjectCreationExpression(
syntax,
ctor,
unliftedOp);
return consequence;
}
private static bool IsIncrement(BoundIncrementOperator node)
{
var op = node.OperatorKind.Operator();
return op == UnaryOperatorKind.PostfixIncrement || op == UnaryOperatorKind.PrefixIncrement;
}
private static bool IsPrefix(BoundIncrementOperator node)
{
var op = node.OperatorKind.Operator();
return op == UnaryOperatorKind.PrefixIncrement || op == UnaryOperatorKind.PrefixDecrement;
}
/// <summary>
/// The rewrites are as follows: suppose the operand x is a variable of type X. The
/// chosen increment/decrement operator is modelled as a static method on a type T,
/// which takes a value of type T and returns the result of incrementing or decrementing
/// that value.
///
/// x++
/// X temp = x
/// x = (X)(T.Increment((T)temp))
/// return temp
/// x--
/// X temp = x
/// x = (X)(T.Decrement((T)temp))
/// return temp
/// ++x
/// X temp = (X)(T.Increment((T)x))
/// x = temp
/// return temp
/// --x
/// X temp = (X)(T.Decrement((T)x))
/// x = temp
/// return temp
///
/// Note:
/// Dev11 implements dynamic prefix operators incorrectly.
///
/// result = ++x.P is emitted as result = SetMember{"P"}(t, UnaryOperation{Inc}(GetMember{"P"}(x)))
///
/// The difference is that Dev11 relies on SetMember returning the same value as it was given as an argument.
/// Failing to do so changes the semantics of ++/-- operator which is undesirable. We emit the same pattern for
/// both dynamic and static operators.
///
/// For example, we might have a class X with user-defined implicit conversions
/// to and from short, but no user-defined increment or decrement operators. We
/// would bind x++ as "X temp = x; x = (X)(short)((int)(short)temp + 1); return temp;"
/// </summary>
/// <param name="node">The unary operator expression representing the increment/decrement.</param>
/// <returns>A bound sequence that uses a temp to achieve the correct side effects and return value.</returns>
public override BoundNode VisitIncrementOperator(BoundIncrementOperator node)
{
bool isPrefix = IsPrefix(node);
bool isDynamic = node.OperatorKind.IsDynamic();
bool isChecked = node.OperatorKind.IsChecked();
ArrayBuilder<LocalSymbol> tempSymbols = ArrayBuilder<LocalSymbol>.GetInstance();
ArrayBuilder<BoundExpression> tempInitializers = ArrayBuilder<BoundExpression>.GetInstance();
SyntaxNode syntax = node.Syntax;
// This will be filled in with the LHS that uses temporaries to prevent
// double-evaluation of side effects.
BoundExpression transformedLHS = TransformCompoundAssignmentLHS(node.Operand, isRegularCompoundAssignment: true, tempInitializers, tempSymbols, isDynamic);
TypeSymbol? operandType = transformedLHS.Type; //type of the variable being incremented
Debug.Assert(operandType is { });
Debug.Assert(TypeSymbol.Equals(operandType, node.Type, TypeCompareKind.ConsiderEverything2));
LocalSymbol tempSymbol = _factory.SynthesizedLocal(operandType);
tempSymbols.Add(tempSymbol);
// Not adding an entry to tempInitializers because the initial value depends on the case.
BoundExpression boundTemp = new BoundLocal(
syntax: syntax,
localSymbol: tempSymbol,
constantValueOpt: null,
type: operandType);
// prefix: (X)(T.Increment((T)operand)))
// postfix: (X)(T.Increment((T)temp)))
var newValue = makeIncrementOperator(node, rewrittenValueToIncrement: (isPrefix ? MakeRValue(transformedLHS) : boundTemp));
// there are two strategies for completing the rewrite.
// The reason is that indirect assignments read the target of the assignment before evaluating
// of the assignment value and that may cause reads of operand and boundTemp to cross which
// in turn would require one of them to be a real temp (not a stack local)
//
// To avoid this issue, in a case of ByRef operand, we perform a "nested sequence" rewrite.
//
// Ex:
// Seq{..., operand = Seq{temp = operand + 1, temp}, ...}
// instead of
// Seq{.... temp = operand + 1, operand = temp, ...}
//
// Such rewrite will nest reads of boundTemp relative to reads of operand so both
// operand and boundTemp could be optimizable (subject to all other conditions of course).
//
// In a case of the non-byref operand we use a single-sequence strategy as it results in shorter
// overall life time of temps and as such more appropriate. (problem of crossed reads does not affect that case)
//
if (isIndirectOrInstanceField(transformedLHS))
{
return rewriteWithRefOperand(isPrefix, isChecked, tempSymbols, tempInitializers, syntax, transformedLHS, boundTemp, newValue);
}
else
{
return rewriteWithNotRefOperand(isPrefix, isChecked, tempSymbols, tempInitializers, syntax, transformedLHS, boundTemp, newValue);
}
static bool isIndirectOrInstanceField(BoundExpression expression)
{
switch (expression.Kind)
{
case BoundKind.Local:
return ((BoundLocal)expression).LocalSymbol.RefKind != RefKind.None;
case BoundKind.Parameter:
Debug.Assert(!IsCapturedPrimaryConstructorParameter(expression));
return ((BoundParameter)expression).ParameterSymbol.RefKind != RefKind.None;
case BoundKind.FieldAccess:
return !((BoundFieldAccess)expression).FieldSymbol.IsStatic;
}
return false;
}
BoundExpression rewriteWithNotRefOperand(
bool isPrefix,
bool isChecked,
ArrayBuilder<LocalSymbol> tempSymbols,
ArrayBuilder<BoundExpression> tempInitializers,
SyntaxNode syntax,
BoundExpression transformedLHS,
BoundExpression boundTemp,
BoundExpression newValue)
{
Debug.Assert(boundTemp.Type is not null);
// prefix: temp = (X)(T.Increment((T)operand))); operand = temp;
// postfix: temp = operand; operand = (X)(T.Increment((T)temp)));
ImmutableArray<BoundExpression> assignments = ImmutableArray.Create<BoundExpression>(
MakeAssignmentOperator(syntax, boundTemp, isPrefix ? newValue : MakeRValue(transformedLHS), used: false, isChecked: isChecked, isCompoundAssignment: false),
MakeAssignmentOperator(syntax, transformedLHS, isPrefix ? boundTemp : newValue, used: false, isChecked: isChecked, isCompoundAssignment: false));
// prefix: Seq( operand initializers; temp = (T)(operand + 1); operand = temp; result: temp)
// postfix: Seq( operand initializers; temp = operand; operand = (T)(temp + 1); result: temp)
return new BoundSequence(
syntax: syntax,
locals: tempSymbols.ToImmutableAndFree(),
sideEffects: tempInitializers.ToImmutableAndFree().Concat(assignments),
value: boundTemp,
type: boundTemp.Type);
}
BoundExpression rewriteWithRefOperand(
bool isPrefix,
bool isChecked,
ArrayBuilder<LocalSymbol> tempSymbols,
ArrayBuilder<BoundExpression> tempInitializers,
SyntaxNode syntax,
BoundExpression operand,
BoundExpression boundTemp,
BoundExpression newValue)
{
Debug.Assert(boundTemp.Type is not null);
var tempValue = isPrefix ? newValue : MakeRValue(operand);
Debug.Assert(tempValue.Type is { });
var tempAssignment = MakeAssignmentOperator(syntax, boundTemp, tempValue, used: false, isChecked: isChecked, isCompoundAssignment: false);
var operandValue = isPrefix ? boundTemp : newValue;
var tempAssignedAndOperandValue = new BoundSequence(
syntax,
ImmutableArray<LocalSymbol>.Empty,
ImmutableArray.Create<BoundExpression>(tempAssignment),
operandValue,
tempValue.Type);
// prefix: operand = Seq{temp = (T)(operand + 1); temp;}
// postfix: operand = Seq{temp = operand; ; (T)(temp + 1);}
BoundExpression operandAssignment = MakeAssignmentOperator(syntax, operand, tempAssignedAndOperandValue, used: false, isChecked: isChecked, isCompoundAssignment: false);
// prefix: Seq{operand initializers; operand = Seq{temp = (T)(operand + 1); temp;} result: temp}
// postfix: Seq{operand initializers; operand = Seq{temp = operand; ; (T)(temp + 1);} result: temp}
tempInitializers.Add(operandAssignment);
return new BoundSequence(
syntax: syntax,
locals: tempSymbols.ToImmutableAndFree(),
sideEffects: tempInitializers.ToImmutableAndFree(),
value: boundTemp,
type: boundTemp.Type);
}
BoundExpression makeIncrementOperator(BoundIncrementOperator node, BoundExpression rewrittenValueToIncrement)
{
if (node.OperatorKind.IsDynamic())
{
return _dynamicFactory.MakeDynamicUnaryOperator(node.OperatorKind, rewrittenValueToIncrement, node.Type).ToExpression();
}
BoundExpression result;
if (node.OperatorKind.OperandTypes() == UnaryOperatorKind.UserDefined)
{
result = MakeUserDefinedIncrementOperator(node, rewrittenValueToIncrement);
}
else
{
result = MakeBuiltInIncrementOperator(node, rewrittenValueToIncrement);
}
// Generate the conversion back to the type of the original expression.
// (X)(short)((int)(short)x + 1)
result = ApplyConversionIfNotIdentity(node.ResultConversion, node.ResultPlaceholder, result);
return result;
}
}
private BoundExpression ApplyConversionIfNotIdentity(BoundExpression? conversion, BoundValuePlaceholder? placeholder, BoundExpression replacement)
{
if (hasNonIdentityConversion(conversion))
{
Debug.Assert(placeholder is not null);
return ApplyConversion(conversion, placeholder, replacement);
}
return replacement;
static bool hasNonIdentityConversion([NotNullWhen(true)] BoundExpression? expression)
{
while (expression is BoundConversion conversion)
{
if (!conversion.Conversion.IsIdentity)
{
return true;
}
expression = conversion.Operand;
}
return false;
}
}
private BoundExpression ApplyConversion(BoundExpression conversion, BoundValuePlaceholder placeholder, BoundExpression replacement)
{
AddPlaceholderReplacement(placeholder, replacement);
replacement = VisitExpression(conversion);
RemovePlaceholderReplacement(placeholder);
return replacement;
}
private BoundExpression MakeUserDefinedIncrementOperator(BoundIncrementOperator node, BoundExpression rewrittenValueToIncrement)
{
Debug.Assert(node.MethodOpt is { });
Debug.Assert(node.MethodOpt.ParameterCount == 1);
bool isLifted = node.OperatorKind.IsLifted();
bool @checked = node.OperatorKind.IsChecked();
SyntaxNode syntax = node.Syntax;
TypeSymbol type = node.MethodOpt.GetParameterType(0);
if (isLifted)
{
type = _compilation.GetSpecialType(SpecialType.System_Nullable_T).Construct(type);
Debug.Assert(TypeSymbol.Equals(node.MethodOpt.GetParameterType(0), node.MethodOpt.ReturnType, TypeCompareKind.ConsiderEverything2));
}
BoundExpression rewrittenArgument = ApplyConversionIfNotIdentity(node.OperandConversion, node.OperandPlaceholder, rewrittenValueToIncrement);
if (!isLifted)
{
return BoundCall.Synthesized(
syntax,
receiverOpt: node.ConstrainedToTypeOpt is null ? null : new BoundTypeExpression(syntax, aliasOpt: null, node.ConstrainedToTypeOpt),
initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown,
node.MethodOpt,
rewrittenArgument);
}
// S? temp = operand;
// S? r = temp.HasValue ?
// new S?(op_Increment(temp.GetValueOrDefault())) :
// default(S?);
// Unlike the other unary operators, we do not attempt to optimize nullable user-defined
// increment or decrement. The operand is a variable (or property), and so we do not know if
// it is always null/never null.
BoundAssignmentOperator tempAssignment;
BoundLocal boundTemp = _factory.StoreToTemp(rewrittenArgument, out tempAssignment);
MethodSymbol getValueOrDefault = UnsafeGetNullableMethod(syntax, type, SpecialMember.System_Nullable_T_GetValueOrDefault);
MethodSymbol ctor = UnsafeGetNullableMethod(syntax, type, SpecialMember.System_Nullable_T__ctor);
// temp.HasValue
BoundExpression condition = _factory.MakeNullableHasValue(node.Syntax, boundTemp);
// temp.GetValueOrDefault()
BoundExpression call_GetValueOrDefault = BoundCall.Synthesized(syntax, boundTemp, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, getValueOrDefault);
// op_Increment(temp.GetValueOrDefault())
BoundExpression userDefinedCall = BoundCall.Synthesized(
syntax,
receiverOpt: node.ConstrainedToTypeOpt is null ? null : new BoundTypeExpression(syntax, aliasOpt: null, node.ConstrainedToTypeOpt),
initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown,
node.MethodOpt,
call_GetValueOrDefault);
// new S?(op_Increment(temp.GetValueOrDefault()))
BoundExpression consequence = new BoundObjectCreationExpression(syntax, ctor, userDefinedCall);
// default(S?)
BoundExpression alternative = new BoundDefaultExpression(syntax, type);
// temp.HasValue ?
// new S?(op_Increment(temp.GetValueOrDefault())) :
// default(S?);
BoundExpression conditionalExpression = RewriteConditionalOperator(
syntax: syntax,
rewrittenCondition: condition,
rewrittenConsequence: consequence,
rewrittenAlternative: alternative,
constantValueOpt: null,
rewrittenType: type,
isRef: false);
// temp = operand;
// temp.HasValue ?
// new S?(op_Increment(temp.GetValueOrDefault())) :
// default(S?);
return new BoundSequence(
syntax: syntax,
locals: ImmutableArray.Create<LocalSymbol>(boundTemp.LocalSymbol),
sideEffects: ImmutableArray.Create<BoundExpression>(tempAssignment),
value: conditionalExpression,
type: type);
}
private BoundExpression MakeBuiltInIncrementOperator(BoundIncrementOperator node, BoundExpression rewrittenValueToIncrement)
{
BoundExpression result;
// If we have a built-in increment or decrement then things get a bit trickier. Suppose for example we have
// a user-defined conversion from X to short and from short to X, but no user-defined increment operator on
// X. The increment portion of "++x" is then: (X)(short)((int)(short)x + 1). That is, first x must be
// converted to short via an implicit user- defined conversion, then to int via an implicit numeric
// conversion, then the addition is performed in integers. The resulting integer is converted back to short,
// and then the short is converted to X.
// This is the input and output type of the unary increment operator we're going to call.
// That is, "short" in the example above.
TypeSymbol unaryOperandType = GetUnaryOperatorType(node);
// This is the kind of binary operator that we're going to realize the unary operator
// as. That is, "int + int --> int" in the example above.
BinaryOperatorKind binaryOperatorKind = GetCorrespondingBinaryOperator(node);
binaryOperatorKind |= IsIncrement(node) ? BinaryOperatorKind.Addition : BinaryOperatorKind.Subtraction;
// The input/output type of the binary operand. "int" in the example.
// The "1" in the example above.
(TypeSymbol binaryOperandType, ConstantValue constantOne) = GetConstantOneForIncrement(_compilation, binaryOperatorKind);
Debug.Assert(constantOne != null);
Debug.Assert(constantOne.SpecialType != SpecialType.None);
Debug.Assert(binaryOperandType.SpecialType != SpecialType.None);
Debug.Assert(binaryOperatorKind.OperandTypes() != 0);
// 1
BoundExpression boundOne = MakeLiteral(
syntax: node.Syntax,
constantValue: constantOne,
type: binaryOperandType);
if (binaryOperatorKind.IsLifted())
{
binaryOperandType = _compilation.GetOrCreateNullableType(binaryOperandType);
MethodSymbol ctor = UnsafeGetNullableMethod(node.Syntax, binaryOperandType, SpecialMember.System_Nullable_T__ctor);
boundOne = new BoundObjectCreationExpression(node.Syntax, ctor, boundOne);
}
// Now we construct the other operand to the binary addition. We start with just plain "x".
BoundExpression binaryOperand = rewrittenValueToIncrement;
bool @checked = node.OperatorKind.IsChecked();
// If we need to make a conversion from the original operand type to the operand type of the
// underlying increment operation, do it now.
binaryOperand = ApplyConversionIfNotIdentity(node.OperandConversion, node.OperandPlaceholder, binaryOperand);
// Early-out for pointer increment - we don't need to convert the operands to a common type.
if (node.OperatorKind.OperandTypes() == UnaryOperatorKind.Pointer)
{
Debug.Assert(binaryOperatorKind.OperandTypes() == BinaryOperatorKind.PointerAndInt);
Debug.Assert(binaryOperand.Type is { TypeKind: TypeKind.Pointer });
Debug.Assert(boundOne.Type is { SpecialType: SpecialType.System_Int32 });
return MakeBinaryOperator(node.Syntax, binaryOperatorKind, binaryOperand, boundOne, binaryOperand.Type, method: null, constrainedToTypeOpt: null);
}
// If we need to make a conversion from the unary operator type to the binary operator type,
// do it now.
// (int)(short)x
binaryOperand = MakeConversionNode(binaryOperand, binaryOperandType, @checked, markAsChecked: true);
// Perform the addition.
// (int)(short)x + 1
BoundExpression binOp;
if (unaryOperandType.SpecialType == SpecialType.System_Decimal)
{
binOp = MakeDecimalIncDecOperator(node.Syntax, binaryOperatorKind, binaryOperand);
}
else if (unaryOperandType.IsNullableType() && unaryOperandType.GetNullableUnderlyingType().SpecialType == SpecialType.System_Decimal)
{
binOp = MakeLiftedDecimalIncDecOperator(node.Syntax, binaryOperatorKind, binaryOperand);
}
else
{
binOp = MakeBinaryOperator(node.Syntax, binaryOperatorKind, binaryOperand, boundOne, binaryOperandType, method: null, constrainedToTypeOpt: null);
}
// Generate the conversion back to the type of the unary operator.
// (short)((int)(short)x + 1)
result = MakeConversionNode(binOp, unaryOperandType, @checked, markAsChecked: true);
return result;
}
private MethodSymbol GetDecimalIncDecOperator(BinaryOperatorKind oper)
{
SpecialMember member;
switch (oper.Operator())
{
case BinaryOperatorKind.Addition: member = SpecialMember.System_Decimal__op_Increment; break;
case BinaryOperatorKind.Subtraction: member = SpecialMember.System_Decimal__op_Decrement; break;
default:
throw ExceptionUtilities.UnexpectedValue(oper.Operator());
}
var method = (MethodSymbol)_compilation.Assembly.GetSpecialTypeMember(member);
Debug.Assert((object)method != null); // Should have been checked during Warnings pass
return method;
}
// Build Decimal.op_Increment((Decimal)operand) or Decimal.op_Decrement((Decimal)operand)
private BoundExpression MakeDecimalIncDecOperator(SyntaxNode syntax, BinaryOperatorKind oper, BoundExpression operand)
{
Debug.Assert(operand.Type is { SpecialType: SpecialType.System_Decimal });
MethodSymbol method = GetDecimalIncDecOperator(oper);
return BoundCall.Synthesized(syntax, receiverOpt: null, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, method, operand);
}
private BoundExpression MakeLiftedDecimalIncDecOperator(SyntaxNode syntax, BinaryOperatorKind oper, BoundExpression operand)
{
Debug.Assert(operand.Type is { } && operand.Type.IsNullableType() && operand.Type.GetNullableUnderlyingType().SpecialType == SpecialType.System_Decimal);
// This method assumes that operand is already a temporary and so there is no need to copy it again.
MethodSymbol method = GetDecimalIncDecOperator(oper);
MethodSymbol getValueOrDefault = UnsafeGetNullableMethod(syntax, operand.Type, SpecialMember.System_Nullable_T_GetValueOrDefault);
MethodSymbol ctor = UnsafeGetNullableMethod(syntax, operand.Type, SpecialMember.System_Nullable_T__ctor);
// x.HasValue
BoundExpression condition = _factory.MakeNullableHasValue(syntax, operand);
// x.GetValueOrDefault()
BoundExpression getValueCall = BoundCall.Synthesized(syntax, operand, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, getValueOrDefault);
// op_Inc(x.GetValueOrDefault())
BoundExpression methodCall = BoundCall.Synthesized(syntax, receiverOpt: null, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, method, getValueCall);
// new decimal?(op_Inc(x.GetValueOrDefault()))
BoundExpression consequence = new BoundObjectCreationExpression(syntax, ctor, methodCall);
// default(decimal?)
BoundExpression alternative = new BoundDefaultExpression(syntax, operand.Type);
// x.HasValue ? new decimal?(op_Inc(x.GetValueOrDefault())) : default(decimal?)
return RewriteConditionalOperator(syntax, condition, consequence, alternative, ConstantValue.NotAvailable, operand.Type, isRef: false);
}
/// <summary>
/// Transform an expression from a form suitable as an lvalue to a form suitable as an rvalue.
/// </summary>
/// <param name="transformedExpression">The children of this node must already be lowered.</param>
/// <returns>Fully lowered node.</returns>
private BoundExpression MakeRValue(BoundExpression transformedExpression)
{
switch (transformedExpression.Kind)
{
case BoundKind.PropertyAccess:
var propertyAccess = (BoundPropertyAccess)transformedExpression;
return MakePropertyGetAccess(transformedExpression.Syntax, propertyAccess.ReceiverOpt, propertyAccess.PropertySymbol, propertyAccess);
case BoundKind.DynamicMemberAccess:
var dynamicMemberAccess = (BoundDynamicMemberAccess)transformedExpression;
return _dynamicFactory.MakeDynamicGetMember(dynamicMemberAccess.Receiver, dynamicMemberAccess.Name, resultIndexed: false).ToExpression();
case BoundKind.IndexerAccess:
var indexerAccess = (BoundIndexerAccess)transformedExpression;
return MakePropertyGetAccess(transformedExpression.Syntax, indexerAccess.ReceiverOpt, indexerAccess.Indexer, indexerAccess.Arguments, indexerAccess.ArgumentRefKindsOpt);
case BoundKind.DynamicIndexerAccess:
var dynamicIndexerAccess = (BoundDynamicIndexerAccess)transformedExpression;
return MakeDynamicGetIndex(
dynamicIndexerAccess,
dynamicIndexerAccess.Receiver,
dynamicIndexerAccess.Arguments,
dynamicIndexerAccess.ArgumentNamesOpt,
dynamicIndexerAccess.ArgumentRefKindsOpt);
default:
return transformedExpression;
}
}
// There are ++ and -- operators defined on sbyte, byte, short, ushort, int,
// uint, long, ulong, char, float, double, decimal and any enum type.
// Given a built-in increment operator, get the associated type. Note
// that this need not be the result type or the operand type of the node!
// We could have a user-defined conversion from the type of the operand
// to short, and a user-defined conversion from short to the result
// type.
private TypeSymbol GetUnaryOperatorType(BoundIncrementOperator node)
{
UnaryOperatorKind kind = node.OperatorKind.OperandTypes();
// If overload resolution chose an enum operator then the operand
// type and the return type really are an enum; we are not in a user-
// defined conversion scenario.
if (kind == UnaryOperatorKind.Enum)
{
return node.Type;
}
SpecialType specialType;
switch (kind)
{
case UnaryOperatorKind.Int:
specialType = SpecialType.System_Int32;
break;
case UnaryOperatorKind.SByte:
specialType = SpecialType.System_SByte;
break;
case UnaryOperatorKind.Short:
specialType = SpecialType.System_Int16;
break;
case UnaryOperatorKind.Byte:
specialType = SpecialType.System_Byte;
break;
case UnaryOperatorKind.UShort:
specialType = SpecialType.System_UInt16;
break;
case UnaryOperatorKind.Char:
specialType = SpecialType.System_Char;
break;
case UnaryOperatorKind.UInt:
specialType = SpecialType.System_UInt32;
break;
case UnaryOperatorKind.Long:
specialType = SpecialType.System_Int64;
break;
case UnaryOperatorKind.ULong:
specialType = SpecialType.System_UInt64;
break;
case UnaryOperatorKind.NInt:
specialType = SpecialType.System_IntPtr;
break;
case UnaryOperatorKind.NUInt:
specialType = SpecialType.System_UIntPtr;
break;
case UnaryOperatorKind.Float:
specialType = SpecialType.System_Single;
break;
case UnaryOperatorKind.Double:
specialType = SpecialType.System_Double;
break;
case UnaryOperatorKind.Decimal:
specialType = SpecialType.System_Decimal;
break;
case UnaryOperatorKind.Pointer:
return node.Type;
case UnaryOperatorKind.UserDefined:
case UnaryOperatorKind.Bool:
default:
throw ExceptionUtilities.UnexpectedValue(kind);
}
NamedTypeSymbol type = _compilation.GetSpecialType(specialType);
if (node.OperatorKind.IsLifted())
{
type = _compilation.GetSpecialType(SpecialType.System_Nullable_T).Construct(type);
}
return type;
}
private static BinaryOperatorKind GetCorrespondingBinaryOperator(BoundIncrementOperator node)
{
// We need to create expressions that have the semantics of incrementing or decrementing:
// sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal and
// any enum. However, the binary addition operators we have at our disposal are just
// int, uint, long, ulong, float, double and decimal.
UnaryOperatorKind unaryOperatorKind = node.OperatorKind;
BinaryOperatorKind result;
switch (unaryOperatorKind.OperandTypes())
{
case UnaryOperatorKind.Int:
case UnaryOperatorKind.SByte:
case UnaryOperatorKind.Short:
result = BinaryOperatorKind.Int;
break;
case UnaryOperatorKind.Byte:
case UnaryOperatorKind.UShort:
case UnaryOperatorKind.Char:
case UnaryOperatorKind.UInt:
result = BinaryOperatorKind.UInt;
break;
case UnaryOperatorKind.Long:
result = BinaryOperatorKind.Long;
break;
case UnaryOperatorKind.ULong:
result = BinaryOperatorKind.ULong;
break;
case UnaryOperatorKind.NInt:
result = BinaryOperatorKind.NInt;
break;
case UnaryOperatorKind.NUInt:
result = BinaryOperatorKind.NUInt;
break;
case UnaryOperatorKind.Float:
result = BinaryOperatorKind.Float;
break;
case UnaryOperatorKind.Double:
result = BinaryOperatorKind.Double;
break;
case UnaryOperatorKind.Decimal: //Dev10 special cased this, but we'll let DecimalRewriter handle it
result = BinaryOperatorKind.Decimal;
break;
case UnaryOperatorKind.Enum:
{
TypeSymbol? underlyingType = node.Type;
Debug.Assert(underlyingType is { });
if (underlyingType.IsNullableType())
{
underlyingType = underlyingType.GetNullableUnderlyingType();
}
Debug.Assert(underlyingType.IsEnumType());
underlyingType = underlyingType.GetEnumUnderlyingType();
Debug.Assert(underlyingType is { });
// Operator overload resolution will not have chosen the enumerated type
// unless the operand actually is of the enumerated type (or nullable enum type.)
switch (underlyingType.SpecialType)
{
case SpecialType.System_SByte:
case SpecialType.System_Int16:
case SpecialType.System_Int32:
result = BinaryOperatorKind.Int;
break;
case SpecialType.System_Byte:
case SpecialType.System_UInt16:
case SpecialType.System_UInt32:
result = BinaryOperatorKind.UInt;
break;
case SpecialType.System_Int64:
result = BinaryOperatorKind.Long;
break;
case SpecialType.System_UInt64:
result = BinaryOperatorKind.ULong;
break;
default:
throw ExceptionUtilities.UnexpectedValue(underlyingType.SpecialType);
}
}
break;
case UnaryOperatorKind.Pointer:
result = BinaryOperatorKind.PointerAndInt;
break;
case UnaryOperatorKind.UserDefined:
case UnaryOperatorKind.Bool:
default:
throw ExceptionUtilities.UnexpectedValue(unaryOperatorKind.OperandTypes());
}
switch (result)
{
case BinaryOperatorKind.UInt:
case BinaryOperatorKind.Int:
case BinaryOperatorKind.ULong:
case BinaryOperatorKind.Long:
case BinaryOperatorKind.NUInt:
case BinaryOperatorKind.NInt:
case BinaryOperatorKind.PointerAndInt:
result |= (BinaryOperatorKind)unaryOperatorKind.OverflowChecks();
break;
}
if (unaryOperatorKind.IsLifted())
{
result |= BinaryOperatorKind.Lifted;
}
return result;
}
private static (TypeSymbol, ConstantValue) GetConstantOneForIncrement(
CSharpCompilation compilation,
BinaryOperatorKind binaryOperatorKind)
{
ConstantValue constantOne;
switch (binaryOperatorKind.OperandTypes())
{
case BinaryOperatorKind.PointerAndInt:
case BinaryOperatorKind.Int:
constantOne = ConstantValue.Create(1);
break;
case BinaryOperatorKind.UInt:
constantOne = ConstantValue.Create(1U);
break;
case BinaryOperatorKind.Long:
constantOne = ConstantValue.Create(1L);
break;
case BinaryOperatorKind.ULong:
constantOne = ConstantValue.Create(1LU);
break;
case BinaryOperatorKind.NInt:
constantOne = ConstantValue.Create(1);
return (compilation.CreateNativeIntegerTypeSymbol(signed: true), constantOne);
case BinaryOperatorKind.NUInt:
constantOne = ConstantValue.Create(1U);
return (compilation.CreateNativeIntegerTypeSymbol(signed: false), constantOne);
case BinaryOperatorKind.Float:
constantOne = ConstantValue.Create(1f);
break;
case BinaryOperatorKind.Double:
constantOne = ConstantValue.Create(1.0);
break;
case BinaryOperatorKind.Decimal:
constantOne = ConstantValue.Create(1m);
break;
default:
throw ExceptionUtilities.UnexpectedValue(binaryOperatorKind.OperandTypes());
}
return (compilation.GetSpecialType(constantOne.SpecialType), constantOne);
}
}
}
|