From 98d215166deb99680d7f3727ac4ace733492ec8d Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Tue, 2 Dec 2025 14:00:18 +0100 Subject: [PATCH] Add ObjCMethodDecl.MethodFamily. --- .../ClangSharp.Interop/Extensions/CXCursor.cs | 2 + .../clang/ObjCMethodFamily.cs | 27 ++++ .../clangsharp/clangsharp.cs | 4 + .../Cursors/Decls/ObjCMethodDecl.cs | 2 + sources/libClangSharp/ClangSharp.cpp | 13 ++ sources/libClangSharp/ClangSharp.h | 2 + tests/ClangSharp.UnitTests/ObjectiveCTest.cs | 146 ++++++++++++++++++ 7 files changed, 196 insertions(+) create mode 100644 sources/ClangSharp.Interop/clang/ObjCMethodFamily.cs diff --git a/sources/ClangSharp.Interop/Extensions/CXCursor.cs b/sources/ClangSharp.Interop/Extensions/CXCursor.cs index f889bdc3..88d6540e 100644 --- a/sources/ClangSharp.Interop/Extensions/CXCursor.cs +++ b/sources/ClangSharp.Interop/Extensions/CXCursor.cs @@ -1197,6 +1197,8 @@ public readonly CXCursor DefaultArg public readonly uint MaxAlignment => clangsharp.Cursor_getMaxAlignment(this); + public readonly ObjCMethodFamily MethodFamily => (ObjCMethodFamily)clangsharp.Cursor_getMethodFamily(this); + public readonly CXModule Module => (CXModule)clang.Cursor_getModule(this); public readonly CXCursor MostRecentDecl => clangsharp.Cursor_getMostRecentDecl(this); diff --git a/sources/ClangSharp.Interop/clang/ObjCMethodFamily.cs b/sources/ClangSharp.Interop/clang/ObjCMethodFamily.cs new file mode 100644 index 00000000..0a4cb9ef --- /dev/null +++ b/sources/ClangSharp.Interop/clang/ObjCMethodFamily.cs @@ -0,0 +1,27 @@ +// 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; + +public enum ObjCMethodFamily +{ + None, + Alloc, + Copy, + Init, + MutableCopy, + New, + Autorelease, + Dealloc, + Finalize, + Release, + Retain, + RetainCount, + Self, + Initialize, + PerformSelector, +} diff --git a/sources/ClangSharp.Interop/clangsharp/clangsharp.cs b/sources/ClangSharp.Interop/clangsharp/clangsharp.cs index cb0ea241..914f1256 100644 --- a/sources/ClangSharp.Interop/clangsharp/clangsharp.cs +++ b/sources/ClangSharp.Interop/clangsharp/clangsharp.cs @@ -651,6 +651,10 @@ public static partial class @clangsharp [DllImport("libClangSharp", CallingConvention = CallingConvention.Cdecl, EntryPoint = "clangsharp_Cursor_getMethod", ExactSpelling = true)] public static extern CXCursor Cursor_getMethod(CXCursor C, [NativeTypeName("unsigned int")] uint i); + [DllImport("libClangSharp", CallingConvention = CallingConvention.Cdecl, EntryPoint = "clangsharp_Cursor_getMethodFamily", ExactSpelling = true)] + [return: NativeTypeName("unsigned int")] + public static extern uint Cursor_getMethodFamily(CXCursor C); + [DllImport("libClangSharp", CallingConvention = CallingConvention.Cdecl, EntryPoint = "clangsharp_Cursor_getMostRecentDecl", ExactSpelling = true)] public static extern CXCursor Cursor_getMostRecentDecl(CXCursor C); diff --git a/sources/ClangSharp/Cursors/Decls/ObjCMethodDecl.cs b/sources/ClangSharp/Cursors/Decls/ObjCMethodDecl.cs index b6400f2d..3b1fbcbd 100644 --- a/sources/ClangSharp/Cursors/Decls/ObjCMethodDecl.cs +++ b/sources/ClangSharp/Cursors/Decls/ObjCMethodDecl.cs @@ -44,6 +44,8 @@ internal ObjCMethodDecl(CXCursor handle) : base(handle, handle.Kind, CX_DeclKind public bool IsThisDeclarationADefinition => Handle.IsThisDeclarationADefinition; + public ObjCMethodFamily MethodFamily => Handle.MethodFamily; + public CXObjCDeclQualifierKind ObjCDeclQualifier => Handle.ObjCDeclQualifiers; public IReadOnlyList Parameters => _parameters; diff --git a/sources/libClangSharp/ClangSharp.cpp b/sources/libClangSharp/ClangSharp.cpp index 1338c0e1..57c9d5de 100644 --- a/sources/libClangSharp/ClangSharp.cpp +++ b/sources/libClangSharp/ClangSharp.cpp @@ -1319,6 +1319,19 @@ CX_ExprDependence clangsharp_Cursor_getExprDependence(CXCursor C) { return CX_ED_None; } +CLANGSHARP_LINKAGE unsigned clangsharp_Cursor_getMethodFamily(CXCursor C) +{ + if (isDeclOrTU(C.kind)) { + const Decl* D = getCursorDecl(C); + + if (const ObjCMethodDecl* OCMD = dyn_cast(D)) { + return OCMD->getMethodFamily(); + } + } + + return 0 /* OMF_None */; +} + int clangsharp_Cursor_getFieldIndex(CXCursor C) { if (isDeclOrTU(C.kind)) { const Decl* D = getCursorDecl(C); diff --git a/sources/libClangSharp/ClangSharp.h b/sources/libClangSharp/ClangSharp.h index c8a609c7..3a112e12 100644 --- a/sources/libClangSharp/ClangSharp.h +++ b/sources/libClangSharp/ClangSharp.h @@ -593,6 +593,8 @@ CLANGSHARP_LINKAGE unsigned clangsharp_Cursor_getMaxAlignment(CXCursor C); CLANGSHARP_LINKAGE CXCursor clangsharp_Cursor_getMethod(CXCursor C, unsigned i); +CLANGSHARP_LINKAGE unsigned clangsharp_Cursor_getMethodFamily(CXCursor C); + CLANGSHARP_LINKAGE CXCursor clangsharp_Cursor_getMostRecentDecl(CXCursor C); CLANGSHARP_LINKAGE CXString clangsharp_Cursor_getName(CXCursor C); diff --git a/tests/ClangSharp.UnitTests/ObjectiveCTest.cs b/tests/ClangSharp.UnitTests/ObjectiveCTest.cs index 7588ba0c..377abac2 100644 --- a/tests/ClangSharp.UnitTests/ObjectiveCTest.cs +++ b/tests/ClangSharp.UnitTests/ObjectiveCTest.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 System.Linq; using ClangSharp.Interop; using NUnit.Framework; @@ -45,6 +46,151 @@ @interface MyClass Assert.That(methodStaticMethod.Selector, Is.EqualTo("staticMethod"), "methodStaticMethod.Selector"); } + [Test] + public void Method_Family() + { + AssertNeedNewClangSharp(); + + var inputContents = $@" +@interface NSObject + -(void) none; + -(void) noneWithArgument:(int)arg; + -(void) something; + -(void) autoreleaseNone; + -(void) deallocNone; + -(void) finalizeNone; + -(void) releaseNone; + -(void) retainNone; + -(unsigned long) retainCountNone; + -(instancetype) selfNone; + -(instancetype) initializeNone; + -(void) performSelectorNone; + + +(instancetype) alloc; + +(instancetype) allocWithZone:(void*)zone; + +(instancetype) _alloc; + +(instancetype) __alloc; + +(instancetype) _allocWithZone:(void*)zone; + + -(instancetype) copy; + -(instancetype) copyWithZone:(void*)zone; + -(instancetype) _copy; + -(instancetype) __copyWithZone:(void*)zone; + + -(instancetype) init; + -(instancetype) initWithValue:(int)value; + -(instancetype) _init; + -(instancetype) __initWithValue:(int)value; + + -(instancetype) mutableCopy; + -(instancetype) mutableCopyWithZone:(void*)zone; + -(instancetype) _mutableCopy; + -(instancetype) __mutableCopyWithZone:(void*)zone; + + +(instancetype) new; + +(instancetype) newWithValue:(int)value; + +(instancetype) _new; + +(instancetype) __newWithValue:(int)value; + + -(void) autorelease; + -(void) dealloc; + -(void) finalize; + -(void) release; + -(void) retain; + -(unsigned long) retainCount; + -(instancetype) self; + +(void) initialize; + -(id) performSelector:(SEL)selector; +@end + +@interface MyClass + -(instancetype) instanceMethod; + +(MyClass*) staticMethod; +@end +"; + + using var translationUnit = CreateTranslationUnit(inputContents, "objective-c++"); + + var classes = translationUnit.TranslationUnitDecl.Decls.OfType().ToList(); + Assert.That(classes.Count, Is.GreaterThanOrEqualTo(2), $"At least two classes"); + + foreach (var cls in classes) + { + var methods = cls.Methods.ToList(); + if (methods.Count == 0) + { + continue; + } + + var take = new Func, ObjCMethodFamily, IEnumerable>((Func filter, ObjCMethodFamily family) => { + var taken = new List(); + for (var v = methods.Count - 1; v >= 0; v--) + { + var method = methods[v]; + if (filter(method.Selector)) + { + methods.RemoveAt(v); + taken.Add(method); + } + } + + Assert.That(taken.Count, Is.GreaterThanOrEqualTo(0), $"family {family} not found, methods remaining: {string.Join(", ", methods.Select(p => p.Selector))}"); + return taken; + }); + + var assertFamily = new Action, ObjCMethodFamily>((Func filter, ObjCMethodFamily family) => { + var methodsInFamily = take(filter, family); + foreach (var method in methodsInFamily) + { + Assert.That(method.MethodFamily, Is.EqualTo(family), $"MethodFamily for {method.Selector}"); + } + }); + + var isSelectorFamily = new Func((string selector, string family) => { + selector = selector.TrimStart('_'); + if (selector == family) + { + return true; + } + if (!selector.StartsWith(family, StringComparison.Ordinal)) + { + return false; + } + var nextChar = selector[family.Length]; + if (nextChar == ':' || char.IsUpper(nextChar)) + { + return true; + } + return false; + }); + + Assert.Multiple(() => { + assertFamily(s => isSelectorFamily(s, "alloc"), ObjCMethodFamily.Alloc); + assertFamily(s => isSelectorFamily(s, "copy"), ObjCMethodFamily.Copy); + assertFamily(s => isSelectorFamily(s, "init"), ObjCMethodFamily.Init); + assertFamily(s => isSelectorFamily(s, "mutableCopy"), ObjCMethodFamily.MutableCopy); + assertFamily(s => isSelectorFamily(s, "new"), ObjCMethodFamily.New); + assertFamily(s => s == "autorelease", ObjCMethodFamily.Autorelease); + assertFamily(s => s == "dealloc", ObjCMethodFamily.Dealloc); + assertFamily(s => s == "finalize", ObjCMethodFamily.Finalize); + assertFamily(s => s == "release", ObjCMethodFamily.Release); + assertFamily(s => s == "retain", ObjCMethodFamily.Retain); + assertFamily(s => s == "retainCount", ObjCMethodFamily.RetainCount); + assertFamily(s => s == "self", ObjCMethodFamily.Self); + assertFamily(s => s == "initialize", ObjCMethodFamily.Initialize); + assertFamily(s => s.StartsWith("performSelector:", StringComparison.Ordinal), ObjCMethodFamily.PerformSelector); + + Assert.That(methods.Count, Is.GreaterThan(0), $"No remaining methods in {cls.Name}"); + + // None of the remaining methods should belong to a family + foreach (var method in methods) + { + Assert.That(method.MethodFamily, Is.EqualTo(ObjCMethodFamily.None), $"MethodFamily for {method.Selector}"); + } + }); + } + } + [Test] public void Category_TypeParamList() {