diff --git a/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/Analyzers/LibraryImportDiagnosticsAnalyzer.cs b/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/Analyzers/LibraryImportDiagnosticsAnalyzer.cs new file mode 100644 index 00000000000000..0800b375fc4f20 --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/Analyzers/LibraryImportDiagnosticsAnalyzer.cs @@ -0,0 +1,300 @@ +// 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.Immutable; +using System.Threading; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.DotnetRuntime.Extensions; +using Microsoft.Interop; + +namespace Microsoft.Interop.Analyzers +{ + /// + /// Analyzer that reports diagnostics for LibraryImport methods. + /// This analyzer runs the same diagnostic logic as LibraryImportGenerator + /// but reports diagnostics separately from the source generator. + /// + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public class LibraryImportDiagnosticsAnalyzer : DiagnosticAnalyzer + { + public override ImmutableArray SupportedDiagnostics { get; } = + ImmutableArray.Create( + GeneratorDiagnostics.InvalidAttributedMethodSignature, + GeneratorDiagnostics.InvalidAttributedMethodContainingTypeMissingModifiers, + GeneratorDiagnostics.InvalidStringMarshallingConfiguration, + GeneratorDiagnostics.ParameterTypeNotSupported, + GeneratorDiagnostics.ReturnTypeNotSupported, + GeneratorDiagnostics.ParameterTypeNotSupportedWithDetails, + GeneratorDiagnostics.ReturnTypeNotSupportedWithDetails, + GeneratorDiagnostics.ParameterConfigurationNotSupported, + GeneratorDiagnostics.ReturnConfigurationNotSupported, + GeneratorDiagnostics.MarshalAsParameterConfigurationNotSupported, + GeneratorDiagnostics.MarshalAsReturnConfigurationNotSupported, + GeneratorDiagnostics.ConfigurationNotSupported, + GeneratorDiagnostics.ConfigurationValueNotSupported, + GeneratorDiagnostics.MarshallingAttributeConfigurationNotSupported, + GeneratorDiagnostics.CannotForwardToDllImport, + GeneratorDiagnostics.RequiresAllowUnsafeBlocks, + GeneratorDiagnostics.UnnecessaryParameterMarshallingInfo, + GeneratorDiagnostics.UnnecessaryReturnMarshallingInfo, + GeneratorDiagnostics.SizeOfInCollectionMustBeDefinedAtCallOutParam, + GeneratorDiagnostics.SizeOfInCollectionMustBeDefinedAtCallReturnValue, + GeneratorDiagnostics.LibraryImportUsageDoesNotFollowBestPractices); + + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + context.RegisterCompilationStartAction(context => + { + // Nothing to do if the LibraryImportAttribute is not in the compilation + INamedTypeSymbol? libraryImportAttrType = context.Compilation.GetBestTypeByMetadataName(TypeNames.LibraryImportAttribute); + if (libraryImportAttrType is null) + return; + + StubEnvironment env = new StubEnvironment( + context.Compilation, + context.Compilation.GetEnvironmentFlags()); + + // Get generator options once per compilation + LibraryImportGeneratorOptions options = new(context.Options.AnalyzerConfigOptionsProvider.GlobalOptions); + + // Track if we found any LibraryImport methods to report RequiresAllowUnsafeBlocks once + bool foundLibraryImportMethod = false; + bool unsafeEnabled = context.Compilation.Options is CSharpCompilationOptions { AllowUnsafe: true }; + + context.RegisterSymbolAction(symbolContext => + { + if (AnalyzeMethod(symbolContext, env, libraryImportAttrType, options)) + { + foundLibraryImportMethod = true; + } + }, SymbolKind.Method); + + // Report RequiresAllowUnsafeBlocks once per compilation if there are LibraryImport methods and unsafe is not enabled + context.RegisterCompilationEndAction(endContext => + { + if (foundLibraryImportMethod && !unsafeEnabled) + { + endContext.ReportDiagnostic(DiagnosticInfo.Create(GeneratorDiagnostics.RequiresAllowUnsafeBlocks, null).ToDiagnostic()); + } + }); + }); + } + + /// + /// Analyzes a method for LibraryImport diagnostics. + /// + /// True if the method has LibraryImportAttribute, false otherwise. + private static bool AnalyzeMethod(SymbolAnalysisContext context, StubEnvironment env, INamedTypeSymbol libraryImportAttrType, LibraryImportGeneratorOptions options) + { + IMethodSymbol method = (IMethodSymbol)context.Symbol; + + // Only analyze methods with LibraryImportAttribute + AttributeData? libraryImportAttr = null; + foreach (AttributeData attr in method.GetAttributes()) + { + if (SymbolEqualityComparer.Default.Equals(attr.AttributeClass, libraryImportAttrType)) + { + libraryImportAttr = attr; + break; + } + } + + if (libraryImportAttr is null) + return false; + + // Find the method syntax + foreach (SyntaxReference syntaxRef in method.DeclaringSyntaxReferences) + { + if (syntaxRef.GetSyntax(context.CancellationToken) is MethodDeclarationSyntax methodSyntax) + { + AnalyzeMethodSyntax(context, methodSyntax, method, libraryImportAttr, env, options); + break; + } + } + + return true; + } + + private static void AnalyzeMethodSyntax( + SymbolAnalysisContext context, + MethodDeclarationSyntax methodSyntax, + IMethodSymbol method, + AttributeData libraryImportAttr, + StubEnvironment env, + LibraryImportGeneratorOptions options) + { + // Check for invalid method signature + DiagnosticInfo? invalidMethodDiagnostic = GetDiagnosticIfInvalidMethodForGeneration(methodSyntax, method); + if (invalidMethodDiagnostic is not null) + { + context.ReportDiagnostic(invalidMethodDiagnostic.ToDiagnostic()); + return; // Don't continue analysis if the method is invalid + } + + // Note: RequiresAllowUnsafeBlocks is reported once per compilation in Initialize method + + // Calculate stub information and collect diagnostics + var diagnostics = CalculateDiagnostics(methodSyntax, method, libraryImportAttr, env, options, context.CancellationToken); + + foreach (DiagnosticInfo diagnostic in diagnostics) + { + context.ReportDiagnostic(diagnostic.ToDiagnostic()); + } + } + + private static ImmutableArray CalculateDiagnostics( + MethodDeclarationSyntax originalSyntax, + IMethodSymbol symbol, + AttributeData libraryImportAttr, + StubEnvironment environment, + LibraryImportGeneratorOptions options, + CancellationToken ct) + { + ct.ThrowIfCancellationRequested(); + + var locations = new MethodSignatureDiagnosticLocations(originalSyntax); + var generatorDiagnostics = new GeneratorDiagnosticsBag( + new DiagnosticDescriptorProvider(), + locations, + SR.ResourceManager, + typeof(FxResources.Microsoft.Interop.LibraryImportGenerator.SR)); + + // Process the LibraryImport attribute + LibraryImportCompilationData? libraryImportData = ProcessLibraryImportAttribute(libraryImportAttr); + + // If we can't parse the attribute, we have an invalid compilation - stop processing + if (libraryImportData is null) + { + return generatorDiagnostics.Diagnostics.ToImmutableArray(); + } + + if (libraryImportData.IsUserDefined.HasFlag(InteropAttributeMember.StringMarshalling)) + { + // User specified StringMarshalling.Custom without specifying StringMarshallingCustomType + if (libraryImportData.StringMarshalling == StringMarshalling.Custom && libraryImportData.StringMarshallingCustomType is null) + { + generatorDiagnostics.ReportInvalidStringMarshallingConfiguration( + libraryImportAttr, symbol.Name, SR.InvalidStringMarshallingConfigurationMissingCustomType); + } + + // User specified something other than StringMarshalling.Custom while specifying StringMarshallingCustomType + if (libraryImportData.StringMarshalling != StringMarshalling.Custom && libraryImportData.StringMarshallingCustomType is not null) + { + generatorDiagnostics.ReportInvalidStringMarshallingConfiguration( + libraryImportAttr, symbol.Name, SR.InvalidStringMarshallingConfigurationNotCustom); + } + } + + // Check for unsupported LCIDConversion attribute + INamedTypeSymbol? lcidConversionAttrType = environment.LcidConversionAttrType; + if (lcidConversionAttrType is not null) + { + foreach (AttributeData attr in symbol.GetAttributes()) + { + if (SymbolEqualityComparer.Default.Equals(attr.AttributeClass, lcidConversionAttrType)) + { + generatorDiagnostics.ReportConfigurationNotSupported(attr, nameof(TypeNames.LCIDConversionAttribute)); + break; + } + } + } + + // Create the signature context to collect marshalling-related diagnostics + var signatureContext = SignatureContext.Create( + symbol, + DefaultMarshallingInfoParser.Create(environment, generatorDiagnostics, symbol, libraryImportData, libraryImportAttr), + environment, + new CodeEmitOptions(SkipInit: true), + typeof(LibraryImportGenerator).Assembly); + + // If forwarders are not being generated, we need to run stub generation logic to get those diagnostics too + if (!options.GenerateForwarders) + { + IMarshallingGeneratorResolver resolver = DefaultMarshallingGeneratorResolver.Create( + environment.EnvironmentFlags, + MarshalDirection.ManagedToUnmanaged, + TypeNames.LibraryImportAttribute_ShortName, + []); + + // Check marshalling generators - this collects diagnostics for marshalling issues + _ = new ManagedToNativeStubGenerator( + signatureContext.ElementTypeInformation, + LibraryImportData.From(libraryImportData).SetLastError, + generatorDiagnostics, + resolver, + new CodeEmitOptions(SkipInit: true)); + } + + return generatorDiagnostics.Diagnostics.ToImmutableArray(); + } + + private static LibraryImportCompilationData? ProcessLibraryImportAttribute(AttributeData attrData) + { + // Found the LibraryImport, but it has an error so report the error. + // This is most likely an issue with targeting an incorrect TFM. + if (attrData.AttributeClass?.TypeKind is null or TypeKind.Error) + { + return null; + } + + if (attrData.ConstructorArguments.Length == 0) + { + return null; + } + + ImmutableDictionary namedArguments = ImmutableDictionary.CreateRange(attrData.NamedArguments); + + string? entryPoint = null; + if (namedArguments.TryGetValue(nameof(LibraryImportCompilationData.EntryPoint), out TypedConstant entryPointValue)) + { + if (entryPointValue.Value is not string) + { + return null; + } + entryPoint = (string)entryPointValue.Value!; + } + + return new LibraryImportCompilationData(attrData.ConstructorArguments[0].Value!.ToString()) + { + EntryPoint = entryPoint, + }.WithValuesFromNamedArguments(namedArguments); + } + + /// + /// Checks if a method is invalid for generation and returns a diagnostic if so. + /// + /// A diagnostic if the method is invalid, null otherwise. + internal static DiagnosticInfo? GetDiagnosticIfInvalidMethodForGeneration(MethodDeclarationSyntax methodSyntax, IMethodSymbol method) + { + // Verify the method has no generic types or defined implementation + // and is marked static and partial. + if (methodSyntax.TypeParameterList is not null + || methodSyntax.Body is not null + || !methodSyntax.Modifiers.Any(SyntaxKind.StaticKeyword) + || !methodSyntax.Modifiers.Any(SyntaxKind.PartialKeyword)) + { + return DiagnosticInfo.Create(GeneratorDiagnostics.InvalidAttributedMethodSignature, methodSyntax.Identifier.GetLocation(), method.Name); + } + + // Verify that the types the method is declared in are marked partial. + if (methodSyntax.Parent is TypeDeclarationSyntax typeDecl && !typeDecl.IsInPartialContext(out var nonPartialIdentifier)) + { + return DiagnosticInfo.Create(GeneratorDiagnostics.InvalidAttributedMethodContainingTypeMissingModifiers, methodSyntax.Identifier.GetLocation(), method.Name, nonPartialIdentifier); + } + + // Verify the method does not have a ref return + if (method.ReturnsByRef || method.ReturnsByRefReadonly) + { + return DiagnosticInfo.Create(GeneratorDiagnostics.ReturnConfigurationNotSupported, methodSyntax.Identifier.GetLocation(), "ref return", method.ToDisplayString()); + } + + return null; + } + } +} diff --git a/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/LibraryImportGenerator.cs b/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/LibraryImportGenerator.cs index bf0d61c87234c4..130686c2db58dc 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/LibraryImportGenerator.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/LibraryImportGenerator.cs @@ -39,8 +39,9 @@ public static class StepNames public void Initialize(IncrementalGeneratorInitializationContext context) { - // Collect all methods adorned with LibraryImportAttribute - var attributedMethods = context.SyntaxProvider + // Collect all methods adorned with LibraryImportAttribute and filter out invalid ones + // (diagnostics for invalid methods are reported by the analyzer) + var methodsToGenerate = context.SyntaxProvider .ForAttributeWithMetadataName( TypeNames.LibraryImportAttribute, static (node, ct) => node is MethodDeclarationSyntax, @@ -48,18 +49,8 @@ public void Initialize(IncrementalGeneratorInitializationContext context) ? new { Syntax = (MethodDeclarationSyntax)context.TargetNode, Symbol = methodSymbol } : null) .Where( - static modelData => modelData is not null); - - // Validate if attributed methods can have source generated - var methodsWithDiagnostics = attributedMethods.Select(static (data, ct) => - { - DiagnosticInfo? diagnostic = GetDiagnosticIfInvalidMethodForGeneration(data.Syntax, data.Symbol); - return diagnostic is not null - ? DiagnosticOr<(MethodDeclarationSyntax Syntax, IMethodSymbol Symbol)>.From(diagnostic) - : DiagnosticOr<(MethodDeclarationSyntax Syntax, IMethodSymbol Symbol)>.From((data.Syntax, data.Symbol)); - }); - - var methodsToGenerate = context.FilterAndReportDiagnostics(methodsWithDiagnostics); + static modelData => modelData is not null + && Analyzers.LibraryImportDiagnosticsAnalyzer.GetDiagnosticIfInvalidMethodForGeneration(modelData.Syntax, modelData.Symbol) is null); // Compute generator options IncrementalValueProvider stubOptions = context.AnalyzerConfigOptionsProvider @@ -67,22 +58,6 @@ public void Initialize(IncrementalGeneratorInitializationContext context) IncrementalValueProvider stubEnvironment = context.CreateStubEnvironmentProvider(); - // Validate environment that is being used to generate stubs. - context.RegisterDiagnostics( - context.CompilationProvider - .Select((comp, ct) => comp.Options is CSharpCompilationOptions { AllowUnsafe: true }) - .Combine(attributedMethods.Collect()) - .SelectMany((data, ct) => - { - if (data.Right.IsEmpty // no attributed methods - || data.Left) // Unsafe code enabled - { - return ImmutableArray.Empty; - } - - return ImmutableArray.Create(DiagnosticInfo.Create(GeneratorDiagnostics.RequiresAllowUnsafeBlocks, null)); - })); - IncrementalValuesProvider<(MemberDeclarationSyntax, ImmutableArray)> generateSingleStub = methodsToGenerate .Combine(stubEnvironment) .Combine(stubOptions) @@ -104,8 +79,6 @@ public void Initialize(IncrementalGeneratorInitializationContext context) .WithComparer(Comparers.GeneratedSyntax) .WithTrackingName(StepNames.GenerateSingleStub); - context.RegisterDiagnostics(generateSingleStub.SelectMany((stubInfo, ct) => stubInfo.Item2)); - context.RegisterConcatenatedSyntaxOutputs(generateSingleStub.Select((data, ct) => data.Item1), "LibraryImports.g.cs"); } @@ -549,32 +522,5 @@ static ExpressionSyntax CreateEnumExpressionSyntax(T value) where T : Enum IdentifierName(value.ToString())); } } - - private static DiagnosticInfo? GetDiagnosticIfInvalidMethodForGeneration(MethodDeclarationSyntax methodSyntax, IMethodSymbol method) - { - // Verify the method has no generic types or defined implementation - // and is marked static and partial. - if (methodSyntax.TypeParameterList is not null - || methodSyntax.Body is not null - || !methodSyntax.Modifiers.Any(SyntaxKind.StaticKeyword) - || !methodSyntax.Modifiers.Any(SyntaxKind.PartialKeyword)) - { - return DiagnosticInfo.Create(GeneratorDiagnostics.InvalidAttributedMethodSignature, methodSyntax.Identifier.GetLocation(), method.Name); - } - - // Verify that the types the method is declared in are marked partial. - if (methodSyntax.Parent is TypeDeclarationSyntax typeDecl && !typeDecl.IsInPartialContext(out var nonPartialIdentifier)) - { - return DiagnosticInfo.Create(GeneratorDiagnostics.InvalidAttributedMethodContainingTypeMissingModifiers, methodSyntax.Identifier.GetLocation(), method.Name, nonPartialIdentifier); - } - - // Verify the method does not have a ref return - if (method.ReturnsByRef || method.ReturnsByRefReadonly) - { - return DiagnosticInfo.Create(GeneratorDiagnostics.ReturnConfigurationNotSupported, methodSyntax.Identifier.GetLocation(), "ref return", method.ToDisplayString()); - } - - return null; - } } } diff --git a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/ByValueContentsMarshalling.cs b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/ByValueContentsMarshalling.cs index fdd2d33d6e7d27..3bd2bae74d92d0 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/ByValueContentsMarshalling.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/ByValueContentsMarshalling.cs @@ -10,7 +10,7 @@ using Xunit; using static Microsoft.Interop.UnitTests.TestUtils; using StringMarshalling = System.Runtime.InteropServices.StringMarshalling; -using VerifyComInterfaceGenerator = Microsoft.Interop.UnitTests.Verifiers.CSharpSourceGeneratorVerifier; +using VerifyComInterfaceGenerator = Microsoft.Interop.UnitTests.Verifiers.CSharpSourceGeneratorVerifier; namespace ComInterfaceGenerator.Unit.Tests { diff --git a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/ComClassGeneratorDiagnostics.cs b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/ComClassGeneratorDiagnostics.cs index 13d63a83333e2d..f07e6d16b6322d 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/ComClassGeneratorDiagnostics.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/ComClassGeneratorDiagnostics.cs @@ -7,7 +7,7 @@ using Microsoft.CodeAnalysis.Testing; using Microsoft.Interop; using Xunit; -using VerifyCS = Microsoft.Interop.UnitTests.Verifiers.CSharpSourceGeneratorVerifier; +using VerifyCS = Microsoft.Interop.UnitTests.Verifiers.CSharpSourceGeneratorVerifier; namespace ComInterfaceGenerator.Unit.Tests { diff --git a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/ComClassGeneratorOutputShape.cs b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/ComClassGeneratorOutputShape.cs index c00ff13aae59f3..c61e00b0a3d7e0 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/ComClassGeneratorOutputShape.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/ComClassGeneratorOutputShape.cs @@ -8,7 +8,7 @@ using Microsoft.CodeAnalysis.Testing; using Xunit; -using VerifyCS = Microsoft.Interop.UnitTests.Verifiers.CSharpSourceGeneratorVerifier; +using VerifyCS = Microsoft.Interop.UnitTests.Verifiers.CSharpSourceGeneratorVerifier; namespace ComInterfaceGenerator.Unit.Tests { diff --git a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/ComInterfaceGeneratorOutputShape.cs b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/ComInterfaceGeneratorOutputShape.cs index efc4bc7f7c35f5..9ab81347a93dcb 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/ComInterfaceGeneratorOutputShape.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/ComInterfaceGeneratorOutputShape.cs @@ -18,7 +18,7 @@ using SourceGenerators.Tests; using Xunit; -using VerifyCS = Microsoft.Interop.UnitTests.Verifiers.CSharpSourceGeneratorVerifier; +using VerifyCS = Microsoft.Interop.UnitTests.Verifiers.CSharpSourceGeneratorVerifier; namespace ComInterfaceGenerator.Unit.Tests { @@ -149,7 +149,7 @@ partial interface J : I } """; - var test = new VerifyCompilationTest(false) + var test = new VerifyCompilationTest(false) { TestCode = source, TestBehaviors = TestBehaviors.SkipGeneratedSourcesCheck | TestBehaviors.SkipGeneratedCodeCheck, @@ -198,7 +198,7 @@ partial interface K : J } """; - var test = new VerifyCompilationTest(false) + var test = new VerifyCompilationTest(false) { TestCode = source, TestBehaviors = TestBehaviors.SkipGeneratedSourcesCheck | TestBehaviors.SkipGeneratedCodeCheck, @@ -256,7 +256,7 @@ partial interface IDerivedIface : IFoo } """; - var test = new VerifyCompilationTest(false) + var test = new VerifyCompilationTest(false) { TestCode = source, TestBehaviors = TestBehaviors.SkipGeneratedSourcesCheck | TestBehaviors.SkipGeneratedCodeCheck, diff --git a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/CompileFails.cs b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/CompileFails.cs index 3e959e4d2ac744..a908d720673dfc 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/CompileFails.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/CompileFails.cs @@ -15,7 +15,7 @@ using Xunit; using static Microsoft.Interop.UnitTests.TestUtils; using StringMarshalling = System.Runtime.InteropServices.StringMarshalling; -using VerifyComInterfaceGenerator = Microsoft.Interop.UnitTests.Verifiers.CSharpSourceGeneratorVerifier; +using VerifyComInterfaceGenerator = Microsoft.Interop.UnitTests.Verifiers.CSharpSourceGeneratorVerifier; namespace ComInterfaceGenerator.Unit.Tests { diff --git a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/Compiles.cs b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/Compiles.cs index ba4928fec1d367..b255a2a29a44df 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/Compiles.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/Compiles.cs @@ -12,8 +12,8 @@ using Microsoft.CodeAnalysis.Testing; using Microsoft.Interop.UnitTests; using Xunit; -using VerifyComInterfaceGenerator = Microsoft.Interop.UnitTests.Verifiers.CSharpSourceGeneratorVerifier; -using VerifyVTableGenerator = Microsoft.Interop.UnitTests.Verifiers.CSharpSourceGeneratorVerifier; +using VerifyComInterfaceGenerator = Microsoft.Interop.UnitTests.Verifiers.CSharpSourceGeneratorVerifier; +using VerifyVTableGenerator = Microsoft.Interop.UnitTests.Verifiers.CSharpSourceGeneratorVerifier; namespace ComInterfaceGenerator.Unit.Tests { @@ -410,7 +410,7 @@ public partial interface MyOtherInterface : IMyInterface } """; - var test = new VerifyCompilationTest(false) + var test = new VerifyCompilationTest(false) { TestCode = source, TestBehaviors = TestBehaviors.SkipGeneratedSourcesCheck | TestBehaviors.SkipGeneratedCodeCheck, diff --git a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/TargetSignatureTests.cs b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/TargetSignatureTests.cs index dbd18badbb78a7..2e7bb5815a27fd 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/TargetSignatureTests.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/TargetSignatureTests.cs @@ -15,7 +15,7 @@ using Microsoft.Interop; using Xunit; -using VerifyCS = Microsoft.Interop.UnitTests.Verifiers.CSharpSourceGeneratorVerifier; +using VerifyCS = Microsoft.Interop.UnitTests.Verifiers.CSharpSourceGeneratorVerifier; namespace ComInterfaceGenerator.Unit.Tests { diff --git a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/VerifyCompilationTest.cs b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/VerifyCompilationTest.cs index 09d6b5e2a58aed..39c9fb01f1add6 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/VerifyCompilationTest.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/VerifyCompilationTest.cs @@ -3,12 +3,14 @@ using System; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.Interop.UnitTests; namespace ComInterfaceGenerator.Unit.Tests { - internal class VerifyCompilationTest : Microsoft.Interop.UnitTests.Verifiers.CSharpSourceGeneratorVerifier.Test + internal class VerifyCompilationTest : Microsoft.Interop.UnitTests.Verifiers.CSharpSourceGeneratorVerifier.Test where T : new() + where TAnalyzer : DiagnosticAnalyzer, new() { public required Action CompilationVerifier { get; init; } diff --git a/src/libraries/System.Runtime.InteropServices/tests/Common/Verifiers/CSharpAnalyzerVerifier.cs b/src/libraries/System.Runtime.InteropServices/tests/Common/Verifiers/CSharpAnalyzerVerifier.cs index 79c12e1d0de624..51b84dba0bc396 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/Common/Verifiers/CSharpAnalyzerVerifier.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/Common/Verifiers/CSharpAnalyzerVerifier.cs @@ -40,6 +40,13 @@ public static async Task VerifyAnalyzerAsync(string source, params DiagnosticRes // Code fix tests support both analyzer and code fix testing. This test class is derived from the code fix test // to avoid the need to maintain duplicate copies of the customization work. internal class Test : CSharpCodeFixVerifier.Test - { } + { + public Test() : base() + { + // Ignore compiler diagnostics since we're only testing the analyzer. + // Without the generator, partial methods won't have implementations which causes CS8795. + CompilerDiagnostics = CompilerDiagnostics.None; + } + } } } diff --git a/src/libraries/System.Runtime.InteropServices/tests/Common/Verifiers/CSharpSourceGeneratorVerifier.cs b/src/libraries/System.Runtime.InteropServices/tests/Common/Verifiers/CSharpSourceGeneratorVerifier.cs index 3836d0243951e1..25dda49d840902 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/Common/Verifiers/CSharpSourceGeneratorVerifier.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/Common/Verifiers/CSharpSourceGeneratorVerifier.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Threading; @@ -14,8 +15,9 @@ namespace Microsoft.Interop.UnitTests.Verifiers { - public static class CSharpSourceGeneratorVerifier + public static class CSharpSourceGeneratorVerifier where TSourceGenerator : new() + where TAnalyzer : DiagnosticAnalyzer, new() { public static DiagnosticResult Diagnostic(string diagnosticId) => new DiagnosticResult(diagnosticId, DiagnosticSeverity.Error); @@ -107,6 +109,9 @@ public Test(bool referenceAncillaryInterop) SolutionTransforms.Add(CSharpVerifierHelper.GetAllDiagnosticsEnabledTransform(GetDiagnosticAnalyzers())); } + protected override IEnumerable GetDiagnosticAnalyzers() + => [..base.GetDiagnosticAnalyzers(), new TAnalyzer()]; + protected override CompilationWithAnalyzers CreateCompilationWithAnalyzers(Compilation compilation, ImmutableArray analyzers, AnalyzerOptions options, CancellationToken cancellationToken) { return new CompilationWithAnalyzers( diff --git a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/AdditionalAttributesOnStub.cs b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/AdditionalAttributesOnStub.cs index e43937d75fa286..ebb295f6d3fe79 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/AdditionalAttributesOnStub.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/AdditionalAttributesOnStub.cs @@ -3,6 +3,7 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Testing; using Microsoft.Interop; using Microsoft.Interop.UnitTests; @@ -12,7 +13,7 @@ using System.Threading.Tasks; using Xunit; -using VerifyCS = Microsoft.Interop.UnitTests.Verifiers.CSharpSourceGeneratorVerifier; +using VerifyCS = Microsoft.Interop.UnitTests.Verifiers.CSharpSourceGeneratorVerifier; namespace LibraryImportGenerator.UnitTests { @@ -243,7 +244,7 @@ static class Marshaller private static Task VerifyDownlevelSourceGeneratorAsync(string source, string typeName, string methodName, string? attributeName, bool attributeAdded, TestTargetFramework targetFramework) { - AttributeAddedTest test = new(typeName, methodName, attributeName, attributeAdded, targetFramework) + AttributeAddedTest test = new(typeName, methodName, attributeName, attributeAdded, targetFramework) { TestCode = source, TestBehaviors = TestBehaviors.SkipGeneratedSourcesCheck @@ -253,7 +254,7 @@ private static Task VerifyDownlevelSourceGeneratorAsync(string source, string ty private static Task VerifySourceGeneratorAsync(string source, string typeName, string methodName, string? attributeName, bool attributeAdded) { - AttributeAddedTest test = new(typeName, methodName, attributeName, attributeAdded) + AttributeAddedTest test = new(typeName, methodName, attributeName, attributeAdded) { TestCode = source, TestBehaviors = TestBehaviors.SkipGeneratedSourcesCheck @@ -261,8 +262,9 @@ private static Task VerifySourceGeneratorAsync(string source, string typeName, s return test.RunAsync(); } - class AttributeAddedTest : Microsoft.Interop.UnitTests.Verifiers.CSharpSourceGeneratorVerifier.Test + class AttributeAddedTest : Microsoft.Interop.UnitTests.Verifiers.CSharpSourceGeneratorVerifier.Test where TSourceGenerator : new() + where TAnalyzer : DiagnosticAnalyzer, new() { private readonly string _typeName; private readonly string _methodName; diff --git a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/AttributeForwarding.cs b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/AttributeForwarding.cs index 5a2b4ba19468f8..6a6de932786ad7 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/AttributeForwarding.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/AttributeForwarding.cs @@ -12,7 +12,7 @@ using System.Runtime.InteropServices; using System.Threading.Tasks; using Xunit; -using VerifyCS = Microsoft.Interop.UnitTests.Verifiers.CSharpSourceGeneratorVerifier; +using VerifyCS = Microsoft.Interop.UnitTests.Verifiers.CSharpSourceGeneratorVerifier; namespace LibraryImportGenerator.UnitTests { diff --git a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/ByValueContentsMarshalling.cs b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/ByValueContentsMarshalling.cs index e632481d52109f..90787cb7a02bd7 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/ByValueContentsMarshalling.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/ByValueContentsMarshalling.cs @@ -8,7 +8,7 @@ using Microsoft.Interop; using Xunit; using static Microsoft.Interop.UnitTests.TestUtils; -using VerifyCS = Microsoft.Interop.UnitTests.Verifiers.CSharpSourceGeneratorVerifier; +using VerifyCS = Microsoft.Interop.UnitTests.Verifiers.CSharpSourceGeneratorVerifier; namespace LibraryImportGenerator.UnitTests { diff --git a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/CompileFails.cs b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/CompileFails.cs index 952f8ef2890c81..8b6e94c043b7dc 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/CompileFails.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/CompileFails.cs @@ -16,7 +16,7 @@ using Xunit; using StringMarshalling = System.Runtime.InteropServices.StringMarshalling; -using VerifyCS = Microsoft.Interop.UnitTests.Verifiers.CSharpSourceGeneratorVerifier; +using VerifyCS = Microsoft.Interop.UnitTests.Verifiers.CSharpSourceGeneratorVerifier; namespace LibraryImportGenerator.UnitTests { diff --git a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/Compiles.cs b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/Compiles.cs index afa6af2bb4c9a6..225ae48288ebc1 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/Compiles.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/Compiles.cs @@ -13,12 +13,13 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Testing; using Microsoft.CodeAnalysis.Text; using Microsoft.DotNet.XUnitExtensions.Attributes; using Microsoft.Interop.UnitTests; using Xunit; -using VerifyCS = Microsoft.Interop.UnitTests.Verifiers.CSharpSourceGeneratorVerifier; +using VerifyCS = Microsoft.Interop.UnitTests.Verifiers.CSharpSourceGeneratorVerifier; namespace LibraryImportGenerator.UnitTests { @@ -578,7 +579,7 @@ public async Task ValidateSnippetsFallbackForwarder(string id, string source, Te await test.RunAsync(); } - class FallbackForwarderTest : Microsoft.Interop.UnitTests.Verifiers.CSharpSourceGeneratorVerifier.Test + class FallbackForwarderTest : Microsoft.Interop.UnitTests.Verifiers.CSharpSourceGeneratorVerifier.Test { private readonly bool _expectFallbackForwarder; @@ -747,7 +748,7 @@ public async Task ValidateNoGeneratedOutputForNoImport() public class Basic { } """; - var test = new NoChangeTest() + var test = new NoChangeTest() { TestCode = source, TestBehaviors = TestBehaviors.SkipGeneratedSourcesCheck @@ -767,7 +768,7 @@ public async Task ValidateNoGeneratedOutputForNoImportDownlevel(TestTargetFramew public class Basic { } """; - var test = new NoChangeTest(framework) + var test = new NoChangeTest(framework) { TestCode = source, TestBehaviors = TestBehaviors.SkipGeneratedSourcesCheck @@ -776,8 +777,9 @@ public class Basic { } await test.RunAsync(); } - class NoChangeTest : Microsoft.Interop.UnitTests.Verifiers.CSharpSourceGeneratorVerifier.Test + class NoChangeTest : Microsoft.Interop.UnitTests.Verifiers.CSharpSourceGeneratorVerifier.Test where TSourceGenerator : new() + where TAnalyzer : DiagnosticAnalyzer, new() { public NoChangeTest() : base(referenceAncillaryInterop: false) diff --git a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/Diagnostics.cs b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/Diagnostics.cs index e064a4b951d290..a440f935b7ffca 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/Diagnostics.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/Diagnostics.cs @@ -14,7 +14,7 @@ using Xunit; using StringMarshalling = Microsoft.Interop.StringMarshalling; -using VerifyCS = Microsoft.Interop.UnitTests.Verifiers.CSharpSourceGeneratorVerifier; +using VerifyCS = Microsoft.Interop.UnitTests.Verifiers.CSharpAnalyzerVerifier; namespace LibraryImportGenerator.UnitTests { @@ -41,7 +41,7 @@ partial class Test } """; - await VerifyCS.VerifySourceGeneratorAsync(source, + await VerifyCS.VerifyAnalyzerAsync(source, VerifyCS.Diagnostic(GeneratorDiagnostics.ParameterTypeNotSupported) .WithLocation(0) .WithArguments("NS.MyClass", "c"), @@ -71,7 +71,7 @@ partial class Test } """; - await VerifyCS.VerifySourceGeneratorAsync(source, + await VerifyCS.VerifyAnalyzerAsync(source, VerifyCS.Diagnostic(GeneratorDiagnostics.ReturnTypeNotSupported) .WithLocation(0) .WithArguments("NS.MyClass", "Method1"), @@ -93,7 +93,7 @@ partial class Test } """; - await VerifyCS.VerifySourceGeneratorAsync(source, + await VerifyCS.VerifyAnalyzerAsync(source, VerifyCS.Diagnostic(GeneratorDiagnostics.ParameterTypeNotSupportedWithDetails) .WithLocation(0) .WithArguments("Runtime marshalling must be disabled in this project by applying the 'System.Runtime.CompilerServices.DisableRuntimeMarshallingAttribute' to the assembly to enable marshalling this type.", "c"), @@ -118,7 +118,7 @@ partial class Test } """; - await VerifyCS.VerifySourceGeneratorAsync(source, + await VerifyCS.VerifyAnalyzerAsync(source, VerifyCS.Diagnostic(GeneratorDiagnostics.ReturnTypeNotSupportedWithDetails) .WithLocation(0) .WithArguments("Runtime marshalling must be disabled in this project by applying the 'System.Runtime.CompilerServices.DisableRuntimeMarshallingAttribute' to the assembly to enable marshalling this type.", "Method1"), @@ -143,7 +143,7 @@ partial class Test } """; - await VerifyCS.VerifySourceGeneratorAsync(source, + await VerifyCS.VerifyAnalyzerAsync(source, VerifyCS.Diagnostic(GeneratorDiagnostics.MarshalAsParameterConfigurationNotSupported) .WithLocation(0) .WithArguments(nameof(MarshalAsAttribute), "i1"), @@ -170,7 +170,7 @@ partial class Test } """; - await VerifyCS.VerifySourceGeneratorAsync(source, + await VerifyCS.VerifyAnalyzerAsync(source, VerifyCS.Diagnostic(GeneratorDiagnostics.MarshalAsReturnConfigurationNotSupported) .WithLocation(0) .WithArguments(nameof(MarshalAsAttribute), "Method1"), @@ -196,7 +196,7 @@ partial class Test } """; - await VerifyCS.VerifySourceGeneratorAsync(source, + await VerifyCS.VerifyAnalyzerAsync(source, VerifyCS.Diagnostic(GeneratorDiagnostics.ConfigurationValueNotSupported) .WithLocation(0) .WithArguments(1, nameof(UnmanagedType)), @@ -228,7 +228,7 @@ partial class Test } """; - await VerifyCS.VerifySourceGeneratorAsync(source, + await VerifyCS.VerifyAnalyzerAsync(source, VerifyCS.Diagnostic(GeneratorDiagnostics.ConfigurationNotSupported) .WithLocation(0) .WithArguments($"{nameof(MarshalAsAttribute)}{Type.Delimiter}{nameof(MarshalAsAttribute.SafeArraySubType)}"), @@ -269,7 +269,7 @@ public Native(string s) { } .WithArguments($"{nameof(TypeNames.LibraryImportAttribute)}{Type.Delimiter}{nameof(StringMarshalling)}={nameof(StringMarshalling)}{Type.Delimiter}{nameof(StringMarshalling.Custom)}") ]; - var test = new Microsoft.Interop.UnitTests.Verifiers.CSharpSourceGeneratorVerifier.Test(TestTargetFramework.Standard2_0) + var test = new Microsoft.Interop.UnitTests.Verifiers.CSharpSourceGeneratorVerifier.Test(TestTargetFramework.Standard2_0) { TestCode = source, TestBehaviors = TestBehaviors.SkipGeneratedSourcesCheck @@ -302,7 +302,7 @@ public Native(string s) { } """; - await VerifyCS.VerifySourceGeneratorAsync(source, + await VerifyCS.VerifyAnalyzerAsync(source, VerifyCS.Diagnostic(GeneratorDiagnostics.InvalidStringMarshallingConfiguration) .WithLocation(0) .WithArguments("Method1", "'StringMarshallingCustomType' must be specified when 'StringMarshalling' is set to 'StringMarshalling.Custom'."), @@ -327,7 +327,7 @@ partial class Test } """; - await VerifyCS.VerifySourceGeneratorAsync(source, + await VerifyCS.VerifyAnalyzerAsync(source, VerifyCS.Diagnostic(GeneratorDiagnostics.InvalidAttributedMethodSignature) .WithLocation(0) .WithArguments("Method"), @@ -349,13 +349,10 @@ partial class Test } """; - await VerifyCS.VerifySourceGeneratorAsync(source, + await VerifyCS.VerifyAnalyzerAsync(source, VerifyCS.Diagnostic(GeneratorDiagnostics.InvalidAttributedMethodSignature) .WithLocation(0) - .WithArguments("Method"), - // Generator ignores the method - DiagnosticResult.CompilerError("CS8795") - .WithLocation(0)); + .WithArguments("Method")); } [Fact] @@ -374,18 +371,13 @@ partial class Test } """; - await VerifyCS.VerifySourceGeneratorAsync(source, + await VerifyCS.VerifyAnalyzerAsync(source, VerifyCS.Diagnostic(GeneratorDiagnostics.InvalidAttributedMethodSignature) .WithLocation(0) .WithArguments("Method1"), VerifyCS.Diagnostic(GeneratorDiagnostics.InvalidAttributedMethodSignature) .WithLocation(1) - .WithArguments("Method2"), - // Generator ignores the method - DiagnosticResult.CompilerError("CS8795") - .WithLocation(0), - DiagnosticResult.CompilerError("CS8795") - .WithLocation(1)); + .WithArguments("Method2")); } [Theory] @@ -404,16 +396,10 @@ public async Task NonPartialParentType_Diagnostic(string typeKind) } """; - await VerifyCS.VerifySourceGeneratorAsync(source, + await VerifyCS.VerifyAnalyzerAsync(source, VerifyCS.Diagnostic(GeneratorDiagnostics.InvalidAttributedMethodContainingTypeMissingModifiers) .WithLocation(0) - .WithArguments("Method", "Test"), - // Generator ignores the method - DiagnosticResult.CompilerError("CS8795") - .WithLocation(0), - // Also expect CS0751: A partial method must be declared within a partial type - DiagnosticResult.CompilerError("CS0751") - .WithLocation(0)); + .WithArguments("Method", "Test")); } [Theory] @@ -435,7 +421,7 @@ partial class TestInner } """; - await VerifyCS.VerifySourceGeneratorAsync(source, + await VerifyCS.VerifyAnalyzerAsync(source, VerifyCS.Diagnostic(GeneratorDiagnostics.InvalidAttributedMethodContainingTypeMissingModifiers) .WithLocation(0) .WithArguments("Method", "Test"));