Update ConvertToLibraryImport analyzer and fixer to limit false-positives/code breaks...
authorJeremy Koritzinsky <jekoritz@microsoft.com>
Fri, 29 Jul 2022 21:18:59 +0000 (14:18 -0700)
committerGitHub <noreply@github.com>
Fri, 29 Jul 2022 21:18:59 +0000 (14:18 -0700)
22 files changed:
src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/Analyzers/ConvertToLibraryImportAnalyzer.cs
src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/Analyzers/ConvertToLibraryImportFixer.cs
src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/Analyzers/FixAllContextExtensions.cs [new file with mode: 0644]
src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/LibraryImportGenerator.cs
src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/LibraryImportGeneratorHelpers.cs [new file with mode: 0644]
src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/Resources/Strings.resx
src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/Resources/xlf/Strings.cs.xlf
src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/Resources/xlf/Strings.de.xlf
src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/Resources/xlf/Strings.es.xlf
src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/Resources/xlf/Strings.fr.xlf
src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/Resources/xlf/Strings.it.xlf
src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/Resources/xlf/Strings.ja.xlf
src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/Resources/xlf/Strings.ko.xlf
src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/Resources/xlf/Strings.pl.xlf
src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/Resources/xlf/Strings.pt-BR.xlf
src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/Resources/xlf/Strings.ru.xlf
src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/Resources/xlf/Strings.tr.xlf
src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/Resources/xlf/Strings.zh-Hans.xlf
src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/Resources/xlf/Strings.zh-Hant.xlf
src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/CompilationExtensions.cs [new file with mode: 0644]
src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/IncrementalGeneratorInitializationContextExtensions.cs
src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/ConvertToLibraryImportFixerTests.cs

index c788d5d..204709c 100644 (file)
@@ -8,7 +8,7 @@ using System.Runtime.InteropServices;
 
 using Microsoft.CodeAnalysis;
 using Microsoft.CodeAnalysis.Diagnostics;
-
+using Microsoft.CodeAnalysis.DotnetRuntime.Extensions;
 using static Microsoft.Interop.Analyzers.AnalyzerDiagnostics;
 
 namespace Microsoft.Interop.Analyzers
@@ -18,13 +18,6 @@ namespace Microsoft.Interop.Analyzers
     {
         private const string Category = "Interoperability";
 
-        private static readonly string[] s_unsupportedTypeNames = new string[]
-        {
-            "System.Runtime.InteropServices.CriticalHandle",
-            "System.Runtime.InteropServices.HandleRef",
-            "System.Text.StringBuilder"
-        };
-
         public static readonly DiagnosticDescriptor ConvertToLibraryImport =
             new DiagnosticDescriptor(
                 Ids.ConvertToLibraryImport,
@@ -39,6 +32,14 @@ namespace Microsoft.Interop.Analyzers
 
         public const string CharSet = nameof(CharSet);
         public const string ExactSpelling = nameof(ExactSpelling);
+        public const string MayRequireAdditionalWork = nameof(MayRequireAdditionalWork);
+
+        private static readonly HashSet<string> s_unsupportedTypeNames = new()
+        {
+            "global::System.Runtime.InteropServices.CriticalHandle",
+            "global::System.Runtime.InteropServices.HandleRef",
+            "global::System.Text.StringBuilder"
+        };
 
         public override void Initialize(AnalysisContext context)
         {
@@ -49,27 +50,15 @@ namespace Microsoft.Interop.Analyzers
                 context =>
                 {
                     // Nothing to do if the LibraryImportAttribute is not in the compilation
-                    INamedTypeSymbol? libraryImportAttrType = context.Compilation.GetTypeByMetadataName(TypeNames.LibraryImportAttribute);
+                    INamedTypeSymbol? libraryImportAttrType = context.Compilation.GetBestTypeByMetadataName(TypeNames.LibraryImportAttribute);
                     if (libraryImportAttrType == null)
                         return;
 
-                    INamedTypeSymbol? marshalAsAttrType = context.Compilation.GetTypeByMetadataName(TypeNames.System_Runtime_InteropServices_MarshalAsAttribute);
-
-                    var knownUnsupportedTypes = new List<ITypeSymbol>(s_unsupportedTypeNames.Length);
-                    foreach (string typeName in s_unsupportedTypeNames)
-                    {
-                        INamedTypeSymbol? unsupportedType = context.Compilation.GetTypeByMetadataName(typeName);
-                        if (unsupportedType != null)
-                        {
-                            knownUnsupportedTypes.Add(unsupportedType);
-                        }
-                    }
-
-                    context.RegisterSymbolAction(symbolContext => AnalyzeSymbol(symbolContext, knownUnsupportedTypes, marshalAsAttrType, libraryImportAttrType), SymbolKind.Method);
+                    context.RegisterSymbolAction(symbolContext => AnalyzeSymbol(symbolContext, libraryImportAttrType), SymbolKind.Method);
                 });
         }
 
-        private static void AnalyzeSymbol(SymbolAnalysisContext context, List<ITypeSymbol> knownUnsupportedTypes, INamedTypeSymbol? marshalAsAttrType, INamedTypeSymbol libraryImportAttrType)
+        private static void AnalyzeSymbol(SymbolAnalysisContext context, INamedTypeSymbol libraryImportAttrType)
         {
             var method = (IMethodSymbol)context.Symbol;
 
@@ -88,54 +77,70 @@ namespace Microsoft.Interop.Analyzers
                 }
             }
 
-            // Ignore methods with unsupported parameters
-            foreach (IParameterSymbol parameter in method.Parameters)
-            {
-                if (knownUnsupportedTypes.Contains(parameter.Type)
-                    || HasUnsupportedUnmanagedTypeValue(parameter.GetAttributes(), marshalAsAttrType))
-                {
-                    return;
-                }
-            }
-
             // Ignore methods with unsupported returns
             if (method.ReturnsByRef || method.ReturnsByRefReadonly)
                 return;
 
-            if (knownUnsupportedTypes.Contains(method.ReturnType) || HasUnsupportedUnmanagedTypeValue(method.GetReturnTypeAttributes(), marshalAsAttrType))
+            // Use the DllImport attribute data and the method signature to do some of the work the generator will do after conversion.
+            // If any diagnostics or failures to marshal are reported, then mark this diagnostic with a property signifying that it may require
+            // later user work.
+            AnyDiagnosticsSink diagnostics = new();
+            StubEnvironment env = context.Compilation.CreateStubEnvironment();
+            SignatureContext targetSignatureContext = SignatureContext.Create(method, CreateInteropAttributeDataFromDllImport(dllImportData), env, diagnostics, typeof(ConvertToLibraryImportAnalyzer).Assembly);
+
+            var generatorFactoryKey = LibraryImportGeneratorHelpers.CreateGeneratorFactory(env, new LibraryImportGeneratorOptions(context.Options.AnalyzerConfigOptionsProvider.GlobalOptions));
+
+            bool mayRequireAdditionalWork = diagnostics.AnyDiagnostics;
+            bool anyExplicitlyUnsupportedInfo = false;
+
+            var stubCodeContext = new ManagedToNativeStubCodeContext(env, "return", "nativeReturn");
+
+            var forwarder = new Forwarder();
+            // We don't actually need the bound generators. We just need them to be attempted to be bound to determine if the generator will be able to bind them.
+            _ = new BoundGenerators(targetSignatureContext.ElementTypeInformation, info =>
+            {
+                if (s_unsupportedTypeNames.Contains(info.ManagedType.FullTypeName))
+                {
+                    anyExplicitlyUnsupportedInfo = true;
+                    return forwarder;
+                }
+                if (HasUnsupportedMarshalAsInfo(info))
+                {
+                    anyExplicitlyUnsupportedInfo = true;
+                    return forwarder;
+                }
+                try
+                {
+                    return generatorFactoryKey.GeneratorFactory.Create(info, stubCodeContext);
+                }
+                catch (MarshallingNotSupportedException)
+                {
+                    mayRequireAdditionalWork = true;
+                    return forwarder;
+                }
+            });
+
+            if (anyExplicitlyUnsupportedInfo)
+            {
+                // If we have any parameters/return value with an explicitly unsupported marshal type or marshalling info,
+                // don't offer the fix. The amount of work for the user to get to pairity would be too expensive.
                 return;
+            }
 
             ImmutableDictionary<string, string>.Builder properties = ImmutableDictionary.CreateBuilder<string, string>();
 
             properties.Add(CharSet, dllImportData.CharacterSet.ToString());
             properties.Add(ExactSpelling, dllImportData.ExactSpelling.ToString());
+            properties.Add(MayRequireAdditionalWork, mayRequireAdditionalWork.ToString());
 
             context.ReportDiagnostic(method.CreateDiagnostic(ConvertToLibraryImport, properties.ToImmutable(), method.Name));
         }
 
-        private static bool HasUnsupportedUnmanagedTypeValue(ImmutableArray<AttributeData> attributes, INamedTypeSymbol? marshalAsAttrType)
+        private static bool HasUnsupportedMarshalAsInfo(TypePositionInfo info)
         {
-            if (marshalAsAttrType == null)
-                return false;
-
-            AttributeData? marshalAsAttr = null;
-            foreach (AttributeData attr in attributes)
-            {
-                if (SymbolEqualityComparer.Default.Equals(attr.AttributeClass, marshalAsAttrType))
-                {
-                    marshalAsAttr = attr;
-                    break;
-                }
-            }
-
-            if (marshalAsAttr == null || marshalAsAttr.ConstructorArguments.IsEmpty)
+            if (info.MarshallingAttributeInfo is not MarshalAsInfo(UnmanagedType unmanagedType, _))
                 return false;
 
-            object unmanagedTypeObj = marshalAsAttr.ConstructorArguments[0].Value!;
-            UnmanagedType unmanagedType = unmanagedTypeObj is short unmanagedTypeAsShort
-                ? (UnmanagedType)unmanagedTypeAsShort
-                : (UnmanagedType)unmanagedTypeObj;
-
             return !System.Enum.IsDefined(typeof(UnmanagedType), unmanagedType)
                 || unmanagedType == UnmanagedType.CustomMarshaler
                 || unmanagedType == UnmanagedType.Interface
@@ -144,5 +149,27 @@ namespace Microsoft.Interop.Analyzers
                 || unmanagedType == UnmanagedType.IUnknown
                 || unmanagedType == UnmanagedType.SafeArray;
         }
+
+        private static InteropAttributeData CreateInteropAttributeDataFromDllImport(DllImportData dllImportData)
+        {
+            InteropAttributeData interopData = new();
+            if (dllImportData.SetLastError)
+            {
+                interopData = interopData with { IsUserDefined = interopData.IsUserDefined | InteropAttributeMember.SetLastError, SetLastError = true };
+            }
+            if (dllImportData.CharacterSet != System.Runtime.InteropServices.CharSet.None)
+            {
+                // Treat all strings as UTF-16 for the purposes of determining if we can marshal the parameters of this signature. We'll handle a more accurate conversion in the fixer.
+                interopData = interopData with { IsUserDefined = interopData.IsUserDefined | InteropAttributeMember.StringMarshalling, StringMarshalling = StringMarshalling.Utf16 };
+            }
+            return interopData;
+        }
+
+        private sealed class AnyDiagnosticsSink : IGeneratorDiagnostics
+        {
+            public bool AnyDiagnostics { get; private set; }
+            public void ReportConfigurationNotSupported(AttributeData attributeData, string configurationName, string? unsupportedValue) => AnyDiagnostics = true;
+            public void ReportInvalidMarshallingAttributeInfo(AttributeData attributeData, string reasonResourceName, params string[] reasonArgs) => AnyDiagnostics = true;
+        }
     }
 }
index dab36a7..9ebbb56 100644 (file)
@@ -15,6 +15,7 @@ using Microsoft.CodeAnalysis.CodeActions;
 using Microsoft.CodeAnalysis.CodeFixes;
 using Microsoft.CodeAnalysis.CSharp;
 using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.DotnetRuntime.Extensions;
 using Microsoft.CodeAnalysis.Editing;
 using Microsoft.CodeAnalysis.FindSymbols;
 using static Microsoft.Interop.Analyzers.AnalyzerDiagnostics;
@@ -28,9 +29,10 @@ namespace Microsoft.Interop.Analyzers
 
         public override FixAllProvider GetFixAllProvider() => CustomFixAllProvider.Instance;
 
-        private const string ConvertToLibraryImportKey = "ConvertToLibraryImport";
-        private const string ConvertToLibraryImportWithASuffixKey = "ConvertToLibraryImportA";
-        private const string ConvertToLibraryImportWithWSuffixKey = "ConvertToLibraryImportW";
+        private const string ConvertToLibraryImportKey = "ConvertToLibraryImport,";
+        private static string AddSuffixKey(string baseKey, char suffix) => $"{baseKey}{suffix},";
+        private static string AddUnsafeKey(string baseKey) => baseKey + "AddUnsafe,";
+        private static string AddMayRequireAdditionalWorkKey(string baseKey) => baseKey + $"{ConvertToLibraryImportAnalyzer.MayRequireAdditionalWork},";
 
         private static readonly string[] s_preferredAttributeArgumentOrder =
             {
@@ -57,20 +59,19 @@ namespace Microsoft.Interop.Analyzers
             if (root.FindNode(context.Span) is not MethodDeclarationSyntax methodSyntax)
                 return;
 
-            // Register code fix
-            context.RegisterCodeFix(
-                CodeAction.Create(
-                    SR.ConvertToLibraryImport,
-                    cancelToken => ConvertToLibraryImport(
-                        context.Document,
-                        methodSyntax,
-                        entryPointSuffix: null,
-                        cancelToken),
-                    equivalenceKey: ConvertToLibraryImportKey),
-                context.Diagnostics);
+            bool enableUnsafe = doc.Project.CompilationOptions is CSharpCompilationOptions { AllowUnsafe: false };
+
+            ICodeActionFactory codeActionFactory = enableUnsafe ? new AddUnsafeCodeActionFactory() : new DefaultCodeActionFactory();
 
             foreach (Diagnostic diagnostic in context.Diagnostics)
             {
+                bool mayRequireAdditionalWork = bool.TryParse(diagnostic.Properties[ConvertToLibraryImportAnalyzer.MayRequireAdditionalWork], out bool mayRequireAdditionalWorkValue)
+                    ? mayRequireAdditionalWorkValue
+                    : false;
+                // Register code fix
+                context.RegisterCodeFix(
+                    codeActionFactory.CreateConvertToLibraryImportCodeFix(doc, methodSyntax, mayRequireAdditionalWork),
+                    context.Diagnostics);
                 if (!bool.Parse(diagnostic.Properties[ConvertToLibraryImportAnalyzer.ExactSpelling]))
                 {
                     CharSet charSet = (CharSet)Enum.Parse(typeof(CharSet), diagnostic.Properties[ConvertToLibraryImportAnalyzer.CharSet]);
@@ -81,96 +82,203 @@ namespace Microsoft.Interop.Analyzers
                     if (charSet is CharSet.None or CharSet.Ansi or CharSet.Auto)
                     {
                         context.RegisterCodeFix(
-                            CodeAction.Create(
-                                string.Format(SR.ConvertToLibraryImportWithSuffix, "A"),
-                                cancelToken => ConvertToLibraryImport(
-                                    context.Document,
-                                    methodSyntax,
-                                    entryPointSuffix: 'A',
-                                    cancelToken),
-                                equivalenceKey: ConvertToLibraryImportWithASuffixKey),
+                            codeActionFactory.CreateConvertToLibraryImportWithSuffixCodeFix(doc, methodSyntax, 'A', mayRequireAdditionalWork),
                             context.Diagnostics);
                     }
                     if (charSet is CharSet.Unicode or CharSet.Auto)
                     {
                         context.RegisterCodeFix(
-                            CodeAction.Create(
-                                string.Format(SR.ConvertToLibraryImportWithSuffix, "W"),
-                                cancelToken => ConvertToLibraryImport(
-                                    context.Document,
-                                    methodSyntax,
-                                    entryPointSuffix: 'W',
-                                    cancelToken),
-                                equivalenceKey: ConvertToLibraryImportWithWSuffixKey),
+                            codeActionFactory.CreateConvertToLibraryImportWithSuffixCodeFix(doc, methodSyntax, 'W', mayRequireAdditionalWork),
                             context.Diagnostics);
                     }
                 }
             }
         }
 
-        private sealed class CustomFixAllProvider : DocumentBasedFixAllProvider
+        internal interface ICodeActionFactory
         {
-            public static readonly CustomFixAllProvider Instance = new();
+            CodeAction CreateConvertToLibraryImportCodeFix(Document document, MethodDeclarationSyntax methodSyntax, bool mayRequireAdditionalWork);
+            CodeAction CreateConvertToLibraryImportWithSuffixCodeFix(Document document, MethodDeclarationSyntax methodSyntax, char suffix, bool mayRequireAdditionalWork);
+        }
 
-            protected override async Task<Document?> FixAllAsync(FixAllContext fixAllContext, Document document, ImmutableArray<Diagnostic> diagnostics)
+        private sealed class DefaultCodeActionFactory : ICodeActionFactory
+        {
+            public CodeAction CreateConvertToLibraryImportCodeFix(Document document, MethodDeclarationSyntax methodSyntax, bool mayRequireAdditionalWork)
             {
-                DocumentEditor editor = await DocumentEditor.CreateAsync(document, fixAllContext.CancellationToken).ConfigureAwait(false);
-                SyntaxGenerator generator = editor.Generator;
+                return CodeAction.Create(
+                    SR.ConvertToLibraryImport,
+                    cancelToken => ConvertToLibraryImport(
+                        document,
+                        methodSyntax,
+                        mayRequireAdditionalWork,
+                        entryPointSuffix: null,
+                        cancelToken),
+                    equivalenceKey: mayRequireAdditionalWork ? AddMayRequireAdditionalWorkKey(ConvertToLibraryImportKey) : ConvertToLibraryImportKey);
+            }
 
-                SyntaxNode? root = await document.GetSyntaxRootAsync(fixAllContext.CancellationToken).ConfigureAwait(false);
-                if (root == null)
-                    return document;
+            public CodeAction CreateConvertToLibraryImportWithSuffixCodeFix(Document document, MethodDeclarationSyntax methodSyntax, char suffix, bool mayRequireAdditionalWork)
+            {
+                return CodeAction.Create(
+                                string.Format(SR.ConvertToLibraryImportWithSuffix, suffix),
+                                cancelToken => ConvertToLibraryImport(
+                                    document,
+                                    methodSyntax,
+                                    mayRequireAdditionalWork,
+                                    entryPointSuffix: suffix,
+                                    cancelToken),
+                                equivalenceKey: AddSuffixKey(mayRequireAdditionalWork ? AddMayRequireAdditionalWorkKey(ConvertToLibraryImportKey) : ConvertToLibraryImportKey, suffix));
+            }
+        }
 
-                foreach (Diagnostic diagnostic in diagnostics)
-                {
-                    // Get the syntax node tied to the diagnostic and check that it is a method declaration
-                    if (root.FindNode(diagnostic.Location.SourceSpan) is not MethodDeclarationSyntax methodSyntax)
-                        continue;
-                    if (editor.SemanticModel.GetDeclaredSymbol(methodSyntax, fixAllContext.CancellationToken) is not IMethodSymbol methodSymbol)
-                        continue;
+        private sealed class AddUnsafeCodeActionFactory : ICodeActionFactory
+        {
+            public CodeAction CreateConvertToLibraryImportCodeFix(Document document, MethodDeclarationSyntax methodSyntax, bool mayRequireAdditionalWork)
+            {
+                return CodeAction.Create(
+                    SR.ConvertToLibraryImportAddUnsafe,
+                    async cancelToken =>
+                    {
+                        Solution solutionWithUnsafe = AddUnsafe(document.Project.Solution, document.Project);
+                        SolutionEditor editor = new SolutionEditor(solutionWithUnsafe);
+
+                        await ConvertToLibraryImport(
+                            await editor.GetDocumentEditorAsync(document.Id, cancelToken).ConfigureAwait(false),
+                            methodSyntax,
+                            mayRequireAdditionalWork,
+                            entryPointSuffix: null,
+                            cancelToken).ConfigureAwait(false);
+                        return editor.GetChangedSolution();
+                    },
+                    equivalenceKey: AddUnsafeKey(mayRequireAdditionalWork ? AddMayRequireAdditionalWorkKey(ConvertToLibraryImportKey) : ConvertToLibraryImportKey));
+            }
 
-                    SyntaxNode generatedDeclaration = await ConvertMethodDeclarationToLibraryImport(methodSyntax, editor, generator, methodSymbol, GetSuffixFromEquivalenceKey(fixAllContext.CodeActionEquivalenceKey), fixAllContext.CancellationToken).ConfigureAwait(false);
+            public CodeAction CreateConvertToLibraryImportWithSuffixCodeFix(Document document, MethodDeclarationSyntax methodSyntax, char suffix, bool mayRequireAdditionalWork)
+            {
+                return CodeAction.Create(
+                    string.Format(SR.ConvertToLibraryImportWithSuffixAddUnsafe, suffix),
+                    async cancelToken =>
+                    {
+                        Solution solutionWithUnsafe = AddUnsafe(document.Project.Solution, document.Project);
+                        SolutionEditor editor = new SolutionEditor(solutionWithUnsafe);
+
+                        await ConvertToLibraryImport(
+                            await editor.GetDocumentEditorAsync(document.Id, cancelToken).ConfigureAwait(false),
+                            methodSyntax,
+                            mayRequireAdditionalWork,
+                            entryPointSuffix: suffix,
+                            cancelToken).ConfigureAwait(false);
+                        return editor.GetChangedSolution();
+                    },
+                    equivalenceKey: AddUnsafeKey(AddSuffixKey(mayRequireAdditionalWork ? AddMayRequireAdditionalWorkKey(ConvertToLibraryImportKey) : ConvertToLibraryImportKey, suffix)));
+            }
+        }
 
-                    if (!methodSymbol.MethodImplementationFlags.HasFlag(System.Reflection.MethodImplAttributes.PreserveSig))
+        private sealed class CustomFixAllProvider : FixAllProvider
+        {
+            public static readonly CustomFixAllProvider Instance = new();
+            public override async Task<CodeAction?> GetFixAsync(FixAllContext fixAllContext)
+            {
+                bool addUnsafe = fixAllContext.CodeActionEquivalenceKey.Contains(",AddUnsafe,");
+                bool includeFixesWithAdditionalWork = fixAllContext.CodeActionEquivalenceKey.Contains($",{ConvertToLibraryImportAnalyzer.MayRequireAdditionalWork},");
+                ImmutableArray<Diagnostic> diagnosticsInScope = await fixAllContext.GetDiagnosticsInScopeAsync().ConfigureAwait(false);
+
+                return CodeAction.Create(addUnsafe ? SR.ConvertToLibraryImportAddUnsafe : SR.ConvertToLibraryImport,
+                    async ct =>
                     {
-                        bool shouldWarn = await TransformCallersOfNoPreserveSigMethod(editor, methodSymbol, fixAllContext.CancellationToken).ConfigureAwait(false);
-                        if (shouldWarn)
+                        HashSet<Project> projectsToAddUnsafe = new();
+                        SolutionEditor solutionEditor = new SolutionEditor(fixAllContext.Solution);
+
+                        foreach (var diagnostic in diagnosticsInScope)
                         {
-                            generatedDeclaration = generatedDeclaration.WithAdditionalAnnotations(WarningAnnotation.Create(SR.ConvertNoPreserveSigDllImportToGeneratedMayProduceInvalidCode));
-                        }
-                    }
+                            bool mayRequireAdditionalWork = bool.TryParse(diagnostic.Properties[ConvertToLibraryImportAnalyzer.MayRequireAdditionalWork], out bool mayRequireAdditionalWorkValue)
+                                ? mayRequireAdditionalWorkValue
+                                : false;
+                            if (mayRequireAdditionalWork && !includeFixesWithAdditionalWork)
+                            {
+                                // Don't fix any diagnostics that require additional work if the "fix all" command wasn't triggered from a location
+                                // that was able to warn the user that additional work may be required.
+                                continue;
+                            }
+                            DocumentId documentId = solutionEditor.OriginalSolution.GetDocumentId(diagnostic.Location.SourceTree)!;
+                            DocumentEditor editor = await solutionEditor.GetDocumentEditorAsync(documentId, ct).ConfigureAwait(false);
+                            SyntaxNode root = await diagnostic.Location.SourceTree.GetRootAsync(ct).ConfigureAwait(false);
 
-                    // Replace the original method with the updated one
-                    editor.ReplaceNode(methodSyntax, generatedDeclaration);
+                            // Get the syntax node tied to the diagnostic and check that it is a method declaration
+                            if (root.FindNode(diagnostic.Location.SourceSpan) is not MethodDeclarationSyntax methodSyntax)
+                                continue;
 
-                    MakeEnclosingTypesPartial(editor, methodSyntax);
-                }
+                            await ConvertToLibraryImport(editor, methodSyntax, mayRequireAdditionalWork, GetSuffixFromEquivalenceKey(fixAllContext.CodeActionEquivalenceKey), ct).ConfigureAwait(false);
+
+                            // Record this project as a project we need to allow unsafe blocks in.
+                            projectsToAddUnsafe.Add(solutionEditor.OriginalSolution.GetDocument(documentId).Project);
+                        }
+
+                        Solution solutionWithUpdatedSources = solutionEditor.GetChangedSolution();
 
-                return editor.GetChangedDocument();
+                        if (addUnsafe)
+                        {
+                            foreach (var project in projectsToAddUnsafe)
+                            {
+                                solutionWithUpdatedSources = AddUnsafe(solutionWithUpdatedSources, project);
+                            }
+                        }
+                        return solutionWithUpdatedSources;
+                    },
+                    equivalenceKey: fixAllContext.CodeActionEquivalenceKey);
             }
 
-            private static char? GetSuffixFromEquivalenceKey(string equivalenceKey) => equivalenceKey switch
+
+            private static char? GetSuffixFromEquivalenceKey(string equivalenceKey)
             {
-                ConvertToLibraryImportWithASuffixKey => 'A',
-                ConvertToLibraryImportWithWSuffixKey => 'W',
-                _ => null
-            };
+                if (equivalenceKey.Contains(",A,"))
+                {
+                    return 'A';
+                }
+                if (equivalenceKey.Contains(",W,"))
+                {
+                    return 'W';
+                }
+                return null;
+            }
+        }
+
+        private static Solution AddUnsafe(Solution solution, Project project)
+        {
+            return solution.WithProjectCompilationOptions(project.Id, ((CSharpCompilationOptions)project.CompilationOptions).WithAllowUnsafe(true));
         }
 
         private static async Task<Document> ConvertToLibraryImport(
             Document doc,
             MethodDeclarationSyntax methodSyntax,
+            bool warnForAdditionalWork,
             char? entryPointSuffix,
             CancellationToken cancellationToken)
         {
             DocumentEditor editor = await DocumentEditor.CreateAsync(doc, cancellationToken).ConfigureAwait(false);
+            await ConvertToLibraryImport(editor, methodSyntax, warnForAdditionalWork, entryPointSuffix, cancellationToken).ConfigureAwait(false);
+            return editor.GetChangedDocument();
+        }
 
+        private static async Task ConvertToLibraryImport(
+            DocumentEditor editor,
+            MethodDeclarationSyntax methodSyntax,
+            bool warnForAdditionalWork,
+            char? entryPointSuffix,
+            CancellationToken cancellationToken)
+        {
             SyntaxGenerator generator = editor.Generator;
 
             if (editor.SemanticModel.GetDeclaredSymbol(methodSyntax, cancellationToken) is not IMethodSymbol methodSymbol)
-                return doc;
+                return;
 
-            SyntaxNode generatedDeclaration = await ConvertMethodDeclarationToLibraryImport(methodSyntax, editor, generator, methodSymbol, entryPointSuffix, cancellationToken).ConfigureAwait(false);
+            SyntaxNode generatedDeclaration = await ConvertMethodDeclarationToLibraryImport(
+                methodSyntax,
+                editor,
+                generator,
+                methodSymbol,
+                warnForAdditionalWork,
+                entryPointSuffix,
+                cancellationToken).ConfigureAwait(false);
 
             if (!methodSymbol.MethodImplementationFlags.HasFlag(System.Reflection.MethodImplAttributes.PreserveSig))
             {
@@ -185,8 +293,6 @@ namespace Microsoft.Interop.Analyzers
             editor.ReplaceNode(methodSyntax, generatedDeclaration);
 
             MakeEnclosingTypesPartial(editor, methodSyntax);
-
-            return editor.GetChangedDocument();
         }
 
         private static async Task<SyntaxNode> ConvertMethodDeclarationToLibraryImport(
@@ -194,6 +300,7 @@ namespace Microsoft.Interop.Analyzers
             DocumentEditor editor,
             SyntaxGenerator generator,
             IMethodSymbol methodSymbol,
+            bool warnForAdditionalWork,
             char? entryPointSuffix,
             CancellationToken cancellationToken)
         {
@@ -221,8 +328,11 @@ namespace Microsoft.Interop.Analyzers
                 out SyntaxNode? unmanagedCallConvAttributeMaybe);
 
             // Add annotation about potential behavioural and compatibility changes
-            libraryImportSyntax = libraryImportSyntax.WithAdditionalAnnotations(
-                WarningAnnotation.Create(string.Format(SR.ConvertToLibraryImportWarning, "[TODO] Documentation link")));
+            if (warnForAdditionalWork)
+            {
+                libraryImportSyntax = libraryImportSyntax.WithAdditionalAnnotations(
+                    WarningAnnotation.Create(SR.ConvertToLibraryImportMayRequireCustomMarshalling));
+            }
 
             // Replace DllImport with LibraryImport
             SyntaxNode generatedDeclaration = generator.ReplaceNode(methodSyntax, dllImportSyntax, libraryImportSyntax);
@@ -475,15 +585,27 @@ namespace Microsoft.Interop.Analyzers
                     {
                         // For Unicode, we can translate the argument to StringMarshalling.Utf16
                         // TODO: Handle ANSI once we have a public marshaller type for ANSI strings that we can use with StringMarshallerCustomType
-                        if (dllImportData.CharacterSet == CharSet.Unicode)
+                        if (dllImportData.CharacterSet == CharSet.Unicode || (dllImportData.CharacterSet == CharSet.Auto && entryPointSuffix is 'W' or null))
                         {
-                            ITypeSymbol stringMarshallingType = editor.SemanticModel.Compilation.GetTypeByMetadataName(TypeNames.StringMarshalling)!;
+                            ITypeSymbol stringMarshallingType = editor.SemanticModel.Compilation.GetBestTypeByMetadataName(TypeNames.StringMarshalling)!;
                             argumentsToAdd.Add(generator.AttributeArgument(
                                 nameof(StringMarshalling),
                                 generator.MemberAccessExpression(
                                     generator.TypeExpression(stringMarshallingType),
                                     generator.IdentifierName(nameof(StringMarshalling.Utf16)))));
                         }
+                        else if (dllImportData.CharacterSet == CharSet.Ansi || (dllImportData.CharacterSet == CharSet.Auto && entryPointSuffix == 'A'))
+                        {
+                            ITypeSymbol stringMarshallingType = editor.SemanticModel.Compilation.GetBestTypeByMetadataName(TypeNames.StringMarshalling)!;
+                            argumentsToAdd.Add(generator.AttributeArgument(
+                                nameof(StringMarshalling),
+                                generator.MemberAccessExpression(
+                                    generator.TypeExpression(stringMarshallingType),
+                                    generator.IdentifierName(nameof(StringMarshalling.Custom)))));
+                            argumentsToAdd.Add(generator.AttributeArgument(
+                                "StringMarshallingCustomType",
+                                generator.TypeOfExpression(generator.TypeExpression(editor.SemanticModel.Compilation.GetBestTypeByMetadataName(TypeNames.AnsiStringMarshaller)))));
+                        }
                     }
 
                     argumentsToRemove.Add(argument);
diff --git a/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/Analyzers/FixAllContextExtensions.cs b/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/Analyzers/FixAllContextExtensions.cs
new file mode 100644 (file)
index 0000000..b41ab48
--- /dev/null
@@ -0,0 +1,75 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CodeFixes;
+
+namespace Microsoft.Interop.Analyzers
+{
+    internal static class FixAllContextExtensions
+    {
+        public static async Task<ImmutableArray<Diagnostic>> GetDiagnosticsInScopeAsync(this FixAllContext context)
+        {
+            switch (context.Scope)
+            {
+                case FixAllScope.Document:
+                    return await context.GetDocumentDiagnosticsAsync(context.Document).ConfigureAwait(false);
+                case FixAllScope.Project:
+                    return await context.GetAllDiagnosticsAsync(context.Project).ConfigureAwait(false);
+                case FixAllScope.Solution:
+                    Solution solution = context.Solution;
+                    ProjectDependencyGraph dependencyGraph = solution.GetProjectDependencyGraph();
+
+                    // Walk through each project in topological order, determining and applying the diagnostics for each
+                    // project.  We do this in topological order so that the compilations for successive projects are readily
+                    // available as we just computed them for dependent projects.  If we were to do it out of order, we might
+                    // start with a project that has a ton of dependencies, and we'd spend an inordinate amount of time just
+                    // building the compilations for it before we could proceed.
+                    //
+                    // By processing one project at a time, we can also let go of a project once done with it, allowing us to
+                    // reclaim lots of the memory so we don't overload the system while processing a large solution.
+                    //
+                    // Note: we have to filter down to projects of the same language as the FixAllContext points at a
+                    // CodeFixProvider, and we can't call into providers of different languages with diagnostics from a
+                    // different language.
+                    IEnumerable<Project?> sortedProjects = dependencyGraph.GetTopologicallySortedProjects(context.CancellationToken)
+                                                        .Select(solution.GetProject)
+                                                        .Where(p => p.Language == context.Project.Language);
+                    return (await Task.WhenAll(sortedProjects.Select(context.GetAllDiagnosticsAsync)).ConfigureAwait(false)).SelectMany(diag => diag).ToImmutableArray();
+                default:
+                    return ImmutableArray<Diagnostic>.Empty;
+            }
+        }
+
+        public static async Task<ImmutableArray<Project>> GetProjectsWithDiagnosticsAsync(this FixAllContext context)
+        {
+            switch (context.Scope)
+            {
+                case FixAllScope.ContainingMember:
+                case FixAllScope.ContainingType:
+                case FixAllScope.Document:
+                case FixAllScope.Project:
+                    return ImmutableArray.Create(context.Project);
+                case FixAllScope.Solution:
+                    ImmutableArray<Project>.Builder projectsWithDiagnostics = ImmutableArray.CreateBuilder<Project>();
+                    foreach (var project in context.Solution.Projects)
+                    {
+                        ImmutableArray<Diagnostic> diagnostics = await context.GetAllDiagnosticsAsync(project).ConfigureAwait(false);
+                        if (diagnostics.Length != 0)
+                        {
+                            projectsWithDiagnostics.Add(project);
+                        }
+                    }
+                    return projectsWithDiagnostics.ToImmutable();
+                default:
+                    return ImmutableArray<Project>.Empty;
+            }
+        }
+    }
+}
index 27a4413..1da4dbd 100644 (file)
@@ -327,65 +327,10 @@ namespace Microsoft.Interop
                 new MethodSignatureDiagnosticLocations(originalSyntax),
                 additionalAttributes.ToImmutableArray(),
                 libraryImportData,
-                CreateGeneratorFactory(environment, options),
+                LibraryImportGeneratorHelpers.CreateGeneratorFactory(environment, options),
                 generatorDiagnostics.Diagnostics.ToImmutableArray());
         }
 
-        private static MarshallingGeneratorFactoryKey<(TargetFramework, Version, LibraryImportGeneratorOptions)> CreateGeneratorFactory(StubEnvironment env, LibraryImportGeneratorOptions options)
-        {
-            IMarshallingGeneratorFactory generatorFactory;
-
-            if (options.GenerateForwarders)
-            {
-                generatorFactory = new ForwarderMarshallingGeneratorFactory();
-            }
-            else
-            {
-                if (env.TargetFramework != TargetFramework.Net || env.TargetFrameworkVersion.Major < 7)
-                {
-                    // If we're using our downstream support, fall back to the Forwarder marshaller when the TypePositionInfo is unhandled.
-                    generatorFactory = new ForwarderMarshallingGeneratorFactory();
-                }
-                else
-                {
-                    // If we're in a "supported" scenario, then emit a diagnostic as our final fallback.
-                    generatorFactory = new UnsupportedMarshallingFactory();
-                }
-
-                generatorFactory = new NoMarshallingInfoErrorMarshallingFactory(generatorFactory);
-
-                // The presence of System.Runtime.CompilerServices.DisableRuntimeMarshallingAttribute is tied to TFM,
-                // so we use TFM in the generator factory key instead of the Compilation as the compilation changes on every keystroke.
-                IAssemblySymbol coreLibraryAssembly = env.Compilation.GetSpecialType(SpecialType.System_Object).ContainingAssembly;
-                ITypeSymbol? disabledRuntimeMarshallingAttributeType = coreLibraryAssembly.GetTypeByMetadataName(TypeNames.System_Runtime_CompilerServices_DisableRuntimeMarshallingAttribute);
-                bool runtimeMarshallingDisabled = disabledRuntimeMarshallingAttributeType is not null
-                    && env.Compilation.Assembly.GetAttributes().Any(attr => SymbolEqualityComparer.Default.Equals(attr.AttributeClass, disabledRuntimeMarshallingAttributeType));
-
-                // Since the char type can go into the P/Invoke signature here, we can only use it when
-                // runtime marshalling is disabled.
-                generatorFactory = new CharMarshallingGeneratorFactory(generatorFactory, useBlittableMarshallerForUtf16: runtimeMarshallingDisabled);
-
-                InteropGenerationOptions interopGenerationOptions = new(options.UseMarshalType);
-                generatorFactory = new MarshalAsMarshallingGeneratorFactory(interopGenerationOptions, generatorFactory);
-
-                IMarshallingGeneratorFactory elementFactory = new AttributedMarshallingModelGeneratorFactory(
-                    // Since the char type in an array will not be part of the P/Invoke signature, we can
-                    // use the regular blittable marshaller in all cases.
-                    new CharMarshallingGeneratorFactory(generatorFactory, useBlittableMarshallerForUtf16: true),
-                    new AttributedMarshallingModelOptions(runtimeMarshallingDisabled, MarshalMode.ElementIn, MarshalMode.ElementRef, MarshalMode.ElementOut));
-                // We don't need to include the later generator factories for collection elements
-                // as the later generator factories only apply to parameters.
-                generatorFactory = new AttributedMarshallingModelGeneratorFactory(
-                    generatorFactory,
-                    elementFactory,
-                    new AttributedMarshallingModelOptions(runtimeMarshallingDisabled, MarshalMode.ManagedToUnmanagedIn, MarshalMode.ManagedToUnmanagedRef, MarshalMode.ManagedToUnmanagedOut));
-
-                generatorFactory = new ByValueContentsMarshalKindValidator(generatorFactory);
-            }
-
-            return MarshallingGeneratorFactoryKey.Create((env.TargetFramework, env.TargetFrameworkVersion, options), generatorFactory);
-        }
-
         private static (MemberDeclarationSyntax, ImmutableArray<Diagnostic>) GenerateSource(
             IncrementalStubGenerationContext pinvokeStub,
             LibraryImportGeneratorOptions options)
diff --git a/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/LibraryImportGeneratorHelpers.cs b/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/LibraryImportGeneratorHelpers.cs
new file mode 100644 (file)
index 0000000..80e48ed
--- /dev/null
@@ -0,0 +1,69 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Microsoft.CodeAnalysis;
+
+namespace Microsoft.Interop
+{
+    internal static class LibraryImportGeneratorHelpers
+    {
+        public static MarshallingGeneratorFactoryKey<(TargetFramework, Version, LibraryImportGeneratorOptions)> CreateGeneratorFactory(StubEnvironment env, LibraryImportGeneratorOptions options)
+        {
+            IMarshallingGeneratorFactory generatorFactory;
+
+            if (options.GenerateForwarders)
+            {
+                generatorFactory = new ForwarderMarshallingGeneratorFactory();
+            }
+            else
+            {
+                if (env.TargetFramework != TargetFramework.Net || env.TargetFrameworkVersion.Major < 7)
+                {
+                    // If we're using our downstream support, fall back to the Forwarder marshaller when the TypePositionInfo is unhandled.
+                    generatorFactory = new ForwarderMarshallingGeneratorFactory();
+                }
+                else
+                {
+                    // If we're in a "supported" scenario, then emit a diagnostic as our final fallback.
+                    generatorFactory = new UnsupportedMarshallingFactory();
+                }
+
+                generatorFactory = new NoMarshallingInfoErrorMarshallingFactory(generatorFactory);
+
+                // The presence of System.Runtime.CompilerServices.DisableRuntimeMarshallingAttribute is tied to TFM,
+                // so we use TFM in the generator factory key instead of the Compilation as the compilation changes on every keystroke.
+                IAssemblySymbol coreLibraryAssembly = env.Compilation.GetSpecialType(SpecialType.System_Object).ContainingAssembly;
+                ITypeSymbol? disabledRuntimeMarshallingAttributeType = coreLibraryAssembly.GetTypeByMetadataName(TypeNames.System_Runtime_CompilerServices_DisableRuntimeMarshallingAttribute);
+                bool runtimeMarshallingDisabled = disabledRuntimeMarshallingAttributeType is not null
+                    && env.Compilation.Assembly.GetAttributes().Any(attr => SymbolEqualityComparer.Default.Equals(attr.AttributeClass, disabledRuntimeMarshallingAttributeType));
+
+                // Since the char type can go into the P/Invoke signature here, we can only use it when
+                // runtime marshalling is disabled.
+                generatorFactory = new CharMarshallingGeneratorFactory(generatorFactory, useBlittableMarshallerForUtf16: runtimeMarshallingDisabled);
+
+                InteropGenerationOptions interopGenerationOptions = new(options.UseMarshalType);
+                generatorFactory = new MarshalAsMarshallingGeneratorFactory(interopGenerationOptions, generatorFactory);
+
+                IMarshallingGeneratorFactory elementFactory = new AttributedMarshallingModelGeneratorFactory(
+                    // Since the char type in an array will not be part of the P/Invoke signature, we can
+                    // use the regular blittable marshaller in all cases.
+                    new CharMarshallingGeneratorFactory(generatorFactory, useBlittableMarshallerForUtf16: true),
+                    new AttributedMarshallingModelOptions(runtimeMarshallingDisabled, MarshalMode.ElementIn, MarshalMode.ElementRef, MarshalMode.ElementOut));
+                // We don't need to include the later generator factories for collection elements
+                // as the later generator factories only apply to parameters.
+                generatorFactory = new AttributedMarshallingModelGeneratorFactory(
+                    generatorFactory,
+                    elementFactory,
+                    new AttributedMarshallingModelOptions(runtimeMarshallingDisabled, MarshalMode.ManagedToUnmanagedIn, MarshalMode.ManagedToUnmanagedRef, MarshalMode.ManagedToUnmanagedOut));
+
+                generatorFactory = new ByValueContentsMarshalKindValidator(generatorFactory);
+            }
+
+            return MarshallingGeneratorFactoryKey.Create((env.TargetFramework, env.TargetFrameworkVersion, options), generatorFactory);
+        }
+    }
+}
index 8ffbfe9..68a8584 100644 (file)
   <data name="ConvertToLibraryImportTitle" xml:space="preserve">
     <value>Use 'LibraryImportAttribute' instead of 'DllImportAttribute' to generate P/Invoke marshalling code at compile time</value>
   </data>
-  <data name="ConvertToLibraryImportWarning" xml:space="preserve">
-    <value>Conversion to 'LibraryImport' may change behavior and compatibility. See {0} for more information.</value>
-    <comment>{0} is a documentation link</comment>
-  </data>
   <data name="ConvertToLibraryImportWithSuffix" xml:space="preserve">
     <value>Convert to 'LibraryImport' with '{0}' suffix</value>
   </data>
   <data name="RequiresAllowUnsafeBlocksTitle" xml:space="preserve">
     <value>LibraryImportAttribute requires unsafe code.</value>
   </data>
+  <data name="ConvertToLibraryImportAddUnsafe" xml:space="preserve">
+    <value>Convert to 'LibraryImport' and enable unsafe code</value>
+  </data>
+  <data name="ConvertToLibraryImportMayRequireCustomMarshalling" xml:space="preserve">
+    <value>Converting this API to 'LibraryImport' will require additional code to provide custom marshallers for some parameters.</value>
+  </data>
+  <data name="ConvertToLibraryImportWithSuffixAddUnsafe" xml:space="preserve">
+    <value>Convert to 'LibraryImport' with '{0}' suffix and enable unsafe code</value>
+  </data>
 </root>
\ No newline at end of file
index f764acb..6347b0a 100644 (file)
         <target state="translated">Převést na LibraryImport</target>
         <note />
       </trans-unit>
+      <trans-unit id="ConvertToLibraryImportAddUnsafe">
+        <source>Convert to 'LibraryImport' and enable unsafe code</source>
+        <target state="new">Convert to 'LibraryImport' and enable unsafe code</target>
+        <note />
+      </trans-unit>
       <trans-unit id="ConvertToLibraryImportDescription">
         <source>Use 'LibraryImportAttribute' instead of 'DllImportAttribute' to generate P/Invoke marshalling code at compile time</source>
         <target state="translated">K vygenerování kódu zařazování P/Invoke v době kompilace použijte LibraryImportAttribute místo DllImportAttribute</target>
         <note />
       </trans-unit>
+      <trans-unit id="ConvertToLibraryImportMayRequireCustomMarshalling">
+        <source>Converting this API to 'LibraryImport' will require additional code to provide custom marshallers for some parameters.</source>
+        <target state="new">Converting this API to 'LibraryImport' will require additional code to provide custom marshallers for some parameters.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="ConvertToLibraryImportMessage">
         <source>Mark the method '{0}' with 'LibraryImportAttribute' instead of 'DllImportAttribute' to generate P/Invoke marshalling code at compile time</source>
         <target state="translated">Označit metodu {0} pomocí LibraryImportAttribute místo DllImportAttribute, aby došlo k vygenerování kódu zařazování volání P/Invoke za kompilace.</target>
         <target state="translated">K vygenerování kódu zařazování P/Invoke v době kompilace použijte LibraryImportAttribute místo DllImportAttribute</target>
         <note />
       </trans-unit>
-      <trans-unit id="ConvertToLibraryImportWarning">
-        <source>Conversion to 'LibraryImport' may change behavior and compatibility. See {0} for more information.</source>
-        <target state="translated">Převod na LibraryImport může vést ke změně chování a kompatibility. Zjistěte více informací přechodem na {0}.</target>
-        <note>{0} is a documentation link</note>
-      </trans-unit>
       <trans-unit id="ConvertToLibraryImportWithSuffix">
         <source>Convert to 'LibraryImport' with '{0}' suffix</source>
         <target state="translated">Převést na LibraryImport s příponou {0}</target>
         <note />
       </trans-unit>
+      <trans-unit id="ConvertToLibraryImportWithSuffixAddUnsafe">
+        <source>Convert to 'LibraryImport' with '{0}' suffix and enable unsafe code</source>
+        <target state="new">Convert to 'LibraryImport' with '{0}' suffix and enable unsafe code</target>
+        <note />
+      </trans-unit>
       <trans-unit id="CustomMarshallerTypeMustHaveRequiredShapeTitle">
         <source>Marshaller type does not have the required shape</source>
         <target state="translated">Zařazovací typ nemá požadovaný tvar</target>
index 437754b..7b1264c 100644 (file)
         <target state="translated">In \"LibraryImport\" konvertieren</target>
         <note />
       </trans-unit>
+      <trans-unit id="ConvertToLibraryImportAddUnsafe">
+        <source>Convert to 'LibraryImport' and enable unsafe code</source>
+        <target state="new">Convert to 'LibraryImport' and enable unsafe code</target>
+        <note />
+      </trans-unit>
       <trans-unit id="ConvertToLibraryImportDescription">
         <source>Use 'LibraryImportAttribute' instead of 'DllImportAttribute' to generate P/Invoke marshalling code at compile time</source>
         <target state="translated">Verwenden Sie \"LibraryImportAttribute\" anstelle von \"DllImportAttribute\", um P/Invoke-Marshallingcode zur Kompilierzeit zu generieren.</target>
         <note />
       </trans-unit>
+      <trans-unit id="ConvertToLibraryImportMayRequireCustomMarshalling">
+        <source>Converting this API to 'LibraryImport' will require additional code to provide custom marshallers for some parameters.</source>
+        <target state="new">Converting this API to 'LibraryImport' will require additional code to provide custom marshallers for some parameters.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="ConvertToLibraryImportMessage">
         <source>Mark the method '{0}' with 'LibraryImportAttribute' instead of 'DllImportAttribute' to generate P/Invoke marshalling code at compile time</source>
         <target state="translated">Markieren Sie die Methode \"{0}\" mit \"LibraryImportAttribute\" anstelle von \"DllImportAttribute\", um zur Kompilierzeit P/Invoke-Marshallingcode zu generieren.</target>
         <target state="translated">Verwenden Sie \"LibraryImportAttribute\" anstelle von \"DllImportAttribute\", um P/Invoke-Marshallingcode zur Kompilierzeit zu generieren.</target>
         <note />
       </trans-unit>
-      <trans-unit id="ConvertToLibraryImportWarning">
-        <source>Conversion to 'LibraryImport' may change behavior and compatibility. See {0} for more information.</source>
-        <target state="translated">Die Konvertierung in \"LibraryImport\" kann das Verhalten und die Kompatibilität ändern. Weitere Informationen finden Sie unter {0}.</target>
-        <note>{0} is a documentation link</note>
-      </trans-unit>
       <trans-unit id="ConvertToLibraryImportWithSuffix">
         <source>Convert to 'LibraryImport' with '{0}' suffix</source>
         <target state="translated">In \"LibraryImport\" mit Suffix \"{0}\" konvertieren</target>
         <note />
       </trans-unit>
+      <trans-unit id="ConvertToLibraryImportWithSuffixAddUnsafe">
+        <source>Convert to 'LibraryImport' with '{0}' suffix and enable unsafe code</source>
+        <target state="new">Convert to 'LibraryImport' with '{0}' suffix and enable unsafe code</target>
+        <note />
+      </trans-unit>
       <trans-unit id="CustomMarshallerTypeMustHaveRequiredShapeTitle">
         <source>Marshaller type does not have the required shape</source>
         <target state="translated">Der Marshaller-Typ weist nicht die erforderliche Form auf</target>
index 49d0ef5..b1f0436 100644 (file)
         <target state="translated">Convertir en “LibraryImport”</target>
         <note />
       </trans-unit>
+      <trans-unit id="ConvertToLibraryImportAddUnsafe">
+        <source>Convert to 'LibraryImport' and enable unsafe code</source>
+        <target state="new">Convert to 'LibraryImport' and enable unsafe code</target>
+        <note />
+      </trans-unit>
       <trans-unit id="ConvertToLibraryImportDescription">
         <source>Use 'LibraryImportAttribute' instead of 'DllImportAttribute' to generate P/Invoke marshalling code at compile time</source>
         <target state="translated">Use “LibraryImportAttribute” en lugar de “DllImportAttribute” para generar código de serialización P/Invoke en el tiempo de compilación</target>
         <note />
       </trans-unit>
+      <trans-unit id="ConvertToLibraryImportMayRequireCustomMarshalling">
+        <source>Converting this API to 'LibraryImport' will require additional code to provide custom marshallers for some parameters.</source>
+        <target state="new">Converting this API to 'LibraryImport' will require additional code to provide custom marshallers for some parameters.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="ConvertToLibraryImportMessage">
         <source>Mark the method '{0}' with 'LibraryImportAttribute' instead of 'DllImportAttribute' to generate P/Invoke marshalling code at compile time</source>
         <target state="translated">Marque el método “{0}” con “LibraryImportAttribute” en lugar de “DllImportAttribute” para generar código de serialización P/Invoke en el tiempo de compilación</target>
         <target state="translated">Use “LibraryImportAttribute” en lugar de “DllImportAttribute” para generar código de serialización P/Invoke en el tiempo de compilación</target>
         <note />
       </trans-unit>
-      <trans-unit id="ConvertToLibraryImportWarning">
-        <source>Conversion to 'LibraryImport' may change behavior and compatibility. See {0} for more information.</source>
-        <target state="translated">La conversión a “LibraryImport” debe cambiar de comportamiento y compatibilidad. Vea {0} para obtener más información.</target>
-        <note>{0} is a documentation link</note>
-      </trans-unit>
       <trans-unit id="ConvertToLibraryImportWithSuffix">
         <source>Convert to 'LibraryImport' with '{0}' suffix</source>
         <target state="translated">Convertir a “LibraryImport” con sufijo “{0}”</target>
         <note />
       </trans-unit>
+      <trans-unit id="ConvertToLibraryImportWithSuffixAddUnsafe">
+        <source>Convert to 'LibraryImport' with '{0}' suffix and enable unsafe code</source>
+        <target state="new">Convert to 'LibraryImport' with '{0}' suffix and enable unsafe code</target>
+        <note />
+      </trans-unit>
       <trans-unit id="CustomMarshallerTypeMustHaveRequiredShapeTitle">
         <source>Marshaller type does not have the required shape</source>
         <target state="translated">El tipo de serializador no tiene la forma necesaria</target>
index 67bb0ff..1963d0a 100644 (file)
         <target state="translated">Convertir en « LibraryImport »</target>
         <note />
       </trans-unit>
+      <trans-unit id="ConvertToLibraryImportAddUnsafe">
+        <source>Convert to 'LibraryImport' and enable unsafe code</source>
+        <target state="new">Convert to 'LibraryImport' and enable unsafe code</target>
+        <note />
+      </trans-unit>
       <trans-unit id="ConvertToLibraryImportDescription">
         <source>Use 'LibraryImportAttribute' instead of 'DllImportAttribute' to generate P/Invoke marshalling code at compile time</source>
         <target state="translated">Utilisez « LibraryImportAttribute » à la place de « DllImportAttribute » pour générer du code de marshaling P/Invoke au moment de la compilation</target>
         <note />
       </trans-unit>
+      <trans-unit id="ConvertToLibraryImportMayRequireCustomMarshalling">
+        <source>Converting this API to 'LibraryImport' will require additional code to provide custom marshallers for some parameters.</source>
+        <target state="new">Converting this API to 'LibraryImport' will require additional code to provide custom marshallers for some parameters.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="ConvertToLibraryImportMessage">
         <source>Mark the method '{0}' with 'LibraryImportAttribute' instead of 'DllImportAttribute' to generate P/Invoke marshalling code at compile time</source>
         <target state="translated">Marquer la méthode « {0} » avec « LibraryImportAttribute » à la place de « DllImportAttribute » pour générer du code de marshaling P/Invoke au moment de la compilation</target>
         <target state="translated">Utilisez « LibraryImportAttribute » à la place de « DllImportAttribute » pour générer du code de marshaling P/Invoke au moment de la compilation</target>
         <note />
       </trans-unit>
-      <trans-unit id="ConvertToLibraryImportWarning">
-        <source>Conversion to 'LibraryImport' may change behavior and compatibility. See {0} for more information.</source>
-        <target state="translated">La conversion en « LibraryImport » peut modifier le comportement et la compatibilité. Pour plus d’informations, consultez {0}.</target>
-        <note>{0} is a documentation link</note>
-      </trans-unit>
       <trans-unit id="ConvertToLibraryImportWithSuffix">
         <source>Convert to 'LibraryImport' with '{0}' suffix</source>
         <target state="translated">Convertir en « LibraryImport » avec suffixe « {0} »</target>
         <note />
       </trans-unit>
+      <trans-unit id="ConvertToLibraryImportWithSuffixAddUnsafe">
+        <source>Convert to 'LibraryImport' with '{0}' suffix and enable unsafe code</source>
+        <target state="new">Convert to 'LibraryImport' with '{0}' suffix and enable unsafe code</target>
+        <note />
+      </trans-unit>
       <trans-unit id="CustomMarshallerTypeMustHaveRequiredShapeTitle">
         <source>Marshaller type does not have the required shape</source>
         <target state="translated">Le type Marshaller n’a pas la forme requise</target>
index 68fecb5..7a6296c 100644 (file)
         <target state="translated">Converti in 'LibraryImport'</target>
         <note />
       </trans-unit>
+      <trans-unit id="ConvertToLibraryImportAddUnsafe">
+        <source>Convert to 'LibraryImport' and enable unsafe code</source>
+        <target state="new">Convert to 'LibraryImport' and enable unsafe code</target>
+        <note />
+      </trans-unit>
       <trans-unit id="ConvertToLibraryImportDescription">
         <source>Use 'LibraryImportAttribute' instead of 'DllImportAttribute' to generate P/Invoke marshalling code at compile time</source>
         <target state="translated">Usare 'LibraryImportAttribute' invece di 'DllImportAttribute' per generare codice di marshalling di P/Invoke in fase di compilazione</target>
         <note />
       </trans-unit>
+      <trans-unit id="ConvertToLibraryImportMayRequireCustomMarshalling">
+        <source>Converting this API to 'LibraryImport' will require additional code to provide custom marshallers for some parameters.</source>
+        <target state="new">Converting this API to 'LibraryImport' will require additional code to provide custom marshallers for some parameters.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="ConvertToLibraryImportMessage">
         <source>Mark the method '{0}' with 'LibraryImportAttribute' instead of 'DllImportAttribute' to generate P/Invoke marshalling code at compile time</source>
         <target state="translated">Contrassegnare il metodo '{0}' con 'LibraryImportAttribute' invece di 'DllImportAttribute' per generare codice di marshalling di P/Invoke in fase di compilazione</target>
         <target state="translated">Usare 'LibraryImportAttribute' invece di 'DllImportAttribute' per generare codice di marshalling di P/Invoke in fase di compilazione</target>
         <note />
       </trans-unit>
-      <trans-unit id="ConvertToLibraryImportWarning">
-        <source>Conversion to 'LibraryImport' may change behavior and compatibility. See {0} for more information.</source>
-        <target state="translated">La conversione in 'LibraryImport' può modificare il comportamento e la compatibilità. Per altre informazioni, vedere {0}.</target>
-        <note>{0} is a documentation link</note>
-      </trans-unit>
       <trans-unit id="ConvertToLibraryImportWithSuffix">
         <source>Convert to 'LibraryImport' with '{0}' suffix</source>
         <target state="translated">Converti in 'LibraryImport' con il suffisso '{0}'</target>
         <note />
       </trans-unit>
+      <trans-unit id="ConvertToLibraryImportWithSuffixAddUnsafe">
+        <source>Convert to 'LibraryImport' with '{0}' suffix and enable unsafe code</source>
+        <target state="new">Convert to 'LibraryImport' with '{0}' suffix and enable unsafe code</target>
+        <note />
+      </trans-unit>
       <trans-unit id="CustomMarshallerTypeMustHaveRequiredShapeTitle">
         <source>Marshaller type does not have the required shape</source>
         <target state="translated">Il tipo di marshaller non ha la forma richiesta</target>
index b29ed77..c91c769 100644 (file)
         <target state="translated">'LibraryImport' への変換</target>
         <note />
       </trans-unit>
+      <trans-unit id="ConvertToLibraryImportAddUnsafe">
+        <source>Convert to 'LibraryImport' and enable unsafe code</source>
+        <target state="new">Convert to 'LibraryImport' and enable unsafe code</target>
+        <note />
+      </trans-unit>
       <trans-unit id="ConvertToLibraryImportDescription">
         <source>Use 'LibraryImportAttribute' instead of 'DllImportAttribute' to generate P/Invoke marshalling code at compile time</source>
         <target state="translated">コンパイル時に P/Invoke マーシャリング コードを生成するには、'DllImportAttribute' の代わりに 'LibraryImportAttribute' を使用します</target>
         <note />
       </trans-unit>
+      <trans-unit id="ConvertToLibraryImportMayRequireCustomMarshalling">
+        <source>Converting this API to 'LibraryImport' will require additional code to provide custom marshallers for some parameters.</source>
+        <target state="new">Converting this API to 'LibraryImport' will require additional code to provide custom marshallers for some parameters.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="ConvertToLibraryImportMessage">
         <source>Mark the method '{0}' with 'LibraryImportAttribute' instead of 'DllImportAttribute' to generate P/Invoke marshalling code at compile time</source>
         <target state="translated">コンパイル時に P/Invoke マーシャリング コードを生成するには、'DllImportAttribute' の代わりに 'LibraryImportAttribute' を持つメソッド '{0}' をマークします</target>
         <target state="translated">コンパイル時に P/Invoke マーシャリング コードを生成するには、'DllImportAttribute' の代わりに 'LibraryImportAttribute' を使用します</target>
         <note />
       </trans-unit>
-      <trans-unit id="ConvertToLibraryImportWarning">
-        <source>Conversion to 'LibraryImport' may change behavior and compatibility. See {0} for more information.</source>
-        <target state="translated">'LibraryImport' に変換すると、動作と互換性が変更される場合があります。詳細については、「{0}」を参照してください。</target>
-        <note>{0} is a documentation link</note>
-      </trans-unit>
       <trans-unit id="ConvertToLibraryImportWithSuffix">
         <source>Convert to 'LibraryImport' with '{0}' suffix</source>
         <target state="translated">'{0}' サフィックスを持つ 'LibraryImport' への変換</target>
         <note />
       </trans-unit>
+      <trans-unit id="ConvertToLibraryImportWithSuffixAddUnsafe">
+        <source>Convert to 'LibraryImport' with '{0}' suffix and enable unsafe code</source>
+        <target state="new">Convert to 'LibraryImport' with '{0}' suffix and enable unsafe code</target>
+        <note />
+      </trans-unit>
       <trans-unit id="CustomMarshallerTypeMustHaveRequiredShapeTitle">
         <source>Marshaller type does not have the required shape</source>
         <target state="translated">マーシャラー型に必要な図形がありません</target>
index 62a44cc..1f6988a 100644 (file)
         <target state="translated">'LibraryImport'로 변환</target>
         <note />
       </trans-unit>
+      <trans-unit id="ConvertToLibraryImportAddUnsafe">
+        <source>Convert to 'LibraryImport' and enable unsafe code</source>
+        <target state="new">Convert to 'LibraryImport' and enable unsafe code</target>
+        <note />
+      </trans-unit>
       <trans-unit id="ConvertToLibraryImportDescription">
         <source>Use 'LibraryImportAttribute' instead of 'DllImportAttribute' to generate P/Invoke marshalling code at compile time</source>
         <target state="translated">컴파일 타임에 P/Invoke 마샬링 코드를 생성하려면 'DllImportAttribute' 대신 'LibraryImportAttribute'를 사용하세요.</target>
         <note />
       </trans-unit>
+      <trans-unit id="ConvertToLibraryImportMayRequireCustomMarshalling">
+        <source>Converting this API to 'LibraryImport' will require additional code to provide custom marshallers for some parameters.</source>
+        <target state="new">Converting this API to 'LibraryImport' will require additional code to provide custom marshallers for some parameters.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="ConvertToLibraryImportMessage">
         <source>Mark the method '{0}' with 'LibraryImportAttribute' instead of 'DllImportAttribute' to generate P/Invoke marshalling code at compile time</source>
         <target state="translated">컴파일 타임에 P/Invoke 마샬링 코드를 생성하려면 'DllImportAttribute' 대신 'LibraryImportAttribute'를 사용하여 '{0}' 메서드를 표시하세요.</target>
         <target state="translated">컴파일 타임에 P/Invoke 마샬링 코드를 생성하려면 'DllImportAttribute' 대신 'LibraryImportAttribute'를 사용하세요.</target>
         <note />
       </trans-unit>
-      <trans-unit id="ConvertToLibraryImportWarning">
-        <source>Conversion to 'LibraryImport' may change behavior and compatibility. See {0} for more information.</source>
-        <target state="translated">'LibraryImport'로 변환하면 동작과 호환성이 변경될 수 있습니다. 자세한 내용은 {0}을(를) 참조하세요.</target>
-        <note>{0} is a documentation link</note>
-      </trans-unit>
       <trans-unit id="ConvertToLibraryImportWithSuffix">
         <source>Convert to 'LibraryImport' with '{0}' suffix</source>
         <target state="translated">'{0}' 접미사가 있는 'LibraryImport'로 변환</target>
         <note />
       </trans-unit>
+      <trans-unit id="ConvertToLibraryImportWithSuffixAddUnsafe">
+        <source>Convert to 'LibraryImport' with '{0}' suffix and enable unsafe code</source>
+        <target state="new">Convert to 'LibraryImport' with '{0}' suffix and enable unsafe code</target>
+        <note />
+      </trans-unit>
       <trans-unit id="CustomMarshallerTypeMustHaveRequiredShapeTitle">
         <source>Marshaller type does not have the required shape</source>
         <target state="translated">마샬러 형식에 필요한 셰이프가 없습니다.</target>
index c2a6735..05f3bad 100644 (file)
         <target state="translated">Konwertuj na element „LibraryImport”</target>
         <note />
       </trans-unit>
+      <trans-unit id="ConvertToLibraryImportAddUnsafe">
+        <source>Convert to 'LibraryImport' and enable unsafe code</source>
+        <target state="new">Convert to 'LibraryImport' and enable unsafe code</target>
+        <note />
+      </trans-unit>
       <trans-unit id="ConvertToLibraryImportDescription">
         <source>Use 'LibraryImportAttribute' instead of 'DllImportAttribute' to generate P/Invoke marshalling code at compile time</source>
         <target state="translated">Użyj elementu „LibraryImportAttribute” zamiast elementu „DllImportAttribute”, aby wygenerować kod skierowania funkcji P/Invoke w czasie kompilacji</target>
         <note />
       </trans-unit>
+      <trans-unit id="ConvertToLibraryImportMayRequireCustomMarshalling">
+        <source>Converting this API to 'LibraryImport' will require additional code to provide custom marshallers for some parameters.</source>
+        <target state="new">Converting this API to 'LibraryImport' will require additional code to provide custom marshallers for some parameters.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="ConvertToLibraryImportMessage">
         <source>Mark the method '{0}' with 'LibraryImportAttribute' instead of 'DllImportAttribute' to generate P/Invoke marshalling code at compile time</source>
         <target state="translated">Oznacz metodę „{0}” za pomocą elementu „LibraryImportAttribute” zamiast elementu „DllImportAttribute”, aby wygenerować kod skierowania funkcji P/Invoke w czasie kompilacji</target>
         <target state="translated">Użyj elementu „LibraryImportAttribute” zamiast elementu „DllImportAttribute”, aby wygenerować kod skierowania funkcji P/Invoke w czasie kompilacji</target>
         <note />
       </trans-unit>
-      <trans-unit id="ConvertToLibraryImportWarning">
-        <source>Conversion to 'LibraryImport' may change behavior and compatibility. See {0} for more information.</source>
-        <target state="translated">Konwertowanie na element „LibraryImport” może zmienić zachowanie i zgodność. Zobacz link {0}, aby uzyskać więcej informacji.</target>
-        <note>{0} is a documentation link</note>
-      </trans-unit>
       <trans-unit id="ConvertToLibraryImportWithSuffix">
         <source>Convert to 'LibraryImport' with '{0}' suffix</source>
         <target state="translated">Konwertuj na element „LibraryImport” z sufiksem „{0}”</target>
         <note />
       </trans-unit>
+      <trans-unit id="ConvertToLibraryImportWithSuffixAddUnsafe">
+        <source>Convert to 'LibraryImport' with '{0}' suffix and enable unsafe code</source>
+        <target state="new">Convert to 'LibraryImport' with '{0}' suffix and enable unsafe code</target>
+        <note />
+      </trans-unit>
       <trans-unit id="CustomMarshallerTypeMustHaveRequiredShapeTitle">
         <source>Marshaller type does not have the required shape</source>
         <target state="translated">Typ marshallera nie ma wymaganego kształtu</target>
index 7941b7b..2786d76 100644 (file)
         <target state="translated">Converter em 'LibraryImport'</target>
         <note />
       </trans-unit>
+      <trans-unit id="ConvertToLibraryImportAddUnsafe">
+        <source>Convert to 'LibraryImport' and enable unsafe code</source>
+        <target state="new">Convert to 'LibraryImport' and enable unsafe code</target>
+        <note />
+      </trans-unit>
       <trans-unit id="ConvertToLibraryImportDescription">
         <source>Use 'LibraryImportAttribute' instead of 'DllImportAttribute' to generate P/Invoke marshalling code at compile time</source>
         <target state="translated">Use 'LibraryImportAttribute' em vez de 'DllImportAttribute' para gerar código de marshalling P/Invoke no tempo de compilação</target>
         <note />
       </trans-unit>
+      <trans-unit id="ConvertToLibraryImportMayRequireCustomMarshalling">
+        <source>Converting this API to 'LibraryImport' will require additional code to provide custom marshallers for some parameters.</source>
+        <target state="new">Converting this API to 'LibraryImport' will require additional code to provide custom marshallers for some parameters.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="ConvertToLibraryImportMessage">
         <source>Mark the method '{0}' with 'LibraryImportAttribute' instead of 'DllImportAttribute' to generate P/Invoke marshalling code at compile time</source>
         <target state="translated">Marque o método '{0}' com 'LibraryImportAttribute' em vez de 'DllImportAttribute' para gerar código de marshaling P/Invoke em tempo de compilação</target>
         <target state="translated">Use 'LibraryImportAttribute' em vez de 'DllImportAttribute' para gerar código de marshalling P/Invoke no tempo de compilação</target>
         <note />
       </trans-unit>
-      <trans-unit id="ConvertToLibraryImportWarning">
-        <source>Conversion to 'LibraryImport' may change behavior and compatibility. See {0} for more information.</source>
-        <target state="translated">A conversão para 'LibraryImport' pode alterar o comportamento e a compatibilidade. Consulte {0} para obter mais informações.</target>
-        <note>{0} is a documentation link</note>
-      </trans-unit>
       <trans-unit id="ConvertToLibraryImportWithSuffix">
         <source>Convert to 'LibraryImport' with '{0}' suffix</source>
         <target state="translated">Converter em 'LibraryImport' com '{0}' sufixo</target>
         <note />
       </trans-unit>
+      <trans-unit id="ConvertToLibraryImportWithSuffixAddUnsafe">
+        <source>Convert to 'LibraryImport' with '{0}' suffix and enable unsafe code</source>
+        <target state="new">Convert to 'LibraryImport' with '{0}' suffix and enable unsafe code</target>
+        <note />
+      </trans-unit>
       <trans-unit id="CustomMarshallerTypeMustHaveRequiredShapeTitle">
         <source>Marshaller type does not have the required shape</source>
         <target state="translated">O tipo de marshaller não tem a forma necessária</target>
index ec84025..9281b48 100644 (file)
         <target state="translated">Преобразовать в \"LibraryImport\"</target>
         <note />
       </trans-unit>
+      <trans-unit id="ConvertToLibraryImportAddUnsafe">
+        <source>Convert to 'LibraryImport' and enable unsafe code</source>
+        <target state="new">Convert to 'LibraryImport' and enable unsafe code</target>
+        <note />
+      </trans-unit>
       <trans-unit id="ConvertToLibraryImportDescription">
         <source>Use 'LibraryImportAttribute' instead of 'DllImportAttribute' to generate P/Invoke marshalling code at compile time</source>
         <target state="translated">Используйте \"LibraryImportAttribute\" вместо \"DllImportAttribute\" для генерирования кода маршализации P/Invoke во время компиляции</target>
         <note />
       </trans-unit>
+      <trans-unit id="ConvertToLibraryImportMayRequireCustomMarshalling">
+        <source>Converting this API to 'LibraryImport' will require additional code to provide custom marshallers for some parameters.</source>
+        <target state="new">Converting this API to 'LibraryImport' will require additional code to provide custom marshallers for some parameters.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="ConvertToLibraryImportMessage">
         <source>Mark the method '{0}' with 'LibraryImportAttribute' instead of 'DllImportAttribute' to generate P/Invoke marshalling code at compile time</source>
         <target state="translated">Пометьте метод \"{0}\" атрибутом \"LibraryImportAttribute\" вместо \"DllImportAttribute\", чтобы генерировать код маршализации P/Invoke во время компиляции</target>
         <target state="translated">Используйте \"LibraryImportAttribute\" вместо \"DllImportAttribute\" для генерирования кода маршализации P/Invoke во время компиляции</target>
         <note />
       </trans-unit>
-      <trans-unit id="ConvertToLibraryImportWarning">
-        <source>Conversion to 'LibraryImport' may change behavior and compatibility. See {0} for more information.</source>
-        <target state="translated">Преобразование в \"LibraryImport\" может изменить поведение и совместимость. Дополнительные сведения см. в {0}.</target>
-        <note>{0} is a documentation link</note>
-      </trans-unit>
       <trans-unit id="ConvertToLibraryImportWithSuffix">
         <source>Convert to 'LibraryImport' with '{0}' suffix</source>
         <target state="translated">Преобразовать в \"LibraryImport\" с суффиксом \"{0}\"</target>
         <note />
       </trans-unit>
+      <trans-unit id="ConvertToLibraryImportWithSuffixAddUnsafe">
+        <source>Convert to 'LibraryImport' with '{0}' suffix and enable unsafe code</source>
+        <target state="new">Convert to 'LibraryImport' with '{0}' suffix and enable unsafe code</target>
+        <note />
+      </trans-unit>
       <trans-unit id="CustomMarshallerTypeMustHaveRequiredShapeTitle">
         <source>Marshaller type does not have the required shape</source>
         <target state="translated">Тип маршалера не имеет требуемой фигуры</target>
index bee4f27..f785683 100644 (file)
         <target state="translated">'LibraryImport' olarak dönüştür</target>
         <note />
       </trans-unit>
+      <trans-unit id="ConvertToLibraryImportAddUnsafe">
+        <source>Convert to 'LibraryImport' and enable unsafe code</source>
+        <target state="new">Convert to 'LibraryImport' and enable unsafe code</target>
+        <note />
+      </trans-unit>
       <trans-unit id="ConvertToLibraryImportDescription">
         <source>Use 'LibraryImportAttribute' instead of 'DllImportAttribute' to generate P/Invoke marshalling code at compile time</source>
         <target state="translated">P/Invoke sıralama kodunu derleme zamanında oluşturmak için 'DllImportAttribute' yerine 'LibraryImportAttribute' kullanın</target>
         <note />
       </trans-unit>
+      <trans-unit id="ConvertToLibraryImportMayRequireCustomMarshalling">
+        <source>Converting this API to 'LibraryImport' will require additional code to provide custom marshallers for some parameters.</source>
+        <target state="new">Converting this API to 'LibraryImport' will require additional code to provide custom marshallers for some parameters.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="ConvertToLibraryImportMessage">
         <source>Mark the method '{0}' with 'LibraryImportAttribute' instead of 'DllImportAttribute' to generate P/Invoke marshalling code at compile time</source>
         <target state="translated">P/Invoke sıralama kodunu derleme zamanında oluşturmak için '{0}' metodunu, 'DllImportAttribute' yerine 'LibraryImportAttribute' ile işaretleyin</target>
         <target state="translated">P/Invoke sıralama kodunu derleme zamanında oluşturmak için 'DllImportAttribute' yerine 'LibraryImportAttribute' kullanın</target>
         <note />
       </trans-unit>
-      <trans-unit id="ConvertToLibraryImportWarning">
-        <source>Conversion to 'LibraryImport' may change behavior and compatibility. See {0} for more information.</source>
-        <target state="translated">'LibraryImport' olarak dönüştürme işlemi davranışı ve uyumluluğu değiştirebilir. Daha fazla bilgi için bkz. {0}</target>
-        <note>{0} is a documentation link</note>
-      </trans-unit>
       <trans-unit id="ConvertToLibraryImportWithSuffix">
         <source>Convert to 'LibraryImport' with '{0}' suffix</source>
         <target state="translated">'{0}' soneki ile 'LibraryImport' olarak dönüştür</target>
         <note />
       </trans-unit>
+      <trans-unit id="ConvertToLibraryImportWithSuffixAddUnsafe">
+        <source>Convert to 'LibraryImport' with '{0}' suffix and enable unsafe code</source>
+        <target state="new">Convert to 'LibraryImport' with '{0}' suffix and enable unsafe code</target>
+        <note />
+      </trans-unit>
       <trans-unit id="CustomMarshallerTypeMustHaveRequiredShapeTitle">
         <source>Marshaller type does not have the required shape</source>
         <target state="translated">Sıralayıcı türü gerekli şekle sahip değil</target>
index 0b4a3fd..946c30c 100644 (file)
         <target state="translated">转换为 “LibraryImport”</target>
         <note />
       </trans-unit>
+      <trans-unit id="ConvertToLibraryImportAddUnsafe">
+        <source>Convert to 'LibraryImport' and enable unsafe code</source>
+        <target state="new">Convert to 'LibraryImport' and enable unsafe code</target>
+        <note />
+      </trans-unit>
       <trans-unit id="ConvertToLibraryImportDescription">
         <source>Use 'LibraryImportAttribute' instead of 'DllImportAttribute' to generate P/Invoke marshalling code at compile time</source>
         <target state="translated">使用 “LibraryImportAttribute” 而不是 “DllImportAttribute” 在编译时生成 P/Invoke 封送代码</target>
         <note />
       </trans-unit>
+      <trans-unit id="ConvertToLibraryImportMayRequireCustomMarshalling">
+        <source>Converting this API to 'LibraryImport' will require additional code to provide custom marshallers for some parameters.</source>
+        <target state="new">Converting this API to 'LibraryImport' will require additional code to provide custom marshallers for some parameters.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="ConvertToLibraryImportMessage">
         <source>Mark the method '{0}' with 'LibraryImportAttribute' instead of 'DllImportAttribute' to generate P/Invoke marshalling code at compile time</source>
         <target state="translated">使用 “LibraryImportAttribute” 而不是 “DllImportAttribute” 标记方法“{0}”,以便在编译时生成 P/Invoke 封送代码</target>
         <target state="translated">使用 “LibraryImportAttribute” 而不是 “DllImportAttribute” 在编译时生成 P/Invoke 封送代码</target>
         <note />
       </trans-unit>
-      <trans-unit id="ConvertToLibraryImportWarning">
-        <source>Conversion to 'LibraryImport' may change behavior and compatibility. See {0} for more information.</source>
-        <target state="translated">转换为 “LibraryImport” 可能会更改行为和兼容性。有关详细信息,请参阅 {0}。</target>
-        <note>{0} is a documentation link</note>
-      </trans-unit>
       <trans-unit id="ConvertToLibraryImportWithSuffix">
         <source>Convert to 'LibraryImport' with '{0}' suffix</source>
         <target state="translated">转换为带“{0}”后缀的 “LibraryImport”</target>
         <note />
       </trans-unit>
+      <trans-unit id="ConvertToLibraryImportWithSuffixAddUnsafe">
+        <source>Convert to 'LibraryImport' with '{0}' suffix and enable unsafe code</source>
+        <target state="new">Convert to 'LibraryImport' with '{0}' suffix and enable unsafe code</target>
+        <note />
+      </trans-unit>
       <trans-unit id="CustomMarshallerTypeMustHaveRequiredShapeTitle">
         <source>Marshaller type does not have the required shape</source>
         <target state="translated">封送程序类型没有必需的形状</target>
index 9d5dded..3fc130c 100644 (file)
         <target state="translated">轉換為 'LibraryImport'</target>
         <note />
       </trans-unit>
+      <trans-unit id="ConvertToLibraryImportAddUnsafe">
+        <source>Convert to 'LibraryImport' and enable unsafe code</source>
+        <target state="new">Convert to 'LibraryImport' and enable unsafe code</target>
+        <note />
+      </trans-unit>
       <trans-unit id="ConvertToLibraryImportDescription">
         <source>Use 'LibraryImportAttribute' instead of 'DllImportAttribute' to generate P/Invoke marshalling code at compile time</source>
         <target state="translated">使用 'LibraryImportAttribute' 而非 'DllImportAttribute' 在編譯時間產生 P/Invoke 封送處理程式碼</target>
         <note />
       </trans-unit>
+      <trans-unit id="ConvertToLibraryImportMayRequireCustomMarshalling">
+        <source>Converting this API to 'LibraryImport' will require additional code to provide custom marshallers for some parameters.</source>
+        <target state="new">Converting this API to 'LibraryImport' will require additional code to provide custom marshallers for some parameters.</target>
+        <note />
+      </trans-unit>
       <trans-unit id="ConvertToLibraryImportMessage">
         <source>Mark the method '{0}' with 'LibraryImportAttribute' instead of 'DllImportAttribute' to generate P/Invoke marshalling code at compile time</source>
         <target state="translated">以 'LibraryImportAttribute' 代替 'DllImportAttribute' 標示方法'{0}',以在編譯時間產生 P/Invoke 封送處理程式碼</target>
         <target state="translated">使用 'LibraryImportAttribute' 而非 'DllImportAttribute' 在編譯時間產生 P/Invoke 封送處理程式碼</target>
         <note />
       </trans-unit>
-      <trans-unit id="ConvertToLibraryImportWarning">
-        <source>Conversion to 'LibraryImport' may change behavior and compatibility. See {0} for more information.</source>
-        <target state="translated">轉換為 'LibraryImport' 可能會變更行為和兼容性。如需詳細資訊,請參閱 {0}。</target>
-        <note>{0} is a documentation link</note>
-      </trans-unit>
       <trans-unit id="ConvertToLibraryImportWithSuffix">
         <source>Convert to 'LibraryImport' with '{0}' suffix</source>
         <target state="translated">轉換為具有 '{0}'後綴的 'LibraryImport'</target>
         <note />
       </trans-unit>
+      <trans-unit id="ConvertToLibraryImportWithSuffixAddUnsafe">
+        <source>Convert to 'LibraryImport' with '{0}' suffix and enable unsafe code</source>
+        <target state="new">Convert to 'LibraryImport' with '{0}' suffix and enable unsafe code</target>
+        <note />
+      </trans-unit>
       <trans-unit id="CustomMarshallerTypeMustHaveRequiredShapeTitle">
         <source>Marshaller type does not have the required shape</source>
         <target state="translated">封送處理器類型沒有必要的圖形</target>
diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/CompilationExtensions.cs b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/CompilationExtensions.cs
new file mode 100644 (file)
index 0000000..570db38
--- /dev/null
@@ -0,0 +1,42 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Microsoft.CodeAnalysis;
+
+namespace Microsoft.Interop
+{
+    public static class CompilationExtensions
+    {
+        public static StubEnvironment CreateStubEnvironment(this Compilation compilation)
+        {
+            TargetFramework targetFramework = DetermineTargetFramework(compilation, out Version targetFrameworkVersion);
+            return new StubEnvironment(
+                            compilation,
+                            targetFramework,
+                            targetFrameworkVersion,
+                            compilation.SourceModule.GetAttributes().Any(attr => attr.AttributeClass?.ToDisplayString() == TypeNames.System_Runtime_CompilerServices_SkipLocalsInitAttribute));
+
+            static TargetFramework DetermineTargetFramework(Compilation compilation, out Version version)
+            {
+                IAssemblySymbol systemAssembly = compilation.GetSpecialType(SpecialType.System_Object).ContainingAssembly;
+                version = systemAssembly.Identity.Version;
+
+                return systemAssembly.Identity.Name switch
+                {
+                    // .NET Framework
+                    "mscorlib" => TargetFramework.Framework,
+                    // .NET Standard
+                    "netstandard" => TargetFramework.Standard,
+                    // .NET Core (when version < 5.0) or .NET
+                    "System.Runtime" or "System.Private.CoreLib" =>
+                        (version.Major < 5) ? TargetFramework.Core : TargetFramework.Net,
+                    _ => TargetFramework.Unknown,
+                };
+            }
+        }
+    }
+}
index d8593bc..0b00879 100644 (file)
@@ -13,38 +13,7 @@ namespace Microsoft.Interop
     {
         public static IncrementalValueProvider<StubEnvironment> CreateStubEnvironmentProvider(this IncrementalGeneratorInitializationContext context)
         {
-            return context.CompilationProvider
-                .Select(static (compilation, ct) =>
-                {
-                    TargetFramework targetFramework = DetermineTargetFramework(compilation, out Version targetFrameworkVersion);
-                    return (compilation, targetFramework, targetFrameworkVersion);
-                })
-                .Select(
-                    static (data, ct) =>
-                        new StubEnvironment(
-                            data.compilation,
-                            data.targetFramework,
-                            data.targetFrameworkVersion,
-                            data.compilation.SourceModule.GetAttributes().Any(attr => attr.AttributeClass?.ToDisplayString() == TypeNames.System_Runtime_CompilerServices_SkipLocalsInitAttribute))
-                );
-
-            static TargetFramework DetermineTargetFramework(Compilation compilation, out Version version)
-            {
-                IAssemblySymbol systemAssembly = compilation.GetSpecialType(SpecialType.System_Object).ContainingAssembly;
-                version = systemAssembly.Identity.Version;
-
-                return systemAssembly.Identity.Name switch
-                {
-                    // .NET Framework
-                    "mscorlib" => TargetFramework.Framework,
-                    // .NET Standard
-                    "netstandard" => TargetFramework.Standard,
-                    // .NET Core (when version < 5.0) or .NET
-                    "System.Runtime" or "System.Private.CoreLib" =>
-                        (version.Major < 5) ? TargetFramework.Core : TargetFramework.Net,
-                    _ => TargetFramework.Unknown,
-                };
-            }
+            return context.CompilationProvider.Select(static (comp, ct) => comp.CreateStubEnvironment());
         }
 
         public static void RegisterDiagnostics(this IncrementalGeneratorInitializationContext context, IncrementalValuesProvider<Diagnostic> diagnostics)
index b7a1801..a080e39 100644 (file)
@@ -5,6 +5,9 @@ using System;
 using System.Runtime.CompilerServices;
 using System.Runtime.InteropServices;
 using System.Threading.Tasks;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.Interop.Analyzers;
 using Xunit;
 using static Microsoft.Interop.Analyzers.ConvertToLibraryImportFixer;
 
@@ -17,7 +20,7 @@ namespace LibraryImportGenerator.UnitTests
     [ActiveIssue("https://github.com/dotnet/runtime/issues/60650", TestRuntimes.Mono)]
     public class ConvertToLibraryImportFixerTests
     {
-        private const string ConvertToLibraryImportKey = "ConvertToLibraryImport";
+        private const string ConvertToLibraryImportKey = "ConvertToLibraryImport,";
 
         [Fact]
         public async Task Basic()
@@ -37,7 +40,7 @@ partial class Test
     [LibraryImport(""DoesNotExist"")]
     public static partial int {{|CS8795:Method|}}(out int ret);
 }}";
-            await VerifyCS.VerifyCodeFixAsync(
+            await VerifyCodeFixAsync(
                 source,
                 fixedSource);
         }
@@ -72,7 +75,7 @@ partial class Test
     // < ... >
     public static partial int {{|CS8795:Method2|}}(out int ret);
 }}";
-            await VerifyCS.VerifyCodeFixAsync(
+            await VerifyCodeFixAsync(
                 source,
                 fixedSource);
         }
@@ -105,7 +108,7 @@ partial class Test
     [return: MarshalAs(UnmanagedType.I4)]
     public static partial int {{|CS8795:Method2|}}(out int ret);
 }}";
-            await VerifyCS.VerifyCodeFixAsync(
+            await VerifyCodeFixAsync(
                 source,
                 fixedSource);
         }
@@ -134,7 +137,7 @@ partial class Test
     [LibraryImport(""DoesNotExist"", EntryPoint = ""Entry"", StringMarshalling = StringMarshalling.Utf16)]
     public static partial string {{|CS8795:Method2|}}(out int ret);
 }}";
-            await VerifyCS.VerifyCodeFixAsync(
+            await VerifyCodeFixAsync(
                 source,
                 fixedSource);
         }
@@ -181,7 +184,7 @@ partial class Test
     [LibraryImport(""DoesNotExist"")]
     public static partial int {{|CS8795:Method4|}}(out int ret);
 }}";
-            await VerifyCS.VerifyCodeFixAsync(
+            await VerifyCodeFixAsync(
                 source,
                 fixedSource);
         }
@@ -204,7 +207,7 @@ partial class Test
     [LibraryImport(""DoesNotExist"", EntryPoint = ""Entry"")]
     public static partial int {{|CS8795:Method1|}}(out int ret);
 }}";
-            await VerifyCS.VerifyCodeFixAsync(
+            await VerifyCodeFixAsync(
                 source,
                 fixedSource);
         }
@@ -232,7 +235,7 @@ partial class Test
     [UnmanagedCallConv(CallConvs = new System.Type[] {{ typeof({callConvType.FullName}) }})]
     public static partial int {{|CS8795:Method1|}}(out int ret);
 }}";
-            await VerifyCS.VerifyCodeFixAsync(
+            await VerifyCodeFixAsync(
                 source,
                 fixedSource);
         }
@@ -255,7 +258,7 @@ partial class Test
     [LibraryImport(""DoesNotExist"", EntryPoint = ""Entry"", SetLastError = true, StringMarshalling = StringMarshalling.Utf16)]
     public static partial string {{|CS8795:Method|}}(out int ret);
 }}";
-            await VerifyCS.VerifyCodeFixAsync(
+            await VerifyCodeFixAsync(
                 source,
                 fixedSource);
         }
@@ -279,7 +282,7 @@ partial class Test
     [LibraryImport(""DoesNotExist"", EntryPoint = ""Entry"")]
     public static partial void {{|CS8795:Method|}}();
 }}";
-            await VerifyCS.VerifyCodeFixAsync(source, fixedSourceNoSuffix, ConvertToLibraryImportKey);
+            await VerifyCodeFixAsync(source, fixedSourceNoSuffix, ConvertToLibraryImportKey);
             string fixedSourceWithSuffix = $@"
 using System.Runtime.InteropServices;
 partial class Test
@@ -287,7 +290,7 @@ partial class Test
     [LibraryImport(""DoesNotExist"", EntryPoint = ""Entry{suffix}"")]
     public static partial void {{|CS8795:Method|}}();
 }}";
-            await VerifyCS.VerifyCodeFixAsync(source, fixedSourceWithSuffix, $"{ConvertToLibraryImportKey}{suffix}");
+            await VerifyCodeFixAsync(source, fixedSourceWithSuffix, $"{ConvertToLibraryImportKey}{suffix},");
         }
 
         [Fact]
@@ -307,7 +310,7 @@ partial class Test
     [LibraryImport(""DoesNotExist"", EntryPoint = ""Entry"")]
     public static partial void {{|CS8795:Method|}}();
 }}";
-            await VerifyCS.VerifyCodeFixAsync(source, fixedSourceNoSuffix, ConvertToLibraryImportKey);
+            await VerifyCodeFixAsync(source, fixedSourceNoSuffix, ConvertToLibraryImportKey);
             string fixedSourceWithASuffix = $@"
 using System.Runtime.InteropServices;
 partial class Test
@@ -315,7 +318,7 @@ partial class Test
     [LibraryImport(""DoesNotExist"", EntryPoint = ""EntryA"")]
     public static partial void {{|CS8795:Method|}}();
 }}";
-            await VerifyCS.VerifyCodeFixAsync(source, fixedSourceWithASuffix, $"{ConvertToLibraryImportKey}A");
+            await VerifyCodeFixAsync(source, fixedSourceWithASuffix, $"{ConvertToLibraryImportKey}A,");
             string fixedSourceWithWSuffix = $@"
 using System.Runtime.InteropServices;
 partial class Test
@@ -323,7 +326,7 @@ partial class Test
     [LibraryImport(""DoesNotExist"", EntryPoint = ""EntryW"")]
     public static partial void {{|CS8795:Method|}}();
 }}";
-            await VerifyCS.VerifyCodeFixAsync(source, fixedSourceWithWSuffix, $"{ConvertToLibraryImportKey}W");
+            await VerifyCodeFixAsync(source, fixedSourceWithWSuffix, $"{ConvertToLibraryImportKey}W,");
         }
 
         [Fact]
@@ -343,7 +346,7 @@ partial class Test
     [LibraryImport(""DoesNotExist"", EntryPoint = ""Entry"")]
     public static partial void {{|CS8795:Method|}}();
 }}";
-            await VerifyCS.VerifyCodeFixAsync(source, fixedSourceNoSuffix, ConvertToLibraryImportKey);
+            await VerifyCodeFixAsync(source, fixedSourceNoSuffix, ConvertToLibraryImportKey);
             string fixedSourceWithASuffix = $@"
 using System.Runtime.InteropServices;
 partial class Test
@@ -351,7 +354,7 @@ partial class Test
     [LibraryImport(""DoesNotExist"", EntryPoint = ""EntryA"")]
     public static partial void {{|CS8795:Method|}}();
 }}";
-            await VerifyCS.VerifyCodeFixAsync(source, fixedSourceWithASuffix, $"{ConvertToLibraryImportKey}A");
+            await VerifyCodeFixAsync(source, fixedSourceWithASuffix, $"{ConvertToLibraryImportKey}A,");
         }
 
         [Fact]
@@ -373,7 +376,7 @@ partial class Test
     [LibraryImport(""DoesNotExist"", EntryPoint = EntryPoint + ""A"")]
     public static partial void {{|CS8795:Method|}}();
 }}";
-            await VerifyCS.VerifyCodeFixAsync(source, fixedSourceWithASuffix, $"{ConvertToLibraryImportKey}A");
+            await VerifyCodeFixAsync(source, fixedSourceWithASuffix, $"{ConvertToLibraryImportKey}A,");
         }
 
         [Fact]
@@ -393,7 +396,7 @@ partial class Test
     [LibraryImport(""DoesNotExist"", EntryPoint = ""MethodA"")]
     public static partial void {{|CS8795:Method|}}();
 }}";
-            await VerifyCS.VerifyCodeFixAsync(source, fixedSourceWithASuffix, $"{ConvertToLibraryImportKey}A");
+            await VerifyCodeFixAsync(source, fixedSourceWithASuffix, $"{ConvertToLibraryImportKey}A,");
         }
 
         [Fact]
@@ -415,7 +418,7 @@ partial class Test
     [LibraryImport(""DoesNotExist"", EntryPoint = nameof(Foo) + ""A"")]
     public static partial void {{|CS8795:Method|}}();
 }}";
-            await VerifyCS.VerifyCodeFixAsync(source, fixedSourceWithASuffix, $"{ConvertToLibraryImportKey}A");
+            await VerifyCodeFixAsync(source, fixedSourceWithASuffix, $"{ConvertToLibraryImportKey}A,");
         }
 
         [Fact]
@@ -435,7 +438,7 @@ partial class Test
     [LibraryImport(""DoesNotExist"", EntryPoint = ""MethodA"")]
     public static partial void {{|CS8795:Method|}}();
 }}";
-            await VerifyCS.VerifyCodeFixAsync(source, fixedSourceWithASuffix, $"{ConvertToLibraryImportKey}A");
+            await VerifyCodeFixAsync(source, fixedSourceWithASuffix, $"{ConvertToLibraryImportKey}A,");
         }
 
         [Fact]
@@ -476,7 +479,7 @@ partial class Test
         Marshal.ThrowExceptionForHR(Test.Method(1, out value));
     }
 }";
-            await VerifyCS.VerifyCodeFixAsync(
+            await VerifyCodeFixAsync(
                 source,
                 fixedSource);
         }
@@ -539,7 +542,7 @@ partial class EnclosingPartial
         public static partial int {|CS8795:Method3|}(out int ret);
     }
 }";
-            await VerifyCS.VerifyCodeFixAsync(
+            await VerifyCodeFixAsync(
                 source,
                 fixedSource);
         }
@@ -563,9 +566,320 @@ partial class Test
     [return: MarshalAs(UnmanagedType.Bool)]
     public static partial bool {{|CS8795:Method|}}([MarshalAs(UnmanagedType.Bool)] bool b);
 }}";
-            await VerifyCS.VerifyCodeFixAsync(
+            await VerifyCodeFixAsync(
                 source,
                 fixedSource);
         }
+
+        // There's not a good way today to add a unit test for any changes to project settings from a code fix.
+        // Roslyn does special testing for their cases.
+        [Fact]
+        public async Task FixThatAddsUnsafeToProjectUpdatesLibraryImport()
+        {
+            string source = @$"
+using System.Runtime.InteropServices;
+partial class Test
+{{
+    [DllImport(""DoesNotExist"")]
+    public static extern int [|Method|](out int ret);
+}}";
+            // Fixed source will have CS8795 (Partial method must have an implementation) without generator run
+            string fixedSource = @$"
+using System.Runtime.InteropServices;
+partial class Test
+{{
+    [LibraryImport(""DoesNotExist"")]
+    public static partial int {{|CS8795:Method|}}(out int ret);
+}}";
+            await VerifyCodeFixNoUnsafeAsync(
+                source,
+                fixedSource,
+                $"{ConvertToLibraryImportKey}AddUnsafe,");
+        }
+
+        [Fact]
+        public async Task UserBlittableStructConvertsWithNoWarningCodeAction()
+        {
+            string source = @$"
+using System.Runtime.InteropServices;
+
+struct Blittable
+{{
+    private short s;
+    private short t;
+}}
+
+partial class Test
+{{
+    [DllImport(""DoesNotExist"")]
+    public static extern void [|Method|](Blittable b);
+}}";
+            // Fixed source will have CS8795 (Partial method must have an implementation) without generator run
+            string fixedSource = @$"
+using System.Runtime.InteropServices;
+
+struct Blittable
+{{
+    private short s;
+    private short t;
+}}
+
+partial class Test
+{{
+    [LibraryImport(""DoesNotExist"")]
+    public static partial void {{|CS8795:Method|}}(Blittable b);
+}}";
+            await VerifyCodeFixAsync(
+                source,
+                fixedSource,
+                ConvertToLibraryImportKey);
+        }
+
+        [Fact]
+        public async Task UserNonBlittableStructConvertsOnlyWithWarningCodeAction()
+        {
+            string source = @$"
+using System.Runtime.InteropServices;
+
+struct NonBlittable
+{{
+    private string s;
+    private short t;
+}}
+
+partial class Test
+{{
+    [DllImport(""DoesNotExist"")]
+    public static extern void [|Method|](NonBlittable b);
+}}";
+            // Fixed source will have CS8795 (Partial method must have an implementation) without generator run
+            string fixedSource = @$"
+using System.Runtime.InteropServices;
+
+struct NonBlittable
+{{
+    private string s;
+    private short t;
+}}
+
+partial class Test
+{{
+    [LibraryImport(""DoesNotExist"")]
+    public static partial void {{|CS8795:Method|}}(NonBlittable b);
+}}";
+            // Verify that we don't update this signature with the "no additional work required" action.
+            await VerifyCodeFixAsync(
+                source,
+                source,
+                ConvertToLibraryImportKey);
+
+            await VerifyCodeFixAsync(
+                source,
+                fixedSource,
+                $"{ConvertToLibraryImportKey}{ConvertToLibraryImportAnalyzer.MayRequireAdditionalWork},");
+        }
+
+        [Fact]
+        public async Task UserBlittableStructFixAllConvertsOnlyNoWarningLocations()
+        {
+            string source = @$"
+using System.Runtime.InteropServices;
+
+struct Blittable
+{{
+    private short s;
+    private short t;
+}}
+
+struct NonBlittable
+{{
+    private string s;
+    private short t;
+}}
+
+partial class Test
+{{
+    [DllImport(""DoesNotExist"")]
+    public static extern void [|Method|](int i);
+    [DllImport(""DoesNotExist"")]
+    public static extern void [|Method|](Blittable b);
+    [DllImport(""DoesNotExist"")]
+    public static extern void [|Method|](NonBlittable b);
+}}";
+            // Fixed sources will have CS8795 (Partial method must have an implementation) without generator run
+            string blittableOnlyFixedSource = @$"
+using System.Runtime.InteropServices;
+
+struct Blittable
+{{
+    private short s;
+    private short t;
+}}
+
+struct NonBlittable
+{{
+    private string s;
+    private short t;
+}}
+
+partial class Test
+{{
+    [LibraryImport(""DoesNotExist"")]
+    public static partial void {{|CS8795:Method|}}(int i);
+    [LibraryImport(""DoesNotExist"")]
+    public static partial void {{|CS8795:Method|}}(Blittable b);
+    [DllImport(""DoesNotExist"")]
+    public static extern void [|Method|](NonBlittable b);
+}}";
+            // Verify that we only fix the blittable cases, not the non-blittable cases.
+            await VerifyCodeFixAsync(
+                source,
+                blittableOnlyFixedSource,
+                ConvertToLibraryImportKey);
+        }
+
+        [Fact]
+        public async Task UserNonBlittableStructFixAllConvertsAllLocations()
+        {
+            string source = @$"
+using System.Runtime.InteropServices;
+
+struct Blittable
+{{
+    private short s;
+    private short t;
+}}
+
+struct NonBlittable
+{{
+    private string s;
+    private short t;
+}}
+
+partial class Test
+{{
+    [DllImport(""DoesNotExist"")]
+    public static extern void [|Method|](int i);
+    [DllImport(""DoesNotExist"")]
+    public static extern void [|Method|](Blittable b);
+    [DllImport(""DoesNotExist"")]
+    public static extern void [|Method|](NonBlittable b);
+}}";
+            // Fixed sources will have CS8795 (Partial method must have an implementation) without generator run
+            string nonBlittableOnlyFixedSource = @$"
+using System.Runtime.InteropServices;
+
+struct Blittable
+{{
+    private short s;
+    private short t;
+}}
+
+struct NonBlittable
+{{
+    private string s;
+    private short t;
+}}
+
+partial class Test
+{{
+    [DllImport(""DoesNotExist"")]
+    public static extern void [|Method|](int i);
+    [DllImport(""DoesNotExist"")]
+    public static extern void [|Method|](Blittable b);
+    [LibraryImport(""DoesNotExist"")]
+    public static partial void {{|CS8795:Method|}}(NonBlittable b);
+}}";
+            // Fixed sources will have CS8795 (Partial method must have an implementation) without generator run
+            string allFixedSource = @$"
+using System.Runtime.InteropServices;
+
+struct Blittable
+{{
+    private short s;
+    private short t;
+}}
+
+struct NonBlittable
+{{
+    private string s;
+    private short t;
+}}
+
+partial class Test
+{{
+    [LibraryImport(""DoesNotExist"")]
+    public static partial void {{|CS8795:Method|}}(int i);
+    [LibraryImport(""DoesNotExist"")]
+    public static partial void {{|CS8795:Method|}}(Blittable b);
+    [LibraryImport(""DoesNotExist"")]
+    public static partial void {{|CS8795:Method|}}(NonBlittable b);
+}}";
+
+            // Verify that we fix all cases when we do fix-all on a P/Invoke with a non-blittable user type which
+            // will require the user to write additional code.
+            await VerifyCodeFixAsync(
+                source,
+                nonBlittableOnlyFixedSource,
+                allFixedSource,
+                $"{ConvertToLibraryImportKey}{ConvertToLibraryImportAnalyzer.MayRequireAdditionalWork},");
+        }
+
+        private static async Task VerifyCodeFixAsync(string source, string fixedSource)
+        {
+            var test = new VerifyCS.Test
+            {
+                TestCode = source,
+                FixedCode = fixedSource,
+            };
+
+            await test.RunAsync();
+        }
+
+        private static async Task VerifyCodeFixAsync(string source, string fixedSource, string equivalenceKey)
+        {
+            var test = new VerifyCS.Test
+            {
+                TestCode = source,
+                FixedCode = fixedSource,
+                CodeActionEquivalenceKey = equivalenceKey,
+            };
+
+            test.FixedState.MarkupHandling = Microsoft.CodeAnalysis.Testing.MarkupMode.Allow;
+
+            await test.RunAsync();
+        }
+
+        private static async Task VerifyCodeFixAsync(string source, string fixedSource, string batchFixedSource, string equivalenceKey)
+        {
+            var test = new VerifyCS.Test
+            {
+                TestCode = source,
+                FixedCode = fixedSource,
+                BatchFixedCode = batchFixedSource,
+                CodeActionEquivalenceKey = equivalenceKey,
+            };
+
+            test.FixedState.MarkupHandling = Microsoft.CodeAnalysis.Testing.MarkupMode.Allow;
+
+            await test.RunAsync();
+        }
+
+        private static async Task VerifyCodeFixNoUnsafeAsync(string source, string fixedSource, string equivalenceKey)
+        {
+            var test = new TestNoUnsafe
+            {
+                TestCode = source,
+                FixedCode = fixedSource,
+                CodeActionEquivalenceKey = equivalenceKey,
+            };
+
+            await test.RunAsync();
+        }
+
+        class TestNoUnsafe : VerifyCS.Test
+        {
+            protected override CompilationOptions CreateCompilationOptions() => ((CSharpCompilationOptions)base.CreateCompilationOptions()).WithAllowUnsafe(false);
+        }
     }
 }