diff --git a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.Naming.cs b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.Naming.cs index ace6923f..8896b0ca 100644 --- a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.Naming.cs +++ b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.Naming.cs @@ -812,6 +812,16 @@ private string GetRemappedTypeName(Cursor? cursor, Cursor? context, Type type, o nativeTypeName = string.Empty; } + if (TryLowerFixedSizeBufferRemapping(remappedName, out var loweredName)) + { + remappedName = loweredName; + } + + return remappedName; + } + + private static bool TryLowerFixedSizeBufferRemapping(string remappedName, out string loweredName) + { if (remappedName.IndexOf('[', StringComparison.Ordinal) is int bracketIndex and >= 0 && (bracketIndex + 1) < remappedName.Length && char.IsAsciiDigit(remappedName[bracketIndex + 1])) { @@ -822,10 +832,12 @@ private string GetRemappedTypeName(Cursor? cursor, Cursor? context, Type type, o // A managed array such as `int[]` has an empty subscript and is left untouched. var rank = remappedName.AsSpan().Count('['); - remappedName = string.Concat(remappedName.AsSpan(0, bracketIndex), new string('*', rank)); + loweredName = string.Concat(remappedName.AsSpan(0, bracketIndex), new string('*', rank)); + return true; } - return remappedName; + loweredName = remappedName; + return false; } } diff --git a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.cs b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.cs index 8b9cb9c0..ce287a2a 100644 --- a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.cs +++ b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.cs @@ -523,6 +523,20 @@ static ReadOnlySpan GetFoundRemappingString(ReadOnlySpan name, HashS return result; } } + + if (_config.LogInvalidRemappings) + { + foreach (var kvp in _config._remappedNames) + { + var name = kvp.Key; + var remappedName = kvp.Value; + + if (TryLowerFixedSizeBufferRemapping(remappedName, out var loweredName)) + { + AddDiagnostic(DiagnosticLevel.Info, $"Potential invalid remapping '{name}={remappedName}'. A fixed-size-buffer type is not directly expressible in an unmanaged signature; it was lowered to the pointer '{loweredName}'."); + } + } + } } catch (Exception e) { diff --git a/sources/ClangSharp.PInvokeGenerator/PInvokeGeneratorConfiguration.cs b/sources/ClangSharp.PInvokeGenerator/PInvokeGeneratorConfiguration.cs index e600d8e0..bbcdb518 100644 --- a/sources/ClangSharp.PInvokeGenerator/PInvokeGeneratorConfiguration.cs +++ b/sources/ClangSharp.PInvokeGenerator/PInvokeGeneratorConfiguration.cs @@ -311,6 +311,8 @@ public string LibraryPath public bool LogPotentialTypedefRemappings => (_options & PInvokeGeneratorConfigurationOptions.LogPotentialTypedefRemappings) != 0; + public bool LogInvalidRemappings => (_options & PInvokeGeneratorConfigurationOptions.LogInvalidRemappings) != 0; + public bool LogVisitedFiles => (_options & PInvokeGeneratorConfigurationOptions.LogVisitedFiles) != 0; [AllowNull] diff --git a/sources/ClangSharp.PInvokeGenerator/PInvokeGeneratorConfigurationOptions.cs b/sources/ClangSharp.PInvokeGenerator/PInvokeGeneratorConfigurationOptions.cs index ab8c6421..a9f755e8 100644 --- a/sources/ClangSharp.PInvokeGenerator/PInvokeGeneratorConfigurationOptions.cs +++ b/sources/ClangSharp.PInvokeGenerator/PInvokeGeneratorConfigurationOptions.cs @@ -92,4 +92,6 @@ public enum PInvokeGeneratorConfigurationOptions : long DontUseUsingStaticsForGuidMember = 1L << 40, GenerateFixedBufferIndexerOverloads = 1L << 41, + + LogInvalidRemappings = 1L << 42, } diff --git a/sources/ClangSharpPInvokeGenerator/Program.Options.cs b/sources/ClangSharpPInvokeGenerator/Program.Options.cs index eafe38a4..9f9b056d 100644 --- a/sources/ClangSharpPInvokeGenerator/Program.Options.cs +++ b/sources/ClangSharpPInvokeGenerator/Program.Options.cs @@ -182,6 +182,7 @@ internal static partial class Program new TwoColumnHelpRow("", ""), new TwoColumnHelpRow("log-exclusions", "A list of excluded declaration types should be generated. This will also log if the exclusion was due to an exact or partial match."), + new TwoColumnHelpRow("log-invalid-remappings", "A diagnostic should be generated for each remapping whose value is not directly expressible in an unmanaged signature, such as a fixed-size-buffer shape that was lowered to a pointer."), new TwoColumnHelpRow("log-potential-typedef-remappings", "A list of potential typedef remappings should be generated. This can help identify missing remappings."), new TwoColumnHelpRow("log-visited-files", "A list of the visited files should be generated. This can help identify traversal issues."), ]; diff --git a/sources/ClangSharpPInvokeGenerator/Program.cs b/sources/ClangSharpPInvokeGenerator/Program.cs index eb3e58a6..dcdb0581 100644 --- a/sources/ClangSharpPInvokeGenerator/Program.cs +++ b/sources/ClangSharpPInvokeGenerator/Program.cs @@ -459,6 +459,12 @@ public static void Run(InvocationContext context) break; } + case "log-invalid-remappings": + { + configOptions |= PInvokeGeneratorConfigurationOptions.LogInvalidRemappings; + break; + } + case "log-potential-typedef-remappings": { configOptions |= PInvokeGeneratorConfigurationOptions.LogPotentialTypedefRemappings; diff --git a/tests/ClangSharp.PInvokeGenerator.UnitTests/LogInvalidRemappingsTest.cs b/tests/ClangSharp.PInvokeGenerator.UnitTests/LogInvalidRemappingsTest.cs new file mode 100644 index 00000000..b333f3d6 --- /dev/null +++ b/tests/ClangSharp.PInvokeGenerator.UnitTests/LogInvalidRemappingsTest.cs @@ -0,0 +1,45 @@ +// Copyright (c) .NET Foundation and Contributors. All Rights Reserved. Licensed under the MIT License (MIT). See License.md in the repository root for more information. + +using System.Collections.Generic; +using System.Threading.Tasks; +using NUnit.Framework; + +namespace ClangSharp.UnitTests; + +public sealed class LogInvalidRemappingsTest : PInvokeGeneratorTest +{ + [Test] + public Task FixedSizeBufferRemappingIsLoggedAndLoweredToPointer() + { + var inputContents = @"typedef int MyBuffer; + +extern ""C"" void MyFunction(MyBuffer value); +extern ""C"" MyBuffer MyOtherFunction();"; + + var expectedOutputContents = @"using System.Runtime.InteropServices; + +namespace ClangSharp.Test +{ + public static unsafe partial class Methods + { + [DllImport(""ClangSharpPInvokeGenerator"", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern void MyFunction([NativeTypeName(""MyBuffer"")] sbyte* value); + + [DllImport(""ClangSharpPInvokeGenerator"", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + [return: NativeTypeName(""MyBuffer"")] + public static extern sbyte* MyOtherFunction(); + } +} +"; + + var remappedNames = new Dictionary { + ["MyBuffer"] = "sbyte[8]" + }; + + var expectedDiagnostics = new[] { + new Diagnostic(DiagnosticLevel.Info, "Potential invalid remapping 'MyBuffer=sbyte[8]'. A fixed-size-buffer type is not directly expressible in an unmanaged signature; it was lowered to the pointer 'sbyte*'.") + }; + + return ValidateGeneratedCSharpLatestWindowsBindingsAsync(inputContents, expectedOutputContents, additionalConfigOptions: PInvokeGeneratorConfigurationOptions.LogInvalidRemappings, remappedNames: remappedNames, expectedDiagnostics: expectedDiagnostics); + } +}