diff --git a/sources/ClangSharp.PInvokeGenerator/Abstractions/FieldDesc.cs b/sources/ClangSharp.PInvokeGenerator/Abstractions/FieldDesc.cs index a10105ee7..8d1bf3cf8 100644 --- a/sources/ClangSharp.PInvokeGenerator/Abstractions/FieldDesc.cs +++ b/sources/ClangSharp.PInvokeGenerator/Abstractions/FieldDesc.cs @@ -1,6 +1,7 @@ // 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; +using System.Collections.Generic; using ClangSharp.Interop; namespace ClangSharp.Abstractions; @@ -9,6 +10,7 @@ internal struct FieldDesc { public AccessSpecifier AccessSpecifier { get; set; } public string? NativeTypeName { get; set; } + public IEnumerable? CppAttributes { get; set; } public string EscapedName { get; set; } public string ParentName { get; set; } public int? Offset { get; set; } diff --git a/sources/ClangSharp.PInvokeGenerator/CSharp/CSharpOutputBuilder.VisitDecl.cs b/sources/ClangSharp.PInvokeGenerator/CSharp/CSharpOutputBuilder.VisitDecl.cs index 56daf3132..262699d33 100644 --- a/sources/ClangSharp.PInvokeGenerator/CSharp/CSharpOutputBuilder.VisitDecl.cs +++ b/sources/ClangSharp.PInvokeGenerator/CSharp/CSharpOutputBuilder.VisitDecl.cs @@ -308,6 +308,11 @@ public void BeginField(in FieldDesc desc) AddNativeTypeNameAttribute(desc.NativeTypeName); } + if (desc.CppAttributes is not null) + { + AddCppAttributes(desc.CppAttributes); + } + if (desc.Location is { } location) { WriteSourceLocation(location, false); diff --git a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs index 3bbe64a55..e94339bbc 100644 --- a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs +++ b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs @@ -449,6 +449,9 @@ private void VisitFieldDecl(FieldDecl fieldDecl) var desc = new FieldDesc { AccessSpecifier = accessSpecifier, NativeTypeName = nativeTypeName, + CppAttributes = _config.GenerateCppAttributes + ? fieldDecl.Attrs.Select(x => EscapeString(x.Spelling)) + : null, EscapedName = escapedName, ParentName = GetRemappedCursorName(parent), Offset = offset, diff --git a/tests/ClangSharp.PInvokeGenerator.UnitTests/SalFieldAttributeTest.cs b/tests/ClangSharp.PInvokeGenerator.UnitTests/SalFieldAttributeTest.cs new file mode 100644 index 000000000..7cf0d5504 --- /dev/null +++ b/tests/ClangSharp.PInvokeGenerator.UnitTests/SalFieldAttributeTest.cs @@ -0,0 +1,49 @@ +// 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 NUnit.Framework; + +namespace ClangSharp.UnitTests; + +/// +/// Regression test for https://github.com/dotnet/ClangSharp/issues/444. +/// Under generate-cpp-attributes, a C++ attribute (e.g. a SAL _Field_size_full_ +/// annotation surfaced as an annotate attribute) on a struct field was not emitted as a +/// [CppAttributeList("...")], even though the same attribute on a parameter was. Fields now +/// emit the attribute the same way parameters do. +/// +[Platform("win")] +public sealed class SalFieldAttributeTest : PInvokeGeneratorTest +{ + private const string InputContents = @"#define _Field_size_full_(x) __attribute__((annotate(""_Field_size_full_("" #x "")""))) + +struct MyStruct +{ + int count; + _Field_size_full_(count) int* data; +}; +"; + + [Test] + public Task FieldSalAnnotationGeneratesCppAttributeList() + { + var expectedOutputContents = @"namespace ClangSharp.Test +{ + public unsafe partial struct MyStruct + { + public int count; + + [CppAttributeList(""_Field_size_full_(count)"")] + [NativeAnnotation(""_Field_size_full_(count)"")] + public int* data; + } +} +"; + + var expectedDiagnostics = new[] { + new Diagnostic(DiagnosticLevel.Warning, "Function like macro definition records are not supported: '_Field_size_full_'. Generated bindings may be incomplete.", "Line 1, Column 9 in ClangUnsavedFile.h") + }; + + return ValidateGeneratedCSharpLatestWindowsBindingsAsync(InputContents, expectedOutputContents, additionalConfigOptions: PInvokeGeneratorConfigurationOptions.GenerateCppAttributes, expectedDiagnostics: expectedDiagnostics); + } +}