Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions eng/generators.targets
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,6 @@
" />
</ItemGroup>

<ItemGroup Condition="'@(EnabledGenerators)' != '' and
@(EnabledGenerators->AnyHaveMetadataValue('Identity', 'DownlevelLibraryImportGenerator'))">
<Compile Include="$(CoreLibSharedDir)System\Runtime\InteropServices\LibraryImportAttribute.cs" />
<Compile Include="$(CoreLibSharedDir)System\Runtime\InteropServices\StringMarshalling.cs" />
</ItemGroup>

<!-- Use this complex item list based filtering to add the ProjectReference to make sure dotnet/runtime stays compatible with NuGet Static Graph Restore.
That is required as the EnabledGenerators condition checks on the ProjectReference items and hence can't be a property condition. -->
<ItemGroup Condition="'@(EnabledGenerators)' != ''">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,7 @@ namespace System.Runtime.InteropServices
/// applied to static, partial, non-generic methods.
/// </remarks>
[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
{
/// <summary>
/// Initializes a new instance of the <see cref="LibraryImportAttribute"/>.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,7 @@ namespace System.Runtime.InteropServices
/// <summary>
/// Specifies how strings should be marshalled for generated p/invokes
/// </summary>
#if SYSTEM_PRIVATE_CORELIB || MICROSOFT_INTEROP_SOURCEGENERATION
public
#else
internal
#endif
enum StringMarshalling
public enum StringMarshalling
{
/// <summary>
/// Indicates the user is supplying a specific marshaller in <see cref="LibraryImportAttribute.StringMarshallingCustomType"/>.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 = """
// <auto-generated/>
#nullable enable
namespace System.Runtime.InteropServices
{
[global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
[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 bool SetLastError { get; set; }
}
[global::Microsoft.CodeAnalysis.Embedded]
internal enum StringMarshalling
{
Utf16 = 2,
}
}
""";

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.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)
var methodsToGenerate = context.SyntaxProvider
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,6 @@ public async Task SkipLocalsInitOnDownlevelTargetFrameworks(TestTargetFramework
{
string source = $$"""
using System.Runtime.InteropServices;
{{CodeSnippets.LibraryImportAttributeDeclaration}}
partial class C
{
[LibraryImportAttribute("DoesNotExist")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -500,64 +500,65 @@ protected override ParseOptions CreateParseOptions()
public static IEnumerable<object[]> CodeSnippetsToValidateFallbackForwarder()
{
// Confirm that all unsupported target frameworks can be generated.
// LibraryImportAttribute and StringMarshalling are injected by the generator via RegisterPostInitializationOutput.
{
string code = CodeSnippets.BasicParametersAndModifiers<byte>(CodeSnippets.LibraryImportAttributeDeclaration);
string code = CodeSnippets.BasicParametersAndModifiers<byte>();
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 };
}

// Confirm that all unsupported target frameworks fall back to a forwarder.
{
string code = CodeSnippets.BasicParametersAndModifiers<byte[]>(CodeSnippets.LibraryImportAttributeDeclaration);
string code = CodeSnippets.BasicParametersAndModifiers<byte[]>();
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 };
}

// Confirm that all unsupported target frameworks fall back to a forwarder.
{
string code = CodeSnippets.BasicParametersAndModifiersWithStringMarshalling<string>(StringMarshalling.Utf16, CodeSnippets.LibraryImportAttributeDeclaration);
string code = CodeSnippets.BasicParametersAndModifiersWithStringMarshalling<string>(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 };
}

// 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 };
}

// 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 };
Expand Down Expand Up @@ -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<Microsoft.Interop.DownlevelLibraryImportGenerator, Microsoft.CodeAnalysis.Testing.EmptyDiagnosticAnalyzer>(framework)
var test = new OnlyTypeDefinitionsOutputTest(framework)
{
TestCode = source,
TestBehaviors = TestBehaviors.SkipGeneratedSourcesCheck
Expand All @@ -777,6 +781,28 @@ public class Basic { }
await test.RunAsync();
}

class OnlyTypeDefinitionsOutputTest : Microsoft.Interop.UnitTests.Verifiers.CSharpSourceGeneratorVerifier<Microsoft.Interop.DownlevelLibraryImportGenerator, Microsoft.CodeAnalysis.Testing.EmptyDiagnosticAnalyzer>.Test
{
public OnlyTypeDefinitionsOutputTest(TestTargetFramework framework)
: base(framework)
{
}

protected override async Task<(Compilation compilation, ImmutableArray<Diagnostic> generatorDiagnostics)> GetProjectCompilationAsync(Project project, IVerifier verifier, CancellationToken cancellationToken)
{
var originalCompilation = await project.GetCompilationAsync(cancellationToken);
var (newCompilation, diagnostics) = await base.GetProjectCompilationAsync(project, verifier, cancellationToken);
// 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);
}
}

class NoChangeTest<TSourceGenerator, TAnalyzer> : Microsoft.Interop.UnitTests.Verifiers.CSharpSourceGeneratorVerifier<TSourceGenerator, TAnalyzer>.Test
where TSourceGenerator : new()
where TAnalyzer : DiagnosticAnalyzer, new()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -241,40 +241,26 @@ await VerifyCS.VerifyAnalyzerAsync(source,
[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.Utf8)]
[LibraryImport("DoesNotExist", StringMarshalling = (StringMarshalling)1)]
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;
}
}
""" + CodeSnippets.LibraryImportAttributeDeclaration;
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<DownlevelLibraryImportGenerator, Microsoft.Interop.Analyzers.DownlevelLibraryImportDiagnosticsAnalyzer>.Test(TestTargetFramework.Standard2_0)
{
TestCode = source,
TestBehaviors = TestBehaviors.SkipGeneratedSourcesCheck
};
test.ExpectedDiagnostics.AddRange(expectedDiags);
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();
}

Expand Down
Loading