diff --git a/DllImportGenerator/Demo/Demo.csproj b/DllImportGenerator/Demo/Demo.csproj index 4cee92efcd88..0ca8c597da9a 100644 --- a/DllImportGenerator/Demo/Demo.csproj +++ b/DllImportGenerator/Demo/Demo.csproj @@ -6,6 +6,10 @@ true preview + + true + true diff --git a/DllImportGenerator/DllImportGenerator.UnitTests/Compiles.cs b/DllImportGenerator/DllImportGenerator.UnitTests/Compiles.cs index 2275072099fa..815db781816b 100644 --- a/DllImportGenerator/DllImportGenerator.UnitTests/Compiles.cs +++ b/DllImportGenerator/DllImportGenerator.UnitTests/Compiles.cs @@ -9,7 +9,7 @@ namespace DllImportGenerator.UnitTests { public class Compiles { - public static IEnumerable CodeSnippetsToCompile() + public static IEnumerable CodeSnippetsToCompile_NoDiagnostics() { yield return new[] { CodeSnippets.TrivialClassDeclarations }; yield return new[] { CodeSnippets.TrivialStructDeclarations }; @@ -20,7 +20,7 @@ public static IEnumerable CodeSnippetsToCompile() yield return new[] { CodeSnippets.AllDllImportNamedArguments }; yield return new[] { CodeSnippets.DefaultParameters }; yield return new[] { CodeSnippets.UseCSharpFeaturesForConstants }; - yield return new[] { CodeSnippets.MarshalAsAttributeOnTypes }; + //yield return new[] { CodeSnippets.MarshalAsAttributeOnTypes }; yield return new[] { CodeSnippets.BasicParametersAndModifiers() }; yield return new[] { CodeSnippets.BasicParametersAndModifiers() }; yield return new[] { CodeSnippets.BasicParametersAndModifiers() }; @@ -32,10 +32,74 @@ public static IEnumerable CodeSnippetsToCompile() yield return new[] { CodeSnippets.BasicParametersAndModifiers() }; yield return new[] { CodeSnippets.BasicParametersAndModifiers() }; yield return new[] { CodeSnippets.BasicParametersAndModifiers() }; - yield return new[] { CodeSnippets.BasicParametersAndModifiers() }; - yield return new[] { CodeSnippets.BasicParametersAndModifiers() }; + //yield return new[] { CodeSnippets.BasicParametersAndModifiers() }; + //yield return new[] { CodeSnippets.BasicParametersAndModifiers() }; yield return new[] { CodeSnippets.BasicParametersAndModifiers() }; yield return new[] { CodeSnippets.BasicParametersAndModifiers() }; + //yield return new[] { CodeSnippets.BasicParametersAndModifiers() }; + //yield return new[] { CodeSnippets.BasicParametersAndModifiers() }; + //yield return new[] { CodeSnippets.BasicParametersAndModifiers() }; + //yield return new[] { CodeSnippets.BasicParametersAndModifiers() }; + //yield return new[] { CodeSnippets.BasicParametersAndModifiers() }; + //yield return new[] { CodeSnippets.BasicParametersAndModifiers() }; + //yield return new[] { CodeSnippets.BasicParametersAndModifiers() }; + //yield return new[] { CodeSnippets.BasicParametersAndModifiers() }; + //yield return new[] { CodeSnippets.BasicParametersAndModifiers() }; + //yield return new[] { CodeSnippets.BasicParametersAndModifiers() }; + //yield return new[] { CodeSnippets.BasicParametersAndModifiers() }; + //yield return new[] { CodeSnippets.BasicParametersAndModifiers() }; + //yield return new[] { CodeSnippets.BasicParametersAndModifiers() }; + //yield return new[] { CodeSnippets.BasicParametersAndModifiers() }; + //yield return new[] { CodeSnippets.BasicParametersAndModifiers() }; + yield return new[] { CodeSnippets.MarshalAsParametersAndModifiers(UnmanagedType.Bool) }; + yield return new[] { CodeSnippets.MarshalAsParametersAndModifiers(UnmanagedType.VariantBool) }; + yield return new[] { CodeSnippets.MarshalAsParametersAndModifiers(UnmanagedType.I1) }; + //yield return new[] { CodeSnippets.EnumParameters }; + yield return new[] { CodeSnippets.PreserveSigFalseVoidReturn }; + yield return new[] { CodeSnippets.PreserveSigFalse() }; + yield return new[] { CodeSnippets.PreserveSigFalse() }; + yield return new[] { CodeSnippets.PreserveSigFalse() }; + yield return new[] { CodeSnippets.PreserveSigFalse() }; + yield return new[] { CodeSnippets.PreserveSigFalse() }; + yield return new[] { CodeSnippets.PreserveSigFalse() }; + yield return new[] { CodeSnippets.PreserveSigFalse() }; + yield return new[] { CodeSnippets.PreserveSigFalse() }; + yield return new[] { CodeSnippets.PreserveSigFalse() }; + yield return new[] { CodeSnippets.PreserveSigFalse() }; + yield return new[] { CodeSnippets.PreserveSigFalse() }; + //yield return new[] { CodeSnippets.PreserveSigFalse() }; + //yield return new[] { CodeSnippets.PreserveSigFalse() }; + yield return new[] { CodeSnippets.PreserveSigFalse() }; + yield return new[] { CodeSnippets.PreserveSigFalse() }; + //yield return new[] { CodeSnippets.PreserveSigFalse() }; + //yield return new[] { CodeSnippets.PreserveSigFalse() }; + //yield return new[] { CodeSnippets.PreserveSigFalse() }; + //yield return new[] { CodeSnippets.PreserveSigFalse() }; + //yield return new[] { CodeSnippets.PreserveSigFalse() }; + //yield return new[] { CodeSnippets.PreserveSigFalse() }; + //yield return new[] { CodeSnippets.PreserveSigFalse() }; + //yield return new[] { CodeSnippets.PreserveSigFalse() }; + //yield return new[] { CodeSnippets.PreserveSigFalse() }; + //yield return new[] { CodeSnippets.PreserveSigFalse() }; + //yield return new[] { CodeSnippets.PreserveSigFalse() }; + //yield return new[] { CodeSnippets.PreserveSigFalse() }; + //yield return new[] { CodeSnippets.PreserveSigFalse() }; + //yield return new[] { CodeSnippets.PreserveSigFalse() }; + //yield return new[] { CodeSnippets.PreserveSigFalse() }; + yield return new[] { CodeSnippets.DelegateParametersAndModifiers }; + yield return new[] { CodeSnippets.DelegateMarshalAsParametersAndModifiers }; + yield return new[] { CodeSnippets.BlittableStructParametersAndModifiers }; + yield return new[] { CodeSnippets.GenericBlittableStructParametersAndModifiers }; + yield return new[] { CodeSnippets.BasicParametersAndModifiers("Microsoft.Win32.SafeHandles.SafeFileHandle") }; + } + + public static IEnumerable CodeSnippetsToCompile_WithDiagnostics() + { + yield return new[] { CodeSnippets.MarshalAsAttributeOnTypes }; + + yield return new[] { CodeSnippets.BasicParametersAndModifiers() }; + yield return new[] { CodeSnippets.BasicParametersAndModifiers() }; + yield return new[] { CodeSnippets.BasicParametersAndModifiers() }; yield return new[] { CodeSnippets.BasicParametersAndModifiers() }; yield return new[] { CodeSnippets.BasicParametersAndModifiers() }; @@ -51,26 +115,12 @@ public static IEnumerable CodeSnippetsToCompile() yield return new[] { CodeSnippets.BasicParametersAndModifiers() }; yield return new[] { CodeSnippets.BasicParametersAndModifiers() }; yield return new[] { CodeSnippets.BasicParametersAndModifiers() }; - yield return new[] { CodeSnippets.MarshalAsParametersAndModifiers(UnmanagedType.Bool) }; - yield return new[] { CodeSnippets.MarshalAsParametersAndModifiers(UnmanagedType.VariantBool) }; - yield return new[] { CodeSnippets.MarshalAsParametersAndModifiers(UnmanagedType.I1) }; + yield return new[] { CodeSnippets.EnumParameters }; - yield return new[] { CodeSnippets.PreserveSigFalseVoidReturn }; - yield return new[] { CodeSnippets.PreserveSigFalse() }; - yield return new[] { CodeSnippets.PreserveSigFalse() }; - yield return new[] { CodeSnippets.PreserveSigFalse() }; - yield return new[] { CodeSnippets.PreserveSigFalse() }; - yield return new[] { CodeSnippets.PreserveSigFalse() }; - yield return new[] { CodeSnippets.PreserveSigFalse() }; - yield return new[] { CodeSnippets.PreserveSigFalse() }; - yield return new[] { CodeSnippets.PreserveSigFalse() }; - yield return new[] { CodeSnippets.PreserveSigFalse() }; - yield return new[] { CodeSnippets.PreserveSigFalse() }; - yield return new[] { CodeSnippets.PreserveSigFalse() }; + yield return new[] { CodeSnippets.PreserveSigFalse() }; yield return new[] { CodeSnippets.PreserveSigFalse() }; - yield return new[] { CodeSnippets.PreserveSigFalse() }; - yield return new[] { CodeSnippets.PreserveSigFalse() }; + yield return new[] { CodeSnippets.PreserveSigFalse() }; yield return new[] { CodeSnippets.PreserveSigFalse() }; yield return new[] { CodeSnippets.PreserveSigFalse() }; @@ -86,16 +136,11 @@ public static IEnumerable CodeSnippetsToCompile() yield return new[] { CodeSnippets.PreserveSigFalse() }; yield return new[] { CodeSnippets.PreserveSigFalse() }; yield return new[] { CodeSnippets.PreserveSigFalse() }; - yield return new[] { CodeSnippets.DelegateParametersAndModifiers }; - yield return new[] { CodeSnippets.DelegateMarshalAsParametersAndModifiers }; - yield return new[] { CodeSnippets.BlittableStructParametersAndModifiers }; - yield return new[] { CodeSnippets.GenericBlittableStructParametersAndModifiers }; - yield return new[] { CodeSnippets.BasicParametersAndModifiers("Microsoft.Win32.SafeHandles.SafeFileHandle") }; } [Theory] - [MemberData(nameof(CodeSnippetsToCompile))] - public async Task ValidateSnippets(string source) + [MemberData(nameof(CodeSnippetsToCompile_NoDiagnostics))] + public async Task ValidateSnippets_NoDiagnostics(string source) { Compilation comp = await TestUtils.CreateCompilation(source); TestUtils.AssertPreSourceGeneratorCompilation(comp); @@ -106,5 +151,20 @@ public async Task ValidateSnippets(string source) var newCompDiags = newComp.GetDiagnostics(); Assert.Empty(newCompDiags); } + + [Theory] + [MemberData(nameof(CodeSnippetsToCompile_WithDiagnostics))] + public async Task ValidateSnippets_WithDiagnostics(string source) + { + Compilation comp = await TestUtils.CreateCompilation(source); + TestUtils.AssertPreSourceGeneratorCompilation(comp); + + var newComp = TestUtils.RunGenerators(comp, out var generatorDiags, new Microsoft.Interop.DllImportGenerator()); + Assert.NotEmpty(generatorDiags); + Assert.All(generatorDiags, d => Assert.StartsWith(Microsoft.Interop.GeneratorDiagnostics.Ids.Prefix, d.Id)); + + var newCompDiags = newComp.GetDiagnostics(); + Assert.Empty(newCompDiags); + } } } diff --git a/DllImportGenerator/DllImportGenerator.UnitTests/Diagnostics.cs b/DllImportGenerator/DllImportGenerator.UnitTests/Diagnostics.cs new file mode 100644 index 000000000000..0ba00530c7c1 --- /dev/null +++ b/DllImportGenerator/DllImportGenerator.UnitTests/Diagnostics.cs @@ -0,0 +1,264 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Threading.Tasks; + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; +using Microsoft.CodeAnalysis.Text; +using Microsoft.Interop; +using Xunit; + +namespace DllImportGenerator.UnitTests +{ + public class Diagnostics + { + [Fact] + public async Task ParameterTypeNotSupported_ReportsDiagnostic() + { + string source = @" +using System.Collections.Generic; +using System.Runtime.InteropServices; +namespace NS +{ + class MyClass { } +} +partial class Test +{ + [GeneratedDllImport(""DoesNotExist"")] + public static partial void Method1(NS.MyClass c); + + [GeneratedDllImport(""DoesNotExist"")] + public static partial void Method2(int i, List list); +} +"; + 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.ParameterTypeNotSupported)) + .WithSpan(11, 51, 11, 52) + .WithArguments("NS.MyClass", "c"), + (new DiagnosticResult(GeneratorDiagnostics.ParameterTypeNotSupported)) + .WithSpan(14, 57, 14, 61) + .WithArguments("System.Collections.Generic.List", "list"), + }; + VerifyDiagnostics(expectedDiags, GetSortedDiagnostics(generatorDiags)); + + var newCompDiags = newComp.GetDiagnostics(); + Assert.Empty(newCompDiags); + } + + [Fact] + public async Task ReturnTypeNotSupported_ReportsDiagnostic() + { + string source = @" +using System.Collections.Generic; +using System.Runtime.InteropServices; +namespace NS +{ + class MyClass { } +} +partial class Test +{ + [GeneratedDllImport(""DoesNotExist"")] + public static partial NS.MyClass Method1(); + + [GeneratedDllImport(""DoesNotExist"")] + public static partial List 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.ReturnTypeNotSupported)) + .WithSpan(11, 38, 11, 45) + .WithArguments("NS.MyClass", "Method1"), + (new DiagnosticResult(GeneratorDiagnostics.ReturnTypeNotSupported)) + .WithSpan(14, 37, 14, 44) + .WithArguments("System.Collections.Generic.List", "Method2"), + }; + VerifyDiagnostics(expectedDiags, GetSortedDiagnostics(generatorDiags)); + + var newCompDiags = newComp.GetDiagnostics(); + Assert.Empty(newCompDiags); + } + + [Fact] + public async Task ParameterConfigurationNotSupported_ReportsDiagnostic() + { + string source = @" +using System.Runtime.InteropServices; +partial class Test +{ + [GeneratedDllImport(""DoesNotExist"")] + public static partial void Method1([MarshalAs(UnmanagedType.BStr)] int i1, int i2); + + [GeneratedDllImport(""DoesNotExist"")] + public static partial void Method2(bool b1, [MarshalAs(UnmanagedType.FunctionPtr)] bool b2); +} +"; + 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.ParameterConfigurationNotSupported)) + .WithSpan(6, 76, 6, 78) + .WithArguments(nameof(MarshalAsAttribute), "i1"), + (new DiagnosticResult(GeneratorDiagnostics.ParameterConfigurationNotSupported)) + .WithSpan(9, 93, 9, 95) + .WithArguments(nameof(MarshalAsAttribute), "b2"), + }; + VerifyDiagnostics(expectedDiags, GetSortedDiagnostics(generatorDiags)); + + var newCompDiags = newComp.GetDiagnostics(); + Assert.Empty(newCompDiags); + } + + [Fact] + public async Task ReturnConfigurationNotSupported_ReportsDiagnostic() + { + string source = @" +using System.Runtime.InteropServices; +partial class Test +{ + [GeneratedDllImport(""DoesNotExist"")] + [return: MarshalAs(UnmanagedType.BStr)] + public static partial int Method1(int i); + + [GeneratedDllImport(""DoesNotExist"")] + [return: MarshalAs(UnmanagedType.FunctionPtr)] + public static partial bool Method2(bool b); +} +"; + 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.ReturnConfigurationNotSupported)) + .WithSpan(7, 31, 7, 38) + .WithArguments(nameof(MarshalAsAttribute), "Method1"), + (new DiagnosticResult(GeneratorDiagnostics.ReturnConfigurationNotSupported)) + .WithSpan(11, 32, 11, 39) + .WithArguments(nameof(MarshalAsAttribute), "Method2"), + }; + VerifyDiagnostics(expectedDiags, GetSortedDiagnostics(generatorDiags)); + + var newCompDiags = newComp.GetDiagnostics(); + Assert.Empty(newCompDiags); + } + + [Fact] + public async Task MarshalAsUnmanagedTypeNotSupported_ReportsDiagnostic() + { + string source = @" +using System.Runtime.InteropServices; +partial class Test +{ + [GeneratedDllImport(""DoesNotExist"")] + [return: MarshalAs(1)] + public static partial int Method1(int i); + + [GeneratedDllImport(""DoesNotExist"")] + public static partial bool Method2([MarshalAs((short)0)] bool b); +} +"; + 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.ConfigurationValueNotSupported)) + .WithSpan(6, 14, 6, 26) + .WithArguments(1, nameof(UnmanagedType)), + (new DiagnosticResult(GeneratorDiagnostics.ReturnConfigurationNotSupported)) + .WithSpan(7, 31, 7, 38) + .WithArguments(nameof(MarshalAsAttribute), "Method1"), + (new DiagnosticResult(GeneratorDiagnostics.ConfigurationValueNotSupported)) + .WithSpan(10, 41, 10, 60) + .WithArguments(0, nameof(UnmanagedType)), + (new DiagnosticResult(GeneratorDiagnostics.ParameterConfigurationNotSupported)) + .WithSpan(10, 67, 10, 68) + .WithArguments(nameof(MarshalAsAttribute), "b"), + }; + VerifyDiagnostics(expectedDiags, GetSortedDiagnostics(generatorDiags)); + + var newCompDiags = newComp.GetDiagnostics(); + Assert.Empty(newCompDiags); + } + + [Fact] + public async Task MarshalAsFieldNotSupported_ReportsDiagnostic() + { + string source = @" +using System.Runtime.InteropServices; +partial class Test +{ + [GeneratedDllImport(""DoesNotExist"")] + [return: MarshalAs(UnmanagedType.I4, SafeArraySubType=VarEnum.VT_I4)] + public static partial int Method1(int i); + + [GeneratedDllImport(""DoesNotExist"")] + public static partial bool Method2([MarshalAs(UnmanagedType.I1, IidParameterIndex = 1)] bool b); +} +"; + 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.ConfigurationNotSupported)) + .WithSpan(6, 14, 6, 73) + .WithArguments($"{nameof(MarshalAsAttribute)}{Type.Delimiter}{nameof(MarshalAsAttribute.SafeArraySubType)}"), + (new DiagnosticResult(GeneratorDiagnostics.ConfigurationNotSupported)) + .WithSpan(10, 41, 10, 91) + .WithArguments($"{nameof(MarshalAsAttribute)}{Type.Delimiter}{nameof(MarshalAsAttribute.IidParameterIndex)}"), + }; + VerifyDiagnostics(expectedDiags, GetSortedDiagnostics(generatorDiags)); + var newCompDiags = newComp.GetDiagnostics(); + Assert.Empty(newCompDiags); + } + + private static void VerifyDiagnostics(DiagnosticResult[] expectedDiagnostics, Diagnostic[] actualDiagnostics) + { + Assert.Equal(expectedDiagnostics.Length, actualDiagnostics.Length); + for (var i = 0; i < expectedDiagnostics.Length; i++) + { + DiagnosticResult expected = expectedDiagnostics[i]; + Diagnostic actual = actualDiagnostics[i]; + + Assert.Equal(expected.Id, actual.Id); + Assert.Equal(expected.Message, actual.GetMessage()); + Assert.Equal(expected.Severity, actual.Severity); + if (expected.HasLocation) + { + FileLinePositionSpan expectedSpan = expected.Spans[0].Span; + FileLinePositionSpan actualSpan = actual.Location.GetLineSpan(); + Assert.Equal(expectedSpan, actualSpan); + } + } + } + + private static Diagnostic[] GetSortedDiagnostics(IEnumerable diagnostics) + { + return diagnostics + .OrderBy(d => d.Location.GetLineSpan().Path, StringComparer.Ordinal) + .ThenBy(d => d.Location.SourceSpan.Start) + .ThenBy(d => d.Location.SourceSpan.End) + .ThenBy(d => d.Id) + .ToArray(); + } + } +} diff --git a/DllImportGenerator/DllImportGenerator/DllImportGenerator.cs b/DllImportGenerator/DllImportGenerator/DllImportGenerator.cs index 4d9ce3989205..7d7e42eacb0c 100644 --- a/DllImportGenerator/DllImportGenerator/DllImportGenerator.cs +++ b/DllImportGenerator/DllImportGenerator/DllImportGenerator.cs @@ -34,6 +34,8 @@ public void Execute(GeneratorExecutionContext context) // this caching. var syntaxToModel = new Dictionary(); + var generatorDiagnostics = new GeneratorDiagnostics(context); + var generatedDllImports = new StringBuilder(); foreach (SyntaxReference synRef in synRec.Methods) { @@ -55,13 +57,7 @@ public void Execute(GeneratorExecutionContext context) Debug.Assert(!(dllImportAttr is null) && !(dllImportData is null)); // Create the stub. - var dllImportStub = DllImportStub.Create(methodSymbolInfo, dllImportData, context.Compilation, context.CancellationToken); - - // Report any diagnostics from the stub generation step. - foreach (var diag in dllImportStub.Diagnostics) - { - context.ReportDiagnostic(diag); - } + var dllImportStub = DllImportStub.Create(methodSymbolInfo, dllImportData, context.Compilation, generatorDiagnostics, context.CancellationToken); PrintGeneratedSource(generatedDllImports, methodSyntax, dllImportStub, dllImportAttr); } diff --git a/DllImportGenerator/DllImportGenerator/DllImportGenerator.csproj b/DllImportGenerator/DllImportGenerator/DllImportGenerator.csproj index 5e60199190e1..20b90b8c0045 100644 --- a/DllImportGenerator/DllImportGenerator/DllImportGenerator.csproj +++ b/DllImportGenerator/DllImportGenerator/DllImportGenerator.csproj @@ -7,6 +7,7 @@ True true Preview + Microsoft.Interop diff --git a/DllImportGenerator/DllImportGenerator/DllImportStub.cs b/DllImportGenerator/DllImportGenerator/DllImportStub.cs index 3218db8e00e1..f062532c7409 100644 --- a/DllImportGenerator/DllImportGenerator/DllImportStub.cs +++ b/DllImportGenerator/DllImportGenerator/DllImportStub.cs @@ -49,8 +49,6 @@ public IEnumerable StubParameters public MethodDeclarationSyntax DllImportDeclaration { get; private set; } - public IEnumerable Diagnostics { get; private set; } - /// /// Flags used to indicate members on GeneratedDllImport attribute. /// @@ -101,6 +99,7 @@ public static DllImportStub Create( IMethodSymbol method, GeneratedDllImportData dllImportData, Compilation compilation, + GeneratorDiagnostics diagnostics, CancellationToken token = default) { // Cancel early if requested @@ -137,13 +136,13 @@ public static DllImportStub Create( for (int i = 0; i < method.Parameters.Length; i++) { var param = method.Parameters[i]; - var typeInfo = TypePositionInfo.CreateForParameter(param, compilation); + var typeInfo = TypePositionInfo.CreateForParameter(param, compilation, diagnostics); typeInfo.ManagedIndex = i; typeInfo.NativeIndex = paramsTypeInfo.Count; paramsTypeInfo.Add(typeInfo); } - TypePositionInfo retTypeInfo = TypePositionInfo.CreateForType(method.ReturnType, method.GetReturnTypeAttributes(), compilation); + TypePositionInfo retTypeInfo = TypePositionInfo.CreateForType(method.ReturnType, method.GetReturnTypeAttributes(), compilation, diagnostics); retTypeInfo.ManagedIndex = TypePositionInfo.ReturnIndex; retTypeInfo.NativeIndex = TypePositionInfo.ReturnIndex; if (!dllImportData.PreserveSig) @@ -162,7 +161,8 @@ public static DllImportStub Create( } // Generate stub code - var (code, dllImport) = StubCodeGenerator.GenerateSyntax(method, paramsTypeInfo, retTypeInfo); + var stubGenerator = new StubCodeGenerator(method, paramsTypeInfo, retTypeInfo, diagnostics); + var (code, dllImport) = stubGenerator.GenerateSyntax(); return new DllImportStub() { @@ -172,7 +172,6 @@ public static DllImportStub Create( StubContainingTypes = containingTypes, StubCode = code, DllImportDeclaration = dllImport, - Diagnostics = Enumerable.Empty(), }; } } diff --git a/DllImportGenerator/DllImportGenerator/GeneratorDiagnostics.cs b/DllImportGenerator/DllImportGenerator/GeneratorDiagnostics.cs new file mode 100644 index 000000000000..17657fe4eaf5 --- /dev/null +++ b/DllImportGenerator/DllImportGenerator/GeneratorDiagnostics.cs @@ -0,0 +1,217 @@ +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; + +using Microsoft.CodeAnalysis; + +#nullable enable + +namespace Microsoft.Interop +{ + internal static class DiagnosticExtensions + { + public static Diagnostic CreateDiagnostic( + this ISymbol symbol, + DiagnosticDescriptor descriptor, + params object[] args) + { + IEnumerable locationsInSource = symbol.Locations.Where(l => l.IsInSource); + if (!locationsInSource.Any()) + return Diagnostic.Create(descriptor, Location.None, args); + + return Diagnostic.Create( + descriptor, + location: locationsInSource.First(), + additionalLocations: locationsInSource.Skip(1), + messageArgs: args); + } + + public static Diagnostic CreateDiagnostic( + this AttributeData attributeData, + DiagnosticDescriptor descriptor, + params object[] args) + { + SyntaxReference? syntaxReference = attributeData.ApplicationSyntaxReference; + Location location = syntaxReference is not null + ? syntaxReference.GetSyntax().GetLocation() + : Location.None; + + return Diagnostic.Create( + descriptor, + location: location.IsInSource ? location : Location.None, + messageArgs: args); + } + } + + /// + /// Class for reporting diagnostics in the DLL import generator + /// + public class GeneratorDiagnostics + { + public class Ids + { + public const string Prefix = "DLLIMPORTGEN"; + public const string TypeNotSupported = Prefix + "001"; + public const string ConfigurationNotSupported = Prefix + "002"; + } + + private const string Category = "DllImportGenerator"; + + public readonly static DiagnosticDescriptor ParameterTypeNotSupported = + new DiagnosticDescriptor( + Ids.TypeNotSupported, + GetResourceString(nameof(Resources.TypeNotSupportedTitle)), + GetResourceString(nameof(Resources.TypeNotSupportedMessageParameter)), + Category, + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: GetResourceString(Resources.TypeNotSupportedDescription)); + + public readonly static DiagnosticDescriptor ReturnTypeNotSupported = + new DiagnosticDescriptor( + Ids.TypeNotSupported, + GetResourceString(nameof(Resources.TypeNotSupportedTitle)), + GetResourceString(nameof(Resources.TypeNotSupportedMessageReturn)), + Category, + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: GetResourceString(Resources.TypeNotSupportedDescription)); + + public readonly static DiagnosticDescriptor ParameterConfigurationNotSupported = + new DiagnosticDescriptor( + Ids.ConfigurationNotSupported, + GetResourceString(nameof(Resources.ConfigurationNotSupportedTitle)), + GetResourceString(nameof(Resources.ConfigurationNotSupportedMessageParameter)), + Category, + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: GetResourceString(Resources.ConfigurationNotSupportedDescription)); + + public readonly static DiagnosticDescriptor ReturnConfigurationNotSupported = + new DiagnosticDescriptor( + Ids.ConfigurationNotSupported, + GetResourceString(nameof(Resources.ConfigurationNotSupportedTitle)), + GetResourceString(nameof(Resources.ConfigurationNotSupportedMessageReturn)), + Category, + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: GetResourceString(Resources.ConfigurationNotSupportedDescription)); + + public readonly static DiagnosticDescriptor ConfigurationNotSupported = + new DiagnosticDescriptor( + Ids.ConfigurationNotSupported, + GetResourceString(nameof(Resources.ConfigurationNotSupportedTitle)), + GetResourceString(nameof(Resources.ConfigurationNotSupportedMessage)), + Category, + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: GetResourceString(Resources.ConfigurationNotSupportedDescription)); + + public readonly static DiagnosticDescriptor ConfigurationValueNotSupported = + new DiagnosticDescriptor( + Ids.ConfigurationNotSupported, + GetResourceString(nameof(Resources.ConfigurationNotSupportedTitle)), + GetResourceString(nameof(Resources.ConfigurationNotSupportedMessageValue)), + Category, + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: GetResourceString(Resources.ConfigurationNotSupportedDescription)); + + private readonly GeneratorExecutionContext context; + + public GeneratorDiagnostics(GeneratorExecutionContext context) + { + this.context = context; + } + + /// + /// Report diagnostic for configuration that is not supported by the DLL import source generator + /// + /// Attribute specifying the unsupported configuration + /// Name of the configuration + /// [Optiona] Unsupported configuration value + public void ReportConfigurationNotSupported( + AttributeData attributeData, + string configurationName, + string? unsupportedValue = null) + { + if (unsupportedValue == null) + { + this.context.ReportDiagnostic( + attributeData.CreateDiagnostic( + GeneratorDiagnostics.ConfigurationNotSupported, + configurationName)); + } + else + { + this.context.ReportDiagnostic( + attributeData.CreateDiagnostic( + GeneratorDiagnostics.ConfigurationValueNotSupported, + unsupportedValue, + configurationName)); + } + } + + /// + /// Report diagnostic for marshalling of a parameter/return that is not supported + /// + /// Method with the parameter/return + /// Type info for the parameter/return + internal void ReportMarshallingNotSupported( + IMethodSymbol method, + TypePositionInfo info) + { + if (info.MarshallingAttributeInfo != null && info.MarshallingAttributeInfo is MarshalAsInfo) + { + // Report that the specified marshalling configuration is not supported. + // We don't forward marshalling attributes, so this is reported differently + // than when there is no attribute and the type itself is not supported. + if (info.IsManagedReturnPosition) + { + this.context.ReportDiagnostic( + method.CreateDiagnostic( + GeneratorDiagnostics.ReturnConfigurationNotSupported, + nameof(System.Runtime.InteropServices.MarshalAsAttribute), + method.Name)); + } + else + { + Debug.Assert(info.ManagedIndex <= method.Parameters.Length); + IParameterSymbol paramSymbol = method.Parameters[info.ManagedIndex]; + this.context.ReportDiagnostic( + paramSymbol.CreateDiagnostic( + GeneratorDiagnostics.ParameterConfigurationNotSupported, + nameof(System.Runtime.InteropServices.MarshalAsAttribute), + paramSymbol.Name)); + } + } + else + { + // Report that the type is not supported + if (info.IsManagedReturnPosition) + { + this.context.ReportDiagnostic( + method.CreateDiagnostic( + GeneratorDiagnostics.ReturnTypeNotSupported, + method.ReturnType.ToDisplayString(), + method.Name)); + } + else + { + Debug.Assert(info.ManagedIndex <= method.Parameters.Length); + IParameterSymbol paramSymbol = method.Parameters[info.ManagedIndex]; + this.context.ReportDiagnostic( + paramSymbol.CreateDiagnostic( + GeneratorDiagnostics.ParameterTypeNotSupported, + paramSymbol.Type.ToDisplayString(), + paramSymbol.Name)); + } + } + } + + private static LocalizableResourceString GetResourceString(string resourceName) + { + return new LocalizableResourceString(resourceName, Resources.ResourceManager, typeof(Resources)); + } + } +} diff --git a/DllImportGenerator/DllImportGenerator/ManualTypeMarshallingAnalyzer.cs b/DllImportGenerator/DllImportGenerator/ManualTypeMarshallingAnalyzer.cs index a58c81291b7c..8366ca698ed9 100644 --- a/DllImportGenerator/DllImportGenerator/ManualTypeMarshallingAnalyzer.cs +++ b/DllImportGenerator/DllImportGenerator/ManualTypeMarshallingAnalyzer.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Immutable; using System.Linq; -using DllImportGenerator; + using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; diff --git a/DllImportGenerator/DllImportGenerator/Marshalling/MarshallingGenerator.cs b/DllImportGenerator/DllImportGenerator/Marshalling/MarshallingGenerator.cs index cebc19932604..e2572ad6cf7b 100644 --- a/DllImportGenerator/DllImportGenerator/Marshalling/MarshallingGenerator.cs +++ b/DllImportGenerator/DllImportGenerator/Marshalling/MarshallingGenerator.cs @@ -53,6 +53,9 @@ internal interface IMarshallingGenerator /// Object to marshal /// Code generation context /// If the marshaller uses an identifier for the native value, true; otherwise, false. + /// + /// of may not be valid. + /// bool UsesNativeIdentifier(TypePositionInfo info, StubCodeContext context); } @@ -117,7 +120,7 @@ public static bool TryCreate(TypePositionInfo info, StubCodeContext context, out generator = Blittable; return true; - // Marshalling in new model + // Marshalling in new model case { MarshallingAttributeInfo: NativeMarshallingAttributeInfo marshalInfo }: generator = Forwarder; return false; @@ -127,10 +130,14 @@ public static bool TryCreate(TypePositionInfo info, StubCodeContext context, out generator = Forwarder; return false; - case { MarshallingAttributeInfo: SafeHandleMarshallingInfo _}: + case { MarshallingAttributeInfo: SafeHandleMarshallingInfo _}: generator = SafeHandle; return true; + case { ManagedType: { SpecialType: SpecialType.System_Void } }: + generator = Forwarder; + return true; + default: generator = Forwarder; return false; diff --git a/DllImportGenerator/DllImportGenerator/Resources.Designer.cs b/DllImportGenerator/DllImportGenerator/Resources.Designer.cs index c7ccc3443b27..67f0b5f785e3 100644 --- a/DllImportGenerator/DllImportGenerator/Resources.Designer.cs +++ b/DllImportGenerator/DllImportGenerator/Resources.Designer.cs @@ -8,7 +8,7 @@ // //------------------------------------------------------------------------------ -namespace DllImportGenerator { +namespace Microsoft.Interop { using System; @@ -39,7 +39,7 @@ internal Resources() { internal static global::System.Resources.ResourceManager ResourceManager { get { if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("DllImportGenerator.Resources", typeof(Resources).Assembly); + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.Interop.Resources", typeof(Resources).Assembly); resourceMan = temp; } return resourceMan; @@ -70,7 +70,7 @@ internal static string BlittableTypeMustBeBlittableDescription { } /// - /// Looks up a localized string similar to Type '{0}' is marked with 'BlittableTypeAttribute' but is not blittable.. + /// Looks up a localized string similar to Type '{0}' is marked with 'BlittableTypeAttribute' but is not blittable. /// internal static string BlittableTypeMustBeBlittableMessage { get { @@ -96,6 +96,60 @@ internal static string CannotHaveMultipleMarshallingAttributesMessage { } } + /// + /// Looks up a localized string similar to Source-generated P/Invokes will ignore any configuration that is not supported.. + /// + internal static string ConfigurationNotSupportedDescription { + get { + return ResourceManager.GetString("ConfigurationNotSupportedDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The '{0}' configuration is not supported by source-generated P/Invokes. If the specified configuration is required, use a regular `DllImport` instead.. + /// + internal static string ConfigurationNotSupportedMessage { + get { + return ResourceManager.GetString("ConfigurationNotSupportedMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The specified '{0}' configuration for parameter '{1}' is not supported by source-generated P/Invokes. If the specified configuration is required, use a regular `DllImport` instead.. + /// + internal static string ConfigurationNotSupportedMessageParameter { + get { + return ResourceManager.GetString("ConfigurationNotSupportedMessageParameter", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The specified '{0}' configuration for the return value of method '{1}' is not supported by source-generated P/Invokes. If the specified configuration is required, use a regular `DllImport` instead.. + /// + internal static string ConfigurationNotSupportedMessageReturn { + get { + return ResourceManager.GetString("ConfigurationNotSupportedMessageReturn", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The specified value '{0}' for '{1}' is not supported by source-generated P/Invokes. If the specified configuration is required, use a regular `DllImport` instead.. + /// + internal static string ConfigurationNotSupportedMessageValue { + get { + return ResourceManager.GetString("ConfigurationNotSupportedMessageValue", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Specified configuration is not supported by source-generated P/Invokes.. + /// + internal static string ConfigurationNotSupportedTitle { + get { + return ResourceManager.GetString("ConfigurationNotSupportedTitle", resourceCulture); + } + } + /// /// Looks up a localized string similar to The return type of 'GetPinnableReference' (after accounting for 'ref') must be blittable.. /// @@ -106,7 +160,7 @@ internal static string GetPinnableReferenceReturnTypeBlittableDescription { } /// - /// 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 The dereferenced type of the return type of the 'GetPinnableReference' method must be blittable. /// internal static string GetPinnableReferenceReturnTypeBlittableMessage { get { @@ -124,7 +178,7 @@ internal static string GetPinnableReferenceShouldSupportAllocatingMarshallingFal } /// - /// 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 Type '{0}' has a 'GetPinnableReference' method but its native type does not support marshalling in scenarios where pinning is impossible. /// internal static string GetPinnableReferenceShouldSupportAllocatingMarshallingFallbackMessage { get { @@ -142,7 +196,7 @@ internal static string NativeTypeMustBeBlittableDescription { } /// - /// Looks up a localized string similar to The native type '{0}' for the type '{1}' is not blittable.. + /// Looks up a localized string similar to The native type '{0}' for the type '{1}' is not blittable. /// internal static string NativeTypeMustBeBlittableMessage { get { @@ -160,7 +214,7 @@ internal static string NativeTypeMustBeNonNullDescription { } /// - /// Looks up a localized string similar to The native type for the type '{0}' is null.. + /// Looks up a localized string similar to The native type for the type '{0}' is null. /// internal static string NativeTypeMustBeNonNullMessage { get { @@ -178,7 +232,7 @@ internal static string NativeTypeMustBePointerSizedDescription { } /// - /// Looks up a localized string similar to The native type '{0}' must be pointer sized because the managed type '{1}' has a 'GetPinnableReference' method.. + /// Looks up a localized string similar to The native type '{0}' must be pointer sized because the managed type '{1}' has a 'GetPinnableReference' method. /// internal static string NativeTypeMustBePointerSizedMessage { get { @@ -196,7 +250,7 @@ internal static string NativeTypeMustHaveRequiredShapeDescription { } /// - /// Looks up a localized string similar to The native type '{0}' must be a value type and have a constructor that takes one parameter of type '{1}' or a parameterless instance method named 'ToManaged' that returns '{1}'.. + /// Looks up a localized string similar to The native type '{0}' must be a value type and have a constructor that takes one parameter of type '{1}' or a parameterless instance method named 'ToManaged' that returns '{1}'. /// internal static string NativeTypeMustHaveRequiredShapeMessage { get { @@ -214,7 +268,7 @@ internal static string StackallocConstructorMustHaveStackBufferSizeConstantDescr } /// - /// Looks up a localized string similar to The native type '{0}' must have a 'public const int StackBufferSize' field that specifies the size of the stack buffer because it has a constructor that takes a stack-allocated Span<byte>.. + /// Looks up a localized string similar to The native type '{0}' must have a 'public const int StackBufferSize' field that specifies the size of the stack buffer because it has a constructor that takes a stack-allocated Span<byte>. /// internal static string StackallocConstructorMustHaveStackBufferSizeConstantMessage { get { @@ -232,7 +286,7 @@ internal static string StackallocMarshallingShouldSupportAllocatingMarshallingFa } /// - /// Looks up a localized string similar to Native type '{0}' has a stack-allocating constructor does not support marshalling in scenarios where stack allocation is impossible.. + /// Looks up a localized string similar to Native type '{0}' has a stack-allocating constructor does not support marshalling in scenarios where stack allocation is impossible. /// internal static string StackallocMarshallingShouldSupportAllocatingMarshallingFallbackMessage { get { @@ -240,6 +294,42 @@ internal static string StackallocMarshallingShouldSupportAllocatingMarshallingFa } } + /// + /// Looks up a localized string similar to For types that are not supported by source-generated P/Invokes, the resulting P/Invoke will rely on the underlying runtime to marshal the specified type.. + /// + internal static string TypeNotSupportedDescription { + get { + return ResourceManager.GetString("TypeNotSupportedDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The type '{0}' is not supported by source-generated P/Invokes. The generated source will not handle marshalling of parameter '{1}'.. + /// + internal static string TypeNotSupportedMessageParameter { + get { + return ResourceManager.GetString("TypeNotSupportedMessageParameter", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The type '{0}' is not supported by source-generated P/Invokes. The generated source will not handle marshalling of the return value of method '{1}'.. + /// + internal static string TypeNotSupportedMessageReturn { + get { + return ResourceManager.GetString("TypeNotSupportedMessageReturn", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Specified type is not supported by source-generated P/Invokes. + /// + internal static string TypeNotSupportedTitle { + get { + return ResourceManager.GetString("TypeNotSupportedTitle", resourceCulture); + } + } + /// /// Looks up a localized string similar to The native type's 'Value' property must have a getter to support marshalling from managed to native.. /// @@ -250,7 +340,7 @@ internal static string ValuePropertyMustHaveGetterDescription { } /// - /// Looks up a localized string similar to The 'Value' property on the native type '{0}' must have a getter.. + /// Looks up a localized string similar to The 'Value' property on the native type '{0}' must have a getter. /// internal static string ValuePropertyMustHaveGetterMessage { get { @@ -268,7 +358,7 @@ internal static string ValuePropertyMustHaveSetterDescription { } /// - /// Looks up a localized string similar to The 'Value' property on the native type '{0}' must have a setter.. + /// Looks up a localized string similar to The 'Value' property on the native type '{0}' must have a setter. /// internal static string ValuePropertyMustHaveSetterMessage { get { diff --git a/DllImportGenerator/DllImportGenerator/Resources.resx b/DllImportGenerator/DllImportGenerator/Resources.resx index e21cecab2e6f..9568b0305d55 100644 --- a/DllImportGenerator/DllImportGenerator/Resources.resx +++ b/DllImportGenerator/DllImportGenerator/Resources.resx @@ -121,7 +121,7 @@ A type marked with 'BlittableTypeAttribute' must be blittable. - Type '{0}' is marked with 'BlittableTypeAttribute' but is not blittable. + Type '{0}' is marked with 'BlittableTypeAttribute' but is not blittable The 'BlittableTypeAttribute' and 'NativeMarshallingAttribute' attributes are mutually exclusive. @@ -129,64 +129,94 @@ Type '{0}' is marked with 'BlittableTypeAttribute' and 'NativeMarshallingAttribute'. A type can only have one of these two attributes. + + Source-generated P/Invokes will ignore any configuration that is not supported. + + + The '{0}' configuration is not supported by source-generated P/Invokes. If the specified configuration is required, use a regular `DllImport` instead. + + + The specified '{0}' configuration for parameter '{1}' is not supported by source-generated P/Invokes. If the specified configuration is required, use a regular `DllImport` instead. + + + The specified '{0}' configuration for the return value of method '{1}' is not supported by source-generated P/Invokes. If the specified configuration is required, use a regular `DllImport` instead. + + + The specified value '{0}' for '{1}' is not supported by source-generated P/Invokes. If the specified configuration is required, use a regular `DllImport` instead. + + + Specified configuration is not supported by source-generated P/Invokes. + The return type of 'GetPinnableReference' (after accounting for 'ref') must be blittable. - The dereferenced type of the return type of the 'GetPinnableReference' method must be blittable. + The dereferenced type of the return type of the 'GetPinnableReference' method must be blittable A type that supports marshalling from managed to native by pinning should also support marshalling from managed to native where pinning is impossible. - Type '{0}' has a 'GetPinnableReference' method but its native type does not support marshalling in scenarios where pinning is impossible. + Type '{0}' has a 'GetPinnableReference' method but its native type does not support marshalling in scenarios where pinning is impossible A native type for a given type must be blittable. - The native type '{0}' for the type '{1}' is not blittable. + The native type '{0}' for the type '{1}' is not blittable A native type for a given type must be non-null. - The native type for the type '{0}' is null. + The native type for the type '{0}' is null The native type must be pointer sized so the pinned result of 'GetPinnableReference' can be cast to the native type. - The native type '{0}' must be pointer sized because the managed type '{1}' has a 'GetPinnableReference' method. + The native type '{0}' must be pointer sized because the managed type '{1}' has a 'GetPinnableReference' method The native type must have at least one of the two marshalling methods to enable marshalling the managed type. - The native type '{0}' must be a value type and have a constructor that takes one parameter of type '{1}' or a parameterless instance method named 'ToManaged' that returns '{1}'. + The native type '{0}' must be a value type and have a constructor that takes one parameter of type '{1}' or a parameterless instance method named 'ToManaged' that returns '{1}' When constructor taking a Span<byte> is specified on the native type, the type must also have a public integer constant named StackBufferSize to provide the size of the stack-allocated buffer. - The native type '{0}' must have a 'public const int StackBufferSize' field that specifies the size of the stack buffer because it has a constructor that takes a stack-allocated Span<byte>. + The native type '{0}' must have a 'public const int StackBufferSize' field that specifies the size of the stack buffer because it has a constructor that takes a stack-allocated Span<byte> A type that supports marshalling from managed to native by stack allocation should also support marshalling from managed to native where stack allocation is impossible. - Native type '{0}' has a stack-allocating constructor does not support marshalling in scenarios where stack allocation is impossible. + Native type '{0}' has a stack-allocating constructor does not support marshalling in scenarios where stack allocation is impossible + + + For types that are not supported by source-generated P/Invokes, the resulting P/Invoke will rely on the underlying runtime to marshal the specified type. + + + The type '{0}' is not supported by source-generated P/Invokes. The generated source will not handle marshalling of parameter '{1}'. + + + The type '{0}' is not supported by source-generated P/Invokes. The generated source will not handle marshalling of the return value of method '{1}'. + + + Specified type is not supported by source-generated P/Invokes The native type's 'Value' property must have a getter to support marshalling from managed to native. - The 'Value' property on the native type '{0}' must have a getter. + The 'Value' property on the native type '{0}' must have a getter The native type's 'Value' property must have a setter to support marshalling from native to managed. - The 'Value' property on the native type '{0}' must have a setter. + The 'Value' property on the native type '{0}' must have a setter \ No newline at end of file diff --git a/DllImportGenerator/DllImportGenerator/StubCodeContext.cs b/DllImportGenerator/DllImportGenerator/StubCodeContext.cs index 0b5f9fe0a862..b3618c898bfc 100644 --- a/DllImportGenerator/DllImportGenerator/StubCodeContext.cs +++ b/DllImportGenerator/DllImportGenerator/StubCodeContext.cs @@ -9,6 +9,11 @@ internal abstract class StubCodeContext /// public enum Stage { + /// + /// Invalid stage + /// + Invalid, + /// /// Perform any setup required /// @@ -55,7 +60,7 @@ public enum Stage GuaranteedUnmarshal } - public Stage CurrentStage { get; protected set; } + public Stage CurrentStage { get; protected set; } = Stage.Invalid; public abstract bool PinningSupported { get; } diff --git a/DllImportGenerator/DllImportGenerator/StubCodeGenerator.cs b/DllImportGenerator/DllImportGenerator/StubCodeGenerator.cs index 043819bc068f..a28c856026ac 100644 --- a/DllImportGenerator/DllImportGenerator/StubCodeGenerator.cs +++ b/DllImportGenerator/DllImportGenerator/StubCodeGenerator.cs @@ -12,11 +12,6 @@ namespace Microsoft.Interop { internal sealed class StubCodeGenerator : StubCodeContext { - private StubCodeGenerator(Stage stage) - { - CurrentStage = stage; - } - public override bool PinningSupported => true; public override bool StackSpaceUsable => true; @@ -34,17 +29,55 @@ private StubCodeGenerator(Stage stage) private const string InvokeReturnIdentifier = "__invokeRetVal"; - /// - /// Generate an identifier for the native return value and update the context with the new value - /// - /// Identifier for the native return value - public void GenerateReturnNativeIdentifier() + private static readonly Stage[] Stages = new Stage[] + { + Stage.Setup, + Stage.Marshal, + Stage.Pin, + Stage.Invoke, + Stage.KeepAlive, + Stage.Unmarshal, + Stage.GuaranteedUnmarshal, + Stage.Cleanup + }; + + private readonly GeneratorDiagnostics diagnostics; + + private readonly IMethodSymbol stubMethod; + private readonly List<(TypePositionInfo TypeInfo, IMarshallingGenerator Generator)> paramMarshallers; + private readonly (TypePositionInfo TypeInfo, IMarshallingGenerator Generator) retMarshaller; + + public StubCodeGenerator( + IMethodSymbol stubMethod, + IEnumerable paramsTypeInfo, + TypePositionInfo retTypeInfo, + GeneratorDiagnostics generatorDiagnostics) { - if (CurrentStage != Stage.Setup) - throw new InvalidOperationException(); + Debug.Assert(retTypeInfo.IsNativeReturnPosition); + + this.stubMethod = stubMethod; + this.diagnostics = generatorDiagnostics; + + // Get marshallers for parameters + this.paramMarshallers = paramsTypeInfo.Select(p => + { + IMarshallingGenerator generator; + if (!MarshallingGenerators.TryCreate(p, this, out generator)) + { + this.diagnostics.ReportMarshallingNotSupported(this.stubMethod, p); + } - // Update the native identifier for the return value - ReturnNativeIdentifier = $"{ReturnIdentifier}{GeneratedNativeIdentifierSuffix}"; + return (p, generator); + }).ToList(); + + // Get marshaller for return + IMarshallingGenerator retGenerator; + if (!MarshallingGenerators.TryCreate(retTypeInfo, this, out retGenerator)) + { + this.diagnostics.ReportMarshallingNotSupported(this.stubMethod, retTypeInfo); + } + + this.retMarshaller = (retTypeInfo, retGenerator); } public override (string managed, string native) GetIdentifiers(TypePositionInfo info) @@ -70,24 +103,15 @@ public override (string managed, string native) GetIdentifiers(TypePositionInfo } } - public static (BlockSyntax Code, MethodDeclarationSyntax DllImport) GenerateSyntax( - IMethodSymbol stubMethod, - IEnumerable paramsTypeInfo, - TypePositionInfo retTypeInfo) + public (BlockSyntax Code, MethodDeclarationSyntax DllImport) GenerateSyntax() { - Debug.Assert(retTypeInfo.IsNativeReturnPosition); - - var context = new StubCodeGenerator(Stage.Setup); - string dllImportName = stubMethod.Name + "__PInvoke__"; - var paramMarshallers = paramsTypeInfo.Select(p => GetMarshalInfo(p, context)).ToList(); - var retMarshaller = GetMarshalInfo(retTypeInfo, context); - var statements = new List(); - if (retMarshaller.Generator.UsesNativeIdentifier(retTypeInfo, context)) + if (retMarshaller.Generator.UsesNativeIdentifier(retMarshaller.TypeInfo, this)) { - context.GenerateReturnNativeIdentifier(); + // Update the native identifier for the return value + ReturnNativeIdentifier = $"{ReturnIdentifier}{GeneratedNativeIdentifierSuffix}"; } foreach (var marshaller in paramMarshallers) @@ -106,21 +130,21 @@ public static (BlockSyntax Code, MethodDeclarationSyntax DllImport) GenerateSynt Token(SyntaxKind.DefaultKeyword))))); } - bool invokeReturnsVoid = retTypeInfo.ManagedType.SpecialType == SpecialType.System_Void; + bool invokeReturnsVoid = retMarshaller.TypeInfo.ManagedType.SpecialType == SpecialType.System_Void; bool stubReturnsVoid = stubMethod.ReturnsVoid; // Stub return is not the same as invoke return - if (!stubReturnsVoid && !retTypeInfo.IsManagedReturnPosition) + if (!stubReturnsVoid && !retMarshaller.TypeInfo.IsManagedReturnPosition) { - Debug.Assert(paramsTypeInfo.Any() && paramsTypeInfo.Last().IsManagedReturnPosition); + Debug.Assert(paramMarshallers.Any() && paramMarshallers.Last().TypeInfo.IsManagedReturnPosition); // Declare variable for stub return value - TypePositionInfo info = paramsTypeInfo.Last(); + TypePositionInfo info = paramMarshallers.Last().TypeInfo; statements.Add(LocalDeclarationStatement( VariableDeclaration( info.ManagedType.AsTypeSyntax(), SingletonSeparatedList( - VariableDeclarator(context.GetIdentifiers(info).managed))))); + VariableDeclarator(this.GetIdentifiers(info).managed))))); } if (!invokeReturnsVoid) @@ -128,34 +152,22 @@ public static (BlockSyntax Code, MethodDeclarationSyntax DllImport) GenerateSynt // Declare variable for invoke return value statements.Add(LocalDeclarationStatement( VariableDeclaration( - retTypeInfo.ManagedType.AsTypeSyntax(), + retMarshaller.TypeInfo.ManagedType.AsTypeSyntax(), SingletonSeparatedList( - VariableDeclarator(context.GetIdentifiers(retTypeInfo).managed))))); + VariableDeclarator(this.GetIdentifiers(retMarshaller.TypeInfo).managed))))); } - var stages = new Stage[] - { - Stage.Setup, - Stage.Marshal, - Stage.Pin, - Stage.Invoke, - Stage.KeepAlive, - Stage.Unmarshal, - Stage.GuaranteedUnmarshal, - Stage.Cleanup - }; - var invoke = InvocationExpression(IdentifierName(dllImportName)); var fixedStatements = new List(); - foreach (var stage in stages) + foreach (var stage in Stages) { int initialCount = statements.Count; - context.CurrentStage = stage; + this.CurrentStage = stage; if (!invokeReturnsVoid && (stage == Stage.Setup || stage == Stage.Unmarshal || stage == Stage.GuaranteedUnmarshal)) { // Handle setup and unmarshalling for return - var retStatements = retMarshaller.Generator.Generate(retMarshaller.TypeInfo, context); + var retStatements = retMarshaller.Generator.Generate(retMarshaller.TypeInfo, this); statements.AddRange(retStatements); } @@ -165,12 +177,12 @@ public static (BlockSyntax Code, MethodDeclarationSyntax DllImport) GenerateSynt if (stage == Stage.Invoke) { // Get arguments for invocation - ArgumentSyntax argSyntax = marshaller.Generator.AsArgument(marshaller.TypeInfo, context); + ArgumentSyntax argSyntax = marshaller.Generator.AsArgument(marshaller.TypeInfo, this); invoke = invoke.AddArgumentListArguments(argSyntax); } else { - var generatedStatements = marshaller.Generator.Generate(marshaller.TypeInfo, context); + var generatedStatements = marshaller.Generator.Generate(marshaller.TypeInfo, this); if (stage == Stage.Pin) { // Collect all the fixed statements. These will be used in the Invoke stage. @@ -203,7 +215,7 @@ public static (BlockSyntax Code, MethodDeclarationSyntax DllImport) GenerateSynt invokeStatement = ExpressionStatement( AssignmentExpression( SyntaxKind.SimpleAssignmentExpression, - IdentifierName(context.GetIdentifiers(retMarshaller.TypeInfo).native), + IdentifierName(this.GetIdentifiers(retMarshaller.TypeInfo).native), invoke)); } @@ -257,16 +269,5 @@ public static (BlockSyntax Code, MethodDeclarationSyntax DllImport) GenerateSynt return (codeBlock, dllImport); } - - private static (TypePositionInfo TypeInfo, IMarshallingGenerator Generator) GetMarshalInfo(TypePositionInfo info, StubCodeContext context) - { - IMarshallingGenerator generator; - if (!MarshallingGenerators.TryCreate(info, context, out generator)) - { - // [TODO] Report warning - } - - return (info, generator); - } } } diff --git a/DllImportGenerator/DllImportGenerator/TypePositionInfo.cs b/DllImportGenerator/DllImportGenerator/TypePositionInfo.cs index e816f8457d79..ac77297b71bb 100644 --- a/DllImportGenerator/DllImportGenerator/TypePositionInfo.cs +++ b/DllImportGenerator/DllImportGenerator/TypePositionInfo.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Runtime.InteropServices; -using DllImportGenerator; + using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -29,7 +29,7 @@ private TypePositionInfo() public RefKind RefKind { get; private set; } public SyntaxKind RefKindSyntax { get; private set; } - + public bool IsByRef => RefKind != RefKind.None; public bool IsManagedReturnPosition { get => this.ManagedIndex == ReturnIndex; } @@ -41,9 +41,9 @@ private TypePositionInfo() public MarshallingInfo MarshallingAttributeInfo { get; private set; } - public static TypePositionInfo CreateForParameter(IParameterSymbol paramSymbol, Compilation compilation) + public static TypePositionInfo CreateForParameter(IParameterSymbol paramSymbol, Compilation compilation, GeneratorDiagnostics diagnostics) { - var marshallingInfo = GetMarshallingInfo(paramSymbol.Type, paramSymbol.GetAttributes(), compilation); + var marshallingInfo = GetMarshallingInfo(paramSymbol.Type, paramSymbol.GetAttributes(), compilation, diagnostics); var typeInfo = new TypePositionInfo() { ManagedType = paramSymbol.Type, @@ -56,9 +56,9 @@ public static TypePositionInfo CreateForParameter(IParameterSymbol paramSymbol, return typeInfo; } - public static TypePositionInfo CreateForType(ITypeSymbol type, IEnumerable attributes, Compilation compilation) + public static TypePositionInfo CreateForType(ITypeSymbol type, IEnumerable attributes, Compilation compilation, GeneratorDiagnostics diagnostics) { - var marshallingInfo = GetMarshallingInfo(type, attributes, compilation); + var marshallingInfo = GetMarshallingInfo(type, attributes, compilation, diagnostics); var typeInfo = new TypePositionInfo() { ManagedType = type, @@ -72,7 +72,7 @@ public static TypePositionInfo CreateForType(ITypeSymbol type, IEnumerable attributes, Compilation compilation) + private static MarshallingInfo? GetMarshallingInfo(ITypeSymbol type, IEnumerable attributes, Compilation compilation, GeneratorDiagnostics diagnostics) { MarshallingInfo? marshallingInfo = null; // Look at attributes on the type. @@ -87,7 +87,7 @@ public static TypePositionInfo CreateForType(ITypeSymbol type, IEnumerable//generated`). A managed assembly with [native exports](./DllImportGenerator/TestAssets/NativeExports) is used in the P/Invoke scenario. ### Recommended scenarios: