File: BoundTree\BoundExpressionExtensions.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.Diagnostics.CodeAnalysis;
using System.Linq;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CSharp
{
    internal static partial class BoundExpressionExtensions
    {
        /// <summary>
        /// Returns the RefKind if the expression represents a symbol
        /// that has a RefKind, or RefKind.None otherwise.
        /// </summary>
        public static RefKind GetRefKind(this BoundExpression node)
        {
            switch (node.Kind)
            {
                case BoundKind.Local:
                    return ((BoundLocal)node).LocalSymbol.RefKind;
 
                case BoundKind.Parameter:
                    return ((BoundParameter)node).ParameterSymbol.RefKind;
 
                case BoundKind.FieldAccess:
                    return ((BoundFieldAccess)node).FieldSymbol.RefKind;
 
                case BoundKind.Call:
                    return ((BoundCall)node).Method.RefKind;
 
                case BoundKind.PropertyAccess:
                    return ((BoundPropertyAccess)node).PropertySymbol.RefKind;
 
                case BoundKind.IndexerAccess:
                    return ((BoundIndexerAccess)node).Indexer.RefKind;
 
                case BoundKind.ImplicitIndexerAccess:
                    return ((BoundImplicitIndexerAccess)node).IndexerOrSliceAccess.GetRefKind();
 
                case BoundKind.InlineArrayAccess:
                    {
                        var elementAccess = (BoundInlineArrayAccess)node;
 
                        if (!elementAccess.IsValue)
                        {
                            switch (elementAccess.GetItemOrSliceHelper)
                            {
                                case WellKnownMember.System_Span_T__get_Item:
                                    return RefKind.Ref;
                                case WellKnownMember.System_ReadOnlySpan_T__get_Item:
                                    return RefKind.RefReadOnly;
                            }
                        }
 
                        return RefKind.None;
                    }
 
                case BoundKind.ObjectInitializerMember:
                    var member = (BoundObjectInitializerMember)node;
                    if (member.HasErrors)
                        return RefKind.None;
 
                    return member.MemberSymbol switch
                    {
                        FieldSymbol f => f.RefKind,
                        PropertySymbol f => f.RefKind,
                        EventSymbol => RefKind.None,
                        var s => throw ExceptionUtilities.UnexpectedValue(s?.Kind)
                    };
 
                default:
                    return RefKind.None;
            }
        }
 
        public static bool IsLiteralNull(this BoundExpression node)
        {
            return node is { Kind: BoundKind.Literal, ConstantValueOpt: { Discriminator: ConstantValueTypeDiscriminator.Null } };
        }
 
        public static bool IsLiteralDefault(this BoundExpression node)
        {
            return node.Kind == BoundKind.DefaultLiteral;
        }
 
        public static bool IsImplicitObjectCreation(this BoundExpression node)
        {
            return node.Kind == BoundKind.UnconvertedObjectCreationExpression;
        }
 
        public static bool IsLiteralDefaultOrImplicitObjectCreation(this BoundExpression node)
        {
            return node.IsLiteralDefault() || node.IsImplicitObjectCreation();
        }
 
        // returns true when expression has no side-effects and produces
        // default value (null, zero, false, default(T) ...)
        //
        // NOTE: This method is a very shallow check.
        //       It does not make any assumptions about what this node could become 
        //       after some folding/propagation/algebraic transformations.
        public static bool IsDefaultValue(this BoundExpression node)
        {
            if (node.Kind == BoundKind.DefaultExpression || node.Kind == BoundKind.DefaultLiteral)
            {
                return true;
            }
 
            var constValue = node.ConstantValueOpt;
            if (constValue != null)
            {
                return constValue.IsDefaultValue;
            }
 
            return false;
        }
 
        public static bool HasExpressionType(this BoundExpression node)
        {
            // null literal, method group, and anonymous function expressions have no type.
            return node.Type is { };
        }
 
        public static bool HasDynamicType(this BoundExpression node)
        {
            var type = node.Type;
            return type is { } && type.IsDynamic();
        }
 
        public static NamedTypeSymbol? GetInferredDelegateType(this BoundExpression expr, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
        {
            Debug.Assert(expr.Kind is BoundKind.MethodGroup or BoundKind.UnboundLambda);
 
            var delegateType = expr.GetFunctionType()?.GetInternalDelegateType();
            delegateType?.AddUseSiteInfo(ref useSiteInfo);
            return delegateType;
        }
 
        public static TypeSymbol? GetTypeOrFunctionType(this BoundExpression expr)
        {
            if (expr.Type is { } type)
            {
                return type;
            }
            return expr.GetFunctionType();
        }
 
        public static FunctionTypeSymbol? GetFunctionType(this BoundExpression expr)
        {
            return expr switch
            {
                BoundMethodGroup methodGroup => methodGroup.FunctionType,
                UnboundLambda unboundLambda => unboundLambda.FunctionType,
                _ => null
            };
        }
 
        public static bool MethodGroupReceiverIsDynamic(this BoundMethodGroup node)
        {
            return node.InstanceOpt != null && node.InstanceOpt.HasDynamicType();
        }
 
        public static void GetExpressionSymbols(this BoundExpression node, ArrayBuilder<Symbol> symbols, BoundNode parent, Binder binder)
        {
            switch (node.Kind)
            {
                case BoundKind.MethodGroup:
                    // Special case: if we are looking for info on "M" in "new Action(M)" in the context of a parent 
                    // then we want to get the symbol that overload resolution chose for M, not on the whole method group M.
                    var delegateCreation = parent as BoundDelegateCreationExpression;
                    if (delegateCreation != null && delegateCreation.MethodOpt is { })
                    {
                        symbols.Add(delegateCreation.MethodOpt);
                    }
                    else
                    {
                        symbols.AddRange(CSharpSemanticModel.GetReducedAndFilteredMethodGroupSymbols(binder, (BoundMethodGroup)node));
                    }
                    break;
 
                case BoundKind.BadExpression:
                    foreach (var s in ((BoundBadExpression)node).Symbols)
                    {
                        if (s is { })
                            symbols.Add(s);
                    }
                    break;
 
                case BoundKind.DelegateCreationExpression:
                    var expr = (BoundDelegateCreationExpression)node;
                    var ctor = expr.Type.GetMembers(WellKnownMemberNames.InstanceConstructorName).FirstOrDefault();
                    if (ctor is { })
                    {
                        symbols.Add(ctor);
                    }
                    break;
 
                case BoundKind.Call:
                    // Either overload resolution succeeded for this call or it did not. If it did not
                    // succeed then we've stashed the original method symbols from the method group,
                    // and we should use those as the symbols displayed for the call. If it did succeed
                    // then we did not stash any symbols; just fall through to the default case.
 
                    var originalMethods = ((BoundCall)node).OriginalMethodsOpt;
                    if (originalMethods.IsDefault)
                    {
                        goto default;
                    }
                    symbols.AddRange(originalMethods);
                    break;
 
                case BoundKind.IndexerAccess:
                    // Same behavior as for a BoundCall: if overload resolution failed, pull out stashed candidates;
                    // otherwise use the default behavior.
 
                    var originalIndexers = ((BoundIndexerAccess)node).OriginalIndexersOpt;
                    if (originalIndexers.IsDefault)
                    {
                        goto default;
                    }
                    symbols.AddRange(originalIndexers);
                    break;
 
                default:
                    var symbol = node.ExpressionSymbol;
                    if (symbol is { })
                    {
                        symbols.Add(symbol);
                    }
                    break;
            }
        }
 
        // Get the conversion associated with a bound node, or else Identity.
        public static Conversion GetConversion(this BoundExpression boundNode)
        {
            switch (boundNode.Kind)
            {
                case BoundKind.Conversion:
                    BoundConversion conversionNode = (BoundConversion)boundNode;
                    return conversionNode.Conversion;
 
                default:
                    return Conversion.Identity;
            }
        }
 
        internal static bool IsExpressionOfComImportType([NotNullWhen(true)] this BoundExpression? expressionOpt)
        {
            // NOTE: Dev11 also returns false if expressionOpt is a TypeExpression.  Unfortunately,
            // that makes it impossible to handle TypeOrValueExpression in a consistent way, since
            // we don't know whether it's a type until after overload resolution and we can't do
            // overload resolution without knowing whether 'ref' can be omitted (which is what this
            // method is used to determine).  Since there is no intuitive reason to disallow
            // omitting 'ref' for static methods, we'll drop the restriction on TypeExpression.
            if (expressionOpt == null)
                return false;
 
            TypeSymbol? receiverType = expressionOpt.Type;
            return receiverType is NamedTypeSymbol { Kind: SymbolKind.NamedType, IsComImport: true };
        }
 
        internal static bool IsDiscardExpression(this BoundExpression expr)
        {
            return expr switch
            {
                BoundDiscardExpression => true,
                OutDeconstructVarPendingInference { IsDiscardExpression: true } => true,
                BoundDeconstructValuePlaceholder { IsDiscardExpression: true } => true,
                _ => false
            };
        }
    }
}