diff --git a/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/Analyzers/ConvertToLibraryImportAnalyzer.cs b/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/Analyzers/ConvertToLibraryImportAnalyzer.cs index 0789028403d9e..f0e9ab5db0562 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/Analyzers/ConvertToLibraryImportAnalyzer.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/Analyzers/ConvertToLibraryImportAnalyzer.cs @@ -76,6 +76,24 @@ private static void AnalyzeSymbol(SymbolAnalysisContext context, INamedTypeSymbo if (dllImportData == null) return; + if (dllImportData.ThrowOnUnmappableCharacter == true) + { + // LibraryImportGenerator doesn't support ThrowOnUnmappableCharacter = true + return; + } + + // LibraryImportGenerator doesn't support BestFitMapping = true + if (IsBestFitMapping(method, dllImportData)) + { + return; + } + + if (method.IsVararg) + { + // LibraryImportGenerator doesn't support varargs + return; + } + // Ignore methods already marked LibraryImport // This can be the case when the generator creates an extern partial function for blittable signatures. foreach (AttributeData attr in method.GetAttributes()) @@ -139,6 +157,28 @@ private static void AnalyzeSymbol(SymbolAnalysisContext context, INamedTypeSymbo context.ReportDiagnostic(method.CreateDiagnosticInfo(ConvertToLibraryImport, properties.ToImmutable(), method.Name).ToDiagnostic()); } + private static bool IsBestFitMapping(IMethodSymbol method, DllImportData? dllImportData) + { + if (dllImportData.BestFitMapping.HasValue) + { + return dllImportData.BestFitMapping.Value; + } + + AttributeData? bestFitMappingContainingType = method.ContainingType.GetAttributes().FirstOrDefault(attr => attr.AttributeClass.ToDisplayString() == TypeNames.System_Runtime_InteropServices_BestFitMappingAttribute); + if (bestFitMappingContainingType is not null) + { + return bestFitMappingContainingType.ConstructorArguments[0].Value is true; + } + + AttributeData? bestFitMappingContainingAssembly = method.ContainingAssembly.GetAttributes().FirstOrDefault(attr => attr.AttributeClass.ToDisplayString() == TypeNames.System_Runtime_InteropServices_BestFitMappingAttribute); + if (bestFitMappingContainingAssembly is not null) + { + return bestFitMappingContainingAssembly.ConstructorArguments[0].Value is true; + } + + return false; + } + private static bool HasUnsupportedMarshalAsInfo(TypePositionInfo info) { if (info.MarshallingAttributeInfo is not MarshalAsInfo(UnmanagedType unmanagedType, _)) diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/TypeNames.cs b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/TypeNames.cs index 04602aa324449..7f869fe2274b1 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/TypeNames.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/TypeNames.cs @@ -154,5 +154,7 @@ public static string MarshalEx(InteropGenerationOptions options) public const string System_Runtime_InteropServices_Marshalling_ComInterfaceMarshaller_Metadata = "System.Runtime.InteropServices.Marshalling.ComInterfaceMarshaller`1"; public const string System_Runtime_InteropServices_Marshalling_ComObject = "System.Runtime.InteropServices.Marshalling.ComObject"; + + public const string System_Runtime_InteropServices_BestFitMappingAttribute = "System.Runtime.InteropServices.BestFitMappingAttribute"; } } diff --git a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/ConvertToLibraryImportAnalyzerTests.cs b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/ConvertToLibraryImportAnalyzerTests.cs index 3288160f3ff1b..854e58c8a73b9 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/ConvertToLibraryImportAnalyzerTests.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/ConvertToLibraryImportAnalyzerTests.cs @@ -223,5 +223,114 @@ unsafe partial class Test public static extern {{typeName}} {|#1:Method_Return|}(); } """; + + [Fact] + public async Task BestFitMapping_True_NoDiagnostic() + { + string source = """ + using System.Runtime.InteropServices; + partial class Test + { + [DllImport("DoesNotExist", BestFitMapping = true)] + public static extern void Method2(); + } + + """; + await VerifyCS.VerifyAnalyzerAsync(source); + } + + [Fact] + public async Task ThrowOnUnmappableChar_True_NoDiagnostic() + { + string source = """ + using System.Runtime.InteropServices; + partial class Test + { + [DllImport("DoesNotExist", ThrowOnUnmappableChar = true)] + public static extern void Method2(); + } + + """; + await VerifyCS.VerifyAnalyzerAsync(source); + } + + [Fact] + public async Task BestFitMapping_Assembly_True_NoDiagnostic() + { + string source = """ + using System.Runtime.InteropServices; + [assembly:BestFitMapping(true)] + partial class Test + { + [DllImport("DoesNotExist")] + public static extern void Method2(); + } + + """; + await VerifyCS.VerifyAnalyzerAsync(source); + } + + [Fact] + public async Task BestFitMapping_Type_True_NoDiagnostic() + { + string source = """ + using System.Runtime.InteropServices; + [BestFitMapping(true)] + partial class Test + { + [DllImport("DoesNotExist")] + public static extern void Method2(); + } + + """; + await VerifyCS.VerifyAnalyzerAsync(source); + } + + [Fact] + public async Task BestFitMapping_Assembly_False_Diagnostic() + { + string source = """ + using System.Runtime.InteropServices; + [assembly:BestFitMapping(false)] + partial class Test + { + [DllImport("DoesNotExist")] + public static extern void [|Method2|](); + } + + """; + await VerifyCS.VerifyAnalyzerAsync(source); + } + + [Fact] + public async Task BestFitMapping_Type_False_Diagnostic() + { + string source = """ + using System.Runtime.InteropServices; + [BestFitMapping(false)] + partial class Test + { + [DllImport("DoesNotExist")] + public static extern void [|Method2|](); + } + + """; + await VerifyCS.VerifyAnalyzerAsync(source); + } + + [Fact] + public async Task Varargs_NoDiagnostic() + { + string source = """ + using System.Runtime.InteropServices; + partial class Test + { + [DllImport("DoesNotExist")] + public static extern void Method2(__arglist); + } + + """; + await VerifyCS.VerifyAnalyzerAsync(source); + } } } diff --git a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/ConvertToLibraryImportFixerTests.cs b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/ConvertToLibraryImportFixerTests.cs index 4bc819409515b..0dfe12b6f5bbc 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/ConvertToLibraryImportFixerTests.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/ConvertToLibraryImportFixerTests.cs @@ -1109,6 +1109,58 @@ await VerifyCodeFixAsync( new Dictionary { { Option.MayRequireAdditionalWork, new Option.Bool(true) } }); } + [Fact] + public async Task BestFitMappingExplicitFalse() + { + string source = """ + using System.Runtime.InteropServices; + partial class Test + { + [DllImport("DoesNotExist", BestFitMapping = false)] + public static extern void [|Method|](); + } + + """; + + string fixedSource = """ + using System.Runtime.InteropServices; + partial class Test + { + [LibraryImport("DoesNotExist")] + public static partial void {|CS8795:Method|}(); + } + + """; + + await VerifyCodeFixAsync(source, fixedSource); + } + + [Fact] + public async Task ThrowOnUnmappableCharExplicitFalse() + { + string source = """ + using System.Runtime.InteropServices; + partial class Test + { + [DllImport("DoesNotExist", ThrowOnUnmappableChar = false)] + public static extern void [|Method|](); + } + + """; + + string fixedSource = """ + using System.Runtime.InteropServices; + partial class Test + { + [LibraryImport("DoesNotExist")] + public static partial void {|CS8795:Method|}(); + } + + """; + + await VerifyCodeFixAsync(source, fixedSource); + } + private static async Task VerifyCodeFixAsync(string source, string fixedSource) { var test = new VerifyCS.Test