File: DynamicallyAccessedMembersAnalyzer.cs
Web Access
Project: src\runtime\src\tools\illink\src\ILLink.RoslynAnalyzer\ILLink.RoslynAnalyzer.csproj (ILLink.RoslynAnalyzer)
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using ILLink.RoslynAnalyzer.TrimAnalysis;
using ILLink.RoslynAnalyzer.DataFlow;
using ILLink.Shared;
using ILLink.Shared.TrimAnalysis;
using ILLink.Shared.TypeSystemProxy;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;

namespace ILLink.RoslynAnalyzer
{
    [DiagnosticAnalyzer(LanguageNames.CSharp)]
    public sealed class DynamicallyAccessedMembersAnalyzer : DiagnosticAnalyzer
    {
        internal const string DynamicallyAccessedMembers = nameof(DynamicallyAccessedMembers);
        internal const string DynamicallyAccessedMembersAttribute = nameof(DynamicallyAccessedMembersAttribute);
        public const string attributeArgument = "attributeArgument";
        public const string FullyQualifiedDynamicallyAccessedMembersAttribute = "System.Diagnostics.CodeAnalysis." + DynamicallyAccessedMembersAttribute;
        public const string FullyQualifiedFeatureGuardAttribute = "System.Diagnostics.CodeAnalysis.FeatureGuardAttribute";
        public static Lazy<ImmutableArray<RequiresAnalyzerBase>> RequiresAnalyzers { get; } = new Lazy<ImmutableArray<RequiresAnalyzerBase>>(GetRequiresAnalyzers);
        private static ImmutableArray<RequiresAnalyzerBase> GetRequiresAnalyzers()
        {
            var builder = ImmutableArray.CreateBuilder<RequiresAnalyzerBase>();
            builder.Add(new RequiresAssemblyFilesAnalyzer());
            builder.Add(new RequiresUnreferencedCodeAnalyzer());
            builder.Add(new RequiresDynamicCodeAnalyzer());
            return builder.ToImmutable();
        }

        public static ImmutableArray<DiagnosticDescriptor> GetSupportedDiagnostics()
        {
            var diagDescriptorsArrayBuilder = ImmutableArray.CreateBuilder<DiagnosticDescriptor>(27);
            diagDescriptorsArrayBuilder.Add(DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.RequiresUnreferencedCode));
            diagDescriptorsArrayBuilder.Add(DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.DynamicallyAccessedMembersIsNotAllowedOnMethods));
            diagDescriptorsArrayBuilder.Add(DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.DynamicallyAccessedMembersIsNotAllowedOnExtensionProperties));
            AddRange(DiagnosticId.MethodParameterCannotBeStaticallyDetermined, DiagnosticId.DynamicallyAccessedMembersMismatchTypeArgumentTargetsGenericParameter);
            AddRange(DiagnosticId.DynamicallyAccessedMembersOnFieldCanOnlyApplyToTypesOrStrings, DiagnosticId.DynamicallyAccessedMembersOnPropertyCanOnlyApplyToTypesOrStrings);
            diagDescriptorsArrayBuilder.Add(DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.DynamicallyAccessedMembersOnMethodReturnValueCanOnlyApplyToTypesOrStrings));
            diagDescriptorsArrayBuilder.Add(DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.DynamicallyAccessedMembersFieldAccessedViaReflection));
            diagDescriptorsArrayBuilder.Add(DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.DynamicallyAccessedMembersMethodAccessedViaReflection));
            diagDescriptorsArrayBuilder.Add(DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.DynamicallyAccessedMembersOnTypeReferencesMemberWithRequiresUnreferencedCode));
            diagDescriptorsArrayBuilder.Add(DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.DynamicallyAccessedMembersOnTypeReferencesMemberOnBaseWithRequiresUnreferencedCode));
            diagDescriptorsArrayBuilder.Add(DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.DynamicallyAccessedMembersOnTypeReferencesMemberWithDynamicallyAccessedMembers));
            diagDescriptorsArrayBuilder.Add(DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.DynamicallyAccessedMembersOnTypeReferencesMemberOnBaseWithDynamicallyAccessedMembers));
            diagDescriptorsArrayBuilder.Add(DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.UnrecognizedTypeInRuntimeHelpersRunClassConstructor));
            diagDescriptorsArrayBuilder.Add(DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.DynamicallyAccessedMembersMismatchOnMethodReturnValueBetweenOverrides));
            diagDescriptorsArrayBuilder.Add(DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.DynamicallyAccessedMembersMismatchOnMethodParameterBetweenOverrides));
            diagDescriptorsArrayBuilder.Add(DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.DynamicallyAccessedMembersMismatchOnGenericParameterBetweenOverrides));
            diagDescriptorsArrayBuilder.Add(DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.DynamicallyAccessedMembersMismatchOnImplicitThisBetweenOverrides));
            diagDescriptorsArrayBuilder.Add(DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.DynamicallyAccessedMembersConflictsBetweenPropertyAndAccessor));
            diagDescriptorsArrayBuilder.Add(DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.PropertyAccessorParameterInLinqExpressionsCannotBeStaticallyDetermined));
            diagDescriptorsArrayBuilder.Add(DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.MakeGenericType));
            diagDescriptorsArrayBuilder.Add(DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.MakeGenericMethod));
            diagDescriptorsArrayBuilder.Add(DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.CaseInsensitiveTypeGetTypeCallIsNotSupported));
            diagDescriptorsArrayBuilder.Add(DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.UnrecognizedTypeNameInTypeGetType));
            diagDescriptorsArrayBuilder.Add(DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.UnrecognizedParameterInMethodCreateInstance));
            diagDescriptorsArrayBuilder.Add(DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.ParametersOfAssemblyCreateInstanceCannotBeAnalyzed));
            diagDescriptorsArrayBuilder.Add(DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.TypeNameIsNotAssemblyQualified));
            diagDescriptorsArrayBuilder.Add(DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.ReturnValueDoesNotMatchFeatureGuards));
            diagDescriptorsArrayBuilder.Add(DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.InvalidFeatureGuard));
            diagDescriptorsArrayBuilder.Add(DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.TypeMapGroupTypeCannotBeStaticallyDetermined));
            diagDescriptorsArrayBuilder.Add(DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.DataflowAnalysisDidNotConverge));

            foreach (var requiresAnalyzer in RequiresAnalyzers.Value)
            {
                foreach (var diagnosticDescriptor in requiresAnalyzer.SupportedDiagnostics)
                    diagDescriptorsArrayBuilder.Add(diagnosticDescriptor);
            }

            return diagDescriptorsArrayBuilder.ToImmutable();

            void AddRange(DiagnosticId first, DiagnosticId last)
            {
                Debug.Assert((int)first < (int)last);

                for (int i = (int)first;
                    i <= (int)last; i++)
                {
                    diagDescriptorsArrayBuilder.Add(DiagnosticDescriptors.GetDiagnosticDescriptor((DiagnosticId)i));
                }
            }
        }

        public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => GetSupportedDiagnostics();

        internal static Location GetPrimaryLocation(ImmutableArray<Location>? locations)
        {
            if (locations is null)
                return Location.None;

            return locations.Value.Length > 0 ? locations.Value[0] : Location.None;
        }

        public override void Initialize(AnalysisContext context)
        {
            context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);

            if (!Debugger.IsAttached)
                context.EnableConcurrentExecution();

            context.RegisterCompilationStartAction(context =>
            {
                var dataFlowAnalyzerContext = DataFlowAnalyzerContext.Create(context.Options, context.Compilation, RequiresAnalyzers.Value);
                if (!dataFlowAnalyzerContext.AnyAnalyzersEnabled)
                    return;

                context.RegisterOperationBlockAction(context =>
                {
                    foreach (var operationBlock in context.OperationBlocks)
                    {
                        TrimDataFlowAnalysis trimDataFlowAnalysis = new(context, dataFlowAnalyzerContext, operationBlock);
                        bool success = trimDataFlowAnalysis.InterproceduralAnalyze();
                        trimDataFlowAnalysis.ReportDiagnostics(context.ReportDiagnostic);
                        if (!success)
                        {
                            context.ReportDiagnostic(
                                Diagnostic.Create(DiagnosticDescriptors.GetDiagnosticDescriptor(
                                    DiagnosticId.DataflowAnalysisDidNotConverge,
                                    diagnosticSeverity: DiagnosticSeverity.Warning),
                                operationBlock.Syntax.GetLocation(),
                                operationBlock.FindContainingSymbol(context.OwningSymbol).GetDisplayName()));
                        }
                    }
                });

                // Remaining actions are only for DynamicallyAccessedMembers analysis.
                if (dataFlowAnalyzerContext.TrimAnalyzer is null)
                    return;

                // Examine generic instantiations in base types and interface list
                context.RegisterSymbolAction(context =>
                {
                    var type = (INamedTypeSymbol)context.Symbol;
                    // RUC on type doesn't silence DAM warnings about generic base/interface types.
                    // This knowledge lives in IsInRequiresUnreferencedCodeAttributeScope,
                    // which we still call for consistency here, but it is expected to return false.
                    if (type.IsInRequiresUnreferencedCodeAttributeScope(out _))
                        return;

                    var location = GetPrimaryLocation(type.Locations);

                    var typeNameResolver = new TypeNameResolver(context.Compilation);
                    var genericArgumentDataFlow = new GenericArgumentDataFlow(dataFlowAnalyzerContext.TrimAnalyzer, FeatureContext.None, typeNameResolver, type, location, context.ReportDiagnostic);

                    foreach (var interfaceType in type.Interfaces)
                        genericArgumentDataFlow.ProcessGenericArgumentDataFlow(interfaceType);

                    DynamicallyAccessedMembersTypeHierarchy.ApplyDynamicallyAccessedMembersToTypeHierarchy(typeNameResolver, location, type, context.ReportDiagnostic);
                }, SymbolKind.NamedType);
                context.RegisterSymbolAction(context =>
                {
                    VerifyMemberOnlyApplyToTypesOrStrings(context, context.Symbol);
                    VerifyDamOnPropertyAndAccessorMatch(context, (IMethodSymbol)context.Symbol);
                    VerifyDamOnDerivedAndBaseMethodsMatch(context, (IMethodSymbol)context.Symbol);
                }, SymbolKind.Method);
                context.RegisterSymbolAction(context =>
                {
                    VerifyDamOnInterfaceAndImplementationMethodsMatch(context, (INamedTypeSymbol)context.Symbol);
                }, SymbolKind.NamedType);
                context.RegisterSymbolAction(context =>
                {
                    VerifyMemberOnlyApplyToTypesOrStrings(context, context.Symbol);
                }, SymbolKind.Property);
                context.RegisterSymbolAction(context =>
                {
                    VerifyMemberOnlyApplyToTypesOrStrings(context, context.Symbol);
                }, SymbolKind.Field);
            });
        }

        private static void VerifyMemberOnlyApplyToTypesOrStrings(SymbolAnalysisContext context, ISymbol member)
        {
            var location = GetPrimaryLocation(member.Locations);
            if (member is IFieldSymbol field && field.GetDynamicallyAccessedMemberTypes() != DynamicallyAccessedMemberTypes.None && !field.Type.IsTypeInterestingForDataflow(isByRef: field.RefKind is not RefKind.None))
                context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.DynamicallyAccessedMembersOnFieldCanOnlyApplyToTypesOrStrings), location, member.GetDisplayName()));
            else if (member is IMethodSymbol method)
            {
                if (method.GetDynamicallyAccessedMemberTypesOnReturnType() != DynamicallyAccessedMemberTypes.None && !method.ReturnType.IsTypeInterestingForDataflow(isByRef: method.ReturnsByRef))
                    context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.DynamicallyAccessedMembersOnMethodReturnValueCanOnlyApplyToTypesOrStrings), location, member.GetDisplayName()));
                if (method.GetDynamicallyAccessedMemberTypes() != DynamicallyAccessedMemberTypes.None && !method.ContainingType.IsTypeInterestingForDataflow(isByRef: false))
                    context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.DynamicallyAccessedMembersIsNotAllowedOnMethods), location));
                foreach (var parameter in method.Parameters)
                {
                    if (parameter.GetDynamicallyAccessedMemberTypes() != DynamicallyAccessedMemberTypes.None && !parameter.Type.IsTypeInterestingForDataflow(isByRef: parameter.RefKind is not RefKind.None))
                        context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.DynamicallyAccessedMembersOnMethodParameterCanOnlyApplyToTypesOrStrings), location, parameter.GetDisplayName(), member.GetDisplayName()));
                }
            }
            else if (member is IPropertySymbol property)
            {
                if (property.GetDynamicallyAccessedMemberTypes() != DynamicallyAccessedMemberTypes.None && !property.Type.IsTypeInterestingForDataflow(isByRef: property.ReturnsByRef))
                    context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.DynamicallyAccessedMembersOnPropertyCanOnlyApplyToTypesOrStrings), location, member.GetDisplayName()));
                if (property.GetDynamicallyAccessedMemberTypes() != DynamicallyAccessedMemberTypes.None && property.ContainingType.ExtensionParameter != null)
                    context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.DynamicallyAccessedMembersIsNotAllowedOnExtensionProperties), location, member.GetDisplayName()));
            }
        }

        private static void VerifyDamOnDerivedAndBaseMethodsMatch(SymbolAnalysisContext context, IMethodSymbol methodSymbol)
        {
            if (methodSymbol.TryGetOverriddenMember(out var overriddenSymbol) && overriddenSymbol is IMethodSymbol overriddenMethod
                && context.Symbol is IMethodSymbol method)
            {
                VerifyDamOnMethodsMatch(context, method, overriddenMethod);
            }
        }

        private static void VerifyDamOnMethodsMatch(
            SymbolAnalysisContext context,
            IMethodSymbol overrideMethod,
            IMethodSymbol baseMethod,
            ISymbol? origin = null,
            bool canOfferCodeFixOnOverride = true)
        {
            var overrideMethodReturnAnnotation = FlowAnnotations.GetMethodReturnValueAnnotation(overrideMethod);
            var baseMethodReturnAnnotation = FlowAnnotations.GetMethodReturnValueAnnotation(baseMethod);
            if (overrideMethodReturnAnnotation != baseMethodReturnAnnotation)
            {
                var returnOrigin = origin ?? overrideMethod;
                Location diagnosticLocation = GetPrimaryLocation(returnOrigin.Locations);

                // Only offer to add the missing annotation to the override. Never change the base method's contract.
                ImmutableDictionary<string, string?>? DAMArgs = (!canOfferCodeFixOnOverride
                    || overrideMethodReturnAnnotation != DynamicallyAccessedMemberTypes.None
                    || overrideMethod.TryGetReturnAttribute(DynamicallyAccessedMembersAnalyzer.DynamicallyAccessedMembersAttribute, out var _))
                        ? null
                        : CreateCodeFixProperties(baseMethodReturnAnnotation);

                context.ReportDiagnostic(Diagnostic.Create(
                    DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.DynamicallyAccessedMembersMismatchOnMethodReturnValueBetweenOverrides),
                    diagnosticLocation, additionalLocations: null, DAMArgs, overrideMethod.GetDisplayName(), baseMethod.GetDisplayName()));
            }

            foreach (var overrideParam in overrideMethod.GetMetadataParameters())
            {
                var baseParam = baseMethod.GetParameter(overrideParam.Index);
                var baseParameterAnnotation = FlowAnnotations.GetMethodParameterAnnotation(baseParam);
                var overrideParameterAnnotation = FlowAnnotations.GetMethodParameterAnnotation(overrideParam);
                if (overrideParameterAnnotation != baseParameterAnnotation)
                {
                    var parameterOrigin = origin ?? overrideParam.ParameterSymbol;
                    Location diagnosticLocation = GetPrimaryLocation(parameterOrigin?.Locations);

                    // Only offer to add the missing annotation to the override. Never change the base method's contract.
                    ImmutableDictionary<string, string?>? DAMArgs = (!canOfferCodeFixOnOverride
                        || overrideParameterAnnotation != DynamicallyAccessedMemberTypes.None
                        || overrideParam.ParameterSymbol!.TryGetAttribute(DynamicallyAccessedMembersAnalyzer.DynamicallyAccessedMembersAttribute, out var _))
                            ? null
                            : CreateCodeFixProperties(baseParameterAnnotation);

                    context.ReportDiagnostic(Diagnostic.Create(
                        DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.DynamicallyAccessedMembersMismatchOnMethodParameterBetweenOverrides),
                        diagnosticLocation, additionalLocations: null, DAMArgs,
                        overrideParam.GetDisplayName(), overrideMethod.GetDisplayName(), baseParam.GetDisplayName(), baseMethod.GetDisplayName()));
                }
            }

            for (int i = 0; i < overrideMethod.TypeParameters.Length; i++)
            {
                var methodTypeParameterAnnotation = overrideMethod.TypeParameters[i].GetDynamicallyAccessedMemberTypes();
                var overriddenMethodTypeParameterAnnotation = baseMethod.TypeParameters[i].GetDynamicallyAccessedMemberTypes();
                if (methodTypeParameterAnnotation != overriddenMethodTypeParameterAnnotation)
                {
                    var typeParameterOrigin = origin ?? overrideMethod.TypeParameters[i];

                    context.ReportDiagnostic(Diagnostic.Create(
                        DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.DynamicallyAccessedMembersMismatchOnGenericParameterBetweenOverrides),
                        GetPrimaryLocation(typeParameterOrigin.Locations),
                        overrideMethod.TypeParameters[i].GetDisplayName(), overrideMethod.GetDisplayName(),
                        baseMethod.TypeParameters[i].GetDisplayName(), baseMethod.GetDisplayName()));
                }
            }

            if (!overrideMethod.IsStatic)
            {
                var overrideMethodThisAnnotation = FlowAnnotations.GetMethodParameterAnnotation(new ParameterProxy(new(overrideMethod), (ParameterIndex)0));
                var baseMethodThisAnnotation = FlowAnnotations.GetMethodParameterAnnotation(new ParameterProxy(new(baseMethod), (ParameterIndex)0));
                if (overrideMethodThisAnnotation != baseMethodThisAnnotation)
                {
                    var methodOrigin = origin ?? overrideMethod;
                    context.ReportDiagnostic(Diagnostic.Create(
                        DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.DynamicallyAccessedMembersMismatchOnImplicitThisBetweenOverrides),
                        GetPrimaryLocation(methodOrigin.Locations),
                        overrideMethod.GetDisplayName(), baseMethod.GetDisplayName()));
                }
            }
        }

        private static void VerifyDamOnInterfaceAndImplementationMethodsMatch(SymbolAnalysisContext context, INamedTypeSymbol type)
        {
            foreach (var (interfaceMember, implementationMember) in type.GetMemberInterfaceImplementationPairs())
            {
                if (implementationMember is IMethodSymbol implementationMethod && interfaceMember is IMethodSymbol interfaceMethod)
                {
                    ISymbol? origin = null;
                    INamedTypeSymbol implementationType = implementationMethod.ContainingType;

                    // If this type implements an interface method through a base class, the origin of the warning is this type,
                    // not the member on the base class.
                    if (!implementationType.IsInterface() && !SymbolEqualityComparer.Default.Equals(implementationType, type))
                        origin = type;

                    VerifyDamOnMethodsMatch(
                        context,
                        implementationMethod,
                        interfaceMethod,
                        origin,
                        canOfferCodeFixOnOverride: SymbolEqualityComparer.Default.Equals(implementationType, type));
                }
            }
        }

        private static void VerifyDamOnPropertyAndAccessorMatch(SymbolAnalysisContext context, IMethodSymbol methodSymbol)
        {
            if ((methodSymbol.MethodKind != MethodKind.PropertyGet && methodSymbol.MethodKind != MethodKind.PropertySet)
                || methodSymbol.AssociatedSymbol is not IPropertySymbol propertySymbol
                || !propertySymbol.Type.IsTypeInterestingForDataflow(isByRef: propertySymbol.RefKind is not RefKind.None)
                || propertySymbol.GetDynamicallyAccessedMemberTypes() == DynamicallyAccessedMemberTypes.None)
                return;

            // For C# 14 extension properties, property-level DAM does not propagate to accessors
            // and we do not consider property vs accessor conflicts meaningful. Skip conflict checks.
            if (methodSymbol.HasExtensionParameterOnType())
                return;

            // None on the return type of 'get' matches unannotated
            if (methodSymbol.MethodKind == MethodKind.PropertyGet
                && methodSymbol.GetDynamicallyAccessedMemberTypesOnReturnType() != DynamicallyAccessedMemberTypes.None
                // None on parameter of 'set' matches unannotated
                || methodSymbol.MethodKind == MethodKind.PropertySet
                && methodSymbol.Parameters[methodSymbol.Parameters.Length - 1].GetDynamicallyAccessedMemberTypes() != DynamicallyAccessedMemberTypes.None)
            {
                context.ReportDiagnostic(Diagnostic.Create(
                    DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.DynamicallyAccessedMembersConflictsBetweenPropertyAndAccessor),
                    GetPrimaryLocation(propertySymbol.Locations),
                    propertySymbol.GetDisplayName(),
                    methodSymbol.GetDisplayName()
                ));
                return;
            }
        }

        private static ImmutableDictionary<string, string?> CreateCodeFixProperties(DynamicallyAccessedMemberTypes annotation)
            => ImmutableDictionary<string, string?>.Empty.Add(attributeArgument, annotation.ToString());
    }
}