File: Lowering\LocalRewriter\LocalRewriter_Range.cs
Web Access
Project: src\src\Compilers\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.csproj (Microsoft.CodeAnalysis.CSharp)
// 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.Diagnostics;
using System.Collections.Immutable;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.PooledObjects;
using System.Linq;
using Roslyn.Utilities;
using System.Diagnostics.CodeAnalysis;
 
namespace Microsoft.CodeAnalysis.CSharp
{
    internal sealed partial class LocalRewriter
    {
        public override BoundNode VisitRangeExpression(BoundRangeExpression node)
        {
            Debug.Assert(node != null && node.MethodOpt != null);
 
            bool needLifting = false;
            var F = _factory;
 
            var left = node.LeftOperandOpt;
            if (left != null)
            {
                left = tryOptimizeOperand(left);
            }
 
            var right = node.RightOperandOpt;
            if (right != null)
            {
                right = tryOptimizeOperand(right);
            }
 
            if (needLifting)
            {
                return LiftRangeExpression(node, left, right);
            }
            else
            {
                BoundExpression rangeCreation = MakeRangeExpression(node.MethodOpt, left, right);
 
                if (node.Type.IsNullableType())
                {
                    return ConvertToNullable(node.Syntax, node.Type, rangeCreation);
                }
 
                return rangeCreation;
            }
 
            BoundExpression tryOptimizeOperand(BoundExpression operand)
            {
                Debug.Assert(operand != null);
                operand = VisitExpression(operand);
                Debug.Assert(operand.Type is { });
 
                if (NullableNeverHasValue(operand))
                {
                    operand = new BoundDefaultExpression(operand.Syntax, operand.Type.GetNullableUnderlyingType());
                }
                else
                {
                    operand = NullableAlwaysHasValue(operand) ?? operand;
                    Debug.Assert(operand.Type is { });
 
                    if (operand.Type.IsNullableType())
                    {
                        needLifting = true;
                    }
                }
 
                return operand;
            }
        }
 
        private BoundExpression LiftRangeExpression(BoundRangeExpression node, BoundExpression? left, BoundExpression? right)
        {
            Debug.Assert(node.Type.IsNullableType());
            Debug.Assert(left?.Type?.IsNullableType() == true || right?.Type?.IsNullableType() == true);
            Debug.Assert(!(left is null && right is null));
            Debug.Assert(node.MethodOpt is { });
 
            var sideeffects = ArrayBuilder<BoundExpression>.GetInstance();
            var locals = ArrayBuilder<LocalSymbol>.GetInstance();
 
            // makeRange(left.GetValueOrDefault(), right.GetValueOrDefault())
            BoundExpression? condition = null;
            left = getIndexFromPossibleNullable(left);
            right = getIndexFromPossibleNullable(right);
            var rangeExpr = MakeRangeExpression(node.MethodOpt, left, right);
 
            Debug.Assert(condition != null);
 
            if (!TryGetNullableMethod(node.Syntax, node.Type, SpecialMember.System_Nullable_T__ctor, out MethodSymbol nullableCtor))
            {
                return BadExpression(node.Syntax, node.Type, node);
            }
 
            // new Nullable(makeRange(left.GetValueOrDefault(), right.GetValueOrDefault()))
            BoundExpression consequence = new BoundObjectCreationExpression(node.Syntax, nullableCtor, rangeExpr);
 
            // default
            BoundExpression alternative = new BoundDefaultExpression(node.Syntax, node.Type);
 
            // left.HasValue && right.HasValue
            //     ? new Nullable(makeRange(left.GetValueOrDefault(), right.GetValueOrDefault()))
            //     : default
            BoundExpression conditionalExpression = RewriteConditionalOperator(
                syntax: node.Syntax,
                rewrittenCondition: condition,
                rewrittenConsequence: consequence,
                rewrittenAlternative: alternative,
                constantValueOpt: null,
                rewrittenType: node.Type,
                isRef: false);
 
            return new BoundSequence(
                syntax: node.Syntax,
                locals: locals.ToImmutableAndFree(),
                sideEffects: sideeffects.ToImmutableAndFree(),
                value: conditionalExpression,
                type: node.Type);
 
            BoundExpression? getIndexFromPossibleNullable(BoundExpression? arg)
            {
                if (arg is null)
                    return null;
 
                BoundExpression tempOperand = CaptureExpressionInTempIfNeeded(arg, sideeffects, locals);
                Debug.Assert(tempOperand.Type is { });
 
                if (tempOperand.Type.IsNullableType())
                {
                    BoundExpression operandHasValue = MakeOptimizedHasValue(tempOperand.Syntax, tempOperand);
 
                    if (condition is null)
                    {
                        condition = operandHasValue;
                    }
                    else
                    {
                        TypeSymbol boolType = _compilation.GetSpecialType(SpecialType.System_Boolean);
                        condition = MakeBinaryOperator(node.Syntax, BinaryOperatorKind.BoolAnd, condition, operandHasValue, boolType, method: null, constrainedToTypeOpt: null);
                    }
 
                    return MakeOptimizedGetValueOrDefault(tempOperand.Syntax, tempOperand);
                }
                else
                {
                    return tempOperand;
                }
            }
        }
 
        private BoundExpression MakeRangeExpression(
            MethodSymbol constructionMethod,
            BoundExpression? left,
            BoundExpression? right)
        {
            var F = _factory;
            // The construction method may vary based on what well-known
            // members were available during binding. Depending on which member
            // is chosen we need to change our adjust our calling node.
            switch (constructionMethod.MethodKind)
            {
                case MethodKind.Constructor:
                    // Represents Range..ctor(Index left, Index right)
                    // The constructor can always be used to construct a range,
                    // but if any of the arguments are missing then we need to
                    // construct replacement Indexes
                    left = left ?? newIndexZero(fromEnd: false);
                    right = right ?? newIndexZero(fromEnd: true);
 
                    return F.New(constructionMethod, ImmutableArray.Create(left, right));
 
                case MethodKind.Ordinary:
                    // Represents either Range.StartAt or Range.EndAt, which
                    // means that the `..` expression is missing an argument on
                    // either the left or the right (i.e., `x..` or `..x`)
                    Debug.Assert(left is null ^ right is null);
                    Debug.Assert(constructionMethod.MetadataName == "StartAt" ||
                                 constructionMethod.MetadataName == "EndAt");
                    Debug.Assert(constructionMethod.IsStatic);
                    var arg = left ?? right;
                    Debug.Assert(arg is { });
                    return F.StaticCall(constructionMethod, ImmutableArray.Create(arg));
 
                case MethodKind.PropertyGet:
                    // The only property is Range.All, so the expression must
                    // be `..` with both arguments missing
                    Debug.Assert(constructionMethod.MetadataName == "get_All");
                    Debug.Assert(constructionMethod.IsStatic);
                    Debug.Assert(left is null && right is null);
                    return F.StaticCall(constructionMethod, ImmutableArray<BoundExpression>.Empty);
 
                default:
                    throw ExceptionUtilities.UnexpectedValue(constructionMethod.MethodKind);
            }
 
            BoundExpression newIndexZero(bool fromEnd) =>
                // new Index(0, fromEnd: fromEnd)
                F.New(
                    WellKnownMember.System_Index__ctor,
                    ImmutableArray.Create<BoundExpression>(F.Literal(0), F.Literal(fromEnd)));
        }
    }
}