public override BoundNode VisitConversion(BoundConversion node) { Debug.Assert(node != null); BoundExpression operand = (BoundExpression)this.Visit(node.Operand); switch (node.ConversionKind) { case ConversionKind.ImplicitNumeric: case ConversionKind.ExplicitNumeric: if (node.Type.SpecialType == SpecialType.System_Decimal) { return RewriteNumericToDecimalConversion(node.Syntax, operand, operand.Type); } else if (operand.Type.SpecialType == SpecialType.System_Decimal) { return RewriteDecimalToNumericConversion(node.Syntax, operand, node.Type); } break; default: break; } return node.Update(operand, node.ConversionKind, node.SymbolOpt, node.Checked, node.ExplicitCastInCode, node.IsExtensionMethod, node.ConstantValueOpt, node.ResultKind, node.Type); }
private void EmitConversionExpression(BoundConversion conversion, bool used) { switch (conversion.ConversionKind) { case ConversionKind.MethodGroup: EmitMethodGroupConversion(conversion, used); return; case ConversionKind.NullToPointer: // The null pointer is represented as 0u. builder.EmitIntConstant(0); builder.EmitOpCode(ILOpCode.Conv_u); return; } if (!used && !ConversionHasSideEffects(conversion)) { EmitExpression(conversion.Operand, false); // just do expr side effects return; } EmitExpression(conversion.Operand, true); EmitConversion(conversion); EmitPopIfUnused(used); }
private static BoundExpression ConvertToLocalTypeHelper(CSharpCompilation compilation, BoundExpression expr, TypeSymbol type, DiagnosticBag diagnostics) { // NOTE: This conversion can fail if some of the types involved are from not-yet-loaded modules. // For example, if System.Exception hasn't been loaded, then this call will fail for $stowedexception. HashSet <DiagnosticInfo> useSiteDiagnostics = null; var conversion = compilation.Conversions.ClassifyConversionFromExpression(expr, type, ref useSiteDiagnostics); diagnostics.Add(expr.Syntax, useSiteDiagnostics); Debug.Assert(conversion.IsValid || diagnostics.HasAnyErrors()); return(BoundConversion.Synthesized( expr.Syntax, expr, conversion, @checked: false, explicitCastInCode: false, constantValueOpt: null, type: type, hasErrors: !conversion.IsValid)); }
private void EmitSpecialUserDefinedConversion(BoundConversion conversion, bool used, BoundExpression operand) { var typeTo = (NamedTypeSymbol)conversion.Type; Debug.Assert((operand.Type.IsArray()) && this._module.Compilation.IsReadOnlySpanType(typeTo), "only special kinds of conversions involving ReadOnlySpan may be handled in emit"); if (!TryEmitReadonlySpanAsBlobWrapper(typeTo, operand, used, inPlace: false)) { // there are several reasons that could prevent us from emitting a wrapper // in such case we just emit the operand and then invoke the conversion method EmitExpression(operand, used); if (used) { // consumes 1 argument (array) and produces one result (span) _builder.EmitOpCode(ILOpCode.Call, stackAdjustment: 0); EmitSymbolToken(conversion.SymbolOpt, conversion.Syntax, optArgList: null); } } }
private void EmitImplicitReferenceConversion(BoundConversion conversion) { // turn operand into an O(operandType) // if the operand is already verifiably an O, we can use it as-is // otherwise we need to box it, so that verifier will start tracking an O if (!conversion.Operand.Type.IsVerifierReference()) { EmitBox(conversion.Operand.Type, conversion.Operand.Syntax); } // here we have O(operandType) that must be compatible with O(targetType) // // if target type is verifiably a reference type, we can leave the value as-is otherwise // we need to unbox to targetType to keep verifier happy. if (!conversion.Type.IsVerifierReference()) { _builder.EmitOpCode(ILOpCode.Unbox_any); EmitSymbolToken(conversion.Type, conversion.Syntax); } return; }
private static bool ConversionHasSideEffects(BoundConversion conversion) { // only some intrinsic conversions are side effect free the only side effect of an // intrinsic conversion is a throw when we fail to convert. switch (conversion.ConversionKind) { case ConversionKind.Identity: // NOTE: even explicit float/double identity conversion does not have side // effects since it does not throw case ConversionKind.ImplicitNumeric: case ConversionKind.ImplicitEnumeration: // implicit ref cast does not throw ... case ConversionKind.ImplicitReference: case ConversionKind.Boxing: return(false); // unchecked numeric conversion does not throw case ConversionKind.ExplicitNumeric: return(conversion.Checked); } return(true); }
public override BoundNode VisitIsOperator(BoundIsOperator node) { // rewrite is needed only for cases where there are no errors and // no warnings (i.e. non-constant result) generated during binding if (!node.HasErrors && node.ConstantValue == null) { BoundExpression operand = node.Operand; var targetType = node.TargetType.Type; var operandType = operand.Type; Debug.Assert(operandType != null); if (operandType.IsNullableType()) { //TODO: handle nullable types once nullable conversions are implemented } else if (!operandType.IsValueType) { if (operandType.IsSameType(targetType)) { // operand with bound identity or implicit conversion // We can replace the "is" instruction with a null check Visit(operand); if (operandType.TypeKind == TypeKind.TypeParameter) { // We need to box the type parameter even if it is a known // reference type to ensure there are no verifier errors operand = new BoundConversion(operand.Syntax, operand.SyntaxTree, operand, ConversionKind.Boxing, this.containingSymbol, false, false, null, compilation.GetSpecialType(SpecialType.System_Object)); } return(new BoundBinaryOperator(node.Syntax, node.SyntaxTree, BinaryOperatorKind.NotEqual, operand, new BoundLiteral(null, null, ConstantValue.Null, null), null, node.Type)); } } } return(base.VisitIsOperator(node)); }
public override BoundNode VisitIsOperator(BoundIsOperator node) { // rewrite is needed only for cases where there are no errors and // no warnings (i.e. non-constant result) generated during binding if (!node.HasErrors && node.ConstantValue == null) { BoundExpression operand = node.Operand; var targetType = node.TargetType.Type; var operandType = operand.Type; Debug.Assert(operandType != null); if (operandType.IsNullableType()) { //TODO: handle nullable types once nullable conversions are implemented } else if (!operandType.IsValueType) { if (operandType.IsSameType(targetType)) { // operand with bound identity or implicit conversion // We can replace the "is" instruction with a null check Visit(operand); if(operandType.TypeKind == TypeKind.TypeParameter) { // We need to box the type parameter even if it is a known // reference type to ensure there are no verifier errors operand = new BoundConversion(operand.Syntax, operand.SyntaxTree, operand, ConversionKind.Boxing, this.containingSymbol, false, false, null, compilation.GetSpecialType(SpecialType.System_Object)); } return new BoundBinaryOperator(node.Syntax, node.SyntaxTree, BinaryOperatorKind.NotEqual, operand, new BoundLiteral(null, null, ConstantValue.Null, null), null, node.Type); } } } return base.VisitIsOperator(node); }
private void EmitConversion(BoundConversion conversion) { switch (conversion.ConversionKind) { case ConversionKind.Identity: EmitIdentityConversion(conversion); break; case ConversionKind.ImplicitNumeric: case ConversionKind.ExplicitNumeric: EmitNumericConversion(conversion); break; case ConversionKind.ImplicitReference: case ConversionKind.Boxing: // from IL prospective ImplicitReference and Boxing conversions are the same thing. // both force operand to be an object (O) - which may involve boxing // and then assume that result has the target type - which may involve unboxing. EmitImplicitReferenceConversion(conversion); break; case ConversionKind.ExplicitReference: case ConversionKind.Unboxing: // from IL prospective ExplicitReference and UnBoxing conversions are the same thing. // both force operand to be an object (O) - which may involve boxing // and then reinterpret result as the target type - which may involve unboxing. EmitExplicitReferenceConversion(conversion); break; case ConversionKind.ImplicitEnumeration: case ConversionKind.ExplicitEnumeration: EmitEnumConversion(conversion); break; case ConversionKind.ImplicitUserDefined: case ConversionKind.ExplicitUserDefined: case ConversionKind.AnonymousFunction: case ConversionKind.MethodGroup: case ConversionKind.ImplicitDynamic: case ConversionKind.ExplicitDynamic: // None of these things should reach codegen (yet? maybe?) throw ExceptionUtilities.UnexpectedValue(conversion.ConversionKind); case ConversionKind.PointerToVoid: case ConversionKind.PointerToPointer: return; //no-op since they all have the same runtime representation case ConversionKind.PointerToInteger: case ConversionKind.IntegerToPointer: var fromType = conversion.Operand.Type; var fromPredefTypeKind = fromType.PrimitiveTypeCode; var toType = conversion.Type; var toPredefTypeKind = toType.PrimitiveTypeCode; #if DEBUG switch (fromPredefTypeKind) { case Microsoft.Cci.PrimitiveTypeCode.IntPtr: case Microsoft.Cci.PrimitiveTypeCode.UIntPtr: case Microsoft.Cci.PrimitiveTypeCode.Pointer: Debug.Assert(toPredefTypeKind.IsNumeric()); break; default: Debug.Assert(fromPredefTypeKind.IsNumeric()); Debug.Assert( toPredefTypeKind == Microsoft.Cci.PrimitiveTypeCode.IntPtr || toPredefTypeKind == Microsoft.Cci.PrimitiveTypeCode.UIntPtr || toPredefTypeKind == Microsoft.Cci.PrimitiveTypeCode.Pointer); break; } #endif builder.EmitNumericConversion(fromPredefTypeKind, toPredefTypeKind, conversion.Checked); break; case ConversionKind.NullToPointer: throw ExceptionUtilities.UnexpectedValue(conversion.ConversionKind); // Should be handled by caller. case ConversionKind.ImplicitNullable: case ConversionKind.ExplicitNullable: default: throw ExceptionUtilities.UnexpectedValue(conversion.ConversionKind); } }
private void EmitMethodGroupConversion(BoundConversion conversion, bool used) { var group = (BoundMethodGroup)conversion.Operand; EmitDelegateCreation(conversion, group.InstanceOpt, conversion.IsExtensionMethod, conversion.SymbolOpt, conversion.Type, used); }
private void EmitEnumConversion(BoundConversion conversion) { // Nullable enumeration conversions should have already been lowered into // implicit or explicit nullable conversions. Debug.Assert(!conversion.Type.IsNullableType()); var fromType = conversion.Operand.Type; if (fromType.IsEnumType()) { fromType = ((NamedTypeSymbol)fromType).EnumUnderlyingType; } var fromPredefTypeKind = fromType.PrimitiveTypeCode; Debug.Assert(fromPredefTypeKind.IsNumeric()); var toType = conversion.Type; if (toType.IsEnumType()) { toType = ((NamedTypeSymbol)toType).EnumUnderlyingType; } var toPredefTypeKind = toType.PrimitiveTypeCode; Debug.Assert(toPredefTypeKind.IsNumeric()); builder.EmitNumericConversion(fromPredefTypeKind, toPredefTypeKind, conversion.Checked); }
private void EmitExplicitReferenceConversion(BoundConversion conversion) { // turn operand into an O(operandType) // if the operand is already verifiably an O, we can use it as-is // otherwise we need to box it, so that verifier will start tracking an O if (!conversion.Operand.Type.IsVerifierReference()) { EmitBox(conversion.Operand.Type, conversion.Operand.Syntax); } // here we have O(operandType) that could be compatible with O(targetType) // // if target type is verifiably a reference type, we can just do a type check otherwise // we unbox which will both do the type check and start tracking actual target type in // verifier. if (conversion.Type.IsVerifierReference()) { builder.EmitOpCode(ILOpCode.Castclass); EmitSymbolToken(conversion.Type, conversion.Syntax); } else { builder.EmitOpCode(ILOpCode.Unbox_any); EmitSymbolToken(conversion.Type, conversion.Syntax); } }
private BoundConversion MakeImplicitReferenceConversionExplicit(BoundConversion implicitConv) { Debug.Assert(implicitConv.ConversionKind == ConversionKind.ImplicitReference); Debug.Assert(implicitConv.Type.TypeKind == TypeKind.Interface); return implicitConv.Update( operand: (BoundExpression)Visit(implicitConv.Operand), //NB: visit conversionKind: ConversionKind.ExplicitReference, symbolOpt: null, @checked: false, explicitCastInCode: false, constantValueOpt: implicitConv.ConstantValueOpt, type: implicitConv.Type); }
private BoundNode RewriteMethodGroupConversion(BoundConversion conversion) { // in a method group conversion, we may need to rewrite the selected method BoundMethodGroup operand = (BoundMethodGroup)conversion.Operand; BoundExpression originalReceiverOpt = operand.ReceiverOpt; BoundExpression receiverOpt; if (originalReceiverOpt == null) { receiverOpt = null; } else if (!conversion.IsExtensionMethod && conversion.SymbolOpt.IsStatic) { receiverOpt = new BoundTypeExpression(originalReceiverOpt.Syntax, null, VisitType(originalReceiverOpt.Type)); } else { receiverOpt = (BoundExpression)Visit(originalReceiverOpt); } TypeSymbol type = this.VisitType(conversion.Type); MethodSymbol method = conversion.SymbolOpt; // if the original receiver was a base access and is was rewritten, // change the method to point to the wrapper method if (BaseReferenceInReceiverWasRewritten(originalReceiverOpt, receiverOpt) && method.IsMetadataVirtual()) { method = GetMethodWrapperForBaseNonVirtualCall(method, conversion.Syntax); } method = VisitMethodSymbol(method); operand = operand.Update( TypeMap.SubstituteTypesWithoutModifiers(operand.TypeArgumentsOpt), method.Name, operand.Methods, operand.LookupSymbolOpt, operand.LookupError, operand.Flags, receiverOpt, operand.ResultKind); var conversionInfo = conversion.Conversion; if (conversionInfo.Method != (object)method) { conversionInfo = conversionInfo.SetConversionMethod(method); } return conversion.Update( operand, conversionInfo, isBaseConversion: conversion.IsBaseConversion, @checked: conversion.Checked, explicitCastInCode: conversion.ExplicitCastInCode, constantValueOpt: conversion.ConstantValueOpt, type: type); }
private void EmitConversion(BoundConversion conversion) { switch (conversion.ConversionKind) { case ConversionKind.Identity: EmitIdentityConversion(conversion); break; case ConversionKind.ImplicitNumeric: case ConversionKind.ExplicitNumeric: EmitNumericConversion(conversion); break; case ConversionKind.ImplicitReference: case ConversionKind.Boxing: // from IL prospective ImplicitReference and Boxing conversions are the same thing. // both force operand to be an object (O) - which may involve boxing // and then assume that result has the target type - which may involve unboxing. EmitImplicitReferenceConversion(conversion); break; case ConversionKind.ExplicitReference: case ConversionKind.Unboxing: // from IL prospective ExplicitReference and UnBoxing conversions are the same thing. // both force operand to be an object (O) - which may involve boxing // and then reinterpret result as the target type - which may involve unboxing. EmitExplicitReferenceConversion(conversion); break; case ConversionKind.ImplicitEnumeration: case ConversionKind.ExplicitEnumeration: EmitEnumConversion(conversion); break; case ConversionKind.ImplicitUserDefined: case ConversionKind.ExplicitUserDefined: case ConversionKind.AnonymousFunction: case ConversionKind.MethodGroup: case ConversionKind.ImplicitTupleLiteral: case ConversionKind.ImplicitTuple: case ConversionKind.ExplicitTuple: case ConversionKind.ImplicitDynamic: case ConversionKind.ExplicitDynamic: // None of these things should reach codegen (yet? maybe?) throw ExceptionUtilities.UnexpectedValue(conversion.ConversionKind); case ConversionKind.PointerToVoid: case ConversionKind.PointerToPointer: return; //no-op since they all have the same runtime representation case ConversionKind.PointerToInteger: case ConversionKind.IntegerToPointer: var fromType = conversion.Operand.Type; var fromPredefTypeKind = fromType.PrimitiveTypeCode; var toType = conversion.Type; var toPredefTypeKind = toType.PrimitiveTypeCode; #if DEBUG switch (fromPredefTypeKind) { case Microsoft.Cci.PrimitiveTypeCode.IntPtr: case Microsoft.Cci.PrimitiveTypeCode.UIntPtr: case Microsoft.Cci.PrimitiveTypeCode.Pointer: Debug.Assert(toPredefTypeKind.IsNumeric()); break; default: Debug.Assert(fromPredefTypeKind.IsNumeric()); Debug.Assert( toPredefTypeKind == Microsoft.Cci.PrimitiveTypeCode.IntPtr || toPredefTypeKind == Microsoft.Cci.PrimitiveTypeCode.UIntPtr || toPredefTypeKind == Microsoft.Cci.PrimitiveTypeCode.Pointer); break; } #endif _builder.EmitNumericConversion(fromPredefTypeKind, toPredefTypeKind, conversion.Checked); break; case ConversionKind.NullToPointer: throw ExceptionUtilities.UnexpectedValue(conversion.ConversionKind); // Should be handled by caller. case ConversionKind.ImplicitNullable: case ConversionKind.ExplicitNullable: default: throw ExceptionUtilities.UnexpectedValue(conversion.ConversionKind); } }
/// <summary> /// The rewrites are as follows: /// /// x++ /// temp = x /// x = temp + 1 /// return temp /// x-- /// temp = x /// x = temp - 1 /// return temp /// ++x /// temp = x + 1 /// x = temp /// return temp /// --x /// temp = x - 1 /// x = temp /// return temp /// /// In each case, the literal 1 is of the type required by the builtin addition/subtraction operator that /// will be used. The temp is of the same type as x, but the sum/difference may be wider, in which case a /// conversion is required. /// </summary> /// <param name="node">The unary operator expression representing the increment/decrement.</param> /// <param name="isPrefix">True for prefix, false for postfix.</param> /// <param name="isIncrement">True for increment, false for decrement.</param> /// <returns>A bound sequence that uses a temp to acheive the correct side effects and return value.</returns> private BoundNode LowerOperator(BoundUnaryOperator node, bool isPrefix, bool isIncrement) { BoundExpression operand = node.Operand; TypeSymbol operandType = operand.Type; //type of the variable being incremented Debug.Assert(operandType == node.Type); ConstantValue constantOne; BinaryOperatorKind binaryOperatorKind; MakeConstantAndOperatorKind(node.OperatorKind.OperandTypes(), node, out constantOne, out binaryOperatorKind); binaryOperatorKind |= isIncrement ? BinaryOperatorKind.Addition : BinaryOperatorKind.Subtraction; Debug.Assert(constantOne != null); Debug.Assert(constantOne.SpecialType != SpecialType.None); Debug.Assert(binaryOperatorKind.OperandTypes() != 0); TypeSymbol constantType = compilation.GetSpecialType(constantOne.SpecialType); BoundExpression boundOne = new BoundLiteral( syntax: null, syntaxTree: null, constantValueOpt: constantOne, type: constantType); LocalSymbol tempSymbol = new TempLocalSymbol(operandType, RefKind.None, containingSymbol); BoundExpression boundTemp = new BoundLocal( syntax: null, syntaxTree: null, localSymbol: tempSymbol, constantValueOpt: null, type: operandType); // NOTE: the LHS may have a narrower type than the operator expects, but that // doesn't seem to cause any problems. If a problem does arise, just add an // explicit BoundConversion. BoundExpression newValue = new BoundBinaryOperator( syntax: null, syntaxTree: null, operatorKind: binaryOperatorKind, left: isPrefix ? operand : boundTemp, right: boundOne, constantValueOpt: null, type: constantType); if (constantType != operandType) { newValue = new BoundConversion( syntax: null, syntaxTree: null, operand: newValue, conversionKind: operandType.IsEnumType() ? ConversionKind.ImplicitEnumeration : ConversionKind.ImplicitNumeric, symbolOpt: null, @checked: false, explicitCastInCode: false, constantValueOpt: null, type: operandType); } ReadOnlyArray<BoundExpression> assignments = ReadOnlyArray<BoundExpression>.CreateFrom( new BoundAssignmentOperator( syntax: null, syntaxTree: null, left: boundTemp, right: isPrefix ? newValue : operand, type: operandType), new BoundAssignmentOperator( syntax: null, syntaxTree: null, left: operand, right: isPrefix ? boundTemp : newValue, type: operandType)); return new BoundSequence( syntax: node.Syntax, syntaxTree: node.SyntaxTree, locals: ReadOnlyArray<LocalSymbol>.CreateFrom(tempSymbol), sideEffects: assignments, value: boundTemp, type: operandType); }
// private static T <Factory>(object[] submissionArray) // { // var submission = new Submission#N(submissionArray); // return submission.<Initialize>(); // } internal override BoundBlock CreateBody() { var syntax = this.GetSyntax(); var ctor = _containingType.GetScriptConstructor(); Debug.Assert(ctor.ParameterCount == 1); var initializer = _containingType.GetScriptInitializer(); Debug.Assert(initializer.ParameterCount == 0); var submissionArrayParameter = new BoundParameter(syntax, _parameters[0]) { WasCompilerGenerated = true }; var submissionLocal = new BoundLocal( syntax, new SynthesizedLocal(this, _containingType, SynthesizedLocalKind.LoweringTemp), null, _containingType) { WasCompilerGenerated = true }; // var submission = new Submission#N(submissionArray); var submissionAssignment = new BoundExpressionStatement( syntax, new BoundAssignmentOperator( syntax, submissionLocal, new BoundObjectCreationExpression( syntax, ctor, ImmutableArray.Create <BoundExpression>(submissionArrayParameter), default(ImmutableArray <string>), default(ImmutableArray <RefKind>), false, default(ImmutableArray <int>), null, null, _containingType) { WasCompilerGenerated = true }, _containingType) { WasCompilerGenerated = true }) { WasCompilerGenerated = true }; // return submission.<Initialize>(); BoundExpression initializeResult = new BoundCall( syntax, submissionLocal, initializer, ImmutableArray <BoundExpression> .Empty, default(ImmutableArray <string>), default(ImmutableArray <RefKind>), isDelegateCall: false, expanded: false, invokedAsExtensionMethod: false, argsToParamsOpt: default(ImmutableArray <int>), resultKind: LookupResultKind.Viable, type: initializer.ReturnType) { WasCompilerGenerated = true }; if (initializeResult.Type.IsStructType() && (_returnType.SpecialType == SpecialType.System_Object)) { initializeResult = new BoundConversion(syntax, initializeResult, Conversion.Boxing, false, true, ConstantValue.NotAvailable, _returnType) { WasCompilerGenerated = true }; } var returnStatement = new BoundReturnStatement( syntax, initializeResult) { WasCompilerGenerated = true }; return(new BoundBlock(syntax, ImmutableArray.Create <LocalSymbol>(submissionLocal.LocalSymbol), ImmutableArray.Create <BoundStatement>(submissionAssignment, returnStatement)) { WasCompilerGenerated = true }); }
private static bool IsSafeForReordering(BoundExpression expression, RefKind kind) { // To be safe for reordering an expression must not cause any observable side effect *or // observe any side effect*. Accessing a local by value, for example, is possibly not // safe for reordering because reading a local can give a different result if reordered // with respect to a write elsewhere. var current = expression; while (true) { if (current.ConstantValue != null) { return(true); } switch (current.Kind) { default: return(false); case BoundKind.Parameter: case BoundKind.Local: // A ref to a local variable or formal parameter is safe to reorder; it // never has a side effect or consumes one. return(kind != RefKind.None); case BoundKind.Conversion: { BoundConversion conv = (BoundConversion)current; switch (conv.ConversionKind) { case ConversionKind.AnonymousFunction: case ConversionKind.ImplicitConstant: case ConversionKind.MethodGroup: case ConversionKind.NullLiteral: return(true); case ConversionKind.Boxing: case ConversionKind.Dynamic: case ConversionKind.ExplicitEnumeration: case ConversionKind.ExplicitNullable: case ConversionKind.ExplicitNumeric: case ConversionKind.ExplicitReference: case ConversionKind.Identity: case ConversionKind.ImplicitEnumeration: case ConversionKind.ImplicitNullable: case ConversionKind.ImplicitNumeric: case ConversionKind.ImplicitReference: case ConversionKind.Unboxing: current = conv.Operand; break; case ConversionKind.ExplicitUserDefined: case ConversionKind.ImplicitUserDefined: return(false); default: Debug.Fail("Unhandled conversion kind in reordering logic"); return(false); } break; } } } }
// private static T <Factory>(object[] submissionArray) // { // var submission = new Submission#N(submissionArray); // return submission.<Initialize>(); // } internal override BoundBlock CreateBody() { var syntax = this.GetSyntax(); var ctor = _containingType.GetScriptConstructor(); Debug.Assert(ctor.ParameterCount == 1); var initializer = _containingType.GetScriptInitializer(); Debug.Assert(initializer.ParameterCount == 0); var submissionArrayParameter = new BoundParameter(syntax, _parameters[0]) { WasCompilerGenerated = true }; var submissionLocal = new BoundLocal( syntax, new SynthesizedLocal(this, _containingType, SynthesizedLocalKind.LoweringTemp), null, _containingType) { WasCompilerGenerated = true }; // var submission = new Submission#N(submissionArray); var submissionAssignment = new BoundExpressionStatement( syntax, new BoundAssignmentOperator( syntax, submissionLocal, new BoundObjectCreationExpression( syntax, ctor, ImmutableArray.Create<BoundExpression>(submissionArrayParameter), default(ImmutableArray<string>), default(ImmutableArray<RefKind>), false, default(ImmutableArray<int>), null, null, _containingType) { WasCompilerGenerated = true }, _containingType) { WasCompilerGenerated = true }) { WasCompilerGenerated = true }; // return submission.<Initialize>(); BoundExpression initializeResult = new BoundCall( syntax, submissionLocal, initializer, ImmutableArray<BoundExpression>.Empty, default(ImmutableArray<string>), default(ImmutableArray<RefKind>), isDelegateCall: false, expanded: false, invokedAsExtensionMethod: false, argsToParamsOpt: default(ImmutableArray<int>), resultKind: LookupResultKind.Viable, type: initializer.ReturnType) { WasCompilerGenerated = true }; if (initializeResult.Type.IsStructType() && (_returnType.SpecialType == SpecialType.System_Object)) { initializeResult = new BoundConversion(syntax, initializeResult, Conversion.Boxing, false, true, ConstantValue.NotAvailable, _returnType) { WasCompilerGenerated = true }; } var returnStatement = new BoundReturnStatement( syntax, initializeResult) { WasCompilerGenerated = true }; return new BoundBlock(syntax, ImmutableArray.Create<LocalSymbol>(submissionLocal.LocalSymbol), ImmutableArray.Create<BoundStatement>(submissionAssignment, returnStatement)) { WasCompilerGenerated = true }; }
// Generates: // // private static T {Factory}(InteractiveSession session) // { // T submissionResult; // new {ThisScriptClass}(session, out submissionResult); // return submissionResult; // } private BoundBlock CreateSubmissionFactoryBody() { Debug.Assert(_containingType.TypeKind == TypeKind.Submission); SyntaxTree syntaxTree = CSharpSyntaxTree.Dummy; CSharpSyntaxNode syntax = (CSharpSyntaxNode)syntaxTree.GetRoot(); var interactiveSessionParam = new BoundParameter(syntax, _parameters[0]) { WasCompilerGenerated = true }; var ctor = _containingType.InstanceConstructors.Single(); Debug.Assert(ctor is SynthesizedInstanceConstructor); Debug.Assert(ctor.ParameterCount == 2); var submissionResultType = ctor.Parameters[1].Type; var resultLocal = new SynthesizedLocal(ctor, submissionResultType, SynthesizedLocalKind.LoweringTemp); var localReference = new BoundLocal(syntax, resultLocal, null, submissionResultType) { WasCompilerGenerated = true }; BoundExpression submissionResult = localReference; if (submissionResultType.IsStructType() && _returnType.SpecialType == SpecialType.System_Object) { submissionResult = new BoundConversion(syntax, submissionResult, Conversion.Boxing, false, true, ConstantValue.NotAvailable, _returnType) { WasCompilerGenerated = true }; } return new BoundBlock(syntax, // T submissionResult; ImmutableArray.Create<LocalSymbol>(resultLocal), ImmutableArray.Create<BoundStatement>( // new Submission(interactiveSession, out submissionResult); new BoundExpressionStatement(syntax, new BoundObjectCreationExpression( syntax, ctor, ImmutableArray.Create<BoundExpression>(interactiveSessionParam, localReference), ImmutableArray<string>.Empty, ImmutableArray.Create<RefKind>(RefKind.None, RefKind.Ref), false, default(ImmutableArray<int>), null, null, _containingType ) { WasCompilerGenerated = true }) { WasCompilerGenerated = true }, // return submissionResult; new BoundReturnStatement(syntax, submissionResult) { WasCompilerGenerated = true })) { WasCompilerGenerated = true }; }
// Generates: // // private static T {Factory}(InteractiveSession session) // { // T submissionResult; // new {ThisScriptClass}(session, out submissionResult); // return submissionResult; // } private BoundBlock CreateSubmissionFactoryBody() { Debug.Assert(containingType.TypeKind == TypeKind.Submission); SyntaxTree syntaxTree = CSharpSyntaxTree.Dummy; CSharpSyntaxNode syntax = (CSharpSyntaxNode)syntaxTree.GetRoot(); var interactiveSessionParam = new BoundParameter(syntax, parameters[0]) { WasCompilerGenerated = true }; var ctor = containingType.InstanceConstructors.Single(); Debug.Assert(ctor is SynthesizedInstanceConstructor); Debug.Assert(ctor.ParameterCount == 2); var submissionResultType = ctor.Parameters[1].Type; var resultLocal = new SynthesizedLocal(ctor, submissionResultType, SynthesizedLocalKind.LoweringTemp); var localReference = new BoundLocal(syntax, resultLocal, null, submissionResultType) { WasCompilerGenerated = true }; BoundExpression submissionResult = localReference; if (submissionResultType.IsStructType() && this.returnType.SpecialType == SpecialType.System_Object) { submissionResult = new BoundConversion(syntax, submissionResult, Conversion.Boxing, false, true, ConstantValue.NotAvailable, this.returnType) { WasCompilerGenerated = true }; } return(new BoundBlock(syntax, // T submissionResult; ImmutableArray.Create <LocalSymbol>(resultLocal), ImmutableArray.Create <BoundStatement>( // new Submission(interactiveSession, out submissionResult); new BoundExpressionStatement(syntax, new BoundObjectCreationExpression( syntax, ctor, ImmutableArray.Create <BoundExpression>(interactiveSessionParam, localReference), ImmutableArray <string> .Empty, ImmutableArray.Create <RefKind>(RefKind.None, RefKind.Ref), false, default(ImmutableArray <int>), null, null, containingType ) { WasCompilerGenerated = true }) { WasCompilerGenerated = true }, // return submissionResult; new BoundReturnStatement(syntax, submissionResult) { WasCompilerGenerated = true })) { WasCompilerGenerated = true }); }
public override object VisitConversion(BoundConversion node, object arg) { Visit(node.Operand); return(null); }
private void EmitIdentityConversion(BoundConversion conversion) { // An _explicit_ identity conversion from double to double or float to float on // non-constants must stay as a conversion. An _implicit_ identity conversion can be // optimized away. Why? Because (double)d1 + d2 has different semantics than d1 + d2. // The former rounds off to 64 bit precision; the latter is permitted to use higher // precision math if d1 is enregistered. if (conversion.ExplicitCastInCode) { switch (conversion.Type.PrimitiveTypeCode) { case Microsoft.Cci.PrimitiveTypeCode.Float32: case Microsoft.Cci.PrimitiveTypeCode.Float64: // For explicitly-written "identity conversions" from float to float or // double to double, we require the generation of conv.r4 or conv.r8. The // runtime can use these instructions to truncate precision, and csc.exe // generates them. It's not ideal, we should consider the possibility of not // doing this or marking somewhere else that this is necessary. // Don't need to do this for constants, however. if (conversion.Operand.ConstantValue == null) { EmitNumericConversion(conversion); } break; } } }
private void EmitNumericConversion(BoundConversion conversion) { var fromType = conversion.Operand.Type; var fromPredefTypeKind = fromType.PrimitiveTypeCode; Debug.Assert(fromPredefTypeKind.IsNumeric()); var toType = conversion.Type; var toPredefTypeKind = toType.PrimitiveTypeCode; Debug.Assert(toPredefTypeKind.IsNumeric()); builder.EmitNumericConversion(fromPredefTypeKind, toPredefTypeKind, conversion.Checked); }
public override BoundNode VisitConversion(BoundConversion conversion) { if (conversion.ConversionKind == ConversionKind.MethodGroup && (object)conversion.SymbolOpt != null) { return RewriteMethodGroupConversion(conversion); } else { return base.VisitConversion(conversion); } }
private void EmitImplicitReferenceConversion(BoundConversion conversion) { // turn operand into an O(operandType) // if the operand is already verifiably an O, we can use it as-is // otherwise we need to box it, so that verifier will start tracking an O if (!conversion.Operand.Type.IsVerifierReference()) { EmitBox(conversion.Operand.Type, conversion.Operand.Syntax); } // here we have O(operandType) that must be compatible with O(targetType) // // if target type is verifiably a reference type, we can leave the value as-is otherwise // we need to unbox to targetType to keep verifier happy. if (!conversion.Type.IsVerifierReference()) { builder.EmitOpCode(ILOpCode.Unbox_any); EmitSymbolToken(conversion.Type, conversion.Syntax); } return; }
private static bool ConversionHasSideEffects(BoundConversion conversion) { // only some intrinsic conversions are side effect free the only side effect of an // intrinsic conversion is a throw when we fail to convert. switch (conversion.ConversionKind) { case ConversionKind.Identity: // NOTE: even explicit float/double identity conversion does not have side // effects since it does not throw case ConversionKind.ImplicitNumeric: case ConversionKind.ImplicitEnumeration: // implicit ref cast does not throw ... case ConversionKind.ImplicitReference: case ConversionKind.Boxing: return false; // unchecked numeric conversion does not throw case ConversionKind.ExplicitNumeric: return conversion.Checked; } return true; }
public override BoundNode VisitCompoundAssignmentOperator(BoundCompoundAssignmentOperator node) { if (node.HasErrors) { return(node); } // There are five possible cases. // // Case 1: receiver.Prop += value is transformed into // temp = receiver // temp.Prop = temp.Prop + value // and a later rewriting will turn that into calls to getters and setters. // // Case 2: collection[i1, i2, i3] += value is transformed into // tc = collection // t1 = i1 // t2 = i2 // t3 = i3 // tc[t1, t2, t3] = tc[t1, t2, t3] + value // and again, a later rewriting will turn that into getters and setters of the indexer. // // Case 3: local += value (and param += value) needs no temporaries; it simply // becomes local = local + value. // // Case 4: staticField += value needs no temporaries either. However, classInst.field += value becomes // temp = classInst // temp.field = temp.field + value // // Case 5: otherwise, it must be structVariable.field += value or array[index] += value. Either way // we have a variable on the left. Transform it into: // ref temp = ref variable // temp = temp + value var temps = ArrayBuilder <LocalSymbol> .GetInstance(); var stores = ArrayBuilder <BoundExpression> .GetInstance(); // This will be filled in with the LHS that uses temporaries to prevent // double-evaluation of side effects. BoundExpression transformedLHS = null; if (node.Left.Kind == BoundKind.PropertyAccess) { // We need to stash away the receiver so that it does not get evaluated twice. // If the receiver is classified as a value of reference type then we can simply say // // R temp = receiver // temp.prop = temp.prop + rhs // // But if the receiver is classified as a variable of struct type then we // cannot make a copy of the value; we need to make sure that we mutate // the original receiver, not the copy. We have to generate // // ref R temp = ref receiver // temp.prop = temp.prop + rhs // // The rules of C# (in section 7.17.1) require that if you have receiver.prop // as the target of an assignment such that receiver is a value type, it must // be classified as a variable. If we've gotten this far in the rewriting, // assume that was the case. var prop = (BoundPropertyAccess)node.Left; // If the property is static then we can just generate prop = prop + value if (prop.ReceiverOpt == null) { transformedLHS = prop; } else { // Can we ever avoid storing the receiver in a temp? If the receiver is a variable then it // might be modified by the computation of the getter, the value, or the operation. // The receiver cannot be a null constant or constant of value type. It could be a // constant of string type, but there are no mutable properties of a string. // Similarly, there are no mutable properties of a Type object, so the receiver // cannot be a typeof(T) expression. The only situation I can think of where we could // optimize away the temp is if the receiver is a readonly field of reference type, // we are not in a constructor, and the receiver of the *field*, if any, is also idempotent. // It doesn't seem worthwhile to pursue an optimization for this exceedingly rare case. var rewrittenReceiver = (BoundExpression)Visit(prop.ReceiverOpt); var receiverTemp = TempHelpers.StoreToTemp(rewrittenReceiver, rewrittenReceiver.Type.IsValueType ? RefKind.Ref : RefKind.None, containingSymbol); stores.Add(receiverTemp.Item1); temps.Add(receiverTemp.Item2.LocalSymbol); transformedLHS = new BoundPropertyAccess(prop.Syntax, prop.SyntaxTree, receiverTemp.Item2, prop.PropertySymbol, prop.Type); } } else if (node.Left.Kind == BoundKind.IndexerAccess) { var indexer = (BoundIndexerAccess)node.Left; BoundExpression transformedReceiver = null; if (indexer.ReceiverOpt != null) { var rewrittenReceiver = (BoundExpression)Visit(indexer.ReceiverOpt); var receiverTemp = TempHelpers.StoreToTemp(rewrittenReceiver, rewrittenReceiver.Type.IsValueType ? RefKind.Ref : RefKind.None, containingSymbol); transformedReceiver = receiverTemp.Item2; stores.Add(receiverTemp.Item1); temps.Add(receiverTemp.Item2.LocalSymbol); } // UNDONE: Dealing with the arguments is a bit tricky because they can be named out-of-order arguments; // UNDONE: we have to preserve both the source-code order of the side effects and the side effects // UNDONE: only being executed once. // UNDONE: // UNDONE: This is a subtly different problem than the problem faced by the conventional call // UNDONE: rewriter; with the conventional call rewriter we already know that the side effects // UNDONE: will only be executed once because the arguments are only being pushed on the stack once. // UNDONE: In a compound equality operator on an indexer the indices are placed on the stack twice. // UNDONE: That is to say, if you have: // UNDONE: // UNDONE: C().M(z : Z(), x : X(), y : Y()) // UNDONE: // UNDONE: then we can rewrite that into // UNDONE: // UNDONE: tempc = C() // UNDONE: tempz = Z() // UNDONE: tempc.M(X(), Y(), tempz) // UNDONE: // UNDONE: See, we can optimize away two of the temporaries, for x and y. But we cannot optimize away any of the // UNDONE: temporaries in // UNDONE: // UNDONE: C().Collection[z : Z(), x : X(), y : Y()] += 1; // UNDONE: // UNDONE: because we have to ensure not just that Z() happens first, but in additioan that X() and Y() are only // UNDONE: called once. We have to generate this as // UNDONE: // UNDONE: tempc = C().Collection // UNDONE: tempz = Z() // UNDONE: tempx = X() // UNDONE: tempy = Y() // UNDONE: tempc[tempx, tempy, tempz] = tempc[tempx, tempy, tempz] + 1; // UNDONE: // UNDONE: Fortunately arguments to indexers are never ref or out, so we don't need to worry about that. // UNDONE: However, we can still do the optimization where constants are not stored in // UNDONE: temporaries; if we have // UNDONE: // UNDONE: C().Collection[z : 123, y : Y(), x : X()] += 1; // UNDONE: // UNDONE: Then we can generate that as // UNDONE: // UNDONE: tempc = C().Collection // UNDONE: tempx = X() // UNDONE: tempy = Y() // UNDONE: tempc[tempx, tempy, 123] = tempc[tempx, tempy, 123] + 1; // UNDONE: // UNDONE: For now, we'll punt on both problems, as indexers are not implemented yet anyway. // UNDONE: We'll just generate one temporary for each argument. This will work, but in the // UNDONE: subsequent rewritings will generate more unnecessary temporaries. var transformedArguments = ArrayBuilder <BoundExpression> .GetInstance(); foreach (var argument in indexer.Arguments) { var rewrittenArgument = (BoundExpression)Visit(argument); var argumentTemp = TempHelpers.StoreToTemp(rewrittenArgument, RefKind.None, containingSymbol); transformedArguments.Add(argumentTemp.Item2); stores.Add(argumentTemp.Item1); temps.Add(argumentTemp.Item2.LocalSymbol); } transformedLHS = new BoundIndexerAccess(indexer.Syntax, indexer.SyntaxTree, transformedArguments.ToReadOnlyAndFree(), transformedReceiver, indexer.IndexerSymbol, indexer.Type); } else if (node.Left.Kind == BoundKind.Local || node.Left.Kind == BoundKind.Parameter) { // No temporaries are needed. Just generate local = local + value transformedLHS = node.Left; } else if (node.Left.Kind == BoundKind.FieldAccess) { // * If the field is static then no temporaries are needed. // * If the field is not static and the receiver is of reference type then generate t = r; t.f = t.f + value // * If the field is not static and the receiver is a variable of value type then we'll fall into the // general variable case below. var fieldAccess = (BoundFieldAccess)node.Left; if (fieldAccess.ReceiverOpt == null) { transformedLHS = fieldAccess; } else if (!fieldAccess.ReceiverOpt.Type.IsValueType) { var rewrittenReceiver = (BoundExpression)Visit(fieldAccess.ReceiverOpt); var receiverTemp = TempHelpers.StoreToTemp(rewrittenReceiver, RefKind.None, containingSymbol); stores.Add(receiverTemp.Item1); temps.Add(receiverTemp.Item2.LocalSymbol); transformedLHS = new BoundFieldAccess(fieldAccess.Syntax, fieldAccess.SyntaxTree, receiverTemp.Item2, fieldAccess.FieldSymbol, null); } } if (transformedLHS == null) { // We made no transformation above. Either we have array[index] += value or // structVariable.field += value; either way we have a potentially complicated variable- // producing expression on the left. Generate // ref temp = ref variable; temp = temp + value var rewrittenVariable = (BoundExpression)Visit(node.Left); var variableTemp = TempHelpers.StoreToTemp(rewrittenVariable, RefKind.Ref, containingSymbol); stores.Add(variableTemp.Item1); temps.Add(variableTemp.Item2.LocalSymbol); transformedLHS = variableTemp.Item2; } // OK, we now have the temporary declarations, the temporary stores, and the transformed left hand side. // We need to generate // // xlhs = (FINAL)((LEFT)xlhs op rhs) // // And then wrap it up with the generated temporaries. // // (The right hand side has already been converted to the type expected by the operator.) BoundExpression opLHS = BoundConversion.SynthesizedConversion(transformedLHS, node.LeftConversion, node.Operator.LeftType); Debug.Assert(node.Right.Type == node.Operator.RightType); BoundExpression op = new BoundBinaryOperator(null, null, node.Operator.Kind, opLHS, node.Right, null, node.Operator.ReturnType); BoundExpression opFinal = BoundConversion.SynthesizedConversion(op, node.FinalConversion, node.Left.Type); BoundExpression assignment = new BoundAssignmentOperator(null, null, transformedLHS, opFinal, node.Left.Type); // OK, at this point we have: // // * temps evaluating and storing portions of the LHS that must be evaluated only once. // * the "transformed" left hand side, rebuilt to use temps where necessary // * the assignment "xlhs = (FINAL)((LEFT)xlhs op (RIGHT)rhs)" // // Notice that we have recursively rewritten the bound nodes that are things stored in // the temps, but we might have more rewriting to do on the assignment. There are three // conversions in there that might be lowered to method calls, an operator that might // be lowered to delegate combine, string concat, and so on, and don't forget, we // haven't lowered the right hand side at all! Let's rewrite all these things at once. BoundExpression rewrittenAssignment = (BoundExpression)Visit(assignment); BoundExpression result = (temps.Count == 0) ? rewrittenAssignment : new BoundSequence(null, null, temps.ToReadOnly(), stores.ToReadOnly(), rewrittenAssignment, rewrittenAssignment.Type); temps.Free(); stores.Free(); return(result); }
private void EmitConversion(BoundConversion conversion) { switch (conversion.ConversionKind) { case ConversionKind.Identity: EmitIdentityConversion(conversion); break; case ConversionKind.ImplicitNumeric: case ConversionKind.ExplicitNumeric: EmitNumericConversion(conversion); break; case ConversionKind.ImplicitReference: case ConversionKind.Boxing: // from IL perspective ImplicitReference and Boxing conversions are the same thing. // both force operand to be an object (O) - which may involve boxing // and then assume that result has the target type - which may involve unboxing. EmitImplicitReferenceConversion(conversion); break; case ConversionKind.ExplicitReference: case ConversionKind.Unboxing: // from IL perspective ExplicitReference and UnBoxing conversions are the same thing. // both force operand to be an object (O) - which may involve boxing // and then reinterpret result as the target type - which may involve unboxing. EmitExplicitReferenceConversion(conversion); break; case ConversionKind.ImplicitEnumeration: case ConversionKind.ExplicitEnumeration: EmitEnumConversion(conversion); break; case ConversionKind.ImplicitUserDefined: case ConversionKind.ExplicitUserDefined: case ConversionKind.AnonymousFunction: case ConversionKind.MethodGroup: case ConversionKind.ImplicitTupleLiteral: case ConversionKind.ImplicitTuple: case ConversionKind.ExplicitTupleLiteral: case ConversionKind.ExplicitTuple: case ConversionKind.ImplicitDynamic: case ConversionKind.ExplicitDynamic: case ConversionKind.ImplicitThrow: // None of these things should reach codegen (yet? maybe?) throw ExceptionUtilities.UnexpectedValue(conversion.ConversionKind); case ConversionKind.ImplicitPointerToVoid: case ConversionKind.ExplicitPointerToPointer: case ConversionKind.ImplicitPointer: return; //no-op since they all have the same runtime representation case ConversionKind.ExplicitPointerToInteger: case ConversionKind.ExplicitIntegerToPointer: var fromType = conversion.Operand.Type; var fromPredefTypeKind = fromType.PrimitiveTypeCode; var toType = conversion.Type; var toPredefTypeKind = toType.PrimitiveTypeCode; #if DEBUG switch (fromPredefTypeKind) { case Microsoft.Cci.PrimitiveTypeCode.IntPtr when !fromType.IsNativeIntegerType: case Microsoft.Cci.PrimitiveTypeCode.UIntPtr when !fromType.IsNativeIntegerType: case Microsoft.Cci.PrimitiveTypeCode.Pointer: case Microsoft.Cci.PrimitiveTypeCode.FunctionPointer: Debug.Assert(IsNumeric(toType)); break; default: Debug.Assert(IsNumeric(fromType)); Debug.Assert( (toPredefTypeKind == Microsoft.Cci.PrimitiveTypeCode.IntPtr || toPredefTypeKind == Microsoft.Cci.PrimitiveTypeCode.UIntPtr) && !toType.IsNativeIntegerType || toPredefTypeKind == Microsoft.Cci.PrimitiveTypeCode.Pointer || toPredefTypeKind == Microsoft.Cci.PrimitiveTypeCode.FunctionPointer); break; } #endif _builder.EmitNumericConversion(fromPredefTypeKind, toPredefTypeKind, conversion.Checked); break; case ConversionKind.PinnedObjectToPointer: // CLR allows unsafe conversion from(O) to native int/uint. // The conversion does not change the representation of the value, // but the value will not be reported to subsequent GC operations (and therefore will not be updated by such operations) _builder.EmitOpCode(ILOpCode.Conv_u); break; case ConversionKind.ImplicitNullToPointer: throw ExceptionUtilities.UnexpectedValue(conversion.ConversionKind); // Should be handled by caller. case ConversionKind.ImplicitNullable: case ConversionKind.ExplicitNullable: default: throw ExceptionUtilities.UnexpectedValue(conversion.ConversionKind); } }
/// <summary> /// The rewrites are as follows: /// /// x++ /// temp = x /// x = temp + 1 /// return temp /// x-- /// temp = x /// x = temp - 1 /// return temp /// ++x /// temp = x + 1 /// x = temp /// return temp /// --x /// temp = x - 1 /// x = temp /// return temp /// /// In each case, the literal 1 is of the type required by the builtin addition/subtraction operator that /// will be used. The temp is of the same type as x, but the sum/difference may be wider, in which case a /// conversion is required. /// </summary> /// <param name="node">The unary operator expression representing the increment/decrement.</param> /// <param name="isPrefix">True for prefix, false for postfix.</param> /// <param name="isIncrement">True for increment, false for decrement.</param> /// <returns>A bound sequence that uses a temp to acheive the correct side effects and return value.</returns> private BoundNode LowerOperator(BoundUnaryOperator node, bool isPrefix, bool isIncrement) { BoundExpression operand = node.Operand; TypeSymbol operandType = operand.Type; //type of the variable being incremented Debug.Assert(operandType == node.Type); ConstantValue constantOne; BinaryOperatorKind binaryOperatorKind; MakeConstantAndOperatorKind(node.OperatorKind.OperandTypes(), node, out constantOne, out binaryOperatorKind); binaryOperatorKind |= isIncrement ? BinaryOperatorKind.Addition : BinaryOperatorKind.Subtraction; Debug.Assert(constantOne != null); Debug.Assert(constantOne.SpecialType != SpecialType.None); Debug.Assert(binaryOperatorKind.OperandTypes() != 0); TypeSymbol constantType = compilation.GetSpecialType(constantOne.SpecialType); BoundExpression boundOne = new BoundLiteral( syntax: null, syntaxTree: null, constantValueOpt: constantOne, type: constantType); LocalSymbol tempSymbol = new TempLocalSymbol(operandType, RefKind.None, containingSymbol); BoundExpression boundTemp = new BoundLocal( syntax: null, syntaxTree: null, localSymbol: tempSymbol, constantValueOpt: null, type: operandType); // NOTE: the LHS may have a narrower type than the operator expects, but that // doesn't seem to cause any problems. If a problem does arise, just add an // explicit BoundConversion. BoundExpression newValue = new BoundBinaryOperator( syntax: null, syntaxTree: null, operatorKind: binaryOperatorKind, left: isPrefix ? operand : boundTemp, right: boundOne, constantValueOpt: null, type: constantType); if (constantType != operandType) { newValue = new BoundConversion( syntax: null, syntaxTree: null, operand: newValue, conversionKind: operandType.IsEnumType() ? ConversionKind.ImplicitEnumeration : ConversionKind.ImplicitNumeric, symbolOpt: null, @checked: false, explicitCastInCode: false, constantValueOpt: null, type: operandType); } ReadOnlyArray <BoundExpression> assignments = ReadOnlyArray <BoundExpression> .CreateFrom( new BoundAssignmentOperator( syntax : null, syntaxTree : null, left : boundTemp, right : isPrefix ? newValue : operand, type : operandType), new BoundAssignmentOperator( syntax : null, syntaxTree : null, left : operand, right : isPrefix ? boundTemp : newValue, type : operandType)); return(new BoundSequence( syntax: node.Syntax, syntaxTree: node.SyntaxTree, locals: ReadOnlyArray <LocalSymbol> .CreateFrom(tempSymbol), sideEffects: assignments, value: boundTemp, type: operandType)); }