diff --git a/sources/ClangSharp.Interop/Extensions/CXCursor.cs b/sources/ClangSharp.Interop/Extensions/CXCursor.cs index 1635c12e..b6943150 100644 --- a/sources/ClangSharp.Interop/Extensions/CXCursor.cs +++ b/sources/ClangSharp.Interop/Extensions/CXCursor.cs @@ -1878,6 +1878,8 @@ public readonly bool GetIsExternalSymbol(out CXString language, out CXString def public readonly CXObjCPropertyAttrKind GetObjCPropertyAttributes(uint reserved) => (CXObjCPropertyAttrKind)clang.Cursor_getObjCPropertyAttributes(this, reserved); + public readonly ObjCPropertyAttributeKind GetPropertyAttributes() => (ObjCPropertyAttributeKind)clangsharp.Cursor_getPropertyAttributes(this); + public readonly CXCursor GetOverloadedDecl(uint index) => clang.getOverloadedDecl(this, index); public readonly int GetPlatformAvailability(out bool alwaysDeprecated, out CXString deprecatedMessage, out bool alwaysUnavailable, out CXString unavailableMessage, Span availability) diff --git a/sources/ClangSharp.Interop/clang/ObjCPropertyAttributeKind.cs b/sources/ClangSharp.Interop/clang/ObjCPropertyAttributeKind.cs new file mode 100644 index 00000000..f106a232 --- /dev/null +++ b/sources/ClangSharp.Interop/clang/ObjCPropertyAttributeKind.cs @@ -0,0 +1,30 @@ +// 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. + +// Ported from https://github.com/llvm/llvm-project/tree/llvmorg-21.1.8/clang/include/clang-c +// Original source is Copyright (c) the LLVM Project and Contributors. Licensed under the Apache License v2.0 with LLVM Exceptions. See NOTICE.txt in the project root for license information. + +using System; + +namespace ClangSharp.Interop; + +[Flags] +public enum ObjCPropertyAttributeKind +{ + NoAttr = 0x00, + ReadOnly = 0x01, + Getter = 0x02, + Assign = 0x04, + ReadWrite = 0x08, + Retain = 0x10, + Copy = 0x20, + NonAtomic = 0x40, + Setter = 0x80, + Atomic = 0x100, + Weak = 0x200, + Strong = 0x400, + UnsafeUnretained = 0x800, + Nullability = 0x1000, + NullResettable = 0x2000, + Class = 0x4000, + Direct = 0x8000, +} diff --git a/sources/ClangSharp.Interop/clangsharp/clangsharp.cs b/sources/ClangSharp.Interop/clangsharp/clangsharp.cs index bdba1616..f6aa618f 100644 --- a/sources/ClangSharp.Interop/clangsharp/clangsharp.cs +++ b/sources/ClangSharp.Interop/clangsharp/clangsharp.cs @@ -779,6 +779,10 @@ public static partial class @clangsharp [DllImport("libClangSharp", CallingConvention = CallingConvention.Cdecl, EntryPoint = "clangsharp_Cursor_getPrimaryTemplate", ExactSpelling = true)] public static extern CXCursor Cursor_getPrimaryTemplate(CXCursor C); + [DllImport("libClangSharp", CallingConvention = CallingConvention.Cdecl, EntryPoint = "clangsharp_Cursor_getPropertyAttributes", ExactSpelling = true)] + [return: NativeTypeName("unsigned int")] + public static extern uint Cursor_getPropertyAttributes(CXCursor C); + [DllImport("libClangSharp", CallingConvention = CallingConvention.Cdecl, EntryPoint = "clangsharp_Cursor_getProtocol", ExactSpelling = true)] public static extern CXCursor Cursor_getProtocol(CXCursor C, [NativeTypeName("unsigned int")] uint i); diff --git a/sources/ClangSharp/Cursors/Decls/ObjCPropertyDecl.cs b/sources/ClangSharp/Cursors/Decls/ObjCPropertyDecl.cs index 70b85062..33ca23b5 100644 --- a/sources/ClangSharp/Cursors/Decls/ObjCPropertyDecl.cs +++ b/sources/ClangSharp/Cursors/Decls/ObjCPropertyDecl.cs @@ -27,8 +27,20 @@ internal ObjCPropertyDecl(CXCursor handle) : base(handle, CXCursor_ObjCPropertyD public bool IsInstanceProperty => !IsClassProperty; +#pragma warning disable CA1721 + // CA1721: The property name 'PropertyAttributes' is confusing given the existence of method 'GetPropertyAttributes'. + // Handle.GetObjCPropertyAttributes() calls Cursor_getObjCPropertyAttributes, which confusingly is implemented using + // 'getPropertyAttributeAsWritten()' (and not 'getPropertyAttributes()'): https://github.com/llvm/llvm-project/blob/1cea4a0841dacefa49241538a55fbf4f34462633/clang/tools/libclang/CIndex.cpp#L9159-L9165 + // We have to keep our 'PropertyAttributes' property for backwards compatibility, so introduce + // a new method, GetPropertyAttributes(), that gets the final property attributes, and not just the as written variety. + + /// This calls ObjCPropertyDecl->getPropertyAttributesAsWritten() public CXObjCPropertyAttrKind PropertyAttributes => Handle.GetObjCPropertyAttributes(0); + /// This calls ObjCPropertyDecl->getPropertyAttributes() + public ObjCPropertyAttributeKind GetPropertyAttributes () => Handle.GetPropertyAttributes(); +#pragma warning restore CA1721 + public ObjCIvarDecl PropertyIvarDecl => _propertyIvarDecl.Value; public ObjCMethodDecl SetterMethodDecl => _setterMethodDecl.Value; diff --git a/sources/libClangSharp/ClangSharp.cpp b/sources/libClangSharp/ClangSharp.cpp index 58ed64b9..ab64711a 100644 --- a/sources/libClangSharp/ClangSharp.cpp +++ b/sources/libClangSharp/ClangSharp.cpp @@ -3815,6 +3815,19 @@ CXCursor clangsharp_Cursor_getPrimaryTemplate(CXCursor C) { return clang_getNullCursor(); } +CLANGSHARP_LINKAGE unsigned clangsharp_Cursor_getPropertyAttributes(CXCursor C) +{ + if (isDeclOrTU(C.kind)) { + const Decl* D = getCursorDecl(C); + + if (const ObjCPropertyDecl* OCPD = dyn_cast(D)) { + return OCPD->getPropertyAttributes(); + } + } + + return 0 /* ObjCPropertyAttribute::Kind::kind_noattr */; +} + CXCursor clangsharp_Cursor_getProtocol(CXCursor C, unsigned i) { if (isDeclOrTU(C.kind)) { const Decl* D = getCursorDecl(C); diff --git a/sources/libClangSharp/ClangSharp.h b/sources/libClangSharp/ClangSharp.h index bbab9a61..320b32fe 100644 --- a/sources/libClangSharp/ClangSharp.h +++ b/sources/libClangSharp/ClangSharp.h @@ -677,6 +677,8 @@ CLANGSHARP_LINKAGE CXCursor clangsharp_Cursor_getPreviousDecl(CXCursor C); CLANGSHARP_LINKAGE CXCursor clangsharp_Cursor_getPrimaryTemplate(CXCursor C); +CLANGSHARP_LINKAGE unsigned clangsharp_Cursor_getPropertyAttributes(CXCursor C); + CLANGSHARP_LINKAGE CXCursor clangsharp_Cursor_getProtocol(CXCursor C, unsigned i); CLANGSHARP_LINKAGE CXCursor clangsharp_Cursor_getRedeclContext(CXCursor C); diff --git a/tests/ClangSharp.UnitTests/ObjectiveCTest.cs b/tests/ClangSharp.UnitTests/ObjectiveCTest.cs index be0d7ffd..da4838e2 100644 --- a/tests/ClangSharp.UnitTests/ObjectiveCTest.cs +++ b/tests/ClangSharp.UnitTests/ObjectiveCTest.cs @@ -356,4 +356,134 @@ @interface MyClass 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"); } + + [Test] + public void Property_PropertyAttributes() + { + AssertNeedNewClangSharp(); + + var inputContents = $$""" +@class NSObject; + +@interface MyClass + @property NSObject* P_none; + + @property (readonly) NSObject* P_readonly; + @property (getter=getGetter) NSObject* P_getter; + @property (assign) NSObject* P_assign; + @property (readwrite) NSObject* P_readwrite; + @property (retain) NSObject* P_retain; + @property (copy) NSObject* P_copy; + @property (nonatomic) NSObject* P_nonatomic; + @property (setter=setSetter:) NSObject* P_setter; + @property (atomic) NSObject* P_atomic; + @property (weak) NSObject* P_weak; + @property (strong) NSObject* P_strong; + @property (unsafe_unretained) NSObject* P_unsafe_unretained; + @property (nonnull) NSObject* P_nonnull; + @property (null_unspecified) NSObject* P_null_unspecified; + @property (null_resettable) NSObject* P_null_resettable; + @property (class) NSObject* P_class; + @property (direct) NSObject* P_direct; + + // multiple + @property (retain,readonly,class) NSObject* P_retain_readonly_class; +@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 properties = myClass.Properties.ToList(); + + var take = new Func((string propertyName) => { + var idx = properties.FindIndex(v => v.Name == propertyName); + Assert.That(idx, Is.GreaterThanOrEqualTo(0), $"property {propertyName} not found, properties remaining: {string.Join(", ", properties.Select(p => p.Name))}"); + var rv = properties[idx]; + properties.RemoveAt(idx); + return rv; + }); + + var property_none = take("P_none"); + Assert.That(property_none.PropertyAttributes, Is.EqualTo(CXObjCPropertyAttrKind.CXObjCPropertyAttr_noattr), "property_none PropertyAttributes"); + Assert.That(property_none.GetPropertyAttributes(), Is.EqualTo(ObjCPropertyAttributeKind.Assign | ObjCPropertyAttributeKind.ReadWrite | ObjCPropertyAttributeKind.Atomic | ObjCPropertyAttributeKind.UnsafeUnretained), "property_none GetPropertyAttributes()"); + + var property_readonly = take("P_readonly"); + Assert.That(property_readonly.PropertyAttributes, Is.EqualTo(CXObjCPropertyAttrKind.CXObjCPropertyAttr_readonly), "property_readonly PropertyAttributes"); + Assert.That(property_readonly.GetPropertyAttributes(), Is.EqualTo(ObjCPropertyAttributeKind.ReadOnly | ObjCPropertyAttributeKind.Atomic), "property_readonly GetPropertyAttributes()"); + + var property_getter = take("P_getter"); + Assert.That(property_getter.PropertyAttributes, Is.EqualTo(CXObjCPropertyAttrKind.CXObjCPropertyAttr_getter), "property_getter PropertyAttributes"); + Assert.That(property_getter.GetPropertyAttributes(), Is.EqualTo(ObjCPropertyAttributeKind.Getter | ObjCPropertyAttributeKind.Assign | ObjCPropertyAttributeKind.ReadWrite | ObjCPropertyAttributeKind.Atomic | ObjCPropertyAttributeKind.UnsafeUnretained), "property_getter GetPropertyAttributes()"); + + var property_assign = take("P_assign"); + Assert.That(property_assign.PropertyAttributes, Is.EqualTo(CXObjCPropertyAttrKind.CXObjCPropertyAttr_assign), "property_assign PropertyAttributes"); + Assert.That(property_assign.GetPropertyAttributes(), Is.EqualTo(ObjCPropertyAttributeKind.Assign | ObjCPropertyAttributeKind.ReadWrite | ObjCPropertyAttributeKind.Atomic | ObjCPropertyAttributeKind.UnsafeUnretained), "property_assign GetPropertyAttributes()"); + + var property_readwrite = take("P_readwrite"); + Assert.That(property_readwrite.PropertyAttributes, Is.EqualTo(CXObjCPropertyAttrKind.CXObjCPropertyAttr_readwrite), "property_readwrite PropertyAttributes"); + Assert.That(property_readwrite.GetPropertyAttributes(), Is.EqualTo(ObjCPropertyAttributeKind.Assign | ObjCPropertyAttributeKind.ReadWrite | ObjCPropertyAttributeKind.Atomic | ObjCPropertyAttributeKind.UnsafeUnretained), "property_readwrite GetPropertyAttributes()"); + + var property_retain = take("P_retain"); + Assert.That(property_retain.PropertyAttributes, Is.EqualTo(CXObjCPropertyAttrKind.CXObjCPropertyAttr_retain), "property_retain PropertyAttributes"); + Assert.That(property_retain.GetPropertyAttributes(), Is.EqualTo(ObjCPropertyAttributeKind.Retain | ObjCPropertyAttributeKind.ReadWrite | ObjCPropertyAttributeKind.Atomic), "property_retain GetPropertyAttributes()"); + + var property_copy = take("P_copy"); + Assert.That(property_copy.PropertyAttributes, Is.EqualTo(CXObjCPropertyAttrKind.CXObjCPropertyAttr_copy), "property_copy PropertyAttributes"); + Assert.That(property_copy.GetPropertyAttributes(), Is.EqualTo(ObjCPropertyAttributeKind.Copy | ObjCPropertyAttributeKind.ReadWrite | ObjCPropertyAttributeKind.Atomic), "property_copy GetPropertyAttributes()"); + + var property_nonatomic = take("P_nonatomic"); + Assert.That(property_nonatomic.PropertyAttributes, Is.EqualTo(CXObjCPropertyAttrKind.CXObjCPropertyAttr_nonatomic), "property_nonatomic PropertyAttributes"); + Assert.That(property_nonatomic.GetPropertyAttributes(), Is.EqualTo(ObjCPropertyAttributeKind.Assign | ObjCPropertyAttributeKind.ReadWrite | ObjCPropertyAttributeKind.NonAtomic | ObjCPropertyAttributeKind.UnsafeUnretained), "property_nonatomic GetPropertyAttributes()"); + + var property_setter = take("P_setter"); + Assert.That(property_setter.PropertyAttributes, Is.EqualTo(CXObjCPropertyAttrKind.CXObjCPropertyAttr_setter), "property_setter PropertyAttributes"); + Assert.That(property_setter.GetPropertyAttributes(), Is.EqualTo(ObjCPropertyAttributeKind.Setter | ObjCPropertyAttributeKind.Assign | ObjCPropertyAttributeKind.ReadWrite | ObjCPropertyAttributeKind.Atomic | ObjCPropertyAttributeKind.UnsafeUnretained), "property_setter GetPropertyAttributes()"); + + var property_atomic = take("P_atomic"); + Assert.That(property_atomic.PropertyAttributes, Is.EqualTo(CXObjCPropertyAttrKind.CXObjCPropertyAttr_atomic), "property_atomic PropertyAttributes"); + Assert.That(property_atomic.GetPropertyAttributes(), Is.EqualTo(ObjCPropertyAttributeKind.Assign | ObjCPropertyAttributeKind.ReadWrite | ObjCPropertyAttributeKind.Atomic | ObjCPropertyAttributeKind.UnsafeUnretained), "property_atomic GetPropertyAttributes()"); + + var property_weak = take("P_weak"); + Assert.That(property_weak.PropertyAttributes, Is.EqualTo(CXObjCPropertyAttrKind.CXObjCPropertyAttr_weak), "property_weak PropertyAttributes"); + Assert.That(property_weak.GetPropertyAttributes(), Is.EqualTo(ObjCPropertyAttributeKind.Weak | ObjCPropertyAttributeKind.ReadWrite | ObjCPropertyAttributeKind.Atomic), "property_weak GetPropertyAttributes()"); + + var property_strong = take("P_strong"); + Assert.That(property_strong.PropertyAttributes, Is.EqualTo(CXObjCPropertyAttrKind.CXObjCPropertyAttr_strong), "property_strong PropertyAttributes"); + Assert.That(property_strong.GetPropertyAttributes(), Is.EqualTo(ObjCPropertyAttributeKind.Strong | ObjCPropertyAttributeKind.ReadWrite | ObjCPropertyAttributeKind.Atomic), "property_strong GetPropertyAttributes()"); + + var property_unsafe_unretained = take("P_unsafe_unretained"); + Assert.That(property_unsafe_unretained.PropertyAttributes, Is.EqualTo(CXObjCPropertyAttrKind.CXObjCPropertyAttr_unsafe_unretained), "property_unsafe_unretained PropertyAttributes"); + Assert.That(property_unsafe_unretained.GetPropertyAttributes(), Is.EqualTo(ObjCPropertyAttributeKind.Assign | ObjCPropertyAttributeKind.ReadWrite | ObjCPropertyAttributeKind.Atomic | ObjCPropertyAttributeKind.UnsafeUnretained), "property_unsafe_unretained GetPropertyAttributes()"); + + var property_null_resettable = take("P_null_resettable"); + Assert.That(property_null_resettable.PropertyAttributes, Is.EqualTo(CXObjCPropertyAttrKind.CXObjCPropertyAttr_noattr), "property_null_resettable PropertyAttributes"); + Assert.That(property_null_resettable.GetPropertyAttributes(), Is.EqualTo(ObjCPropertyAttributeKind.Assign | ObjCPropertyAttributeKind.ReadWrite | ObjCPropertyAttributeKind.Atomic | ObjCPropertyAttributeKind.UnsafeUnretained | ObjCPropertyAttributeKind.Nullability | ObjCPropertyAttributeKind.NullResettable), "property_null_resettable GetPropertyAttributes()"); + + var property_class = take("P_class"); + Assert.That(property_class.PropertyAttributes, Is.EqualTo(CXObjCPropertyAttrKind.CXObjCPropertyAttr_class), "property_class PropertyAttributes"); + Assert.That(property_class.GetPropertyAttributes(), Is.EqualTo(ObjCPropertyAttributeKind.Class | ObjCPropertyAttributeKind.Assign | ObjCPropertyAttributeKind.ReadWrite | ObjCPropertyAttributeKind.Atomic | ObjCPropertyAttributeKind.UnsafeUnretained), "property_class GetPropertyAttributes()"); + + var property_direct = take("P_direct"); + Assert.That(property_direct.PropertyAttributes, Is.EqualTo(CXObjCPropertyAttrKind.CXObjCPropertyAttr_noattr), "property_direct PropertyAttributes"); + Assert.That(property_direct.GetPropertyAttributes(), Is.EqualTo(ObjCPropertyAttributeKind.Direct | ObjCPropertyAttributeKind.Assign | ObjCPropertyAttributeKind.ReadWrite | ObjCPropertyAttributeKind.Atomic | ObjCPropertyAttributeKind.UnsafeUnretained), "property_direct GetPropertyAttributes()"); + + var property_nonnull = take("P_nonnull"); + Assert.That(property_nonnull.PropertyAttributes, Is.EqualTo(CXObjCPropertyAttrKind.CXObjCPropertyAttr_noattr), "property_nonnull PropertyAttributes"); + Assert.That(property_nonnull.GetPropertyAttributes(), Is.EqualTo(ObjCPropertyAttributeKind.Assign | ObjCPropertyAttributeKind.ReadWrite | ObjCPropertyAttributeKind.Atomic | ObjCPropertyAttributeKind.UnsafeUnretained | ObjCPropertyAttributeKind.Nullability), "property_nonnull GetPropertyAttributes()"); + + var property_unspecified = take("P_null_unspecified"); + Assert.That(property_unspecified.PropertyAttributes, Is.EqualTo(CXObjCPropertyAttrKind.CXObjCPropertyAttr_noattr), "property_unspecified PropertyAttributes"); + Assert.That(property_unspecified.GetPropertyAttributes(), Is.EqualTo(ObjCPropertyAttributeKind.Assign | ObjCPropertyAttributeKind.ReadWrite | ObjCPropertyAttributeKind.Atomic | ObjCPropertyAttributeKind.UnsafeUnretained | ObjCPropertyAttributeKind.Nullability), "property_unspecified GetPropertyAttributes()"); + + var property_retain_readonly_class = take("P_retain_readonly_class"); + Assert.That(property_retain_readonly_class.PropertyAttributes, Is.EqualTo(CXObjCPropertyAttrKind.CXObjCPropertyAttr_retain | CXObjCPropertyAttrKind.CXObjCPropertyAttr_readonly | CXObjCPropertyAttrKind.CXObjCPropertyAttr_class), "property_retain_readonly_class PropertyAttributes"); + Assert.That(property_retain_readonly_class.GetPropertyAttributes(), Is.EqualTo(ObjCPropertyAttributeKind.Retain | ObjCPropertyAttributeKind.ReadOnly | ObjCPropertyAttributeKind.Atomic | ObjCPropertyAttributeKind.Class), "property_retain_readonly_class GetPropertyAttributes()"); + + Assert.That(properties, Is.Empty, "All properties processed"); + } }