File: src\Analyzers\Core\CodeFixes\GenerateMember\AbstractGenerateMemberService.cs
Web Access
Project: src\src\Features\Core\Portable\Microsoft.CodeAnalysis.Features.csproj (Microsoft.CodeAnalysis.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 System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.GenerateMember;
 
internal abstract partial class AbstractGenerateMemberService<TSimpleNameSyntax, TExpressionSyntax>
    where TSimpleNameSyntax : TExpressionSyntax
    where TExpressionSyntax : SyntaxNode
{
    protected AbstractGenerateMemberService()
    {
    }
 
    protected static readonly ISet<TypeKind> EnumType = new HashSet<TypeKind> { TypeKind.Enum };
    protected static readonly ISet<TypeKind> ClassInterfaceModuleStructTypes = new HashSet<TypeKind>
    {
        TypeKind.Class,
        TypeKind.Module,
        TypeKind.Struct,
        TypeKind.Interface
    };
 
    protected static bool ValidateTypeToGenerateIn(
        [NotNullWhen(true)] INamedTypeSymbol? typeToGenerateIn,
        bool isStatic,
        ISet<TypeKind> typeKinds)
    {
        if (typeToGenerateIn == null)
            return false;
 
        if (typeToGenerateIn.IsAnonymousType)
            return false;
 
        if (!typeKinds.Contains(typeToGenerateIn.TypeKind))
            return false;
 
        if (typeToGenerateIn.TypeKind == TypeKind.Interface && isStatic)
            return false;
 
        // TODO(cyrusn): Make sure that there is a totally visible part somewhere (i.e.
        // venus) that we can generate into.
        var locations = typeToGenerateIn.Locations;
        return locations.Any(static loc => loc.IsInSource);
    }
 
    protected static bool TryDetermineTypeToGenerateIn(
        SemanticDocument document,
        INamedTypeSymbol containingType,
        TExpressionSyntax simpleNameOrMemberAccessExpression,
        CancellationToken cancellationToken,
        [NotNullWhen(true)] out INamedTypeSymbol? typeToGenerateIn,
        out bool isStatic,
        out bool isColorColorCase)
    {
        TryDetermineTypeToGenerateInWorker(
            document, containingType, simpleNameOrMemberAccessExpression, cancellationToken, out typeToGenerateIn, out isStatic, out isColorColorCase);
 
        typeToGenerateIn = typeToGenerateIn?.OriginalDefinition;
 
        return typeToGenerateIn != null;
    }
 
    private static void TryDetermineTypeToGenerateInWorker(
        SemanticDocument semanticDocument,
        INamedTypeSymbol containingType,
        TExpressionSyntax expression,
        CancellationToken cancellationToken,
        out INamedTypeSymbol? typeToGenerateIn,
        out bool isStatic,
        out bool isColorColorCase)
    {
        typeToGenerateIn = null;
        isStatic = false;
        isColorColorCase = false;
 
        var syntaxFacts = semanticDocument.Document.GetRequiredLanguageService<ISyntaxFactsService>();
        var semanticModel = semanticDocument.SemanticModel;
        if (syntaxFacts.IsSimpleMemberAccessExpression(expression))
        {
            // Figure out what's before the dot.  For VB, that also means finding out 
            // what ".X" might mean, even when there's nothing before the dot itself.
            var beforeDotExpression = syntaxFacts.GetExpressionOfMemberAccessExpression(
                expression, allowImplicitTarget: true);
 
            if (beforeDotExpression != null)
            {
                DetermineTypeToGenerateInWorker(
                    semanticModel, beforeDotExpression, out typeToGenerateIn, out isStatic, out isColorColorCase, cancellationToken);
            }
 
            return;
        }
 
        if (syntaxFacts.IsConditionalAccessExpression(expression))
        {
            var beforeDotExpression = syntaxFacts.GetExpressionOfConditionalAccessExpression(expression);
 
            if (beforeDotExpression != null)
            {
                DetermineTypeToGenerateInWorker(
                    semanticModel, beforeDotExpression, out typeToGenerateIn, out isStatic, out isColorColorCase, cancellationToken);
                if (typeToGenerateIn.IsNullable(out var underlyingType) &&
                    underlyingType is INamedTypeSymbol underlyingNamedType)
                {
                    typeToGenerateIn = underlyingNamedType;
                }
            }
 
            return;
        }
 
        if (syntaxFacts.IsPointerMemberAccessExpression(expression))
        {
            var beforeArrowExpression = syntaxFacts.GetExpressionOfMemberAccessExpression(expression);
            if (beforeArrowExpression != null)
            {
                var typeInfo = semanticModel.GetTypeInfo(beforeArrowExpression, cancellationToken);
 
                if (typeInfo.Type is IPointerTypeSymbol pointerType)
                {
                    typeToGenerateIn = pointerType.PointedAtType as INamedTypeSymbol;
                    isStatic = false;
                }
            }
 
            return;
        }
 
        if (syntaxFacts.IsAttributeNamedArgumentIdentifier(expression))
        {
            var attributeNode = expression.GetAncestors().FirstOrDefault(syntaxFacts.IsAttribute);
            Contract.ThrowIfNull(attributeNode);
 
            var attributeName = syntaxFacts.GetNameOfAttribute(attributeNode);
            var attributeType = semanticModel.GetTypeInfo(attributeName, cancellationToken);
 
            typeToGenerateIn = attributeType.Type as INamedTypeSymbol;
            isStatic = false;
            return;
        }
 
        if (syntaxFacts.IsMemberInitializerNamedAssignmentIdentifier(
                expression, out var initializedObject))
        {
            typeToGenerateIn = semanticModel.GetTypeInfo(initializedObject, cancellationToken).Type as INamedTypeSymbol;
            isStatic = false;
            return;
        }
        else if (syntaxFacts.IsNameOfSubpattern(expression))
        {
            var propertyPatternClause = expression.Ancestors().FirstOrDefault(syntaxFacts.IsPropertyPatternClause);
 
            if (propertyPatternClause != null)
            {
                // something like: { [|X|]: int i } or like: Blah { [|X|]: int i }
                var inferenceService = semanticDocument.Document.GetRequiredLanguageService<ITypeInferenceService>();
                typeToGenerateIn = inferenceService.InferType(semanticModel, propertyPatternClause, objectAsDefault: true, cancellationToken) as INamedTypeSymbol;
 
                isStatic = false;
                return;
            }
        }
 
        // Generating into the containing type.
        typeToGenerateIn = containingType;
        isStatic = syntaxFacts.IsInStaticContext(expression);
    }
 
    private static void DetermineTypeToGenerateInWorker(
        SemanticModel semanticModel,
        SyntaxNode expression,
        out INamedTypeSymbol? typeToGenerateIn,
        out bool isStatic,
        out bool isColorColorCase,
        CancellationToken cancellationToken)
    {
        var typeInfo = semanticModel.GetTypeInfo(expression, cancellationToken);
        var semanticInfo = semanticModel.GetSymbolInfo(expression, cancellationToken);
 
        typeToGenerateIn = typeInfo.Type is ITypeParameterSymbol typeParameter
            ? typeParameter.GetNamedTypeSymbolConstraint()
            : typeInfo.Type as INamedTypeSymbol;
 
        isStatic = semanticInfo.Symbol is INamedTypeSymbol;
        isColorColorCase = typeInfo.Type != null && semanticInfo.Symbol != null && semanticInfo.Symbol.Name == typeInfo.Type.Name;
    }
}