/// <summary>
        /// If the specified type is some specific Nullable(T) type, returns its underlying type.
        /// Otherwise, returns the supplied type.
        /// </summary>
        /// <param name="target">The type</param>
        /// <exception cref="ArgumentNullException"><paramref name="target"/> is null</exception>
        public static Type GetUnderlyingType(this Type target)
        {
            if (target == null)
            {
                throw new ArgumentNullException("target");
            }

            return(target.IsNullableType() ? UnboxableNullable.GetUnderlyingType(target) : target);
        }
        /// <summary>
        /// Attempts to auto-adjust the size of argument to the desired type.
        /// Integers grow up to Int64, Double or Decimal, floats grow up to Decimal.
        /// </summary>
        /// <param name="root">Optional, corresponding parse tree node</param>
        /// <param name="expr">Expression whose value is to be adjusted, must be of numeric value type</param>
        /// <param name="targetType">Desired type</param>
        /// <param name="adjusted">New or same expression</param>
        /// <exception cref="ArgumentNullException"><paramref name="expr"/> is null</exception>
        /// <exception cref="ArgumentNullException"><paramref name="targetType"/> is null</exception>
        public static bool TryAdjustReturnType(ParseTreeNode root, Expression expr, Type targetType, out Expression adjusted)
        {
            if (expr == null)
            {
                throw new ArgumentNullException("expr");
            }

            if (targetType == null)
            {
                throw new ArgumentNullException("targetType");
            }

            // do we actually have to adjust them?
            if (ReferenceEquals(expr.Type, targetType))
            {
                adjusted = expr;
                return(true);
            }

            if (expr.IsVoid())
            {
                adjusted = GetDefaultExpression(targetType);
                return(true);
            }

            if (targetType.IsNullableType())
            {
                if (expr.IsNullableType())
                {
                    var        variable = Expression.Variable(expr.Type);
                    var        setvar   = Expression.Assign(variable, expr);
                    var        hasvalue = Expression.Field(variable, "HasValue");
                    Expression value    = Expression.Field(variable, "Value");
                    Expression adjustedValue;
                    if (TryAdjustReturnType(root, value, UnboxableNullable.GetUnderlyingType(targetType), out adjustedValue))
                    {
                        adjusted = Expression.Block(
                            targetType,
                            new [] { variable },
                            setvar,
                            Expression.Condition(hasvalue, MakeNewNullable(targetType, adjustedValue), MakeNewNullable(targetType)));
                        return(true);
                    }
                }
                else
                {
                    Expression adjustedValue;
                    if (TryAdjustReturnType(root, expr, UnboxableNullable.GetUnderlyingType(targetType), out adjustedValue))
                    {
                        adjusted = MakeNewNullable(targetType, adjustedValue);
                        return(true);
                    }
                }
            }
            else if (expr.IsNullableType())
            {
                var nonnullable = expr.RemoveNullability();
                return(TryAdjustReturnType(root, nonnullable, targetType, out adjusted));
            }
            else
            {
                return(TryAdjustNumericType(root, expr, targetType, out adjusted));
            }

            adjusted = null;
            return(false);
        }