Esempio n. 1
0
        /// <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));
        }