Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions sources/ClangSharp.Interop/Extensions/CXCursor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1201,6 +1201,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);
Expand Down
27 changes: 27 additions & 0 deletions sources/ClangSharp.Interop/clang/ObjCMethodFamily.cs
Original file line number Diff line number Diff line change
@@ -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,
}
4 changes: 4 additions & 0 deletions sources/ClangSharp.Interop/clangsharp/clangsharp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -659,6 +659,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);

Expand Down
2 changes: 2 additions & 0 deletions sources/ClangSharp/Cursors/Decls/ObjCMethodDecl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<ParmVarDecl> Parameters => _parameters;
Expand Down
13 changes: 13 additions & 0 deletions sources/libClangSharp/ClangSharp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1331,6 +1331,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<ObjCMethodDecl>(D)) {
return OCMD->getMethodFamily();
}
}

return 0 /* OMF_None */;
}

int clangsharp_Cursor_getFieldIndex(CXCursor C) {
if (isDeclOrTU(C.kind)) {
const Decl* D = getCursorDecl(C);
Expand Down
2 changes: 2 additions & 0 deletions sources/libClangSharp/ClangSharp.h
Original file line number Diff line number Diff line change
Expand Up @@ -597,6 +597,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);
Expand Down
146 changes: 146 additions & 0 deletions tests/ClangSharp.UnitTests/ObjectiveCTest.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -111,6 +112,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<ObjCInterfaceDecl>().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<Func<string, bool>, ObjCMethodFamily, IEnumerable<ObjCMethodDecl>>((Func<string, bool> filter, ObjCMethodFamily family) => {
var taken = new List<ObjCMethodDecl>();
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<Func<string, bool>, ObjCMethodFamily>((Func<string, bool> 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, string, bool>((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()
{
Expand Down