diff --git a/sources/ClangSharp.PInvokeGenerator/CSharp/CSharpOutputBuilder.Visit.cs b/sources/ClangSharp.PInvokeGenerator/CSharp/CSharpOutputBuilder.Visit.cs index c5afdc19..706eeeab 100644 --- a/sources/ClangSharp.PInvokeGenerator/CSharp/CSharpOutputBuilder.Visit.cs +++ b/sources/ClangSharp.PInvokeGenerator/CSharp/CSharpOutputBuilder.Visit.cs @@ -36,6 +36,10 @@ public void WriteCustomAttribute(string attribute, Action? callback = null) { AddUsingDirective("System.Runtime.Versioning"); } + else if (attribute.StartsWith("GeneratedCode(", StringComparison.Ordinal)) + { + AddUsingDirective("System.CodeDom.Compiler"); + } if (!_customAttrIsForParameter) { diff --git a/sources/ClangSharp.PInvokeGenerator/GeneratedCodeAttributeMode.cs b/sources/ClangSharp.PInvokeGenerator/GeneratedCodeAttributeMode.cs new file mode 100644 index 00000000..d619a3a0 --- /dev/null +++ b/sources/ClangSharp.PInvokeGenerator/GeneratedCodeAttributeMode.cs @@ -0,0 +1,10 @@ +// Copyright (c) .NET Foundation and Contributors. All Rights Reserved. Licensed under the MIT License (MIT). See License.md in the repository root for more information. + +namespace ClangSharp; + +public enum GeneratedCodeAttributeMode +{ + Assembly, + Type, + None +} diff --git a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.Close.cs b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.Close.cs index fe837916..6f4d13fa 100644 --- a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.Close.cs +++ b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.Close.cs @@ -87,6 +87,14 @@ public void Close() _ = staticUsingDirectives.Add(staticUsingDirective); } + // The method class emits its [GeneratedCode] attribute directly rather than through + // WriteCustomAttribute, so its System.CodeDom.Compiler using is not tracked on the + // builder and must be hoisted here for single-file output. + if (!csharpOutputBuilder.IsTestOutput && _config.GeneratedCodeAttributeMode == GeneratedCodeAttributeMode.Type && csharpOutputBuilder.Contents.Any() && _topLevelClassNames.Contains(csharpOutputBuilder.Name)) + { + _ = usingDirectives.Add("System.CodeDom.Compiler"); + } + if (csharpOutputBuilder.IsTestOutput) { testHasAnyContents |= csharpOutputBuilder.Contents.Any(); @@ -119,6 +127,11 @@ public void Close() _ = usingDirectives.Add("System"); _ = usingDirectives.Add("System.Diagnostics"); + if (_config.GeneratedCodeAttributeMode == GeneratedCodeAttributeMode.Assembly) + { + _ = usingDirectives.Add("System.CodeDom.Compiler"); + } + if (_config.GenerateSetsLastSystemErrorAttribute) { _ = usingDirectives.Add("System.Runtime.InteropServices"); @@ -167,14 +180,36 @@ public void Close() sw.WriteLine(); } - if (generateHelperTypes && _config.GenerateDisableRuntimeMarshalling) + if (generateHelperTypes) { - // Assembly attributes must precede the namespace declaration, so the - // [assembly: DisableRuntimeMarshalling] attribute is emitted here rather - // than alongside the other helper types. + var emittedAssemblyAttribute = false; - sw.WriteLine("[assembly: DisableRuntimeMarshalling]"); - sw.WriteLine(); + if (_config.GeneratedCodeAttributeMode == GeneratedCodeAttributeMode.Assembly) + { + // Assembly attributes must precede the namespace declaration. The generated + // helper types identify the assembly as containing generated code, so the + // [assembly: GeneratedCode] marker is emitted here rather than per type. + + sw.Write("[assembly: "); + sw.Write(GeneratedCodeAttribute); + sw.WriteLine(']'); + emittedAssemblyAttribute = true; + } + + if (_config.GenerateDisableRuntimeMarshalling) + { + // Assembly attributes must precede the namespace declaration, so the + // [assembly: DisableRuntimeMarshalling] attribute is emitted here rather + // than alongside the other helper types. + + sw.WriteLine("[assembly: DisableRuntimeMarshalling]"); + emittedAssemblyAttribute = true; + } + + if (emittedAssemblyAttribute) + { + sw.WriteLine(); + } } } else if (_config.OutputMode == PInvokeGeneratorOutputMode.Xml) @@ -362,6 +397,7 @@ public void Close() } } + GenerateGeneratedCodeAssemblyAttribute(this, stream, leaveStreamOpen); GenerateDisableRuntimeMarshallingAttribute(this, stream, leaveStreamOpen); hasNamespaceContent = GenerateNativeBitfieldAttribute(this, stream, leaveStreamOpen, hasNamespaceContent); hasNamespaceContent = GenerateNativeInheritanceAttribute(this, stream, leaveStreamOpen, hasNamespaceContent); @@ -412,6 +448,49 @@ public void Close() _uuidsToGenerate.Clear(); _visitedFiles.Clear(); + static void GenerateGeneratedCodeAssemblyAttribute(PInvokeGenerator generator, Stream? stream, bool leaveStreamOpen) + { + var config = generator.Config; + + if (config.GeneratedCodeAttributeMode != GeneratedCodeAttributeMode.Assembly) + { + return; + } + + if (!config.GenerateMultipleFiles) + { + // In single-file mode the [assembly: GeneratedCode] attribute is emitted at the top + // of the file, before the namespace declaration, since assembly attributes cannot + // appear after a namespace. + return; + } + + if (stream is null) + { + var outputPath = Path.Combine(config.OutputLocation, "GeneratedCode.cs"); + stream = generator._outputStreamFactory(outputPath); + } + + using var sw = new StreamWriter(stream, s_defaultStreamWriterEncoding, DefaultStreamWriterBufferSize, leaveStreamOpen); + sw.NewLine = "\n"; + + if (!string.IsNullOrEmpty(config.HeaderText)) + { + sw.WriteLine(config.HeaderText); + } + + sw.WriteLine("using System.CodeDom.Compiler;"); + sw.WriteLine(); + sw.Write("[assembly: "); + sw.Write(GeneratedCodeAttribute); + sw.WriteLine(']'); + + if (!leaveStreamOpen) + { + stream = null; + } + } + static void GenerateDisableRuntimeMarshallingAttribute(PInvokeGenerator generator, Stream? stream, bool leaveStreamOpen) { var config = generator.Config; diff --git a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs index 92551446..4dd8775e 100644 --- a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs +++ b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs @@ -399,7 +399,7 @@ private void VisitEnumDecl(EnumDecl enumDecl) WriteCustomAttrs = static context => { (var enumDecl, var generator) = ((EnumDecl, PInvokeGenerator))context; - generator.WithAttributes(enumDecl); + generator.WithAttributes(enumDecl, emitGeneratedCodeAttribute: true); generator.WithUsings(enumDecl); }, CustomAttrGeneratorData = (enumDecl, this), @@ -1401,7 +1401,7 @@ void ForFunctionProtoType(TypedefDecl typedefDecl, FunctionProtoType functionPro WriteCustomAttrs = static context => { (var typedefDecl, var generator) = ((TypedefDecl, PInvokeGenerator))context; - generator.WithAttributes(typedefDecl); + generator.WithAttributes(typedefDecl, emitGeneratedCodeAttribute: true); generator.WithUsings(typedefDecl); }, CustomAttrGeneratorData = (typedefDecl, this), diff --git a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitRecordDecl.cs b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitRecordDecl.cs index 905c725e..b1899a5b 100644 --- a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitRecordDecl.cs +++ b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitRecordDecl.cs @@ -217,7 +217,7 @@ private void VisitRecordDecl(RecordDecl recordDecl) WriteCustomAttrs = static context => { (var recordDecl, var generator) = ((RecordDecl, PInvokeGenerator))context; - generator.WithAttributes(recordDecl); + generator.WithAttributes(recordDecl, emitGeneratedCodeAttribute: true); generator.WithUsings(recordDecl); }, CustomAttrGeneratorData = (recordDecl, this), diff --git a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.cs b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.cs index 6261db19..61300722 100644 --- a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.cs +++ b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.cs @@ -42,6 +42,13 @@ public sealed partial class PInvokeGenerator : IDisposable private const string AnonymousRecordPrefix = $"{AnonymousNamePrefix}Record_"; private const string AnonymousTypeKindTag = "_e__"; + // The [GeneratedCode] annotation emitted to mark output as generated. By default it rides on the + // assembly (via [assembly: GeneratedCode]) when helper types are generated; --config + // generate-generated-code=type instead annotates each generated top-level type, and =none emits + // neither. The version is the ClangSharp assembly version (without any prerelease/build metadata) so + // it is deterministic within a release and only churns the assembly/helper-type baselines on bump. + private static readonly string GeneratedCodeAttribute = $"GeneratedCode(\"ClangSharp\", \"{typeof(PInvokeGenerator).Assembly.GetName().Version?.ToString() ?? ""}\")"; + private static readonly Encoding s_defaultStreamWriterEncoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true); private static readonly string[] s_doubleColonSeparator = ["::"]; private static readonly char[] s_doubleQuoteSeparator = ['"']; @@ -630,6 +637,11 @@ private void CloseOutputBuilder(Stream stream, IOutputBuilder outputBuilder, boo csharpOutputBuilder.AddUsingDirective(withUsing); } } + + if (!outputBuilder.IsTestOutput && _config.GeneratedCodeAttributeMode == GeneratedCodeAttributeMode.Type) + { + csharpOutputBuilder.AddUsingDirective("System.CodeDom.Compiler"); + } } var usingDirectives = new SortedSet(csharpOutputBuilder.UsingDirectives, StringComparer.Ordinal); @@ -740,6 +752,14 @@ void ForCSharp(CSharpOutputBuilder outputBuilder) sw.WriteLine("."); } + if (!outputBuilder.IsTestOutput && _config.GeneratedCodeAttributeMode == GeneratedCodeAttributeMode.Type) + { + sw.Write(indentationString); + sw.Write('['); + sw.Write(GeneratedCodeAttribute); + sw.WriteLine(']'); + } + if (_topLevelClassAttributes.GetAlternateLookup>().TryGetValue(nonTestName, out var withAttributes)) { if (withAttributes.Count != 0) @@ -2151,11 +2171,16 @@ private static IEnumerable GetAttributesFor(NamedDecl namedDecl) return declAttrs; } - private void WithAttributes(NamedDecl namedDecl, bool onlySupportedOSPlatform = false, bool isTestOutput = false) + private void WithAttributes(NamedDecl namedDecl, bool onlySupportedOSPlatform = false, bool isTestOutput = false, bool emitGeneratedCodeAttribute = false) { var outputBuilder = isTestOutput ? _testOutputBuilder : _outputBuilder; Debug.Assert(outputBuilder is not null); + if (emitGeneratedCodeAttribute && !isTestOutput && !onlySupportedOSPlatform && _config.GeneratedCodeAttributeMode == GeneratedCodeAttributeMode.Type) + { + outputBuilder.WriteCustomAttribute(GeneratedCodeAttribute); + } + if (TryGetRemappedValue(namedDecl, _config._withAttributes, out var attributes, matchStar: true)) { foreach (var attribute in attributes.Where((a) => !onlySupportedOSPlatform || a.StartsWith("SupportedOSPlatform(", StringComparison.Ordinal))) diff --git a/sources/ClangSharp.PInvokeGenerator/PInvokeGeneratorConfiguration.cs b/sources/ClangSharp.PInvokeGenerator/PInvokeGeneratorConfiguration.cs index c0af7859..1199bb15 100644 --- a/sources/ClangSharp.PInvokeGenerator/PInvokeGeneratorConfiguration.cs +++ b/sources/ClangSharp.PInvokeGenerator/PInvokeGeneratorConfiguration.cs @@ -295,6 +295,24 @@ public IReadOnlyCollection ExcludedNames public bool GenerateFixedBufferIndexerOverloads => (_options & PInvokeGeneratorConfigurationOptions.GenerateFixedBufferIndexerOverloads) != 0; + public GeneratedCodeAttributeMode GeneratedCodeAttributeMode + { + get + { + if ((_options & PInvokeGeneratorConfigurationOptions.ExcludeGeneratedCodeAttribute) != 0) + { + return GeneratedCodeAttributeMode.None; + } + + if ((_options & PInvokeGeneratorConfigurationOptions.GenerateGeneratedCodeAttributeAsType) != 0) + { + return GeneratedCodeAttributeMode.Type; + } + + return GeneratedCodeAttributeMode.Assembly; + } + } + public bool GenerateGenericPointerWrapper => (_options & PInvokeGeneratorConfigurationOptions.GenerateGenericPointerWrapper) != 0; public bool GenerateGuidMember => (_options & PInvokeGeneratorConfigurationOptions.GenerateGuidMember) != 0; diff --git a/sources/ClangSharp.PInvokeGenerator/PInvokeGeneratorConfigurationOptions.cs b/sources/ClangSharp.PInvokeGenerator/PInvokeGeneratorConfigurationOptions.cs index ab8c6421..7853a260 100644 --- a/sources/ClangSharp.PInvokeGenerator/PInvokeGeneratorConfigurationOptions.cs +++ b/sources/ClangSharp.PInvokeGenerator/PInvokeGeneratorConfigurationOptions.cs @@ -92,4 +92,8 @@ public enum PInvokeGeneratorConfigurationOptions : long DontUseUsingStaticsForGuidMember = 1L << 40, GenerateFixedBufferIndexerOverloads = 1L << 41, + + GenerateGeneratedCodeAttributeAsType = 1L << 42, + + ExcludeGeneratedCodeAttribute = 1L << 43, } diff --git a/sources/ClangSharpPInvokeGenerator/Program.Options.cs b/sources/ClangSharpPInvokeGenerator/Program.Options.cs index e1630e39..bd413554 100644 --- a/sources/ClangSharpPInvokeGenerator/Program.Options.cs +++ b/sources/ClangSharpPInvokeGenerator/Program.Options.cs @@ -201,6 +201,7 @@ internal static partial class Program new HelpRow("generate-doc-includes", " xml documentation tags should be generated for declarations."), new HelpRow("generate-file-scoped-namespaces", "Namespaces should be scoped to the file to reduce nesting."), new HelpRow("generate-fixed-buffer-indexer-overloads", "Fixed sized buffer helper types should generate additional uint, nint, and nuint indexer overloads."), + new HelpRow("generate-generated-code=", "Controls the emission of the GeneratedCode attribute. 'assembly' (default) emits a single '[assembly: GeneratedCode]' when helper types are generated; 'type' instead annotates each generated top-level type; 'none' emits neither."), new HelpRow("generate-guid-member", "Types with an associated GUID should have a corresponding member generated."), new HelpRow("generate-helper-types", "Code files should be generated for various helper attributes and declared transparent structs."), new HelpRow("generate-macro-bindings", "Bindings for macro-definitions should be generated. This currently only works with value like macros and not function-like ones."), diff --git a/sources/ClangSharpPInvokeGenerator/Program.cs b/sources/ClangSharpPInvokeGenerator/Program.cs index 39e49066..833812ea 100644 --- a/sources/ClangSharpPInvokeGenerator/Program.cs +++ b/sources/ClangSharpPInvokeGenerator/Program.cs @@ -143,8 +143,12 @@ public static int Run() var configOptions = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? PInvokeGeneratorConfigurationOptions.None : PInvokeGeneratorConfigurationOptions.GenerateUnixTypes; var printConfigHelp = false; - foreach (var configSwitch in configSwitches) + foreach (var configSwitchRaw in configSwitches) { + var separatorIndex = configSwitchRaw.IndexOf('=', StringComparison.Ordinal); + var configSwitch = separatorIndex >= 0 ? configSwitchRaw[..separatorIndex] : configSwitchRaw; + var configSwitchValue = separatorIndex >= 0 ? configSwitchRaw[(separatorIndex + 1)..] : ""; + switch (configSwitch) { case "?": @@ -381,6 +385,40 @@ public static int Run() break; } + case "generate-generated-code": + { + switch (configSwitchValue) + { + case "" or "assembly": + { + configOptions &= ~PInvokeGeneratorConfigurationOptions.GenerateGeneratedCodeAttributeAsType; + configOptions &= ~PInvokeGeneratorConfigurationOptions.ExcludeGeneratedCodeAttribute; + break; + } + + case "type": + { + configOptions |= PInvokeGeneratorConfigurationOptions.GenerateGeneratedCodeAttributeAsType; + configOptions &= ~PInvokeGeneratorConfigurationOptions.ExcludeGeneratedCodeAttribute; + break; + } + + case "none": + { + configOptions |= PInvokeGeneratorConfigurationOptions.ExcludeGeneratedCodeAttribute; + configOptions &= ~PInvokeGeneratorConfigurationOptions.GenerateGeneratedCodeAttributeAsType; + break; + } + + default: + { + errorList.Add($"Error: Unrecognized generate-generated-code value: {configSwitchValue}. Expected 'assembly', 'type', or 'none'."); + break; + } + } + break; + } + case "generate-guid-member": { configOptions |= PInvokeGeneratorConfigurationOptions.GenerateGuidMember; diff --git a/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/GeneratedCodeAttribute/EmitsAssemblyAttributeWithHelperTypes.CSharp.Latest.Windows.cs b/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/GeneratedCodeAttribute/EmitsAssemblyAttributeWithHelperTypes.CSharp.Latest.Windows.cs new file mode 100644 index 00000000..8eec117b --- /dev/null +++ b/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/GeneratedCodeAttribute/EmitsAssemblyAttributeWithHelperTypes.CSharp.Latest.Windows.cs @@ -0,0 +1,61 @@ +using System; +using System.CodeDom.Compiler; +using System.Diagnostics; +using System.Runtime.InteropServices; + +[assembly: GeneratedCode("ClangSharp", "21.1.8.3")] + +namespace ClangSharp.Test +{ + public enum MyEnum + { + MyEnum_Value, + } + + public partial struct MyStruct + { + public int value; + } + + public static unsafe partial class Methods + { + [DllImport("ClangSharpPInvokeGenerator", CallingConvention = CallingConvention.Cdecl, EntryPoint = "?MyFunction@@YAXP6AXH@Z@Z", ExactSpelling = true)] + public static extern void MyFunction([NativeTypeName("MyCallback")] delegate* unmanaged[Cdecl] callback); + } + + /// Defines the type of a member as it was used in the native signature. + [AttributeUsage(AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.ReturnValue, AllowMultiple = false, Inherited = true)] + [Conditional("DEBUG")] + internal sealed partial class NativeTypeNameAttribute : Attribute + { + private readonly string _name; + + /// Initializes a new instance of the class. + /// The name of the type that was used in the native signature. + public NativeTypeNameAttribute(string name) + { + _name = name; + } + + /// Gets the name of the type that was used in the native signature. + public string Name => _name; + } + + /// Defines the annotation found in a native declaration. + [AttributeUsage(AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.ReturnValue, AllowMultiple = true, Inherited = false)] + [Conditional("DEBUG")] + internal sealed partial class NativeAnnotationAttribute : Attribute + { + private readonly string _annotation; + + /// Initializes a new instance of the class. + /// The annotation that was used in the native declaration. + public NativeAnnotationAttribute(string annotation) + { + _annotation = annotation; + } + + /// Gets the annotation that was used in the native declaration. + public string Annotation => _annotation; + } +} diff --git a/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/GeneratedCodeAttribute/EmitsPerTypeAttributeInTypeMode.CSharp.Latest.Windows.cs b/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/GeneratedCodeAttribute/EmitsPerTypeAttributeInTypeMode.CSharp.Latest.Windows.cs new file mode 100644 index 00000000..e728ef91 --- /dev/null +++ b/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/GeneratedCodeAttribute/EmitsPerTypeAttributeInTypeMode.CSharp.Latest.Windows.cs @@ -0,0 +1,24 @@ +using System.CodeDom.Compiler; +using System.Runtime.InteropServices; + +namespace ClangSharp.Test +{ + [GeneratedCode("ClangSharp", "21.1.8.3")] + public enum MyEnum + { + MyEnum_Value, + } + + [GeneratedCode("ClangSharp", "21.1.8.3")] + public partial struct MyStruct + { + public int value; + } + + [GeneratedCode("ClangSharp", "21.1.8.3")] + public static unsafe partial class Methods + { + [DllImport("ClangSharpPInvokeGenerator", CallingConvention = CallingConvention.Cdecl, EntryPoint = "?MyFunction@@YAXP6AXH@Z@Z", ExactSpelling = true)] + public static extern void MyFunction([NativeTypeName("MyCallback")] delegate* unmanaged[Cdecl] callback); + } +} diff --git a/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/GeneratedCodeAttribute/EmitsPerTypeAttributeOnDelegateInTypeMode.CSharp.Compatible.Windows.cs b/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/GeneratedCodeAttribute/EmitsPerTypeAttributeOnDelegateInTypeMode.CSharp.Compatible.Windows.cs new file mode 100644 index 00000000..bd843a50 --- /dev/null +++ b/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/GeneratedCodeAttribute/EmitsPerTypeAttributeOnDelegateInTypeMode.CSharp.Compatible.Windows.cs @@ -0,0 +1,29 @@ +using System; +using System.CodeDom.Compiler; +using System.Runtime.InteropServices; + +namespace ClangSharp.Test +{ + [GeneratedCode("ClangSharp", "21.1.8.3")] + public enum MyEnum + { + MyEnum_Value, + } + + [GeneratedCode("ClangSharp", "21.1.8.3")] + public partial struct MyStruct + { + public int value; + } + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + [GeneratedCode("ClangSharp", "21.1.8.3")] + public delegate void MyCallback(int value); + + [GeneratedCode("ClangSharp", "21.1.8.3")] + public static partial class Methods + { + [DllImport("ClangSharpPInvokeGenerator", CallingConvention = CallingConvention.Cdecl, EntryPoint = "?MyFunction@@YAXP6AXH@Z@Z", ExactSpelling = true)] + public static extern void MyFunction([NativeTypeName("MyCallback")] IntPtr callback); + } +} diff --git a/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/GeneratedCodeAttribute/EmitsPerTypeInsteadOfAssemblyWithHelperTypes.CSharp.Latest.Windows.cs b/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/GeneratedCodeAttribute/EmitsPerTypeInsteadOfAssemblyWithHelperTypes.CSharp.Latest.Windows.cs new file mode 100644 index 00000000..1ea046ab --- /dev/null +++ b/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/GeneratedCodeAttribute/EmitsPerTypeInsteadOfAssemblyWithHelperTypes.CSharp.Latest.Windows.cs @@ -0,0 +1,62 @@ +using System; +using System.CodeDom.Compiler; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace ClangSharp.Test +{ + [GeneratedCode("ClangSharp", "21.1.8.3")] + public enum MyEnum + { + MyEnum_Value, + } + + [GeneratedCode("ClangSharp", "21.1.8.3")] + public partial struct MyStruct + { + public int value; + } + + [GeneratedCode("ClangSharp", "21.1.8.3")] + public static unsafe partial class Methods + { + [DllImport("ClangSharpPInvokeGenerator", CallingConvention = CallingConvention.Cdecl, EntryPoint = "?MyFunction@@YAXP6AXH@Z@Z", ExactSpelling = true)] + public static extern void MyFunction([NativeTypeName("MyCallback")] delegate* unmanaged[Cdecl] callback); + } + + /// Defines the type of a member as it was used in the native signature. + [AttributeUsage(AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.ReturnValue, AllowMultiple = false, Inherited = true)] + [Conditional("DEBUG")] + internal sealed partial class NativeTypeNameAttribute : Attribute + { + private readonly string _name; + + /// Initializes a new instance of the class. + /// The name of the type that was used in the native signature. + public NativeTypeNameAttribute(string name) + { + _name = name; + } + + /// Gets the name of the type that was used in the native signature. + public string Name => _name; + } + + /// Defines the annotation found in a native declaration. + [AttributeUsage(AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.ReturnValue, AllowMultiple = true, Inherited = false)] + [Conditional("DEBUG")] + internal sealed partial class NativeAnnotationAttribute : Attribute + { + private readonly string _annotation; + + /// Initializes a new instance of the class. + /// The annotation that was used in the native declaration. + public NativeAnnotationAttribute(string annotation) + { + _annotation = annotation; + } + + /// Gets the annotation that was used in the native declaration. + public string Annotation => _annotation; + } +} diff --git a/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/GeneratedCodeAttribute/OmitsAllAttributesInNoneModeWithHelperTypes.CSharp.Latest.Windows.cs b/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/GeneratedCodeAttribute/OmitsAllAttributesInNoneModeWithHelperTypes.CSharp.Latest.Windows.cs new file mode 100644 index 00000000..edc1cd59 --- /dev/null +++ b/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/GeneratedCodeAttribute/OmitsAllAttributesInNoneModeWithHelperTypes.CSharp.Latest.Windows.cs @@ -0,0 +1,58 @@ +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace ClangSharp.Test +{ + public enum MyEnum + { + MyEnum_Value, + } + + public partial struct MyStruct + { + public int value; + } + + public static unsafe partial class Methods + { + [DllImport("ClangSharpPInvokeGenerator", CallingConvention = CallingConvention.Cdecl, EntryPoint = "?MyFunction@@YAXP6AXH@Z@Z", ExactSpelling = true)] + public static extern void MyFunction([NativeTypeName("MyCallback")] delegate* unmanaged[Cdecl] callback); + } + + /// Defines the type of a member as it was used in the native signature. + [AttributeUsage(AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.ReturnValue, AllowMultiple = false, Inherited = true)] + [Conditional("DEBUG")] + internal sealed partial class NativeTypeNameAttribute : Attribute + { + private readonly string _name; + + /// Initializes a new instance of the class. + /// The name of the type that was used in the native signature. + public NativeTypeNameAttribute(string name) + { + _name = name; + } + + /// Gets the name of the type that was used in the native signature. + public string Name => _name; + } + + /// Defines the annotation found in a native declaration. + [AttributeUsage(AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.ReturnValue, AllowMultiple = true, Inherited = false)] + [Conditional("DEBUG")] + internal sealed partial class NativeAnnotationAttribute : Attribute + { + private readonly string _annotation; + + /// Initializes a new instance of the class. + /// The annotation that was used in the native declaration. + public NativeAnnotationAttribute(string annotation) + { + _annotation = annotation; + } + + /// Gets the annotation that was used in the native declaration. + public string Annotation => _annotation; + } +} diff --git a/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/GeneratedCodeAttribute/OmitsAssemblyAttributeWithoutHelperTypes.CSharp.Latest.Windows.cs b/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/GeneratedCodeAttribute/OmitsAssemblyAttributeWithoutHelperTypes.CSharp.Latest.Windows.cs new file mode 100644 index 00000000..0170cadd --- /dev/null +++ b/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/GeneratedCodeAttribute/OmitsAssemblyAttributeWithoutHelperTypes.CSharp.Latest.Windows.cs @@ -0,0 +1,20 @@ +using System.Runtime.InteropServices; + +namespace ClangSharp.Test +{ + public enum MyEnum + { + MyEnum_Value, + } + + public partial struct MyStruct + { + public int value; + } + + public static unsafe partial class Methods + { + [DllImport("ClangSharpPInvokeGenerator", CallingConvention = CallingConvention.Cdecl, EntryPoint = "?MyFunction@@YAXP6AXH@Z@Z", ExactSpelling = true)] + public static extern void MyFunction([NativeTypeName("MyCallback")] delegate* unmanaged[Cdecl] callback); + } +} diff --git a/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/HelperTypes/HelperTypesEmittedAfterMethodClass.CSharp.Latest.Windows.cs b/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/HelperTypes/HelperTypesEmittedAfterMethodClass.CSharp.Latest.Windows.cs index 9051b364..f1e36a3d 100644 --- a/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/HelperTypes/HelperTypesEmittedAfterMethodClass.CSharp.Latest.Windows.cs +++ b/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/HelperTypes/HelperTypesEmittedAfterMethodClass.CSharp.Latest.Windows.cs @@ -1,7 +1,10 @@ using System; +using System.CodeDom.Compiler; using System.Diagnostics; using System.Runtime.InteropServices; +[assembly: GeneratedCode("ClangSharp", "21.1.8.3")] + namespace ClangSharp.Test { public unsafe partial struct SRC_DATA diff --git a/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/HelperTypes/HelperTypesEmittedAfterMethodClassFileScoped.CSharp.Latest.Windows.cs b/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/HelperTypes/HelperTypesEmittedAfterMethodClassFileScoped.CSharp.Latest.Windows.cs index 8f6ba8d5..4c894360 100644 --- a/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/HelperTypes/HelperTypesEmittedAfterMethodClassFileScoped.CSharp.Latest.Windows.cs +++ b/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/HelperTypes/HelperTypesEmittedAfterMethodClassFileScoped.CSharp.Latest.Windows.cs @@ -1,7 +1,10 @@ using System; +using System.CodeDom.Compiler; using System.Diagnostics; using System.Runtime.InteropServices; +[assembly: GeneratedCode("ClangSharp", "21.1.8.3")] + namespace ClangSharp.Test; public unsafe partial struct SRC_DATA diff --git a/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/HelperTypes/HelperTypesEmittedInSharedFileScopedNamespace.CSharp.Latest.Windows.cs b/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/HelperTypes/HelperTypesEmittedInSharedFileScopedNamespace.CSharp.Latest.Windows.cs index 43ca195f..1e8f3ba2 100644 --- a/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/HelperTypes/HelperTypesEmittedInSharedFileScopedNamespace.CSharp.Latest.Windows.cs +++ b/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/HelperTypes/HelperTypesEmittedInSharedFileScopedNamespace.CSharp.Latest.Windows.cs @@ -1,6 +1,9 @@ using System; +using System.CodeDom.Compiler; using System.Diagnostics; +[assembly: GeneratedCode("ClangSharp", "21.1.8.3")] + namespace ClangSharp.Test; public unsafe partial struct SRC_DATA diff --git a/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/HelperTypes/HelperTypesEmittedInSharedNamespace.CSharp.Latest.Windows.cs b/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/HelperTypes/HelperTypesEmittedInSharedNamespace.CSharp.Latest.Windows.cs index 719c4991..ade31a5c 100644 --- a/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/HelperTypes/HelperTypesEmittedInSharedNamespace.CSharp.Latest.Windows.cs +++ b/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/HelperTypes/HelperTypesEmittedInSharedNamespace.CSharp.Latest.Windows.cs @@ -1,6 +1,9 @@ using System; +using System.CodeDom.Compiler; using System.Diagnostics; +[assembly: GeneratedCode("ClangSharp", "21.1.8.3")] + namespace ClangSharp.Test { public unsafe partial struct SRC_DATA diff --git a/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/TransparentStruct/BooleanKindGeneratesValidHelper.CSharp.Latest.Windows.cs b/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/TransparentStruct/BooleanKindGeneratesValidHelper.CSharp.Latest.Windows.cs index c5b15dee..c1b367b1 100644 --- a/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/TransparentStruct/BooleanKindGeneratesValidHelper.CSharp.Latest.Windows.cs +++ b/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/TransparentStruct/BooleanKindGeneratesValidHelper.CSharp.Latest.Windows.cs @@ -1,6 +1,9 @@ using System; +using System.CodeDom.Compiler; using System.Diagnostics; +[assembly: GeneratedCode("ClangSharp", "21.1.8.3")] + namespace ClangSharp.Test { /// Defines the type of a member as it was used in the native signature. diff --git a/tests/ClangSharp.PInvokeGenerator.UnitTests/GeneratedCodeAttributeTest.cs b/tests/ClangSharp.PInvokeGenerator.UnitTests/GeneratedCodeAttributeTest.cs new file mode 100644 index 00000000..20718a29 --- /dev/null +++ b/tests/ClangSharp.PInvokeGenerator.UnitTests/GeneratedCodeAttributeTest.cs @@ -0,0 +1,61 @@ +// Copyright (c) .NET Foundation and Contributors. All Rights Reserved. Licensed under the MIT License (MIT). See License.md in the repository root for more information. + +using System.Threading.Tasks; +using ClangSharp.UnitTests.Baseline; +using NUnit.Framework; + +namespace ClangSharp.UnitTests; + +/// +/// Tests for https://github.com/dotnet/ClangSharp/issues/631. +/// The GeneratedCode attribute has three modes. In the default 'assembly' mode a single +/// [assembly: GeneratedCode("ClangSharp", version)] marker is emitted when helper types are +/// generated. In 'type' mode (generate-generated-code=type) each generated top-level type is +/// annotated with [GeneratedCode("ClangSharp", version)] instead, and no assembly marker is +/// emitted. In 'none' mode (generate-generated-code=none) neither is emitted. The version is the +/// ClangSharp assembly version. +/// +[Platform("win")] +public sealed class GeneratedCodeAttributeTest : StandaloneBaselineTest +{ + protected override string Area => "GeneratedCodeAttribute"; + + private const string InputContents = @"enum MyEnum +{ + MyEnum_Value, +}; + +struct MyStruct +{ + int value; +}; + +typedef void (*MyCallback)(int value); + +void MyFunction(MyCallback callback); +"; + + [Test] + public Task OmitsAssemblyAttributeWithoutHelperTypes() + => ValidateGeneratedCSharpLatestWindowsBaselineAsync(InputContents); + + [Test] + public Task EmitsAssemblyAttributeWithHelperTypes() + => ValidateGeneratedCSharpLatestWindowsBaselineAsync(InputContents, additionalConfigOptions: PInvokeGeneratorConfigurationOptions.GenerateHelperTypes); + + [Test] + public Task EmitsPerTypeAttributeInTypeMode() + => ValidateGeneratedCSharpLatestWindowsBaselineAsync(InputContents, additionalConfigOptions: PInvokeGeneratorConfigurationOptions.GenerateGeneratedCodeAttributeAsType); + + [Test] + public Task EmitsPerTypeAttributeOnDelegateInTypeMode() + => ValidateGeneratedCSharpCompatibleWindowsBaselineAsync(InputContents, additionalConfigOptions: PInvokeGeneratorConfigurationOptions.GenerateGeneratedCodeAttributeAsType); + + [Test] + public Task EmitsPerTypeInsteadOfAssemblyWithHelperTypes() + => ValidateGeneratedCSharpLatestWindowsBaselineAsync(InputContents, additionalConfigOptions: PInvokeGeneratorConfigurationOptions.GenerateHelperTypes | PInvokeGeneratorConfigurationOptions.GenerateGeneratedCodeAttributeAsType); + + [Test] + public Task OmitsAllAttributesInNoneModeWithHelperTypes() + => ValidateGeneratedCSharpLatestWindowsBaselineAsync(InputContents, additionalConfigOptions: PInvokeGeneratorConfigurationOptions.GenerateHelperTypes | PInvokeGeneratorConfigurationOptions.ExcludeGeneratedCodeAttribute); +}