From 5c4d8ac1900ce7b0dd7bdfa121a3dff921a13a33 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Mon, 10 Nov 2025 18:09:22 +0100 Subject: [PATCH 1/2] Add Attr.PrettyPrint(). --- .../ClangSharp.Interop/Extensions/CXCursor.cs | 2 + .../clangsharp/clangsharp.cs | 3 ++ sources/ClangSharp/Cursors/Attrs/Attr.cs | 2 + sources/libClangSharp/ClangSharp.cpp | 14 ++++++ sources/libClangSharp/ClangSharp.h | 2 + tests/ClangSharp.UnitTests/ObjectiveCTest.cs | 49 +++++++++++++++++++ 6 files changed, 72 insertions(+) diff --git a/sources/ClangSharp.Interop/Extensions/CXCursor.cs b/sources/ClangSharp.Interop/Extensions/CXCursor.cs index 3a962f2c..f889bdc3 100644 --- a/sources/ClangSharp.Interop/Extensions/CXCursor.cs +++ b/sources/ClangSharp.Interop/Extensions/CXCursor.cs @@ -1323,6 +1323,8 @@ public readonly ReadOnlySpan OverriddenCursors public readonly CXString Selector => clangsharp.Cursor_getSelector(this); + public readonly CXString PrettyPrintAttribute() => clangsharp.Cursor_prettyPrintAttribute(this); + public readonly CXCursor SemanticParent => clang.getCursorSemanticParent(this); public readonly bool ShouldCopy => clangsharp.Cursor_getShouldCopy(this) != 0; diff --git a/sources/ClangSharp.Interop/clangsharp/clangsharp.cs b/sources/ClangSharp.Interop/clangsharp/clangsharp.cs index e7ce47fa..cb0ea241 100644 --- a/sources/ClangSharp.Interop/clangsharp/clangsharp.cs +++ b/sources/ClangSharp.Interop/clangsharp/clangsharp.cs @@ -892,6 +892,9 @@ public static partial class @clangsharp [return: NativeTypeName("int64_t")] public static extern long Cursor_getVtblIdx(CXCursor C); + [DllImport("libClangSharp", CallingConvention = CallingConvention.Cdecl, EntryPoint = "clangsharp_Cursor_prettyPrintAttribute", ExactSpelling = true)] + public static extern CXString Cursor_prettyPrintAttribute(CXCursor C); + [DllImport("libClangSharp", CallingConvention = CallingConvention.Cdecl, EntryPoint = "clangsharp_getVersion", ExactSpelling = true)] public static extern CXString getVersion(); diff --git a/sources/ClangSharp/Cursors/Attrs/Attr.cs b/sources/ClangSharp/Cursors/Attrs/Attr.cs index 258890fc..3d3a79b5 100644 --- a/sources/ClangSharp/Cursors/Attrs/Attr.cs +++ b/sources/ClangSharp/Cursors/Attrs/Attr.cs @@ -471,4 +471,6 @@ private protected Attr(CXCursor handle) : base(handle, handle.Kind) public CX_AttrKind Kind => Handle.AttrKind; public string KindSpelling => Handle.AttrKindSpelling; + + public string PrettyPrint() => Handle.PrettyPrintAttribute().ToString(); } diff --git a/sources/libClangSharp/ClangSharp.cpp b/sources/libClangSharp/ClangSharp.cpp index 6ded87f1..1338c0e1 100644 --- a/sources/libClangSharp/ClangSharp.cpp +++ b/sources/libClangSharp/ClangSharp.cpp @@ -4846,6 +4846,20 @@ int64_t clangsharp_Cursor_getVtblIdx(CXCursor C) { return -1; } +CXString clangsharp_Cursor_prettyPrintAttribute(CXCursor C) { + if (clang_isAttribute(C.kind)) { + const Attr* A = getCursorAttr(C); + + std::string printedExpr; + llvm::raw_string_ostream printedExprStream(printedExpr); + PrintingPolicy policy(getCursorASTUnit(C)->getASTContext().getLangOpts()); + A->printPretty(printedExprStream, policy); + return createDup(printedExprStream.str()); + } + + return createEmpty(); +} + CXString clangsharp_getVersion() { return cxstring::createDup("clangsharp version 21.1.8"); } diff --git a/sources/libClangSharp/ClangSharp.h b/sources/libClangSharp/ClangSharp.h index 259dd7a7..c8a609c7 100644 --- a/sources/libClangSharp/ClangSharp.h +++ b/sources/libClangSharp/ClangSharp.h @@ -751,6 +751,8 @@ CLANGSHARP_LINKAGE int64_t clangsharp_Cursor_getDtorVtblIdx(CXCursor C, CX_Destr CLANGSHARP_LINKAGE int64_t clangsharp_Cursor_getVtblIdx(CXCursor C); +CLANGSHARP_LINKAGE CXString clangsharp_Cursor_prettyPrintAttribute(CXCursor C); + CLANGSHARP_LINKAGE CXString clangsharp_getVersion(); CLANGSHARP_LINKAGE void clangsharp_TemplateArgument_dispose(CX_TemplateArgument T); diff --git a/tests/ClangSharp.UnitTests/ObjectiveCTest.cs b/tests/ClangSharp.UnitTests/ObjectiveCTest.cs index 380d7058..257b9eb6 100644 --- a/tests/ClangSharp.UnitTests/ObjectiveCTest.cs +++ b/tests/ClangSharp.UnitTests/ObjectiveCTest.cs @@ -175,6 +175,55 @@ @interface MyClass } } + [Test] + public void Attribute_PrettyPrint() + { + AssertNeedNewClangSharp(); + + var inputContents = $@" +__attribute__((availability(ios,introduced=10.0))) +@interface MyClass + @property int P1 __attribute__((availability(ios,introduced=11.0))); + -(void) instanceMethod __attribute__((availability(ios,introduced=12.0))); + +(void) staticMethod __attribute__((availability(ios,introduced=13.0))); +@end +"; + + using var translationUnit = CreateTranslationUnit(inputContents, "objective-c++"); + + var classes = translationUnit.TranslationUnitDecl.Decls.OfType().ToList(); + Assert.That(classes.Count, Is.GreaterThanOrEqualTo(1), $"At least one class"); + var myClass = classes.SingleOrDefault(v => v.Name == "MyClass")!; + Assert.That(myClass, Is.Not.Null, "MyClass"); + var myClassAttrs = myClass.Attrs; + Assert.That(myClassAttrs.Count, Is.EqualTo(1), "myClassAttrs.Count"); + Assert.That(myClassAttrs[0].PrettyPrint(), Is.EqualTo("__attribute__((availability(ios, introduced=10.0)))"), "myClass.Attr.PrettyPrint"); + + var methodP1 = myClass.Methods.SingleOrDefault(v => v.Name == "P1")!; + Assert.That(methodP1, Is.Not.Null, "methodP1"); + var methodP1Attrs = methodP1.Attrs; + Assert.That(methodP1Attrs.Count, Is.EqualTo(1), "methodP1Attrs.Count"); + Assert.That(methodP1Attrs[0].PrettyPrint(), Is.EqualTo("__attribute__((availability(ios, introduced=11.0)))"), "methodP1.Attr.PrettyPrint"); + + var methodSetP1 = myClass.Methods.SingleOrDefault(v => v.Name == "setP1:")!; + Assert.That(methodSetP1, Is.Not.Null, "methodSetP1"); + var methodSetP1Attrs = methodSetP1.Attrs; + Assert.That(methodSetP1Attrs.Count, Is.EqualTo(1), "methodSetP1Attrs.Count"); + Assert.That(methodSetP1Attrs[0].PrettyPrint(), Is.EqualTo("__attribute__((availability(ios, introduced=11.0)))"), "methodSetP1.Attr.PrettyPrint"); + + var methodInstanceMethod = myClass.Methods.SingleOrDefault(v => v.Name == "instanceMethod")!; + Assert.That(methodInstanceMethod, Is.Not.Null, "methodInstanceMethod"); + var methodInstanceMethodAttrs = methodInstanceMethod.Attrs; + Assert.That(methodInstanceMethodAttrs.Count, Is.EqualTo(1), "methodInstanceMethodAttrs.Count"); + Assert.That(methodInstanceMethodAttrs[0].PrettyPrint(), Is.EqualTo("__attribute__((availability(ios, introduced=12.0)))"), "methodInstanceMethod.Attr.PrettyPrint"); + + var methodStaticMethod = myClass.Methods.SingleOrDefault(v => v.Name == "staticMethod")!; + Assert.That(methodStaticMethod, Is.Not.Null, "methodStaticMethod"); + var methodStaticMethodAttrs = methodStaticMethod.Attrs; + Assert.That(methodStaticMethodAttrs.Count, Is.EqualTo(1), "methodStaticMethodAttrs.Count"); + Assert.That(methodStaticMethodAttrs[0].PrettyPrint(), Is.EqualTo("__attribute__((availability(ios, introduced=13.0)))"), "methodStaticMethod.Attr.PrettyPrint"); + } + private static void AssertNeedNewClangSharp() { var forceRun = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("FORCE_RUN")); From e31a3627b21be2bc1d536f9c7a08f7e99781a34b Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Mon, 5 Jan 2026 11:26:10 -0800 Subject: [PATCH 2/2] Apply suggestion from @tannergooding --- tests/ClangSharp.UnitTests/ObjectiveCTest.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/ClangSharp.UnitTests/ObjectiveCTest.cs b/tests/ClangSharp.UnitTests/ObjectiveCTest.cs index 5da8f0e4..7588ba0c 100644 --- a/tests/ClangSharp.UnitTests/ObjectiveCTest.cs +++ b/tests/ClangSharp.UnitTests/ObjectiveCTest.cs @@ -1,5 +1,6 @@ // 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.Linq; using ClangSharp.Interop; using NUnit.Framework;