/// <summary> /// Adds casts (if necessary) to convert this expression to the specified target type. /// </summary> /// <remarks> /// If the target type is narrower than the source type, the value is truncated. /// If the target type is wider than the source type, the value is sign- or zero-extended based on the /// sign of the source type. /// This fits with the ExpressionBuilder's post-condition, so e.g. an assignment can simply /// call <c>Translate(stloc.Value).ConvertTo(stloc.Variable.Type)</c> and have the overall C# semantics match the IL semantics. /// /// From the caller's perspective, IntPtr/UIntPtr behave like normal C# integers except that they have native int size. /// All the special cases necessary to make IntPtr/UIntPtr behave sanely are handled internally in ConvertTo(). /// </remarks> public TranslatedExpression ConvertTo(IType targetType, ExpressionBuilder expressionBuilder, bool checkForOverflow = false, bool allowImplicitConversion = false) { var type = this.Type; if (type.Equals(targetType)) { // Remove boxing conversion if possible if (allowImplicitConversion && type.IsKnownType(KnownTypeCode.Object)) { if (Expression is CastExpression cast && ResolveResult is ConversionResolveResult conversion && conversion.Conversion.IsBoxingConversion) { return(this.UnwrapChild(cast.Expression)); } } return(this); } var compilation = expressionBuilder.compilation; if (type.IsKnownType(KnownTypeCode.Boolean) && targetType.GetStackType().IsIntegerType()) { // convert from boolean to integer (or enum) return(new ConditionalExpression( this.Expression, LdcI4(compilation, 1).ConvertTo(targetType, expressionBuilder, checkForOverflow), LdcI4(compilation, 0).ConvertTo(targetType, expressionBuilder, checkForOverflow) ).WithoutILInstruction().WithRR(new ResolveResult(targetType))); } if (targetType.IsKnownType(KnownTypeCode.Boolean)) { // convert to boolean through byte, to simulate the truncation to 8 bits return(this.ConvertTo(compilation.FindType(KnownTypeCode.Byte), expressionBuilder, checkForOverflow) .ConvertToBoolean(expressionBuilder)); } // Special-case IntPtr and UIntPtr: they behave extremely weird, see IntPtr.txt for details. if (type.IsKnownType(KnownTypeCode.IntPtr)) // Conversion from IntPtr // Direct cast only works correctly for IntPtr -> long. // IntPtr -> int works correctly only in checked context. // Everything else can be worked around by casting via long. { if (!(targetType.IsKnownType(KnownTypeCode.Int64) || checkForOverflow && targetType.IsKnownType(KnownTypeCode.Int32))) { return(this.ConvertTo(compilation.FindType(KnownTypeCode.Int64), expressionBuilder, checkForOverflow) .ConvertTo(targetType, expressionBuilder, checkForOverflow)); } } else if (type.IsKnownType(KnownTypeCode.UIntPtr)) // Conversion from UIntPtr // Direct cast only works correctly for UIntPtr -> ulong. // UIntPtr -> uint works correctly only in checked context. // Everything else can be worked around by casting via ulong. { if (!(targetType.IsKnownType(KnownTypeCode.UInt64) || checkForOverflow && targetType.IsKnownType(KnownTypeCode.UInt32))) { return(this.ConvertTo(compilation.FindType(KnownTypeCode.UInt64), expressionBuilder, checkForOverflow) .ConvertTo(targetType, expressionBuilder, checkForOverflow)); } } if (targetType.IsKnownType(KnownTypeCode.IntPtr)) // Conversion to IntPtr { if (type.IsKnownType(KnownTypeCode.Int32)) { // normal casts work for int (both in checked and unchecked context) } else if (checkForOverflow) { // if overflow-checking is enabled, we can simply cast via long: // (and long itself works directly in checked context) if (!type.IsKnownType(KnownTypeCode.Int64)) { return(this.ConvertTo(compilation.FindType(KnownTypeCode.Int64), expressionBuilder, checkForOverflow) .ConvertTo(targetType, expressionBuilder, checkForOverflow)); } } else { // If overflow-checking is disabled, the only way to truncate to native size // without throwing an exception in 32-bit mode is to use a pointer type. if (type.Kind != TypeKind.Pointer) { return(this.ConvertTo(new PointerType(compilation.FindType(KnownTypeCode.Void)), expressionBuilder, checkForOverflow) .ConvertTo(targetType, expressionBuilder, checkForOverflow)); } } } else if (targetType.IsKnownType(KnownTypeCode.UIntPtr)) // Conversion to UIntPtr { if (type.IsKnownType(KnownTypeCode.UInt32) || type.Kind == TypeKind.Pointer) { // normal casts work for uint and pointers (both in checked and unchecked context) } else if (checkForOverflow) { // if overflow-checking is enabled, we can simply cast via ulong: // (and ulong itself works directly in checked context) if (!type.IsKnownType(KnownTypeCode.UInt64)) { return(this.ConvertTo(compilation.FindType(KnownTypeCode.UInt64), expressionBuilder, checkForOverflow) .ConvertTo(targetType, expressionBuilder, checkForOverflow)); } } else { // If overflow-checking is disabled, the only way to truncate to native size // without throwing an exception in 32-bit mode is to use a pointer type. return(this.ConvertTo(new PointerType(compilation.FindType(KnownTypeCode.Void)), expressionBuilder, checkForOverflow) .ConvertTo(targetType, expressionBuilder, checkForOverflow)); } } if (targetType.Kind == TypeKind.Pointer && type.Kind == TypeKind.Enum) { // enum to pointer: C# doesn't allow such casts // -> convert via underlying type return(this.ConvertTo(type.GetEnumUnderlyingType(), expressionBuilder, checkForOverflow) .ConvertTo(targetType, expressionBuilder, checkForOverflow)); } else if (targetType.Kind == TypeKind.Enum && type.Kind == TypeKind.Pointer) { // pointer to enum: C# doesn't allow such casts // -> convert via underlying type return(this.ConvertTo(targetType.GetEnumUnderlyingType(), expressionBuilder, checkForOverflow) .ConvertTo(targetType, expressionBuilder, checkForOverflow)); } if (targetType.Kind == TypeKind.Pointer && type.IsKnownType(KnownTypeCode.Char) || targetType.IsKnownType(KnownTypeCode.Char) && type.Kind == TypeKind.Pointer) { // char <-> pointer: C# doesn't allow such casts // -> convert via ushort return(this.ConvertTo(compilation.FindType(KnownTypeCode.UInt16), expressionBuilder, checkForOverflow) .ConvertTo(targetType, expressionBuilder, checkForOverflow)); } if (targetType.Kind == TypeKind.Pointer && type.Kind == TypeKind.ByReference && Expression is DirectionExpression) { // convert from reference to pointer Expression arg = ((DirectionExpression)Expression).Expression.Detach(); var pointerType = new PointerType(((ByReferenceType)type).ElementType); var pointerExpr = new UnaryOperatorExpression(UnaryOperatorType.AddressOf, arg) .WithILInstruction(this.ILInstructions) .WithRR(new ResolveResult(pointerType)); // perform remaining pointer cast, if necessary return(pointerExpr.ConvertTo(targetType, expressionBuilder)); } if (targetType.Kind == TypeKind.ByReference) { // Convert from integer/pointer to reference. // First, convert to the corresponding pointer type: var elementType = ((ByReferenceType)targetType).ElementType; var arg = this.ConvertTo(new PointerType(elementType), expressionBuilder, checkForOverflow); // Then dereference the pointer: var derefExpr = new UnaryOperatorExpression(UnaryOperatorType.Dereference, arg.Expression); var elementRR = new ResolveResult(elementType); derefExpr.AddAnnotation(elementRR); // And then take a reference: return(new DirectionExpression(FieldDirection.Ref, derefExpr) .WithoutILInstruction() .WithRR(new ByReferenceResolveResult(elementRR, false))); } var rr = expressionBuilder.resolver.WithCheckForOverflow(checkForOverflow).ResolveCast(targetType, ResolveResult); if (rr.IsCompileTimeConstant && !rr.IsError) { return(expressionBuilder.ConvertConstantValue(rr) .WithILInstruction(this.ILInstructions)); } if (targetType.Kind == TypeKind.Pointer && (0.Equals(ResolveResult.ConstantValue) || 0u.Equals(ResolveResult.ConstantValue))) { if (allowImplicitConversion) { return(new NullReferenceExpression() .WithILInstruction(this.ILInstructions) .WithRR(new ConstantResolveResult(targetType, null))); } return(new CastExpression(expressionBuilder.ConvertType(targetType), new NullReferenceExpression()) .WithILInstruction(this.ILInstructions) .WithRR(new ConstantResolveResult(targetType, null))); } var conversions = Resolver.CSharpConversions.Get(compilation); if (allowImplicitConversion && conversions.ImplicitConversion(type, targetType).IsValid) { return(this); } var castExpr = new CastExpression(expressionBuilder.ConvertType(targetType), Expression); castExpr.AddAnnotation(checkForOverflow ? AddCheckedBlocks.CheckedAnnotation : AddCheckedBlocks.UncheckedAnnotation); return(castExpr.WithoutILInstruction().WithRR(rr)); }