From 4ff8f28599cf3919682d9fedd68e7c0c73d5bf7d Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Thu, 4 Dec 2025 15:43:34 +0100 Subject: [PATCH] Expose ObjCTypeParamDecl->hasExplicitBound|getVariance to ClangSharp. --- .../ClangSharp.Interop/Extensions/CXCursor.cs | 4 ++ .../clang/ObjCTypeParamVariance.cs | 15 ++++++++ .../clangsharp/clangsharp.cs | 8 ++++ .../Cursors/Decls/ObjCTypeParamDecl.cs | 4 ++ sources/libClangSharp/ClangSharp.cpp | 26 +++++++++++++ sources/libClangSharp/ClangSharp.h | 4 ++ tests/ClangSharp.UnitTests/ObjectiveCTest.cs | 38 +++++++++++++++++++ 7 files changed, 99 insertions(+) create mode 100644 sources/ClangSharp.Interop/clang/ObjCTypeParamVariance.cs diff --git a/sources/ClangSharp.Interop/Extensions/CXCursor.cs b/sources/ClangSharp.Interop/Extensions/CXCursor.cs index 3af7012f..c85ba840 100644 --- a/sources/ClangSharp.Interop/Extensions/CXCursor.cs +++ b/sources/ClangSharp.Interop/Extensions/CXCursor.cs @@ -1917,6 +1917,10 @@ public readonly int GetPlatformAvailability(out bool alwaysDeprecated, out CXStr public readonly CXCursor GetTypeParam(uint index) => clangsharp.Cursor_getTypeParam(this, index); + public readonly bool TypeParamHasExplicitBound => clangsharp.Cursor_getTypeParamHasExplicitBound(this) != 0; + + public readonly ObjCTypeParamVariance TypeParamVariance => (ObjCTypeParamVariance)clangsharp.Cursor_getTypeParamVariance(this); + public readonly CXCursor GetVBase(uint index) => clangsharp.Cursor_getVBase(this, index); public override readonly string ToString() => Spelling.ToString(); diff --git a/sources/ClangSharp.Interop/clang/ObjCTypeParamVariance.cs b/sources/ClangSharp.Interop/clang/ObjCTypeParamVariance.cs new file mode 100644 index 00000000..a060db50 --- /dev/null +++ b/sources/ClangSharp.Interop/clang/ObjCTypeParamVariance.cs @@ -0,0 +1,15 @@ +// 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 ObjCTypeParamVariance +{ + Invariant, + Covariant, + Contravariant, +} diff --git a/sources/ClangSharp.Interop/clangsharp/clangsharp.cs b/sources/ClangSharp.Interop/clangsharp/clangsharp.cs index f430268d..1258ff83 100644 --- a/sources/ClangSharp.Interop/clangsharp/clangsharp.cs +++ b/sources/ClangSharp.Interop/clangsharp/clangsharp.cs @@ -891,6 +891,14 @@ public static partial class @clangsharp [DllImport("libClangSharp", CallingConvention = CallingConvention.Cdecl, EntryPoint = "clangsharp_Cursor_getUsedContext", ExactSpelling = true)] public static extern CXCursor Cursor_getUsedContext(CXCursor C); + [DllImport("libClangSharp", CallingConvention = CallingConvention.Cdecl, EntryPoint = "clangsharp_Cursor_getTypeParamHasExplicitBound", ExactSpelling = true)] + [return: NativeTypeName("unsigned int")] + public static extern uint Cursor_getTypeParamHasExplicitBound(CXCursor C); + + [DllImport("libClangSharp", CallingConvention = CallingConvention.Cdecl, EntryPoint = "clangsharp_Cursor_getTypeParamVariance", ExactSpelling = true)] + [return: NativeTypeName("unsigned int")] + public static extern uint Cursor_getTypeParamVariance(CXCursor C); + [DllImport("libClangSharp", CallingConvention = CallingConvention.Cdecl, EntryPoint = "clangsharp_Cursor_getVBase", ExactSpelling = true)] public static extern CXCursor Cursor_getVBase(CXCursor C, [NativeTypeName("unsigned int")] uint i); diff --git a/sources/ClangSharp/Cursors/Decls/ObjCTypeParamDecl.cs b/sources/ClangSharp/Cursors/Decls/ObjCTypeParamDecl.cs index a0a6b0ac..f971a8a8 100644 --- a/sources/ClangSharp/Cursors/Decls/ObjCTypeParamDecl.cs +++ b/sources/ClangSharp/Cursors/Decls/ObjCTypeParamDecl.cs @@ -13,4 +13,8 @@ internal ObjCTypeParamDecl(CXCursor handle) : base(handle, CXCursor_TemplateType } public uint Index => unchecked((uint)Handle.TemplateTypeParmIndex); + + public bool HasExplicitBound => Handle.TypeParamHasExplicitBound; + + public ObjCTypeParamVariance Variance => Handle.TypeParamVariance; } diff --git a/sources/libClangSharp/ClangSharp.cpp b/sources/libClangSharp/ClangSharp.cpp index 70389fb9..2682d677 100644 --- a/sources/libClangSharp/ClangSharp.cpp +++ b/sources/libClangSharp/ClangSharp.cpp @@ -4886,6 +4886,32 @@ CXCursor clangsharp_Cursor_getUsedContext(CXCursor C) { return clang_getNullCursor(); } +CLANGSHARP_LINKAGE unsigned clangsharp_Cursor_getTypeParamHasExplicitBound(CXCursor C) +{ + if (isDeclOrTU(C.kind)) { + const Decl* D = getCursorDecl(C); + + if (const ObjCTypeParamDecl* OCTPD = dyn_cast(D)) { + return OCTPD->hasExplicitBound(); + } + } + + return 0; +} + +CLANGSHARP_LINKAGE unsigned clangsharp_Cursor_getTypeParamVariance(CXCursor C) +{ + if (isDeclOrTU(C.kind)) { + const Decl* D = getCursorDecl(C); + + if (const ObjCTypeParamDecl* OCTPD = dyn_cast(D)) { + return (unsigned int) OCTPD->getVariance(); + } + } + + return 0 /* ObjCTypeParamVariance::Invariant */; +} + CXCursor clangsharp_Cursor_getVBase(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 4e5384b9..b3324d19 100644 --- a/sources/libClangSharp/ClangSharp.h +++ b/sources/libClangSharp/ClangSharp.h @@ -751,6 +751,10 @@ CLANGSHARP_LINKAGE CXCursor clangsharp_Cursor_getUninstantiatedDefaultArg(CXCurs CLANGSHARP_LINKAGE CXCursor clangsharp_Cursor_getUsedContext(CXCursor C); +CLANGSHARP_LINKAGE unsigned clangsharp_Cursor_getTypeParamHasExplicitBound(CXCursor C); + +CLANGSHARP_LINKAGE unsigned clangsharp_Cursor_getTypeParamVariance(CXCursor C); + CLANGSHARP_LINKAGE CXCursor clangsharp_Cursor_getVBase(CXCursor C, unsigned i); CLANGSHARP_LINKAGE int64_t clangsharp_Cursor_getDtorVtblIdx(CXCursor C, CX_DestructorType dtor); diff --git a/tests/ClangSharp.UnitTests/ObjectiveCTest.cs b/tests/ClangSharp.UnitTests/ObjectiveCTest.cs index b00947cd..53fb4ad6 100644 --- a/tests/ClangSharp.UnitTests/ObjectiveCTest.cs +++ b/tests/ClangSharp.UnitTests/ObjectiveCTest.cs @@ -175,6 +175,12 @@ @interface NSObject @interface MyClass (MyCategory) @end +@interface MyVariantClass<__contravariant T, __covariant U, V> : NSObject +@end + +@interface MyBoundedClass : NSObject +@end + """; using var translationUnit = CreateTranslationUnit(inputContents, "objective-c++"); @@ -186,7 +192,11 @@ @interface MyClass (MyCategory) var myClassTypeParams = myClass.TypeParamList.ToList(); Assert.That(myClassTypeParams.Count, Is.EqualTo(2), "myClassTypeParams.Count"); Assert.That(myClassTypeParams[0].Name, Is.EqualTo("A"), "myClassTypeParams[0].Name"); + Assert.That(myClassTypeParams[0].HasExplicitBound, Is.EqualTo(false), "myClassTypeParams[0].HasExplicitBound"); + Assert.That(myClassTypeParams[0].Variance, Is.EqualTo(ObjCTypeParamVariance.Invariant), "myClassTypeParams[0].Variance"); Assert.That(myClassTypeParams[1].Name, Is.EqualTo("B"), "myClassTypeParams[1].Name"); + Assert.That(myClassTypeParams[1].HasExplicitBound, Is.EqualTo(false), "myClassTypeParams[1].HasExplicitBound"); + Assert.That(myClassTypeParams[1].Variance, Is.EqualTo(ObjCTypeParamVariance.Invariant), "myClassTypeParams[1].Variance"); var categories = translationUnit.TranslationUnitDecl.Decls.OfType().ToList(); var myCategory = categories.SingleOrDefault(v => v.Name == "MyCategory")!; @@ -195,7 +205,35 @@ @interface MyClass (MyCategory) var myCategoryTypeParams = myCategory.TypeParamList.ToList(); Assert.That(myCategoryTypeParams.Count, Is.EqualTo(2), "myCategoryTypeParams.Count"); Assert.That(myCategoryTypeParams[0].Name, Is.EqualTo("T"), "myCategoryTypeParams[0].Name"); + Assert.That(myCategoryTypeParams[0].HasExplicitBound, Is.EqualTo(false), "myCategoryTypeParams[0].HasExplicitBound"); + Assert.That(myCategoryTypeParams[0].Variance, Is.EqualTo(ObjCTypeParamVariance.Invariant), "myCategoryTypeParams[0].Variance"); Assert.That(myCategoryTypeParams[1].Name, Is.EqualTo("Y"), "myCategoryTypeParams[1].Name"); + Assert.That(myCategoryTypeParams[1].HasExplicitBound, Is.EqualTo(false), "myCategoryTypeParams[1].HasExplicitBound"); + Assert.That(myCategoryTypeParams[1].Variance, Is.EqualTo(ObjCTypeParamVariance.Invariant), "myCategoryTypeParams[1].Variance"); + + var myVariantClass = classes.SingleOrDefault(v => v.Name == "MyVariantClass")!; + Assert.That(myVariantClass, Is.Not.Null, "MyVariantClass"); + Assert.That(myVariantClass.TypeParamList, Is.Not.Null, "myVariantClass TypeParamList"); + var myVariantClassTypeParams = myVariantClass.TypeParamList.ToList(); + Assert.That(myVariantClassTypeParams.Count, Is.EqualTo(3), "myVariantClassTypeParams.Count"); + Assert.That(myVariantClassTypeParams[0].Name, Is.EqualTo("T"), "myVariantClassTypeParams[0].Name"); + Assert.That(myVariantClassTypeParams[1].Name, Is.EqualTo("U"), "myVariantClassTypeParams[1].Name"); + Assert.That(myVariantClassTypeParams[2].Name, Is.EqualTo("V"), "myVariantClassTypeParams[2].Name"); + Assert.That(myVariantClassTypeParams[0].HasExplicitBound, Is.EqualTo(false), "myVariantClassTypeParams[0].HasExplicitBound"); + Assert.That(myVariantClassTypeParams[1].HasExplicitBound, Is.EqualTo(false), "myVariantClassTypeParams[1].HasExplicitBound"); + Assert.That(myVariantClassTypeParams[2].HasExplicitBound, Is.EqualTo(false), "myBoundedClassTypeParams[2].HasExplicitBound"); + Assert.That(myVariantClassTypeParams[0].Variance, Is.EqualTo(ObjCTypeParamVariance.Contravariant), "myVariantClassTypeParams[0].Variance"); + Assert.That(myVariantClassTypeParams[1].Variance, Is.EqualTo(ObjCTypeParamVariance.Covariant), "myVariantClassTypeParams[1].Variance"); + Assert.That(myVariantClassTypeParams[2].Variance, Is.EqualTo(ObjCTypeParamVariance.Invariant), "myVariantClassTypeParams[2].Variance"); + + var myBoundedClass = classes.SingleOrDefault(v => v.Name == "MyBoundedClass")!; + Assert.That(myBoundedClass, Is.Not.Null, "MyBoundedClass"); + Assert.That(myBoundedClass.TypeParamList, Is.Not.Null, "myBoundedClass TypeParamList"); + var myBoundedClassTypeParams = myBoundedClass.TypeParamList.ToList(); + Assert.That(myBoundedClassTypeParams.Count, Is.EqualTo(1), "myBoundedClassTypeParams.Count"); + Assert.That(myBoundedClassTypeParams[0].Name, Is.EqualTo("T"), "myBoundedClassTypeParams[0].Name"); + Assert.That(myBoundedClassTypeParams[0].HasExplicitBound, Is.EqualTo(true), "myBoundedClassTypeParams[0].HasExplicitBound"); + Assert.That(myBoundedClassTypeParams[0].Variance, Is.EqualTo(ObjCTypeParamVariance.Invariant), "myBoundedClassTypeParams[0].Variance"); } [Test]