diff --git a/docs/design/libraries/DllImportGenerator/Diagnostics.md b/docs/design/libraries/DllImportGenerator/Diagnostics.md deleted file mode 100644 index d04a3851914780..00000000000000 --- a/docs/design/libraries/DllImportGenerator/Diagnostics.md +++ /dev/null @@ -1,47 +0,0 @@ -# Generator Diagnostics - -For all [Roslyn diagnostics](https://docs.microsoft.com/dotnet/api/microsoft.codeanalysis.diagnostic) reported by the P/Invoke source generator: - -| Setting | Value | -| -------- | ------------------ | -| Category | SourceGeneration | -| Severity | Error | -| Enabled | True | - -The P/Invoke source generator emits the following diagnostics. - -## `DLLIMPORTGEN001`: Specified type is not supported by source-generated P/Invokes - -A method marked `GeneratedDllImport` has a parameter or return type that is not supported by source-generated P/Invokes. - -```C# -// 'object' without any specific marshalling configuration -[GeneratedDllImport("NativeLib")] -public static partial void Method(object o); -``` - -## `DLLIMPORTGEN002`: Specified configuration is not supported by source-generated P/Invokes - -A method marked `GeneratedDllImport` has configuration that is not supported by source-generated P/Invokes. This may be configuration of the method itself or its parameter or return types. - -```C# -// MarshalAs value that does not map to an UnmanagedType -[GeneratedDllImport("NativeLib")] -public static partial void Method([MarshalAs(1)] int i); - -// Unsupported field on MarshalAs (SafeArraySubType, SafeArrayUserDefinedSubType, IidParameterIndex) -[GeneratedDllImport("NativeLib")] -public static partial void Method([MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_BOOL)] bool[] bArr); - -// Unsupported combination of MarshalAs and type being marshalled -[GeneratedDllImport("NativeLib")] -public static partial void Method([MarshalAs(UnmanagedType.LPStr)] bool b); -``` - -## `DLLIMPORTGEN003`: Current target framework is not supported by source-generated P/Invokes - -The `GeneratedDllImport` is being used when targeting a framework that is not supported by source-generated P/Invokes. The generated code is currently only compatible with .NET 5.0 or above. - -# Analyzer Diagnostics - -The P/Invoke source generator library also contains analyzers that emit diagnostics. These diagnostics flag issues around usage (i.e. of P/Invoke and marshalling attributes) rather than the source generation scenario support issues flagged by the generator itself. diff --git a/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/Analyzers/GeneratedDllImportAnalyzer.cs b/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/Analyzers/GeneratedDllImportAnalyzer.cs deleted file mode 100644 index b3d2b3a5cf38a9..00000000000000 --- a/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/Analyzers/GeneratedDllImportAnalyzer.cs +++ /dev/null @@ -1,104 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections.Immutable; -using System.Linq; - -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Diagnostics; - -using static Microsoft.Interop.Analyzers.AnalyzerDiagnostics; - -namespace Microsoft.Interop.Analyzers -{ - [DiagnosticAnalyzer(LanguageNames.CSharp)] - public class GeneratedDllImportAnalyzer : DiagnosticAnalyzer - { - private const string Category = "Usage"; - - public static readonly DiagnosticDescriptor GeneratedDllImportMissingModifiers = - new DiagnosticDescriptor( - Ids.GeneratedDllImportMissingRequiredModifiers, - GetResourceString(nameof(Resources.GeneratedDllImportMissingModifiersTitle)), - GetResourceString(nameof(Resources.GeneratedDllImportMissingModifiersMessage)), - Category, - DiagnosticSeverity.Warning, - isEnabledByDefault: true, - description: GetResourceString(nameof(Resources.GeneratedDllImportMissingModifiersDescription))); - - public static readonly DiagnosticDescriptor GeneratedDllImportContainingTypeMissingModifiers = - new DiagnosticDescriptor( - Ids.GeneratedDllImportContaiingTypeMissingRequiredModifiers, - GetResourceString(nameof(Resources.GeneratedDllImportContainingTypeMissingModifiersTitle)), - GetResourceString(nameof(Resources.GeneratedDllImportContainingTypeMissingModifiersMessage)), - Category, - DiagnosticSeverity.Warning, - isEnabledByDefault: true, - description: GetResourceString(nameof(Resources.GeneratedDllImportContainingTypeMissingModifiersDescription))); - - public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(GeneratedDllImportMissingModifiers, GeneratedDllImportContainingTypeMissingModifiers); - - public override void Initialize(AnalysisContext context) - { - // Don't analyze generated code - context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); - context.EnableConcurrentExecution(); - context.RegisterCompilationStartAction( - compilationContext => - { - INamedTypeSymbol? generatedDllImportAttributeType = compilationContext.Compilation.GetTypeByMetadataName(TypeNames.GeneratedDllImportAttribute); - if (generatedDllImportAttributeType == null) - return; - - compilationContext.RegisterSymbolAction(symbolContext => AnalyzeSymbol(symbolContext, generatedDllImportAttributeType), SymbolKind.Method); - }); - } - - private static void AnalyzeSymbol(SymbolAnalysisContext context, INamedTypeSymbol generatedDllImportAttributeType) - { - var methodSymbol = (IMethodSymbol)context.Symbol; - - // Check if method is marked with GeneratedDllImportAttribute - ImmutableArray attributes = methodSymbol.GetAttributes(); - if (!attributes.Any(attr => SymbolEqualityComparer.Default.Equals(attr.AttributeClass, generatedDllImportAttributeType))) - return; - - if (!methodSymbol.IsStatic) - { - // Must be marked static - context.ReportDiagnostic(methodSymbol.CreateDiagnostic(GeneratedDllImportMissingModifiers, methodSymbol.Name)); - } - else - { - // Make sure declarations are marked partial. Technically, we can just check one - // declaration, since Roslyn would error on inconsistent partial declarations. - foreach (SyntaxReference reference in methodSymbol.DeclaringSyntaxReferences) - { - SyntaxNode syntax = reference.GetSyntax(context.CancellationToken); - if (syntax is MethodDeclarationSyntax methodSyntax && !methodSyntax.Modifiers.Any(SyntaxKind.PartialKeyword)) - { - // Must be marked partial - context.ReportDiagnostic(methodSymbol.CreateDiagnostic(GeneratedDllImportMissingModifiers, methodSymbol.Name)); - break; - } - } - - for (INamedTypeSymbol? typeSymbol = methodSymbol.ContainingType; typeSymbol is not null; typeSymbol = typeSymbol.ContainingType) - { - foreach (SyntaxReference reference in typeSymbol.DeclaringSyntaxReferences) - { - SyntaxNode syntax = reference.GetSyntax(context.CancellationToken); - if (syntax is TypeDeclarationSyntax typeSyntax && !typeSyntax.Modifiers.Any(SyntaxKind.PartialKeyword)) - { - // Must be marked partial - context.ReportDiagnostic(typeSymbol.CreateDiagnostic(GeneratedDllImportContainingTypeMissingModifiers, typeSymbol.Name)); - break; - } - } - } - } - } - } -} diff --git a/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/DllImportGenerator.cs b/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/DllImportGenerator.cs index b36afe3eef75dd..908f32e3d8fff1 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/DllImportGenerator.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/DllImportGenerator.cs @@ -75,23 +75,31 @@ public void Initialize(IncrementalGeneratorInitializationContext context) .CreateSyntaxProvider( static (node, ct) => ShouldVisitNode(node), static (context, ct) => - new + { + MethodDeclarationSyntax syntax = (MethodDeclarationSyntax)context.Node; + if (context.SemanticModel.GetDeclaredSymbol(syntax, ct) is IMethodSymbol methodSymbol + && methodSymbol.GetAttributes().Any(static attribute => attribute.AttributeClass?.ToDisplayString() == TypeNames.GeneratedDllImportAttribute)) { - Syntax = (MethodDeclarationSyntax)context.Node, - Symbol = (IMethodSymbol)context.SemanticModel.GetDeclaredSymbol(context.Node, ct)! - }) + return new { Syntax = syntax, Symbol = methodSymbol }; + } + + return null; + }) .Where( - static modelData => modelData.Symbol.IsStatic && modelData.Symbol.GetAttributes().Any( - static attribute => attribute.AttributeClass?.ToDisplayString() == TypeNames.GeneratedDllImportAttribute) - ); + static modelData => modelData is not null); - var methodsToGenerate = attributedMethods.Where(static data => !data.Symbol.ReturnsByRef && !data.Symbol.ReturnsByRefReadonly); + var methodsWithDiagnostics = attributedMethods.Select(static (data, ct) => + { + Diagnostic? diagnostic = GetDiagnosticIfInvalidMethodForGeneration(data.Syntax, data.Symbol); + return new { Syntax = data.Syntax, Symbol = data.Symbol, Diagnostic = diagnostic }; + }); - var refReturnMethods = attributedMethods.Where(static data => data.Symbol.ReturnsByRef || data.Symbol.ReturnsByRefReadonly); + var methodsToGenerate = methodsWithDiagnostics.Where(static data => data.Diagnostic is null); + var invalidMethodDiagnostics = methodsWithDiagnostics.Where(static data => data.Diagnostic is not null); - context.RegisterSourceOutput(refReturnMethods, static (context, refReturnMethod) => + context.RegisterSourceOutput(invalidMethodDiagnostics, static (context, invalidMethod) => { - context.ReportDiagnostic(Diagnostic.Create(GeneratorDiagnostics.ReturnConfigurationNotSupported, refReturnMethod.Syntax.GetLocation(), "ref return", refReturnMethod.Symbol.ToDisplayString())); + context.ReportDiagnostic(invalidMethod.Diagnostic); }); IncrementalValueProvider<(Compilation compilation, TargetFramework targetFramework, Version targetFrameworkVersion)> compilationAndTargetFramework = context.CompilationProvider @@ -739,8 +747,12 @@ private static bool ShouldVisitNode(SyntaxNode syntaxNode) return false; } - var methodSyntax = (MethodDeclarationSyntax)syntaxNode; + // Filter out methods with no attributes early. + return ((MethodDeclarationSyntax)syntaxNode).AttributeLists.Count > 0; + } + private static Diagnostic? 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 @@ -748,7 +760,7 @@ private static bool ShouldVisitNode(SyntaxNode syntaxNode) || !methodSyntax.Modifiers.Any(SyntaxKind.StaticKeyword) || !methodSyntax.Modifiers.Any(SyntaxKind.PartialKeyword)) { - return false; + return Diagnostic.Create(GeneratorDiagnostics.InvalidAttributedMethodSignature, methodSyntax.Identifier.GetLocation(), method.Name); } // Verify that the types the method is declared in are marked partial. @@ -756,17 +768,17 @@ private static bool ShouldVisitNode(SyntaxNode syntaxNode) { if (!typeDecl.Modifiers.Any(SyntaxKind.PartialKeyword)) { - return false; + return Diagnostic.Create(GeneratorDiagnostics.InvalidAttributedMethodContainingTypeMissingModifiers, methodSyntax.Identifier.GetLocation(), method.Name, typeDecl.Identifier); } } - // Filter out methods with no attributes early. - if (methodSyntax.AttributeLists.Count == 0) + // Verify the method does not have a ref return + if (method.ReturnsByRef || method.ReturnsByRefReadonly) { - return false; + return Diagnostic.Create(GeneratorDiagnostics.ReturnConfigurationNotSupported, methodSyntax.Identifier.GetLocation(), "ref return", method.ToDisplayString()); } - return true; + return null; } } } diff --git a/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/GeneratorDiagnostics.cs b/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/GeneratorDiagnostics.cs index 216d746b1080c0..380b5a3200e284 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/GeneratorDiagnostics.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/GeneratorDiagnostics.cs @@ -28,6 +28,26 @@ public class Ids private const string Category = "SourceGeneration"; + public static readonly DiagnosticDescriptor InvalidAttributedMethodSignature = + new DiagnosticDescriptor( + Ids.InvalidGeneratedDllImportAttributeUsage, + GetResourceString(nameof(Resources.InvalidLibraryImportAttributeUsageTitle)), + GetResourceString(nameof(Resources.InvalidAttributedMethodSignatureMessage)), + Category, + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: GetResourceString(nameof(Resources.InvalidAttributedMethodDescription))); + + public static readonly DiagnosticDescriptor InvalidAttributedMethodContainingTypeMissingModifiers = + new DiagnosticDescriptor( + Ids.InvalidGeneratedDllImportAttributeUsage, + GetResourceString(nameof(Resources.InvalidLibraryImportAttributeUsageTitle)), + GetResourceString(nameof(Resources.InvalidAttributedMethodContainingTypeMissingModifiersMessage)), + Category, + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: GetResourceString(nameof(Resources.InvalidAttributedMethodDescription))); + public static readonly DiagnosticDescriptor InvalidStringMarshallingConfiguration = new DiagnosticDescriptor( Ids.InvalidGeneratedDllImportAttributeUsage, diff --git a/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/Resources.Designer.cs b/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/Resources.Designer.cs index 113162ddddcd62..25b5ba113605e1 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/Resources.Designer.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/Resources.Designer.cs @@ -331,92 +331,65 @@ internal static string CustomTypeMarshallingNativeToManagedUnsupported { } /// - /// Looks up a localized string similar to Types that contain methods marked with 'GeneratedDllImportAttribute' must be 'partial'. P/Invoke source generation will ignore methods contained within non-partial types.. - /// - internal static string GeneratedDllImportContainingTypeMissingModifiersDescription { - get { - return ResourceManager.GetString("GeneratedDllImportContainingTypeMissingModifiersDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Type '{0}' contains methods marked with 'GeneratedDllImportAttribute' and should be 'partial'. P/Invoke source generation will ignore methods contained within non-partial types.. - /// - internal static string GeneratedDllImportContainingTypeMissingModifiersMessage { - get { - return ResourceManager.GetString("GeneratedDllImportContainingTypeMissingModifiersMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Types that contain methods marked with 'GeneratedDllImportAttribute' must be 'partial'.. - /// - internal static string GeneratedDllImportContainingTypeMissingModifiersTitle { - get { - return ResourceManager.GetString("GeneratedDllImportContainingTypeMissingModifiersTitle", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Methods marked with 'GeneratedDllImportAttribute' should be 'static' and 'partial'. P/Invoke source generation will ignore methods that are not 'static' and 'partial'.. + /// Looks up a localized string similar to The return type of 'GetPinnableReference' (after accounting for 'ref') must be blittable.. /// - internal static string GeneratedDllImportMissingModifiersDescription { + internal static string GetPinnableReferenceReturnTypeBlittableDescription { get { - return ResourceManager.GetString("GeneratedDllImportMissingModifiersDescription", resourceCulture); + return ResourceManager.GetString("GetPinnableReferenceReturnTypeBlittableDescription", resourceCulture); } } /// - /// Looks up a localized string similar to Method '{0}' should be 'static' and 'partial' when marked with 'GeneratedDllImportAttribute'. P/Invoke source generation will ignore methods that are not 'static' and 'partial'.. + /// Looks up a localized string similar to The dereferenced type of the return type of the 'GetPinnableReference' method must be blittable. /// - internal static string GeneratedDllImportMissingModifiersMessage { + internal static string GetPinnableReferenceReturnTypeBlittableMessage { get { - return ResourceManager.GetString("GeneratedDllImportMissingModifiersMessage", resourceCulture); + return ResourceManager.GetString("GetPinnableReferenceReturnTypeBlittableMessage", resourceCulture); } } /// - /// Looks up a localized string similar to Method marked with 'GeneratedDllImportAttribute' should be 'static' and 'partial'. + /// Looks up a localized string similar to A type that supports marshalling from managed to native by pinning should also support marshalling from managed to native where pinning is impossible.. /// - internal static string GeneratedDllImportMissingModifiersTitle { + internal static string GetPinnableReferenceShouldSupportAllocatingMarshallingFallbackDescription { get { - return ResourceManager.GetString("GeneratedDllImportMissingModifiersTitle", resourceCulture); + return ResourceManager.GetString("GetPinnableReferenceShouldSupportAllocatingMarshallingFallbackDescription", resourceCulture); } } /// - /// Looks up a localized string similar to The return type of 'GetPinnableReference' (after accounting for 'ref') must be blittable.. + /// Looks up a localized string similar to Type '{0}' has a 'GetPinnableReference' method but its native type does not support marshalling in scenarios where pinning is impossible. /// - internal static string GetPinnableReferenceReturnTypeBlittableDescription { + internal static string GetPinnableReferenceShouldSupportAllocatingMarshallingFallbackMessage { get { - return ResourceManager.GetString("GetPinnableReferenceReturnTypeBlittableDescription", resourceCulture); + return ResourceManager.GetString("GetPinnableReferenceShouldSupportAllocatingMarshallingFallbackMessage", resourceCulture); } } /// - /// Looks up a localized string similar to The dereferenced type of the return type of the 'GetPinnableReference' method must be blittable. + /// Looks up a localized string similar to Method '{0}' is contained in a type '{1}' that is not marked 'partial'. P/Invoke source generation will ignore method '{0}'.. /// - internal static string GetPinnableReferenceReturnTypeBlittableMessage { + internal static string InvalidAttributedMethodContainingTypeMissingModifiersMessage { get { - return ResourceManager.GetString("GetPinnableReferenceReturnTypeBlittableMessage", resourceCulture); + return ResourceManager.GetString("InvalidAttributedMethodContainingTypeMissingModifiersMessage", resourceCulture); } } /// - /// Looks up a localized string similar to A type that supports marshalling from managed to native by pinning should also support marshalling from managed to native where pinning is impossible.. + /// Looks up a localized string similar to Methods marked with 'GeneratedDllImportAttribute' should be 'static', 'partial', and non-generic. P/Invoke source generation will ignore methods that are non-'static', non-'partial', or generic.. /// - internal static string GetPinnableReferenceShouldSupportAllocatingMarshallingFallbackDescription { + internal static string InvalidAttributedMethodDescription { get { - return ResourceManager.GetString("GetPinnableReferenceShouldSupportAllocatingMarshallingFallbackDescription", resourceCulture); + return ResourceManager.GetString("InvalidAttributedMethodDescription", resourceCulture); } } /// - /// Looks up a localized string similar to Type '{0}' has a 'GetPinnableReference' method but its native type does not support marshalling in scenarios where pinning is impossible. + /// Looks up a localized string similar to Method '{0}' should be 'static', 'partial', and non-generic when marked with 'GeneratedDllImportAttribute'. P/Invoke source generation will ignore method '{0}'.. /// - internal static string GetPinnableReferenceShouldSupportAllocatingMarshallingFallbackMessage { + internal static string InvalidAttributedMethodSignatureMessage { get { - return ResourceManager.GetString("GetPinnableReferenceShouldSupportAllocatingMarshallingFallbackMessage", resourceCulture); + return ResourceManager.GetString("InvalidAttributedMethodSignatureMessage", resourceCulture); } } diff --git a/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/Resources.resx b/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/Resources.resx index 2118908f8d7eb7..7dba15aadf9553 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/Resources.resx +++ b/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/Resources.resx @@ -208,24 +208,6 @@ The specified parameter needs to be marshalled from native to managed, but the native type '{0}' does not support it. - - Types that contain methods marked with 'GeneratedDllImportAttribute' must be 'partial'. P/Invoke source generation will ignore methods contained within non-partial types. - - - Type '{0}' contains methods marked with 'GeneratedDllImportAttribute' and should be 'partial'. P/Invoke source generation will ignore methods contained within non-partial types. - - - Types that contain methods marked with 'GeneratedDllImportAttribute' must be 'partial'. - - - Methods marked with 'GeneratedDllImportAttribute' should be 'static' and 'partial'. P/Invoke source generation will ignore methods that are not 'static' and 'partial'. - - - Method '{0}' should be 'static' and 'partial' when marked with 'GeneratedDllImportAttribute'. P/Invoke source generation will ignore methods that are not 'static' and 'partial'. - - - Method marked with 'GeneratedDllImportAttribute' should be 'static' and 'partial' - The return type of 'GetPinnableReference' (after accounting for 'ref') must be blittable. @@ -238,6 +220,15 @@ Type '{0}' has a 'GetPinnableReference' method but its native type does not support marshalling in scenarios where pinning is impossible + + Method '{0}' is contained in a type '{1}' that is not marked 'partial'. P/Invoke source generation will ignore method '{0}'. + + + Methods marked with 'GeneratedDllImportAttribute' should be 'static', 'partial', and non-generic. P/Invoke source generation will ignore methods that are non-'static', non-'partial', or generic. + + + Method '{0}' should be 'static', 'partial', and non-generic when marked with 'GeneratedDllImportAttribute'. P/Invoke source generation will ignore method '{0}'. + Invalid 'LibraryImportAttribute' usage diff --git a/src/libraries/System.Runtime.InteropServices/tests/DllImportGenerator.UnitTests/Diagnostics.cs b/src/libraries/System.Runtime.InteropServices/tests/DllImportGenerator.UnitTests/Diagnostics.cs index 181d57c78f609f..2c343469039137 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/DllImportGenerator.UnitTests/Diagnostics.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/DllImportGenerator.UnitTests/Diagnostics.cs @@ -426,6 +426,164 @@ public Native(string s) {{ }} Assert.Empty(newCompDiags); } + [ConditionalFact] + public async Task NonPartialMethod_ReportsDiagnostic() + { + string source = @" +using System.Runtime.InteropServices; +partial class Test +{ + [GeneratedDllImport(""DoesNotExist"")] + public static void Method() { } + + [GeneratedDllImport(""DoesNotExist"")] + public static extern void ExternMethod(); +} +"; + Compilation comp = await TestUtils.CreateCompilation(source); + TestUtils.AssertPreSourceGeneratorCompilation(comp); + + var newComp = TestUtils.RunGenerators(comp, out var generatorDiags, new Microsoft.Interop.DllImportGenerator()); + DiagnosticResult[] expectedDiags = new DiagnosticResult[] + { + (new DiagnosticResult(GeneratorDiagnostics.InvalidAttributedMethodSignature)) + .WithSpan(6, 24, 6, 30) + .WithArguments("Method"), + (new DiagnosticResult(GeneratorDiagnostics.InvalidAttributedMethodSignature)) + .WithSpan(9, 31, 9, 43) + .WithArguments("ExternMethod"), + }; + VerifyDiagnostics(expectedDiags, GetSortedDiagnostics(generatorDiags)); + var newCompDiags = newComp.GetDiagnostics(); + Assert.Empty(newCompDiags); + } + + [ConditionalFact] + public async Task NonStaticMethod_ReportsDiagnostic() + { + string source = @" +using System.Runtime.InteropServices; +partial class Test +{ + [GeneratedDllImport(""DoesNotExist"")] + public partial void Method(); +} +"; + Compilation comp = await TestUtils.CreateCompilation(source); + TestUtils.AssertPreSourceGeneratorCompilation(comp); + + var newComp = TestUtils.RunGenerators(comp, out var generatorDiags, new Microsoft.Interop.DllImportGenerator()); + DiagnosticResult[] expectedDiags = new DiagnosticResult[] + { + (new DiagnosticResult(GeneratorDiagnostics.InvalidAttributedMethodSignature)) + .WithSpan(6, 25, 6, 31) + .WithArguments("Method") + }; + VerifyDiagnostics(expectedDiags, GetSortedDiagnostics(generatorDiags)); + + // Generator ignores the method + TestUtils.AssertPreSourceGeneratorCompilation(newComp); + } + + [ConditionalFact] + public async Task GenericMethod_ReportsDiagnostic() + { + string source = @" +using System.Runtime.InteropServices; +partial class Test +{ + [GeneratedDllImport(""DoesNotExist"")] + public static partial void Method1(); + + [GeneratedDllImport(""DoesNotExist"")] + public static partial void Method2(); +} +"; + Compilation comp = await TestUtils.CreateCompilation(source); + TestUtils.AssertPreSourceGeneratorCompilation(comp); + + var newComp = TestUtils.RunGenerators(comp, out var generatorDiags, new Microsoft.Interop.DllImportGenerator()); + DiagnosticResult[] expectedDiags = new DiagnosticResult[] + { + (new DiagnosticResult(GeneratorDiagnostics.InvalidAttributedMethodSignature)) + .WithSpan(6, 32, 6, 39) + .WithArguments("Method1"), + (new DiagnosticResult(GeneratorDiagnostics.InvalidAttributedMethodSignature)) + .WithSpan(9, 32, 9, 39) + .WithArguments("Method2"), + }; + VerifyDiagnostics(expectedDiags, GetSortedDiagnostics(generatorDiags)); + + // Generator ignores the method + TestUtils.AssertPreSourceGeneratorCompilation(newComp); + } + + [ConditionalTheory] + [InlineData("class")] + [InlineData("struct")] + [InlineData("record")] + public async Task NonPartialParentType_Diagnostic(string typeKind) + { + string source = $@" +using System.Runtime.InteropServices; +{typeKind} Test +{{ + [GeneratedDllImport(""DoesNotExist"")] + public static partial void Method(); +}} +"; + Compilation comp = await TestUtils.CreateCompilation(source); + + // Also expect CS0751: A partial method must be declared within a partial type + string additionalDiag = "CS0751"; + TestUtils.AssertPreSourceGeneratorCompilation(comp, additionalDiag); + + var newComp = TestUtils.RunGenerators(comp, out var generatorDiags, new Microsoft.Interop.DllImportGenerator()); + DiagnosticResult[] expectedDiags = new DiagnosticResult[] + { + (new DiagnosticResult(GeneratorDiagnostics.InvalidAttributedMethodContainingTypeMissingModifiers)) + .WithSpan(6, 32, 6, 38) + .WithArguments("Method", "Test"), + }; + VerifyDiagnostics(expectedDiags, GetSortedDiagnostics(generatorDiags)); + + // Generator ignores the method + TestUtils.AssertPreSourceGeneratorCompilation(newComp, additionalDiag); + } + + [ConditionalTheory] + [InlineData("class")] + [InlineData("struct")] + [InlineData("record")] + public async Task NonPartialGrandparentType_Diagnostic(string typeKind) + { + string source = $@" +using System.Runtime.InteropServices; +{typeKind} Test +{{ + partial class TestInner + {{ + [GeneratedDllImport(""DoesNotExist"")] + static partial void Method(); + }} +}} +"; + Compilation comp = await TestUtils.CreateCompilation(source); + TestUtils.AssertPreSourceGeneratorCompilation(comp); + + var newComp = TestUtils.RunGenerators(comp, out var generatorDiags, new Microsoft.Interop.DllImportGenerator()); + DiagnosticResult[] expectedDiags = new DiagnosticResult[] + { + (new DiagnosticResult(GeneratorDiagnostics.InvalidAttributedMethodContainingTypeMissingModifiers)) + .WithSpan(8, 29, 8, 35) + .WithArguments("Method", "Test"), + }; + VerifyDiagnostics(expectedDiags, GetSortedDiagnostics(generatorDiags)); + + // Generator ignores the method + TestUtils.AssertPreSourceGeneratorCompilation(newComp); + } + private static void VerifyDiagnostics(DiagnosticResult[] expectedDiagnostics, Diagnostic[] actualDiagnostics) { Assert.True(expectedDiagnostics.Length == actualDiagnostics.Length, diff --git a/src/libraries/System.Runtime.InteropServices/tests/DllImportGenerator.UnitTests/GeneratedDllImportAnalyzerTests.cs b/src/libraries/System.Runtime.InteropServices/tests/DllImportGenerator.UnitTests/GeneratedDllImportAnalyzerTests.cs deleted file mode 100644 index 0d54d8ce058ecd..00000000000000 --- a/src/libraries/System.Runtime.InteropServices/tests/DllImportGenerator.UnitTests/GeneratedDllImportAnalyzerTests.cs +++ /dev/null @@ -1,197 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Threading.Tasks; -using Xunit; -using static Microsoft.Interop.Analyzers.GeneratedDllImportAnalyzer; - -using VerifyCS = DllImportGenerator.UnitTests.Verifiers.CSharpAnalyzerVerifier; - -namespace DllImportGenerator.UnitTests -{ - [ActiveIssue("https://github.com/dotnet/runtime/issues/60650", TestRuntimes.Mono)] - public class GeneratedDllImportAnalyzerTests - { - [ConditionalFact] - public async Task NonPartialMethod_ReportsDiagnostic() - { - string source = @" -using System.Runtime.InteropServices; -partial class Test -{ - [GeneratedDllImport(""DoesNotExist"")] - public static void {|#0:Method1|}() { } - - [GeneratedDllImport(""DoesNotExist"")] - static void {|#1:Method2|}() { } - - [GeneratedDllImport(""DoesNotExist"")] - public static extern void {|#2:ExternMethod1|}(); - - [GeneratedDllImport(""DoesNotExist"")] - static extern void {|#3:ExternMethod2|}(); -} -"; - await VerifyCS.VerifyAnalyzerAsync( - source, - VerifyCS.Diagnostic(GeneratedDllImportMissingModifiers) - .WithLocation(0) - .WithArguments("Method1"), - VerifyCS.Diagnostic(GeneratedDllImportMissingModifiers) - .WithLocation(1) - .WithArguments("Method2"), - VerifyCS.Diagnostic(GeneratedDllImportMissingModifiers) - .WithLocation(2) - .WithArguments("ExternMethod1"), - VerifyCS.Diagnostic(GeneratedDllImportMissingModifiers) - .WithLocation(3) - .WithArguments("ExternMethod2")); - } - - [ConditionalFact] - public async Task NonStaticMethod_ReportsDiagnostic() - { - string source = @" -using System.Runtime.InteropServices; -partial class Test -{ - [GeneratedDllImport(""DoesNotExist"")] - public partial void {|#0:Method1|}(); - - [GeneratedDllImport(""DoesNotExist"")] - partial void {|#1:Method2|}(); -} - -partial class Test -{ - public partial void {|#3:Method1|}() { } -} -"; - await VerifyCS.VerifyAnalyzerAsync( - source, - VerifyCS.Diagnostic(GeneratedDllImportMissingModifiers) - .WithLocation(0) - .WithArguments("Method1"), - VerifyCS.Diagnostic(GeneratedDllImportMissingModifiers) - .WithLocation(1) - .WithArguments("Method2"), - VerifyCS.Diagnostic(GeneratedDllImportMissingModifiers) - .WithLocation(3) - .WithArguments("Method1")); - } - - [ConditionalFact] - public async Task NonPartialNonStaticMethod_ReportsDiagnostic() - { - string source = @" -using System.Runtime.InteropServices; -partial class Test -{ - [GeneratedDllImport(""DoesNotExist"")] - public void {|#0:Method1|}() { } - - [GeneratedDllImport(""DoesNotExist"")] - void {|#1:Method2|}() { } - - [GeneratedDllImport(""DoesNotExist"")] - public extern void {|#2:ExternMethod1|}(); - - [GeneratedDllImport(""DoesNotExist"")] - extern void {|#3:ExternMethod2|}(); -} -"; - await VerifyCS.VerifyAnalyzerAsync( - source, - VerifyCS.Diagnostic(GeneratedDllImportMissingModifiers) - .WithLocation(0) - .WithArguments("Method1"), - VerifyCS.Diagnostic(GeneratedDllImportMissingModifiers) - .WithLocation(1) - .WithArguments("Method2"), - VerifyCS.Diagnostic(GeneratedDllImportMissingModifiers) - .WithLocation(2) - .WithArguments("ExternMethod1"), - VerifyCS.Diagnostic(GeneratedDllImportMissingModifiers) - .WithLocation(3) - .WithArguments("ExternMethod2")); - } - - [ConditionalFact] - public async Task NotGeneratedDllImport_NoDiagnostic() - { - string source = @" -using System.Runtime.InteropServices; -partial class Test -{ - public void Method1() { } - partial void Method2(); -} -"; - await VerifyCS.VerifyAnalyzerAsync(source); - } - - [ConditionalFact] - public async Task StaticPartialMethod_NoDiagnostic() - { - string source = @" -using System.Runtime.InteropServices; -partial class Test -{ - [GeneratedDllImport(""DoesNotExist"")] - static partial void Method2(); -} -"; - await VerifyCS.VerifyAnalyzerAsync(source); - } - - [ConditionalTheory] - [InlineData("class")] - [InlineData("struct")] - [InlineData("record")] - - public async Task NonPartialParentType_Diagnostic(string typeKind) - { - string source = $@" -using System.Runtime.InteropServices; -{typeKind} {{|#0:Test|}} -{{ - [GeneratedDllImport(""DoesNotExist"")] - static partial void {{|CS0751:Method2|}}(); -}} -"; - - await VerifyCS.VerifyAnalyzerAsync( - source, - VerifyCS.Diagnostic(GeneratedDllImportContainingTypeMissingModifiers) - .WithLocation(0) - .WithArguments("Test")); - } - - [ConditionalTheory] - [InlineData("class")] - [InlineData("struct")] - [InlineData("record")] - - public async Task NonPartialGrandparentType_Diagnostic(string typeKind) - { - - string source = $@" -using System.Runtime.InteropServices; -{typeKind} {{|#0:Test|}} -{{ - partial class TestInner - {{ - [GeneratedDllImport(""DoesNotExist"")] - static partial void Method2(); - }} -}} -"; - - await VerifyCS.VerifyAnalyzerAsync( - source, - VerifyCS.Diagnostic(GeneratedDllImportContainingTypeMissingModifiers) - .WithLocation(0) - .WithArguments("Test")); - } - } -} diff --git a/src/libraries/System.Runtime.InteropServices/tests/DllImportGenerator.UnitTests/TestUtils.cs b/src/libraries/System.Runtime.InteropServices/tests/DllImportGenerator.UnitTests/TestUtils.cs index 154af90a4c1405..edff3a4a995400 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/DllImportGenerator.UnitTests/TestUtils.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/DllImportGenerator.UnitTests/TestUtils.cs @@ -70,7 +70,7 @@ public static class TestUtils /// the expected failure diagnostics. /// /// - public static void AssertPreSourceGeneratorCompilation(Compilation comp) + public static void AssertPreSourceGeneratorCompilation(Compilation comp, params string[] additionalAllowedDiagnostics) { var allowedDiagnostics = new HashSet() { @@ -79,6 +79,12 @@ public static void AssertPreSourceGeneratorCompilation(Compilation comp) "CS0246", // Missing type or namespace - GeneratedDllImportAttribute "CS8019", // Unnecessary using }; + + foreach (string diagnostic in additionalAllowedDiagnostics) + { + allowedDiagnostics.Add(diagnostic); + } + var compDiags = comp.GetDiagnostics(); Assert.All(compDiags, diag => {