[clang] [libclang] Add clang_CXXMethod_getQualifiers, clang_Cursor_isConstexpr, and clang_CXXMethod_isExplicitObjectMemberFunction (PR #183305)
via cfe-commits
cfe-commits at lists.llvm.org
Tue Apr 14 09:00:19 PDT 2026
https://github.com/fscheidl updated https://github.com/llvm/llvm-project/pull/183305
>From a0f8fcbebc23af700a38509622898600934b5c6e Mon Sep 17 00:00:00 2001
From: Fabian Scheidl <fscheidl at apple.com>
Date: Mon, 9 Mar 2026 14:19:47 +0100
Subject: [PATCH 1/4] [libclang] Add clang_CXXMethod_isVolatile and
clang_Cursor_isConstexpr
---
clang/bindings/python/clang/cindex.py | 27 ++++++
.../python/tests/cindex/test_cursor.py | 93 +++++++++++++++++++
clang/docs/ReleaseNotes.rst | 4 +
clang/include/clang-c/Index.h | 28 ++++++
clang/test/Index/cursor-properties.cpp | 22 +++++
clang/tools/c-index-test/c-index-test.c | 13 ++-
clang/tools/libclang/CIndex.cpp | 33 +++++++
clang/tools/libclang/libclang.map | 2 +
8 files changed, 220 insertions(+), 2 deletions(-)
create mode 100644 clang/test/Index/cursor-properties.cpp
diff --git a/clang/bindings/python/clang/cindex.py b/clang/bindings/python/clang/cindex.py
index b71f9ed2275e0..7d3b54af3f8a5 100644
--- a/clang/bindings/python/clang/cindex.py
+++ b/clang/bindings/python/clang/cindex.py
@@ -1636,6 +1636,18 @@ def inner(self, *args, **kwargs):
return inner
+class Qualifiers(Structure):
+ """Represents the set of qualifiers (const, volatile, __restrict)
+ of a C++ member function or member function template.
+ """
+
+ _fields_ = [
+ ("Const", c_uint, 1),
+ ("Volatile", c_uint, 1),
+ ("Restrict", c_uint, 1),
+ ]
+
+
class Cursor(Structure):
"""
The Cursor class represents a reference to an element within the AST. It
@@ -1685,6 +1697,14 @@ def is_const_method(self) -> bool:
"""
return bool(conf.lib.clang_CXXMethod_isConst(self))
+ @cursor_null_guard
+ def get_method_qualifiers(self) -> Qualifiers:
+ """Returns the set of qualifiers for a C++ member function or member
+ function template. If the cursor does not refer to a C++ member function
+ or member function template, a zero-initialized Qualifiers is returned.
+ """
+ return conf.lib.clang_CXXMethod_getQualifiers(self)
+
@cursor_null_guard
def is_converting_constructor(self) -> bool:
"""Returns True if the cursor refers to a C++ converting constructor."""
@@ -1857,6 +1877,11 @@ def is_scoped_enum(self) -> bool:
"""Returns True if the cursor refers to a scoped enum declaration."""
return bool(conf.lib.clang_EnumDecl_isScoped(self))
+ @cursor_null_guard
+ def is_constexpr(self) -> bool:
+ """Returns True if the cursor refers to a constexpr declaration."""
+ return bool(conf.lib.clang_Cursor_isConstexpr(self))
+
@cursor_null_guard
def get_definition(self) -> Cursor | None:
"""
@@ -4306,6 +4331,7 @@ def set_property(self, property, value):
("clang_CXXConstructor_isMoveConstructor", [Cursor], c_uint),
("clang_CXXField_isMutable", [Cursor], c_uint),
("clang_CXXMethod_isConst", [Cursor], c_uint),
+ ("clang_CXXMethod_getQualifiers", [Cursor], Qualifiers),
("clang_CXXMethod_isDefaulted", [Cursor], c_uint),
("clang_CXXMethod_isDeleted", [Cursor], c_uint),
("clang_CXXMethod_isCopyAssignmentOperator", [Cursor], c_uint),
@@ -4492,6 +4518,7 @@ def set_property(self, property, value):
("clang_Cursor_isAnonymousRecordDecl", [Cursor], c_uint),
("clang_Cursor_isBitField", [Cursor], c_uint),
("clang_Cursor_isFunctionInlined", [Cursor], c_uint),
+ ("clang_Cursor_isConstexpr", [Cursor], c_uint),
("clang_Location_isInSystemHeader", [SourceLocation], c_int),
("clang_PrintingPolicy_dispose", [PrintingPolicy]),
("clang_PrintingPolicy_getProperty", [PrintingPolicy, c_int], c_uint),
diff --git a/clang/bindings/python/tests/cindex/test_cursor.py b/clang/bindings/python/tests/cindex/test_cursor.py
index 76680e576b307..13b848170b069 100644
--- a/clang/bindings/python/tests/cindex/test_cursor.py
+++ b/clang/bindings/python/tests/cindex/test_cursor.py
@@ -1,6 +1,7 @@
from clang.cindex import (
AvailabilityKind,
BinaryOperator,
+ Qualifiers,
Cursor,
CursorKind,
PrintingPolicy,
@@ -197,6 +198,64 @@ def test_is_const_method(self):
self.assertTrue(foo.is_const_method())
self.assertFalse(bar.is_const_method())
+ def test_get_method_qualifiers(self):
+ """Ensure Cursor.get_method_qualifiers works."""
+ source = """
+ class X {
+ void unqualified();
+ void c() const;
+ void v() volatile;
+ void cv() const volatile;
+ void r() __restrict;
+ void cvr() const volatile __restrict;
+ };
+ """
+ tu = get_tu(source, lang="cpp")
+
+ unqualified = get_cursor(tu, "unqualified")
+ c = get_cursor(tu, "c")
+ v = get_cursor(tu, "v")
+ cv = get_cursor(tu, "cv")
+ r = get_cursor(tu, "r")
+ cvr = get_cursor(tu, "cvr")
+ self.assertIsNotNone(unqualified)
+ self.assertIsNotNone(c)
+ self.assertIsNotNone(v)
+ self.assertIsNotNone(cv)
+ self.assertIsNotNone(r)
+ self.assertIsNotNone(cvr)
+
+ q = unqualified.get_method_qualifiers()
+ self.assertIsInstance(q, Qualifiers)
+ self.assertFalse(q.Const)
+ self.assertFalse(q.Volatile)
+ self.assertFalse(q.Restrict)
+
+ q = c.get_method_qualifiers()
+ self.assertTrue(q.Const)
+ self.assertFalse(q.Volatile)
+ self.assertFalse(q.Restrict)
+
+ q = v.get_method_qualifiers()
+ self.assertFalse(q.Const)
+ self.assertTrue(q.Volatile)
+ self.assertFalse(q.Restrict)
+
+ q = cv.get_method_qualifiers()
+ self.assertTrue(q.Const)
+ self.assertTrue(q.Volatile)
+ self.assertFalse(q.Restrict)
+
+ q = r.get_method_qualifiers()
+ self.assertFalse(q.Const)
+ self.assertFalse(q.Volatile)
+ self.assertTrue(q.Restrict)
+
+ q = cvr.get_method_qualifiers()
+ self.assertTrue(q.Const)
+ self.assertTrue(q.Volatile)
+ self.assertTrue(q.Restrict)
+
def test_is_converting_constructor(self):
"""Ensure Cursor.is_converting_constructor works."""
source = "class X { explicit X(int); X(double); X(); };"
@@ -565,6 +624,40 @@ def test_is_scoped_enum(self):
self.assertFalse(regular_enum.is_scoped_enum())
self.assertTrue(scoped_enum.is_scoped_enum())
+ def test_is_constexpr(self):
+ """Ensure Cursor.is_constexpr works."""
+ source = """
+ constexpr int x = 42;
+ int y = 1;
+ struct S {
+ constexpr int foo() { return 1; }
+ int bar() { return 2; }
+ };
+ template<typename T> constexpr T tmpl(T v) { return v; }
+ template<typename T> T tmpl_nc(T v) { return v; }
+ """
+ tu = get_tu(source, lang="cpp", flags=["--std=c++14"])
+
+ x = get_cursor(tu, "x")
+ y = get_cursor(tu, "y")
+ foo = get_cursor(tu, "foo")
+ bar = get_cursor(tu, "bar")
+ tmpl = get_cursor(tu, "tmpl")
+ tmpl_nc = get_cursor(tu, "tmpl_nc")
+ self.assertIsNotNone(x)
+ self.assertIsNotNone(y)
+ self.assertIsNotNone(foo)
+ self.assertIsNotNone(bar)
+ self.assertIsNotNone(tmpl)
+ self.assertIsNotNone(tmpl_nc)
+
+ self.assertTrue(x.is_constexpr())
+ self.assertFalse(y.is_constexpr())
+ self.assertTrue(foo.is_constexpr())
+ self.assertFalse(bar.is_constexpr())
+ self.assertTrue(tmpl.is_constexpr())
+ self.assertFalse(tmpl_nc.is_constexpr())
+
def test_get_definition(self):
"""Ensure Cursor.get_definition works."""
tu = get_tu(
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 2da7175b51ea3..9d4069d2479ac 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -583,6 +583,8 @@ libclang
- Visit switch initializer statements (https://bugs.kde.org/show_bug.cgi?id=415537#c2)
- Fix crash in clang_getBinaryOperatorKindSpelling and clang_getUnaryOperatorKindSpelling
- The clang_Module_getASTFile API is deprecated and now always returns nullptr
+- Added ``clang_CXXMethod_getQualifiers`` to query const/volatile/__restrict qualifiers of a member function.
+- Added ``clang_Cursor_isConstexpr`` to determine if a cursor refers to a constexpr declaration.
Code Completion
---------------
@@ -623,6 +625,8 @@ Python Binding Changes
so it can be used the same as ``CodeCompletionResults.results``.
- Added a new helper method ``get_clang_version`` to the class ``Config`` to
read the version string of the libclang in use.
+- Added ``Cursor.get_method_qualifiers``, a binding for ``clang_CXXMethod_getQualifiers``.
+- Added ``Cursor.is_constexpr``, a binding for ``clang_Cursor_isConstexpr``.
OpenMP Support
--------------
diff --git a/clang/include/clang-c/Index.h b/clang/include/clang-c/Index.h
index dcf1f4f1b4258..9ae9824b65c43 100644
--- a/clang/include/clang-c/Index.h
+++ b/clang/include/clang-c/Index.h
@@ -3351,6 +3351,14 @@ CINDEX_LINKAGE unsigned clang_Cursor_isMacroBuiltin(CXCursor C);
*/
CINDEX_LINKAGE unsigned clang_Cursor_isFunctionInlined(CXCursor C);
+/**
+ * Determine whether a cursor refers to a constexpr declaration.
+ *
+ * If the cursor does not refer to a constexpr variable or function
+ * declaration, 0 is returned.
+ */
+CINDEX_LINKAGE unsigned clang_Cursor_isConstexpr(CXCursor C);
+
/**
* Determine whether a CXType has the "volatile" qualifier set,
* without looking through typedefs that may have added "volatile" at
@@ -4885,6 +4893,26 @@ CINDEX_LINKAGE unsigned clang_EnumDecl_isScoped(CXCursor C);
*/
CINDEX_LINKAGE unsigned clang_CXXMethod_isConst(CXCursor C);
+/**
+ * Set of qualifiers (const, volatile, __restrict) of a C++ member function
+ * or member function template.
+ */
+typedef struct {
+ unsigned Const : 1;
+ unsigned Volatile : 1;
+ unsigned Restrict : 1;
+ unsigned /*Reserved for other qualifiers*/ : 29;
+} CXQualifiers;
+
+/**
+ * Retrieve the set of qualifiers for a C++ member function or member
+ * function template.
+ *
+ * If the cursor does not refer to a C++ member function or member function
+ * template, a zero-initialized CXQualifiers is returned.
+ */
+CINDEX_LINKAGE CXQualifiers clang_CXXMethod_getQualifiers(CXCursor C);
+
/**
* Given a cursor that represents a template, determine
* the cursor kind of the specializations would be generated by instantiating
diff --git a/clang/test/Index/cursor-properties.cpp b/clang/test/Index/cursor-properties.cpp
new file mode 100644
index 0000000000000..ad9dc3efa2abe
--- /dev/null
+++ b/clang/test/Index/cursor-properties.cpp
@@ -0,0 +1,22 @@
+struct Foo {
+ void normal();
+ void c() const;
+ void v() volatile;
+ void cv() const volatile;
+ void r() __restrict;
+ void cvr() const volatile __restrict;
+ constexpr int baz() { return 1; }
+};
+constexpr int x = 42;
+int y = 1;
+
+// RUN: c-index-test -test-print-type --std=c++14 %s | FileCheck %s
+// CHECK: CXXMethod=normal:2:8 [type=void ()] [typekind=FunctionProto] [resulttype=void] [resulttypekind=Void] [isPOD=0]
+// CHECK: CXXMethod=c:3:8 (const) [type=void () const] [typekind=FunctionProto] [resulttype=void] [resulttypekind=Void] [isPOD=0]
+// CHECK: CXXMethod=v:4:8 (volatile) [type=void () volatile] [typekind=FunctionProto] [resulttype=void] [resulttypekind=Void] [isPOD=0]
+// CHECK: CXXMethod=cv:5:8 (const) (volatile) [type=void () const volatile] [typekind=FunctionProto] [resulttype=void] [resulttypekind=Void] [isPOD=0]
+// CHECK: CXXMethod=r:6:8 (restrict) [type=void () __restrict] [typekind=FunctionProto] [resulttype=void] [resulttypekind=Void] [isPOD=0]
+// CHECK: CXXMethod=cvr:7:8 (const) (volatile) (restrict) [type=void () const volatile __restrict] [typekind=FunctionProto] [resulttype=void] [resulttypekind=Void] [isPOD=0]
+// CHECK: CXXMethod=baz:8:17 (Definition) (constexpr) [type=int ()] [typekind=FunctionProto] [resulttype=int] [resulttypekind=Int] [isPOD=0]
+// CHECK: VarDecl=x:10:15 (Definition) (constexpr) [type=const int] [typekind=Int] const [isPOD=1]
+// CHECK: VarDecl=y:11:5 (Definition) [type=int] [typekind=Int] [isPOD=1]
diff --git a/clang/tools/c-index-test/c-index-test.c b/clang/tools/c-index-test/c-index-test.c
index 4b3e105aa7aff..bd863840f2ed7 100644
--- a/clang/tools/c-index-test/c-index-test.c
+++ b/clang/tools/c-index-test/c-index-test.c
@@ -952,8 +952,15 @@ static void PrintCursor(CXCursor Cursor, const char *CommentSchemaFile) {
printf(" (static)");
if (clang_CXXMethod_isVirtual(Cursor))
printf(" (virtual)");
- if (clang_CXXMethod_isConst(Cursor))
- printf(" (const)");
+ {
+ CXQualifiers Q = clang_CXXMethod_getQualifiers(Cursor);
+ if (Q.Const)
+ printf(" (const)");
+ if (Q.Volatile)
+ printf(" (volatile)");
+ if (Q.Restrict)
+ printf(" (restrict)");
+ }
if (clang_CXXMethod_isPureVirtual(Cursor))
printf(" (pure)");
if (clang_CXXMethod_isCopyAssignmentOperator(Cursor))
@@ -966,6 +973,8 @@ static void PrintCursor(CXCursor Cursor, const char *CommentSchemaFile) {
printf(" (abstract)");
if (clang_EnumDecl_isScoped(Cursor))
printf(" (scoped)");
+ if (clang_Cursor_isConstexpr(Cursor))
+ printf(" (constexpr)");
if (clang_Cursor_isVariadic(Cursor))
printf(" (variadic)");
if (clang_Cursor_isObjCOptional(Cursor))
diff --git a/clang/tools/libclang/CIndex.cpp b/clang/tools/libclang/CIndex.cpp
index 3ee37ed2dfc27..2a887cdfb7f58 100644
--- a/clang/tools/libclang/CIndex.cpp
+++ b/clang/tools/libclang/CIndex.cpp
@@ -9533,6 +9533,23 @@ unsigned clang_CXXMethod_isConst(CXCursor C) {
return (Method && Method->getMethodQualifiers().hasConst()) ? 1 : 0;
}
+CXQualifiers clang_CXXMethod_getQualifiers(CXCursor C) {
+ CXQualifiers Q = {};
+ if (!clang_isDeclaration(C.kind))
+ return Q;
+
+ const Decl *D = cxcursor::getCursorDecl(C);
+ const CXXMethodDecl *Method =
+ D ? dyn_cast_or_null<CXXMethodDecl>(D->getAsFunction()) : nullptr;
+ if (Method) {
+ Qualifiers MQ = Method->getMethodQualifiers();
+ Q.Const = MQ.hasConst();
+ Q.Volatile = MQ.hasVolatile();
+ Q.Restrict = MQ.hasRestrict();
+ }
+ return Q;
+}
+
unsigned clang_CXXMethod_isDefaulted(CXCursor C) {
if (!clang_isDeclaration(C.kind))
return 0;
@@ -9614,6 +9631,22 @@ unsigned clang_CXXMethod_isExplicit(CXCursor C) {
return 0;
}
+unsigned clang_Cursor_isConstexpr(CXCursor C) {
+ if (!clang_isDeclaration(C.kind))
+ return 0;
+
+ const Decl *D = cxcursor::getCursorDecl(C);
+ if (!D)
+ return 0;
+
+ if (const auto *VD = dyn_cast<VarDecl>(D))
+ return VD->isConstexpr();
+ if (const FunctionDecl *FD = D->getAsFunction())
+ return FD->isConstexpr();
+
+ return 0;
+}
+
unsigned clang_CXXRecord_isAbstract(CXCursor C) {
if (!clang_isDeclaration(C.kind))
return 0;
diff --git a/clang/tools/libclang/libclang.map b/clang/tools/libclang/libclang.map
index 2691602d432f6..c33579219d42b 100644
--- a/clang/tools/libclang/libclang.map
+++ b/clang/tools/libclang/libclang.map
@@ -460,6 +460,8 @@ LLVM_21 {
LLVM_23 {
global:
clang_ModuleCache_prune;
+ clang_Cursor_isConstexpr;
+ clang_CXXMethod_getQualifiers;
};
# Example of how to add a new symbol version entry. If you do add a new symbol
>From 6acaf02df235d9aa648b251b5a3432329997ba48 Mon Sep 17 00:00:00 2001
From: Fabian Scheidl <fscheidl at apple.com>
Date: Tue, 7 Apr 2026 19:10:41 +0200
Subject: [PATCH 2/4] [libclang] Add
clang_CXXMethod_isExplicitObjectMemberFunction
---
clang/bindings/python/clang/cindex.py | 10 ++++
.../python/tests/cindex/test_cursor.py | 46 ++++++++++++++++++-
clang/docs/ReleaseNotes.rst | 4 ++
clang/include/clang-c/Index.h | 11 +++++
clang/test/Index/cursor-properties.cpp | 11 ++++-
clang/tools/c-index-test/c-index-test.c | 2 +
clang/tools/libclang/CIndex.cpp | 10 ++++
clang/tools/libclang/libclang.map | 1 +
8 files changed, 92 insertions(+), 3 deletions(-)
diff --git a/clang/bindings/python/clang/cindex.py b/clang/bindings/python/clang/cindex.py
index 7d3b54af3f8a5..2c55171c1101d 100644
--- a/clang/bindings/python/clang/cindex.py
+++ b/clang/bindings/python/clang/cindex.py
@@ -1837,6 +1837,15 @@ class Foo {
"""
return bool(conf.lib.clang_CXXMethod_isExplicit(self))
+ @cursor_null_guard
+ def is_explicit_object_member_function(self) -> bool:
+ """Returns True if the cursor refers to a C++ member function with an
+ explicit object parameter (C++23).
+ """
+ return bool(
+ conf.lib.clang_CXXMethod_isExplicitObjectMemberFunction(self)
+ )
+
@cursor_null_guard
def is_mutable_field(self) -> bool:
"""Returns True if the cursor refers to a C++ field that is declared
@@ -4337,6 +4346,7 @@ def set_property(self, property, value):
("clang_CXXMethod_isCopyAssignmentOperator", [Cursor], c_uint),
("clang_CXXMethod_isMoveAssignmentOperator", [Cursor], c_uint),
("clang_CXXMethod_isExplicit", [Cursor], c_uint),
+ ("clang_CXXMethod_isExplicitObjectMemberFunction", [Cursor], c_uint),
("clang_CXXMethod_isPureVirtual", [Cursor], c_uint),
("clang_CXXMethod_isStatic", [Cursor], c_uint),
("clang_CXXMethod_isVirtual", [Cursor], c_uint),
diff --git a/clang/bindings/python/tests/cindex/test_cursor.py b/clang/bindings/python/tests/cindex/test_cursor.py
index 13b848170b069..bcb81cde04087 100644
--- a/clang/bindings/python/tests/cindex/test_cursor.py
+++ b/clang/bindings/python/tests/cindex/test_cursor.py
@@ -1,11 +1,11 @@
from clang.cindex import (
AvailabilityKind,
BinaryOperator,
- Qualifiers,
Cursor,
CursorKind,
PrintingPolicy,
PrintingPolicyProperty,
+ Qualifiers,
StorageClass,
TemplateArgumentKind,
TranslationUnit,
@@ -256,6 +256,29 @@ class X {
self.assertTrue(q.Volatile)
self.assertTrue(q.Restrict)
+ # Explicit object member functions have no method qualifiers
+ source = """
+ struct S {
+ void explicit_const(this const S&);
+ void explicit_cv(this const volatile S&);
+ };
+ """
+ tu = get_tu(source, lang="cpp", flags=["-std=c++23"])
+ ec = get_cursor(tu, "explicit_const")
+ ecv = get_cursor(tu, "explicit_cv")
+ self.assertIsNotNone(ec)
+ self.assertIsNotNone(ecv)
+
+ q = ec.get_method_qualifiers()
+ self.assertFalse(q.Const)
+ self.assertFalse(q.Volatile)
+ self.assertFalse(q.Restrict)
+
+ q = ecv.get_method_qualifiers()
+ self.assertFalse(q.Const)
+ self.assertFalse(q.Volatile)
+ self.assertFalse(q.Restrict)
+
def test_is_converting_constructor(self):
"""Ensure Cursor.is_converting_constructor works."""
source = "class X { explicit X(int); X(double); X(); };"
@@ -567,6 +590,25 @@ def test_is_static_method(self):
self.assertTrue(foo.is_static_method())
self.assertFalse(bar.is_static_method())
+ def test_is_explicit_object_member_function(self):
+ source = """
+ struct S {
+ void regular();
+ void explicit_obj(this const S&);
+ static void static_method();
+ };
+ """
+ tu = get_tu(source, lang="cpp", flags=["-std=c++23"])
+ regular = get_cursor(tu, "regular")
+ explicit_obj = get_cursor(tu, "explicit_obj")
+ static_method = get_cursor(tu, "static_method")
+ self.assertIsNotNone(regular)
+ self.assertIsNotNone(explicit_obj)
+ self.assertIsNotNone(static_method)
+ self.assertFalse(regular.is_explicit_object_member_function())
+ self.assertTrue(explicit_obj.is_explicit_object_member_function())
+ self.assertFalse(static_method.is_explicit_object_member_function())
+
def test_is_pure_virtual_method(self):
"""Ensure Cursor.is_pure_virtual_method works."""
source = "class X { virtual void foo() = 0; virtual void bar(); };"
@@ -636,7 +678,7 @@ def test_is_constexpr(self):
template<typename T> constexpr T tmpl(T v) { return v; }
template<typename T> T tmpl_nc(T v) { return v; }
"""
- tu = get_tu(source, lang="cpp", flags=["--std=c++14"])
+ tu = get_tu(source, lang="cpp", flags=["-std=c++14"])
x = get_cursor(tu, "x")
y = get_cursor(tu, "y")
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 9d4069d2479ac..3efc7df494316 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -585,6 +585,8 @@ libclang
- The clang_Module_getASTFile API is deprecated and now always returns nullptr
- Added ``clang_CXXMethod_getQualifiers`` to query const/volatile/__restrict qualifiers of a member function.
- Added ``clang_Cursor_isConstexpr`` to determine if a cursor refers to a constexpr declaration.
+- Added ``clang_CXXMethod_isExplicitObjectMemberFunction`` to determine if a method has an
+ explicit object parameter (C++23).
Code Completion
---------------
@@ -627,6 +629,8 @@ Python Binding Changes
read the version string of the libclang in use.
- Added ``Cursor.get_method_qualifiers``, a binding for ``clang_CXXMethod_getQualifiers``.
- Added ``Cursor.is_constexpr``, a binding for ``clang_Cursor_isConstexpr``.
+- Added ``Cursor.is_explicit_object_member_function``, a binding for
+ ``clang_CXXMethod_isExplicitObjectMemberFunction``.
OpenMP Support
--------------
diff --git a/clang/include/clang-c/Index.h b/clang/include/clang-c/Index.h
index 9ae9824b65c43..b061f846358f0 100644
--- a/clang/include/clang-c/Index.h
+++ b/clang/include/clang-c/Index.h
@@ -4910,9 +4910,20 @@ typedef struct {
*
* If the cursor does not refer to a C++ member function or member function
* template, a zero-initialized CXQualifiers is returned.
+ *
+ * For explicit object member functions (C++23), this returns zero-initialized
+ * CXQualifiers. Use \c clang_CXXMethod_isExplicitObjectMemberFunction to
+ * distinguish explicit from implicit object member functions.
*/
CINDEX_LINKAGE CXQualifiers clang_CXXMethod_getQualifiers(CXCursor C);
+/**
+ * Determine if a C++ member function or member function template is an
+ * explicit object member function (C++23).
+ */
+CINDEX_LINKAGE unsigned
+clang_CXXMethod_isExplicitObjectMemberFunction(CXCursor C);
+
/**
* Given a cursor that represents a template, determine
* the cursor kind of the specializations would be generated by instantiating
diff --git a/clang/test/Index/cursor-properties.cpp b/clang/test/Index/cursor-properties.cpp
index ad9dc3efa2abe..f1f576d82b230 100644
--- a/clang/test/Index/cursor-properties.cpp
+++ b/clang/test/Index/cursor-properties.cpp
@@ -10,7 +10,13 @@ struct Foo {
constexpr int x = 42;
int y = 1;
-// RUN: c-index-test -test-print-type --std=c++14 %s | FileCheck %s
+struct Bar {
+ void explicit_const(this const Bar&);
+ void explicit_volatile(this volatile Bar&);
+ void explicit_cv(this const volatile Bar&);
+};
+
+// RUN: c-index-test -test-print-type --std=c++23 %s | FileCheck %s
// CHECK: CXXMethod=normal:2:8 [type=void ()] [typekind=FunctionProto] [resulttype=void] [resulttypekind=Void] [isPOD=0]
// CHECK: CXXMethod=c:3:8 (const) [type=void () const] [typekind=FunctionProto] [resulttype=void] [resulttypekind=Void] [isPOD=0]
// CHECK: CXXMethod=v:4:8 (volatile) [type=void () volatile] [typekind=FunctionProto] [resulttype=void] [resulttypekind=Void] [isPOD=0]
@@ -20,3 +26,6 @@ int y = 1;
// CHECK: CXXMethod=baz:8:17 (Definition) (constexpr) [type=int ()] [typekind=FunctionProto] [resulttype=int] [resulttypekind=Int] [isPOD=0]
// CHECK: VarDecl=x:10:15 (Definition) (constexpr) [type=const int] [typekind=Int] const [isPOD=1]
// CHECK: VarDecl=y:11:5 (Definition) [type=int] [typekind=Int] [isPOD=1]
+// CHECK: CXXMethod=explicit_const:14:8 (explicit object) [type=void (const Bar &)] [typekind=FunctionProto] [canonicaltype=void (const Bar &)] [canonicaltypekind=FunctionProto] [resulttype=void] [resulttypekind=Void] [args= [const Bar &] [LValueReference]] [isPOD=0]
+// CHECK: CXXMethod=explicit_volatile:15:8 (explicit object) [type=void (volatile Bar &)] [typekind=FunctionProto] [canonicaltype=void (volatile Bar &)] [canonicaltypekind=FunctionProto] [resulttype=void] [resulttypekind=Void] [args= [volatile Bar &] [LValueReference]] [isPOD=0]
+// CHECK: CXXMethod=explicit_cv:16:8 (explicit object) [type=void (const volatile Bar &)] [typekind=FunctionProto] [canonicaltype=void (const volatile Bar &)] [canonicaltypekind=FunctionProto] [resulttype=void] [resulttypekind=Void] [args= [const volatile Bar &] [LValueReference]] [isPOD=0]
diff --git a/clang/tools/c-index-test/c-index-test.c b/clang/tools/c-index-test/c-index-test.c
index bd863840f2ed7..3287283494029 100644
--- a/clang/tools/c-index-test/c-index-test.c
+++ b/clang/tools/c-index-test/c-index-test.c
@@ -961,6 +961,8 @@ static void PrintCursor(CXCursor Cursor, const char *CommentSchemaFile) {
if (Q.Restrict)
printf(" (restrict)");
}
+ if (clang_CXXMethod_isExplicitObjectMemberFunction(Cursor))
+ printf(" (explicit object)");
if (clang_CXXMethod_isPureVirtual(Cursor))
printf(" (pure)");
if (clang_CXXMethod_isCopyAssignmentOperator(Cursor))
diff --git a/clang/tools/libclang/CIndex.cpp b/clang/tools/libclang/CIndex.cpp
index 2a887cdfb7f58..e564f199009a9 100644
--- a/clang/tools/libclang/CIndex.cpp
+++ b/clang/tools/libclang/CIndex.cpp
@@ -9550,6 +9550,16 @@ CXQualifiers clang_CXXMethod_getQualifiers(CXCursor C) {
return Q;
}
+unsigned clang_CXXMethod_isExplicitObjectMemberFunction(CXCursor C) {
+ if (!clang_isDeclaration(C.kind))
+ return 0;
+
+ const Decl *D = cxcursor::getCursorDecl(C);
+ const CXXMethodDecl *Method =
+ D ? dyn_cast_or_null<CXXMethodDecl>(D->getAsFunction()) : nullptr;
+ return (Method && Method->isExplicitObjectMemberFunction()) ? 1 : 0;
+}
+
unsigned clang_CXXMethod_isDefaulted(CXCursor C) {
if (!clang_isDeclaration(C.kind))
return 0;
diff --git a/clang/tools/libclang/libclang.map b/clang/tools/libclang/libclang.map
index c33579219d42b..a2756645db48c 100644
--- a/clang/tools/libclang/libclang.map
+++ b/clang/tools/libclang/libclang.map
@@ -462,6 +462,7 @@ LLVM_23 {
clang_ModuleCache_prune;
clang_Cursor_isConstexpr;
clang_CXXMethod_getQualifiers;
+ clang_CXXMethod_isExplicitObjectMemberFunction;
};
# Example of how to add a new symbol version entry. If you do add a new symbol
>From 974f2abe781ddbc782735a669e0fa4d9815e5525 Mon Sep 17 00:00:00 2001
From: Fabian Scheidl <fscheidl at apple.com>
Date: Tue, 14 Apr 2026 09:05:35 +0200
Subject: [PATCH 3/4] Separate get_method_qualifiers test for explicit object
member function
---
clang/bindings/python/tests/cindex/test_cursor.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/clang/bindings/python/tests/cindex/test_cursor.py b/clang/bindings/python/tests/cindex/test_cursor.py
index bcb81cde04087..31b68c7667656 100644
--- a/clang/bindings/python/tests/cindex/test_cursor.py
+++ b/clang/bindings/python/tests/cindex/test_cursor.py
@@ -256,7 +256,8 @@ class X {
self.assertTrue(q.Volatile)
self.assertTrue(q.Restrict)
- # Explicit object member functions have no method qualifiers
+ def test_get_method_qualifiers_explicit_object(self):
+ """Ensure explicit object member functions have no method qualifiers."""
source = """
struct S {
void explicit_const(this const S&);
>From 861c52413d44d2c8a8d62666fa575ac85e4fca97 Mon Sep 17 00:00:00 2001
From: Fabian Scheidl <fscheidl at apple.com>
Date: Tue, 14 Apr 2026 17:59:58 +0200
Subject: [PATCH 4/4] Fix format
---
clang/bindings/python/clang/cindex.py | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/clang/bindings/python/clang/cindex.py b/clang/bindings/python/clang/cindex.py
index 2c55171c1101d..fd1a134b31cb9 100644
--- a/clang/bindings/python/clang/cindex.py
+++ b/clang/bindings/python/clang/cindex.py
@@ -1842,9 +1842,7 @@ def is_explicit_object_member_function(self) -> bool:
"""Returns True if the cursor refers to a C++ member function with an
explicit object parameter (C++23).
"""
- return bool(
- conf.lib.clang_CXXMethod_isExplicitObjectMemberFunction(self)
- )
+ return bool(conf.lib.clang_CXXMethod_isExplicitObjectMemberFunction(self))
@cursor_null_guard
def is_mutable_field(self) -> bool:
More information about the cfe-commits
mailing list