File: ExtractMethod\CSharpSelectionResult.ExpressionResult.cs
Web Access
Project: src\src\Features\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.Features.csproj (Microsoft.CodeAnalysis.CSharp.Features)
// 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 Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.LanguageService;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CSharp.ExtractMethod;
 
internal abstract partial class CSharpSelectionResult
{
    private sealed class ExpressionResult(
        TextSpan originalSpan,
        TextSpan finalSpan,
        bool selectionInExpression,
        SemanticDocument document,
        SyntaxAnnotation firstTokenAnnotation,
        SyntaxAnnotation lastTokenAnnotation,
        bool selectionChanged) : CSharpSelectionResult(
            originalSpan, finalSpan, selectionInExpression, document, firstTokenAnnotation, lastTokenAnnotation, selectionChanged)
    {
        public override bool ContainingScopeHasAsyncKeyword()
            => false;
 
        public override SyntaxNode? GetContainingScope()
        {
            Contract.ThrowIfNull(SemanticDocument);
            Contract.ThrowIfFalse(SelectionInExpression);
 
            var firstToken = GetFirstTokenInSelection();
            var lastToken = GetLastTokenInSelection();
            var scope = firstToken.GetCommonRoot(lastToken).GetAncestorOrThis<ExpressionSyntax>();
            if (scope == null)
                return null;
 
            return CSharpSyntaxFacts.Instance.GetRootStandaloneExpression(scope);
        }
 
        public override (ITypeSymbol? returnType, bool returnsByRef) GetReturnType()
        {
            if (GetContainingScope() is not ExpressionSyntax node)
            {
                throw ExceptionUtilities.Unreachable();
            }
 
            var model = SemanticDocument.SemanticModel;
 
            // special case for array initializer and explicit cast
            if (node.IsArrayInitializer())
            {
                var variableDeclExpression = node.GetAncestorOrThis<VariableDeclarationSyntax>();
                if (variableDeclExpression != null)
                    return (model.GetTypeInfo(variableDeclExpression.Type).Type, returnsByRef: false);
            }
 
            if (node.IsExpressionInCast())
            {
                // bug # 12774 and # 4780
                // if the expression is under cast, we use the heuristic below
                // 1. if regular binding returns a meaningful type, we use it as it is
                // 2. if it doesn't, even if the cast itself wasn't included in the selection, we will treat it 
                //    as it was in the selection
                var (regularType, returnsByRef) = GetRegularExpressionType(model, node);
                if (regularType != null)
                    return (regularType, returnsByRef);
 
                if (node.Parent is CastExpressionSyntax castExpression)
                    return (model.GetTypeInfo(castExpression).Type, returnsByRef: false);
            }
 
            return GetRegularExpressionType(model, node);
        }
 
        private static (ITypeSymbol? typeSymbol, bool returnsByRef) GetRegularExpressionType(SemanticModel semanticModel, ExpressionSyntax node)
        {
            // regular case. always use ConvertedType to get implicit conversion right.
            var expression = node.WalkDownParentheses();
            var returnsByRef = false;
            if (expression is RefExpressionSyntax refExpression)
            {
                expression = refExpression.Expression;
                returnsByRef = true;
            }
 
            var typeSymbol = GetRegularExpressionTypeWorker();
            return (typeSymbol, returnsByRef);
 
            ITypeSymbol? GetRegularExpressionTypeWorker()
            {
                var info = semanticModel.GetTypeInfo(expression);
                var conv = semanticModel.GetConversion(expression);
 
                if (info.ConvertedType == null || info.ConvertedType.IsErrorType())
                {
                    // there is no implicit conversion involved. no need to go further
                    return info.GetTypeWithAnnotatedNullability();
                }
 
                // always use converted type if method group
                if ((!node.IsKind(SyntaxKind.ObjectCreationExpression) && semanticModel.GetMemberGroup(expression).Length > 0) ||
                    IsCoClassImplicitConversion(info, conv, semanticModel.Compilation.CoClassType()))
                {
                    return info.GetConvertedTypeWithAnnotatedNullability();
                }
 
                // check implicit conversion
                if (conv.IsImplicit && (conv.IsConstantExpression || conv.IsEnumeration))
                {
                    return info.GetConvertedTypeWithAnnotatedNullability();
                }
 
                // use FormattableString if conversion between String and FormattableString
                if (info.Type?.SpecialType == SpecialType.System_String &&
                    info.ConvertedType?.IsFormattableStringOrIFormattable() == true)
                {
                    return info.GetConvertedTypeWithAnnotatedNullability();
                }
 
                // always try to use type that is more specific than object type if possible.
                return !info.Type.IsObjectType() ? info.GetTypeWithAnnotatedNullability() : info.GetConvertedTypeWithAnnotatedNullability();
            }
        }
    }
 
    private static bool IsCoClassImplicitConversion(TypeInfo info, Conversion conversion, ISymbol? coclassSymbol)
    {
        if (!conversion.IsImplicit ||
             info.ConvertedType == null ||
             info.ConvertedType.TypeKind != TypeKind.Interface)
        {
            return false;
        }
 
        // let's see whether this interface has coclass attribute
        return info.ConvertedType.GetAttributes().Any(static (c, coclassSymbol) => c.AttributeClass?.Equals(coclassSymbol) == true, coclassSymbol);
    }
}