File: Analyzers\NativeMarshallingAttributeAnalyzer.cs
Web Access
Project: src\src\libraries\System.Runtime.InteropServices\gen\LibraryImportGenerator\LibraryImportGenerator.csproj (Microsoft.Interop.LibraryImportGenerator)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.DotnetRuntime.Extensions;
using static Microsoft.Interop.Analyzers.AnalyzerDiagnostics;
using Microsoft.CodeAnalysis.Operations;
 
namespace Microsoft.Interop.Analyzers
{
    [DiagnosticAnalyzer(LanguageNames.CSharp)]
    public class NativeMarshallingAttributeAnalyzer : DiagnosticAnalyzer
    {
        private const string Category = "Usage";
 
        public static readonly DiagnosticDescriptor MarshallerEntryPointTypeMustHaveCustomMarshallerAttributeWithMatchingManagedTypeRule =
            DiagnosticDescriptorHelper.Create(
                Ids.InvalidNativeMarshallingAttributeUsage,
                GetResourceString(nameof(SR.InvalidNativeMarshallingAttributeUsageTitle)),
                GetResourceString(nameof(SR.EntryPointTypeMustHaveCustomMarshallerAttributeWithMatchingManagedTypeMessage)),
                Category,
                DiagnosticSeverity.Error,
                isEnabledByDefault: true,
                description: GetResourceString(nameof(SR.EntryPointTypeMustHaveCustomMarshallerAttributeWithMatchingManagedTypeDescription)));
 
        public static readonly DiagnosticDescriptor MarshallerEntryPointTypeMustBeNonNullRule =
            DiagnosticDescriptorHelper.Create(
                Ids.InvalidNativeMarshallingAttributeUsage,
                GetResourceString(nameof(SR.InvalidNativeMarshallingAttributeUsageTitle)),
                GetResourceString(nameof(SR.EntryPointTypeMustBeNonNullMessage)),
                Category,
                DiagnosticSeverity.Error,
                isEnabledByDefault: true,
                description: GetResourceString(nameof(SR.EntryPointTypeMustBeNonNullDescription)));
 
        public static readonly DiagnosticDescriptor GenericEntryPointMarshallerTypeMustBeClosedOrMatchArityRule =
            DiagnosticDescriptorHelper.Create(
                Ids.InvalidNativeMarshallingAttributeUsage,
                GetResourceString(nameof(SR.InvalidNativeMarshallingAttributeUsageTitle)),
                GetResourceString(nameof(SR.GenericEntryPointMarshallerTypeMustBeClosedOrMatchArityMessage)),
                Category,
                DiagnosticSeverity.Error,
                isEnabledByDefault: true,
                description: GetResourceString(nameof(SR.GenericEntryPointMarshallerTypeMustBeClosedOrMatchArityDescription)));
 
        public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
            ImmutableArray.Create(
                MarshallerEntryPointTypeMustHaveCustomMarshallerAttributeWithMatchingManagedTypeRule,
                MarshallerEntryPointTypeMustBeNonNullRule,
                GenericEntryPointMarshallerTypeMustBeClosedOrMatchArityRule);
 
        public override void Initialize(AnalysisContext context)
        {
            // Don't analyze generated code
            context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
            context.EnableConcurrentExecution();
            context.RegisterCompilationStartAction(PrepareForAnalysis);
        }
 
        private void PrepareForAnalysis(CompilationStartAnalysisContext context)
        {
            var perCompilationAnalyzer = new PerCompilationAnalyzer(context.Compilation);
 
            context.RegisterOperationAction(perCompilationAnalyzer.AnalyzeAttribute, OperationKind.Attribute);
        }
 
        private sealed partial class PerCompilationAnalyzer
        {
            private readonly Compilation _compilation;
 
            public PerCompilationAnalyzer(Compilation compilation)
            {
                _compilation = compilation;
            }
 
            public void AnalyzeAttribute(OperationAnalysisContext context)
            {
                IAttributeOperation attr = (IAttributeOperation)context.Operation;
                if (attr.Operation is IObjectCreationOperation attrCreation
                    && attrCreation.Type.ToDisplayString() == TypeNames.NativeMarshallingAttribute)
                {
                    IArgumentOperation marshallerEntryPointTypeArgument = attrCreation.GetArgumentByOrdinal(0);
                    if (marshallerEntryPointTypeArgument.Value.IsNullLiteralOperation())
                    {
                        DiagnosticReporter diagnosticFactory = DiagnosticReporter.CreateForLocation(marshallerEntryPointTypeArgument.Value.Syntax.GetLocation(), context.ReportDiagnostic);
                        diagnosticFactory.CreateAndReportDiagnostic(
                            MarshallerEntryPointTypeMustBeNonNullRule,
                            GetSymbolType(context.ContainingSymbol!).ToDisplayString());
                    }
                    if (marshallerEntryPointTypeArgument.Value is ITypeOfOperation typeOfOp)
                    {
                        AnalyzeManagedTypeMarshallingInfo(
                            GetSymbolType(context.ContainingSymbol!),
                            DiagnosticReporter.CreateForLocation(((TypeOfExpressionSyntax)typeOfOp.Syntax).Type.GetLocation(), context.ReportDiagnostic),
                            (INamedTypeSymbol?)typeOfOp.TypeOperand);
                    }
                }
            }
 
            private void AnalyzeManagedTypeMarshallingInfo(
                ITypeSymbol managedType,
                DiagnosticReporter diagnosticFactory,
                INamedTypeSymbol? entryType)
            {
                if (!ManualTypeMarshallingHelper.HasEntryPointMarshallerAttribute(entryType))
                {
                    diagnosticFactory.CreateAndReportDiagnostic(
                        MarshallerEntryPointTypeMustHaveCustomMarshallerAttributeWithMatchingManagedTypeRule,
                        entryType.ToDisplayString(),
                        managedType.ToDisplayString());
                    return;
                }
 
                bool isLinearCollectionMarshaller = ManualTypeMarshallingHelper.IsLinearCollectionEntryPoint(entryType);
                if (entryType.IsUnboundGenericType)
                {
                    if (managedType is not INamedTypeSymbol namedManagedType)
                    {
                        diagnosticFactory.CreateAndReportDiagnostic(
                            GenericEntryPointMarshallerTypeMustBeClosedOrMatchArityRule,
                            entryType.ToDisplayString(),
                            managedType.ToDisplayString());
                        return;
                    }
                    if (!ManualTypeMarshallingHelper.TryResolveEntryPointType(
                        namedManagedType,
                        entryType,
                        isLinearCollectionMarshaller,
                        (managedType, entryType) => diagnosticFactory.CreateAndReportDiagnostic(
                            GenericEntryPointMarshallerTypeMustBeClosedOrMatchArityRule,
                            entryType.ToDisplayString(),
                            managedType.ToDisplayString()),
                        out ITypeSymbol resolvedEntryType))
                    {
                        return;
                    }
                    entryType = (INamedTypeSymbol)resolvedEntryType;
                }
 
                if (!ManualTypeMarshallingHelper.TryGetMarshallersFromEntryTypeIgnoringElements(
                    entryType,
                    managedType,
                    _compilation,
                    (entryType, managedType) =>
                        diagnosticFactory.CreateAndReportDiagnostic(
                            GenericEntryPointMarshallerTypeMustBeClosedOrMatchArityRule,
                            entryType.ToDisplayString(),
                            managedType.ToDisplayString()), out _))
                {
                    diagnosticFactory.CreateAndReportDiagnostic(
                        MarshallerEntryPointTypeMustHaveCustomMarshallerAttributeWithMatchingManagedTypeRule,
                        entryType.ToDisplayString(),
                        managedType.ToDisplayString());
                }
            }
 
            private static ITypeSymbol GetSymbolType(ISymbol symbol)
            {
                return symbol switch
                {
                    IMethodSymbol method => method.ReturnType,
                    IParameterSymbol param => param.Type,
                    IFieldSymbol field => field.Type,
                    ITypeSymbol type => type,
                    _ => throw new InvalidOperationException()
                };
            }
        }
    }
}