From 6fb4d8581fc867241cf8d81993368e6aa639067e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Mar 2026 22:20:20 +0000 Subject: [PATCH 1/7] Use RegisterPostInitializationOutput in DownlevelLibraryImportGenerator to inject internal LibraryImportAttribute and StringMarshalling definitions Co-authored-by: jkoritzinsky <1571408+jkoritzinsky@users.noreply.github.com> --- eng/generators.targets | 6 ---- .../DownlevelLibraryImportGenerator.cs | 34 +++++++++++++++++++ .../AdditionalAttributesOnStub.cs | 1 - .../Compiles.cs | 26 ++++++++------ .../Diagnostics.cs | 2 +- 5 files changed, 50 insertions(+), 19 deletions(-) diff --git a/eng/generators.targets b/eng/generators.targets index c24eca9be55dd1..3290f37580be29 100644 --- a/eng/generators.targets +++ b/eng/generators.targets @@ -48,12 +48,6 @@ " /> - - - - - diff --git a/src/libraries/System.Runtime.InteropServices/gen/DownlevelLibraryImportGenerator/DownlevelLibraryImportGenerator.cs b/src/libraries/System.Runtime.InteropServices/gen/DownlevelLibraryImportGenerator/DownlevelLibraryImportGenerator.cs index 63ca6ca49ff81d..101bc1df0ebc3e 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/DownlevelLibraryImportGenerator/DownlevelLibraryImportGenerator.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/DownlevelLibraryImportGenerator/DownlevelLibraryImportGenerator.cs @@ -35,8 +35,42 @@ public static class StepNames public const string GenerateSingleStub = nameof(GenerateSingleStub); } + // Internal definitions of LibraryImportAttribute and StringMarshalling that are injected + // into user projects targeting downlevel frameworks that don't have these types in the BCL. + // These definitions only expose the properties and enum values supported downlevel. + private const string GeneratedInteropTypes = """ + // + #pragma warning disable CS0436 + #nullable enable + namespace System.Runtime.InteropServices + { + [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = false, Inherited = false)] + internal sealed class LibraryImportAttribute : global::System.Attribute + { + public LibraryImportAttribute(string libraryName) { } + public string? EntryPoint { get; set; } + public StringMarshalling StringMarshalling { get; set; } + public global::System.Type? StringMarshallingCustomType { get; set; } + public bool SetLastError { get; set; } + } + + internal enum StringMarshalling + { + Custom = 0, + Utf8 = 1, + Utf16 = 2, + } + } + #pragma warning restore CS0436 + """; + public void Initialize(IncrementalGeneratorInitializationContext context) { + // Inject internal definitions of LibraryImportAttribute and StringMarshalling into the compilation + // so that users targeting downlevel frameworks can apply [LibraryImport] to their methods. + context.RegisterPostInitializationOutput(static ctx => + ctx.AddSource("LibraryImportInteropTypes.g.cs", GeneratedInteropTypes)); + // Collect all methods adorned with LibraryImportAttribute and filter out invalid ones // (diagnostics for invalid methods are reported by the analyzer) var methodsToGenerate = context.SyntaxProvider diff --git a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/AdditionalAttributesOnStub.cs b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/AdditionalAttributesOnStub.cs index ebb295f6d3fe79..8d58aa3679f45c 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/AdditionalAttributesOnStub.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/AdditionalAttributesOnStub.cs @@ -128,7 +128,6 @@ public async Task SkipLocalsInitOnDownlevelTargetFrameworks(TestTargetFramework { string source = $$""" using System.Runtime.InteropServices; - {{CodeSnippets.LibraryImportAttributeDeclaration}} partial class C { [LibraryImportAttribute("DoesNotExist")] diff --git a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/Compiles.cs b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/Compiles.cs index 225ae48288ebc1..3bc8b99d0adcc5 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/Compiles.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/Compiles.cs @@ -500,8 +500,9 @@ protected override ParseOptions CreateParseOptions() public static IEnumerable CodeSnippetsToValidateFallbackForwarder() { // Confirm that all unsupported target frameworks can be generated. + // LibraryImportAttribute and StringMarshalling are injected by the generator via RegisterPostInitializationOutput. { - string code = CodeSnippets.BasicParametersAndModifiers(CodeSnippets.LibraryImportAttributeDeclaration); + string code = CodeSnippets.BasicParametersAndModifiers(); yield return new object[] { ID(), code, TestTargetFramework.Standard2_0, false }; yield return new object[] { ID(), code, TestTargetFramework.Standard2_1, false }; yield return new object[] { ID(), code, TestTargetFramework.Framework, false }; @@ -509,7 +510,7 @@ public static IEnumerable CodeSnippetsToValidateFallbackForwarder() // Confirm that all unsupported target frameworks fall back to a forwarder. { - string code = CodeSnippets.BasicParametersAndModifiers(CodeSnippets.LibraryImportAttributeDeclaration); + string code = CodeSnippets.BasicParametersAndModifiers(); yield return new object[] { ID(), code, TestTargetFramework.Standard2_0, true }; yield return new object[] { ID(), code, TestTargetFramework.Standard2_1, true }; yield return new object[] { ID(), code, TestTargetFramework.Framework, true }; @@ -517,7 +518,7 @@ public static IEnumerable CodeSnippetsToValidateFallbackForwarder() // Confirm that all unsupported target frameworks fall back to a forwarder. { - string code = CodeSnippets.BasicParametersAndModifiersWithStringMarshalling(StringMarshalling.Utf16, CodeSnippets.LibraryImportAttributeDeclaration); + string code = CodeSnippets.BasicParametersAndModifiersWithStringMarshalling(StringMarshalling.Utf16); yield return new object[] { ID(), code, TestTargetFramework.Standard2_0, true }; yield return new object[] { ID(), code, TestTargetFramework.Standard2_1, true }; yield return new object[] { ID(), code, TestTargetFramework.Framework, true }; @@ -525,25 +526,25 @@ public static IEnumerable CodeSnippetsToValidateFallbackForwarder() // Confirm that if support is missing for a type with an ITypeBasedMarshallingInfoProvider (like arrays and SafeHandles), we fall back to a forwarder even if other types are supported. { - string code = CodeSnippets.BasicReturnAndParameterWithAlwaysSupportedParameter("void", "System.Runtime.InteropServices.SafeHandle", CodeSnippets.LibraryImportAttributeDeclaration); + string code = CodeSnippets.BasicReturnAndParameterWithAlwaysSupportedParameter("void", "System.Runtime.InteropServices.SafeHandle"); yield return new object[] { ID(), code, TestTargetFramework.Standard2_0, true }; yield return new object[] { ID(), code, TestTargetFramework.Standard2_1, true }; yield return new object[] { ID(), code, TestTargetFramework.Framework, true }; } { - string code = CodeSnippets.BasicReturnAndParameterWithAlwaysSupportedParameter("System.Runtime.InteropServices.SafeHandle", "int", CodeSnippets.LibraryImportAttributeDeclaration); + string code = CodeSnippets.BasicReturnAndParameterWithAlwaysSupportedParameter("System.Runtime.InteropServices.SafeHandle", "int"); yield return new object[] { ID(), code, TestTargetFramework.Standard2_0, true }; yield return new object[] { ID(), code, TestTargetFramework.Standard2_1, true }; yield return new object[] { ID(), code, TestTargetFramework.Framework, true }; } { - string code = CodeSnippets.BasicReturnAndParameterWithAlwaysSupportedParameter("void", "int[]", CodeSnippets.LibraryImportAttributeDeclaration); + string code = CodeSnippets.BasicReturnAndParameterWithAlwaysSupportedParameter("void", "int[]"); yield return new object[] { ID(), code, TestTargetFramework.Standard2_0, true }; yield return new object[] { ID(), code, TestTargetFramework.Standard2_1, true }; yield return new object[] { ID(), code, TestTargetFramework.Framework, true }; } { - string code = CodeSnippets.BasicReturnAndParameterWithAlwaysSupportedParameter("int", "int[]", CodeSnippets.LibraryImportAttributeDeclaration); + string code = CodeSnippets.BasicReturnAndParameterWithAlwaysSupportedParameter("int", "int[]"); yield return new object[] { ID(), code, TestTargetFramework.Standard2_0, true }; yield return new object[] { ID(), code, TestTargetFramework.Standard2_1, true }; yield return new object[] { ID(), code, TestTargetFramework.Framework, true }; @@ -551,13 +552,13 @@ public static IEnumerable CodeSnippetsToValidateFallbackForwarder() // Confirm that if support is missing for a type without an ITypeBasedMarshallingInfoProvider (like StringBuilder), we fall back to a forwarder even if other types are supported. { - string code = CodeSnippets.BasicReturnAndParameterWithAlwaysSupportedParameter("void", "System.Text.StringBuilder", CodeSnippets.LibraryImportAttributeDeclaration); + string code = CodeSnippets.BasicReturnAndParameterWithAlwaysSupportedParameter("void", "System.Text.StringBuilder"); yield return new object[] { ID(), code, TestTargetFramework.Standard2_0, true }; yield return new object[] { ID(), code, TestTargetFramework.Standard2_1, true }; yield return new object[] { ID(), code, TestTargetFramework.Framework, true }; } { - string code = CodeSnippets.BasicReturnAndParameterWithAlwaysSupportedParameter("int", "System.Text.StringBuilder", CodeSnippets.LibraryImportAttributeDeclaration); + string code = CodeSnippets.BasicReturnAndParameterWithAlwaysSupportedParameter("int", "System.Text.StringBuilder"); yield return new object[] { ID(), code, TestTargetFramework.Standard2_0, true }; yield return new object[] { ID(), code, TestTargetFramework.Standard2_1, true }; yield return new object[] { ID(), code, TestTargetFramework.Framework, true }; @@ -761,14 +762,17 @@ public class Basic { } [InlineData(TestTargetFramework.Standard2_0)] [InlineData(TestTargetFramework.Framework)] [Theory] - public async Task ValidateNoGeneratedOutputForNoImportDownlevel(TestTargetFramework framework) + public async Task ValidateNoStubOutputForNoImportDownlevel(TestTargetFramework framework) { + // The DownlevelLibraryImportGenerator always injects type definitions for LibraryImportAttribute + // and StringMarshalling via RegisterPostInitializationOutput. When there are no [LibraryImport] + // usages, no method stubs should be generated beyond those type definitions. string source = """ using System.Runtime.InteropServices; public class Basic { } """; - var test = new NoChangeTest(framework) + var test = new Microsoft.Interop.UnitTests.Verifiers.CSharpSourceGeneratorVerifier.Test(framework) { TestCode = source, TestBehaviors = TestBehaviors.SkipGeneratedSourcesCheck diff --git a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/Diagnostics.cs b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/Diagnostics.cs index b33c26d2cb22a6..f4ad43651ebaef 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/Diagnostics.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/Diagnostics.cs @@ -258,7 +258,7 @@ public Native(string s) { } public string ToManaged() => default; } } - """ + CodeSnippets.LibraryImportAttributeDeclaration; + """; DiagnosticResult[] expectedDiags = [ VerifyCS.Diagnostic(GeneratorDiagnostics.CannotForwardToDllImport) From de2391832a7c3d58da2bd6385f5d34424bbd4627 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Mar 2026 22:34:54 +0000 Subject: [PATCH 2/7] Add nullable restore to generated types, improve test rigor for no-import downlevel test Co-authored-by: jkoritzinsky <1571408+jkoritzinsky@users.noreply.github.com> --- .../DownlevelLibraryImportGenerator.cs | 1 + .../Compiles.cs | 20 ++++++++++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Runtime.InteropServices/gen/DownlevelLibraryImportGenerator/DownlevelLibraryImportGenerator.cs b/src/libraries/System.Runtime.InteropServices/gen/DownlevelLibraryImportGenerator/DownlevelLibraryImportGenerator.cs index 101bc1df0ebc3e..02be9befb72826 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/DownlevelLibraryImportGenerator/DownlevelLibraryImportGenerator.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/DownlevelLibraryImportGenerator/DownlevelLibraryImportGenerator.cs @@ -61,6 +61,7 @@ internal enum StringMarshalling Utf16 = 2, } } + #nullable restore #pragma warning restore CS0436 """; diff --git a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/Compiles.cs b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/Compiles.cs index 3bc8b99d0adcc5..d77ce3727475f1 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/Compiles.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/Compiles.cs @@ -772,7 +772,7 @@ public async Task ValidateNoStubOutputForNoImportDownlevel(TestTargetFramework f public class Basic { } """; - var test = new Microsoft.Interop.UnitTests.Verifiers.CSharpSourceGeneratorVerifier.Test(framework) + var test = new OnlyTypeDefinitionsOutputTest(framework) { TestCode = source, TestBehaviors = TestBehaviors.SkipGeneratedSourcesCheck @@ -781,6 +781,24 @@ public class Basic { } await test.RunAsync(); } + class OnlyTypeDefinitionsOutputTest : Microsoft.Interop.UnitTests.Verifiers.CSharpSourceGeneratorVerifier.Test + { + public OnlyTypeDefinitionsOutputTest(TestTargetFramework framework) + : base(framework) + { + } + + protected override async Task<(Compilation compilation, ImmutableArray generatorDiagnostics)> GetProjectCompilationAsync(Project project, IVerifier verifier, CancellationToken cancellationToken) + { + var originalCompilation = await project.GetCompilationAsync(cancellationToken); + var (newCompilation, diagnostics) = await base.GetProjectCompilationAsync(project, verifier, cancellationToken); + // The generator should inject exactly one extra syntax tree (the type definitions file), + // but no method stubs. + Assert.Equal(originalCompilation!.SyntaxTrees.Count() + 1, newCompilation.SyntaxTrees.Count()); + return (newCompilation, diagnostics); + } + } + class NoChangeTest : Microsoft.Interop.UnitTests.Verifiers.CSharpSourceGeneratorVerifier.Test where TSourceGenerator : new() where TAnalyzer : DiagnosticAnalyzer, new() From 7d6a0635f9f0af553b1236c143608d3087a157ba Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Mar 2026 18:43:31 +0000 Subject: [PATCH 3/7] Trim downlevel LibraryImportAttribute/StringMarshalling, add EmbeddedAttribute, remove visibility ifdefs Co-authored-by: jkoritzinsky <1571408+jkoritzinsky@users.noreply.github.com> Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/7e3db6e3-c81a-48ae-a946-ed5279403813 --- .../InteropServices/LibraryImportAttribute.cs | 11 +---- .../InteropServices/StringMarshalling.cs | 7 +--- .../DownlevelLibraryImportGenerator.cs | 12 +++--- .../Compiles.cs | 5 ++- .../Diagnostics.cs | 41 ------------------- 5 files changed, 12 insertions(+), 64 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/LibraryImportAttribute.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/LibraryImportAttribute.cs index b5dd3804d58f34..c4dedf19b05fbc 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/LibraryImportAttribute.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/LibraryImportAttribute.cs @@ -14,16 +14,7 @@ namespace System.Runtime.InteropServices /// applied to static, partial, non-generic methods. /// [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)] -#if SYSTEM_PRIVATE_CORELIB - public -#else -#pragma warning disable CS0436 // Type conflicts with imported type - // Some assemblies that target downlevel have InternalsVisibleTo to their test assemblies. - // As this is only used in this repo and isn't a problem in shipping code, - // just disable the duplicate type warning. - internal -#endif - sealed class LibraryImportAttribute : Attribute + public sealed class LibraryImportAttribute : Attribute { /// /// Initializes a new instance of the . diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/StringMarshalling.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/StringMarshalling.cs index b31d024cea8e68..0970d95135b9b1 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/StringMarshalling.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/StringMarshalling.cs @@ -13,12 +13,7 @@ namespace System.Runtime.InteropServices /// /// Specifies how strings should be marshalled for generated p/invokes /// -#if SYSTEM_PRIVATE_CORELIB || MICROSOFT_INTEROP_SOURCEGENERATION - public -#else - internal -#endif - enum StringMarshalling + public enum StringMarshalling { /// /// Indicates the user is supplying a specific marshaller in . diff --git a/src/libraries/System.Runtime.InteropServices/gen/DownlevelLibraryImportGenerator/DownlevelLibraryImportGenerator.cs b/src/libraries/System.Runtime.InteropServices/gen/DownlevelLibraryImportGenerator/DownlevelLibraryImportGenerator.cs index 02be9befb72826..c1d40ed40b5f1c 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/DownlevelLibraryImportGenerator/DownlevelLibraryImportGenerator.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/DownlevelLibraryImportGenerator/DownlevelLibraryImportGenerator.cs @@ -45,19 +45,18 @@ public static class StepNames namespace System.Runtime.InteropServices { [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = false, Inherited = false)] - internal sealed class LibraryImportAttribute : global::System.Attribute + [global::Microsoft.CodeAnalysis.Embedded] + internal sealed partial class LibraryImportAttribute : global::System.Attribute { public LibraryImportAttribute(string libraryName) { } public string? EntryPoint { get; set; } public StringMarshalling StringMarshalling { get; set; } - public global::System.Type? StringMarshallingCustomType { get; set; } public bool SetLastError { get; set; } } + [global::Microsoft.CodeAnalysis.Embedded] internal enum StringMarshalling { - Custom = 0, - Utf8 = 1, Utf16 = 2, } } @@ -70,7 +69,10 @@ public void Initialize(IncrementalGeneratorInitializationContext context) // Inject internal definitions of LibraryImportAttribute and StringMarshalling into the compilation // so that users targeting downlevel frameworks can apply [LibraryImport] to their methods. context.RegisterPostInitializationOutput(static ctx => - ctx.AddSource("LibraryImportInteropTypes.g.cs", GeneratedInteropTypes)); + { + ctx.AddEmbeddedAttributeDefinition(); + ctx.AddSource("LibraryImportInteropTypes.g.cs", GeneratedInteropTypes); + }); // Collect all methods adorned with LibraryImportAttribute and filter out invalid ones // (diagnostics for invalid methods are reported by the analyzer) diff --git a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/Compiles.cs b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/Compiles.cs index d77ce3727475f1..26074b949beef8 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/Compiles.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/Compiles.cs @@ -792,9 +792,10 @@ public OnlyTypeDefinitionsOutputTest(TestTargetFramework framework) { var originalCompilation = await project.GetCompilationAsync(cancellationToken); var (newCompilation, diagnostics) = await base.GetProjectCompilationAsync(project, verifier, cancellationToken); - // The generator should inject exactly one extra syntax tree (the type definitions file), + // The generator should inject exactly two extra syntax trees: + // one for the EmbeddedAttribute definition and one for the LibraryImport interop types, // but no method stubs. - Assert.Equal(originalCompilation!.SyntaxTrees.Count() + 1, newCompilation.SyntaxTrees.Count()); + Assert.Equal(originalCompilation!.SyntaxTrees.Count() + 2, newCompilation.SyntaxTrees.Count()); return (newCompilation, diagnostics); } } diff --git a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/Diagnostics.cs b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/Diagnostics.cs index f4ad43651ebaef..b6ea59ce8d4787 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/Diagnostics.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/Diagnostics.cs @@ -237,47 +237,6 @@ await VerifyCS.VerifyAnalyzerAsync(source, .WithArguments($"{nameof(MarshalAsAttribute)}{Type.Delimiter}{nameof(MarshalAsAttribute.IidParameterIndex)}")); } - [Fact] - [OuterLoop("Uses the network for downlevel ref packs")] - public async Task StringMarshallingForwardingNotSupported_ReportsDiagnostic() - { - string source = """ - - using System.Runtime.InteropServices; - partial class Test - { - [LibraryImport("DoesNotExist", StringMarshalling = StringMarshalling.Utf8)] - public static partial void {|#0:Method1|}(string s); - - [LibraryImport("DoesNotExist", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(Native))] - public static partial void {|#1:Method2|}(string s); - - struct Native - { - public Native(string s) { } - public string ToManaged() => default; - } - } - """; - DiagnosticResult[] expectedDiags = - [ - VerifyCS.Diagnostic(GeneratorDiagnostics.CannotForwardToDllImport) - .WithLocation(0) - .WithArguments($"{nameof(TypeNames.LibraryImportAttribute)}{Type.Delimiter}{nameof(StringMarshalling)}={nameof(StringMarshalling)}{Type.Delimiter}{nameof(StringMarshalling.Utf8)}"), - VerifyCS.Diagnostic(GeneratorDiagnostics.CannotForwardToDllImport) - .WithLocation(1) - .WithArguments($"{nameof(TypeNames.LibraryImportAttribute)}{Type.Delimiter}{nameof(StringMarshalling)}={nameof(StringMarshalling)}{Type.Delimiter}{nameof(StringMarshalling.Custom)}") - ]; - - var test = new Microsoft.Interop.UnitTests.Verifiers.CSharpSourceGeneratorVerifier.Test(TestTargetFramework.Standard2_0) - { - TestCode = source, - TestBehaviors = TestBehaviors.SkipGeneratedSourcesCheck - }; - test.ExpectedDiagnostics.AddRange(expectedDiags); - await test.RunAsync(); - } - [Fact] public async Task InvalidStringMarshallingConfiguration_ReportsDiagnostic() { From 4966da586393de3aea100969792124dcd7054e13 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Mar 2026 22:00:50 +0000 Subject: [PATCH 4/7] Remove CS0436 suppression pragmas from generated interop types (not needed with [Embedded]) Co-authored-by: jkoritzinsky <1571408+jkoritzinsky@users.noreply.github.com> Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/e54fed70-d31f-4d62-bc70-d698d655e325 --- .../DownlevelLibraryImportGenerator.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/libraries/System.Runtime.InteropServices/gen/DownlevelLibraryImportGenerator/DownlevelLibraryImportGenerator.cs b/src/libraries/System.Runtime.InteropServices/gen/DownlevelLibraryImportGenerator/DownlevelLibraryImportGenerator.cs index c1d40ed40b5f1c..0f313f9f840e81 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/DownlevelLibraryImportGenerator/DownlevelLibraryImportGenerator.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/DownlevelLibraryImportGenerator/DownlevelLibraryImportGenerator.cs @@ -40,7 +40,6 @@ public static class StepNames // These definitions only expose the properties and enum values supported downlevel. private const string GeneratedInteropTypes = """ // - #pragma warning disable CS0436 #nullable enable namespace System.Runtime.InteropServices { @@ -61,7 +60,6 @@ internal enum StringMarshalling } } #nullable restore - #pragma warning restore CS0436 """; public void Initialize(IncrementalGeneratorInitializationContext context) From 0d777de9b6496674e62f90b930f7a2c19734f6b1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 23 Mar 2026 21:20:47 +0000 Subject: [PATCH 5/7] Remove #nullable restore from GeneratedInteropTypes (no effect at end of file) Co-authored-by: jkoritzinsky <1571408+jkoritzinsky@users.noreply.github.com> Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/ef2d7d3b-0418-42e0-9dfd-5c4def8bd451 --- .../DownlevelLibraryImportGenerator.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libraries/System.Runtime.InteropServices/gen/DownlevelLibraryImportGenerator/DownlevelLibraryImportGenerator.cs b/src/libraries/System.Runtime.InteropServices/gen/DownlevelLibraryImportGenerator/DownlevelLibraryImportGenerator.cs index 0f313f9f840e81..aa84f8615811f9 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/DownlevelLibraryImportGenerator/DownlevelLibraryImportGenerator.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/DownlevelLibraryImportGenerator/DownlevelLibraryImportGenerator.cs @@ -59,7 +59,6 @@ internal enum StringMarshalling Utf16 = 2, } } - #nullable restore """; public void Initialize(IncrementalGeneratorInitializationContext context) From d446c834656cc3d2a0034ddd6ecec85c9fd611d6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Mar 2026 22:43:56 +0000 Subject: [PATCH 6/7] Apply review feedback: fix brittle tree count assertion, add CannotForwardToDllImport downlevel test Co-authored-by: jkoritzinsky <1571408+jkoritzinsky@users.noreply.github.com> Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/e82b595f-cd92-40d5-a099-e993fc9a1a12 --- .../Compiles.cs | 11 +++++--- .../Diagnostics.cs | 27 +++++++++++++++++++ 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/Compiles.cs b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/Compiles.cs index 26074b949beef8..81b10efc82d4af 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/Compiles.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/Compiles.cs @@ -792,10 +792,13 @@ public OnlyTypeDefinitionsOutputTest(TestTargetFramework framework) { var originalCompilation = await project.GetCompilationAsync(cancellationToken); var (newCompilation, diagnostics) = await base.GetProjectCompilationAsync(project, verifier, cancellationToken); - // The generator should inject exactly two extra syntax trees: - // one for the EmbeddedAttribute definition and one for the LibraryImport interop types, - // but no method stubs. - Assert.Equal(originalCompilation!.SyntaxTrees.Count() + 2, newCompilation.SyntaxTrees.Count()); + // The DownlevelLibraryImportGenerator should inject type definitions (for example, via + // RegisterPostInitializationOutput) but, when there are no [LibraryImport] usages, it + // must not emit any stub output such as the generated "LibraryImports" stub tree. + var originalTrees = originalCompilation!.SyntaxTrees.ToImmutableArray(); + var newTrees = newCompilation.SyntaxTrees.ToImmutableArray(); + var addedTrees = newTrees.Except(originalTrees).ToImmutableArray(); + Assert.DoesNotContain(addedTrees, tree => tree.FilePath.Contains("LibraryImports", StringComparison.Ordinal)); return (newCompilation, diagnostics); } } diff --git a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/Diagnostics.cs b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/Diagnostics.cs index b6ea59ce8d4787..9894e9141ed14d 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/Diagnostics.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/Diagnostics.cs @@ -237,6 +237,33 @@ await VerifyCS.VerifyAnalyzerAsync(source, .WithArguments($"{nameof(MarshalAsAttribute)}{Type.Delimiter}{nameof(MarshalAsAttribute.IidParameterIndex)}")); } + [Fact] + [OuterLoop("Uses the network for downlevel ref packs")] + public async Task StringMarshallingForwardingNotSupported_ReportsDiagnostic() + { + // The downlevel injected StringMarshalling enum only has Utf16 = 2. + // Use a numeric cast to exercise a non-Utf16 value that should trigger CannotForwardToDllImport. + string source = """ + using System.Runtime.InteropServices; + partial class Test + { + [LibraryImport("DoesNotExist", StringMarshalling = (StringMarshalling)1)] + public static partial void {|#0:Method1|}(string s); + } + """; + + var test = new Microsoft.Interop.UnitTests.Verifiers.CSharpSourceGeneratorVerifier.Test(TestTargetFramework.Standard2_0) + { + TestCode = source, + TestBehaviors = TestBehaviors.SkipGeneratedSourcesCheck + }; + test.ExpectedDiagnostics.Add( + VerifyCS.Diagnostic(GeneratorDiagnostics.CannotForwardToDllImport) + .WithLocation(0) + .WithArguments($"{nameof(TypeNames.LibraryImportAttribute)}{Type.Delimiter}{nameof(StringMarshalling)}={nameof(StringMarshalling)}{Type.Delimiter}{nameof(StringMarshalling.Utf8)}")); + await test.RunAsync(); + } + [Fact] public async Task InvalidStringMarshallingConfiguration_ReportsDiagnostic() { From a77afe79783a6fcd48aacc4d37b8ff7cf5dad871 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Fri, 27 Mar 2026 21:15:43 -0700 Subject: [PATCH 7/7] Remove downlevel code snippet --- .../CodeSnippets.cs | 22 ------------------- 1 file changed, 22 deletions(-) diff --git a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/CodeSnippets.cs b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/CodeSnippets.cs index c0896cc09e8f5d..08c9804f7f0993 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/CodeSnippets.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/CodeSnippets.cs @@ -46,28 +46,6 @@ string ICustomMarshallingSignatureTestProvider.MarshalUsingCollectionReturnValue string ICustomMarshallingSignatureTestProvider.CustomElementMarshalling(string type, string marshallerType, string preDeclaration) => CustomElementMarshalling(type, marshallerType, preDeclaration); - /// - /// Partially define attribute for pre-.NET 7.0 - /// - public static readonly string LibraryImportAttributeDeclaration = """ - namespace System.Runtime.InteropServices - { - internal enum StringMarshalling - { - Custom = 0, - Utf8, - Utf16, - } - - sealed class LibraryImportAttribute : System.Attribute - { - public LibraryImportAttribute(string a) { } - public StringMarshalling StringMarshalling { get; set; } - public Type StringMarshallingCustomType { get; set; } - } - } - """; - /// /// Trivial declaration of LibraryImport usage ///