[clang] [libclang] Add support for constexpr in Python bindings and C API (PR #188965)
Jonathan B. Coe via cfe-commits
cfe-commits at lists.llvm.org
Fri Mar 27 04:10:58 PDT 2026
https://github.com/jbcoe updated https://github.com/llvm/llvm-project/pull/188965
>From e8eee3a13213f8262e3d46c22d97ece7c09af6db Mon Sep 17 00:00:00 2001
From: "Jonathan B. Coe" <jonathanbcoe at gmail.com>
Date: Fri, 27 Mar 2026 10:59:40 +0000
Subject: [PATCH 1/2] [libclang] Add support for constexpr declarations in
Python bindings and C API
---
clang/bindings/python/clang/cindex.py | 9 ++++
.../python/tests/cindex/test_constexpr.py | 54 +++++++++++++++++++
clang/include/clang-c/Index.h | 6 +++
clang/tools/libclang/CIndex.cpp | 9 ++++
clang/tools/libclang/libclang.map | 1 +
5 files changed, 79 insertions(+)
create mode 100644 clang/bindings/python/tests/cindex/test_constexpr.py
diff --git a/clang/bindings/python/clang/cindex.py b/clang/bindings/python/clang/cindex.py
index b71f9ed2275e0..d86f7d07296dd 100644
--- a/clang/bindings/python/clang/cindex.py
+++ b/clang/bindings/python/clang/cindex.py
@@ -2375,6 +2375,14 @@ def is_function_inlined(self) -> bool:
"""
return bool(conf.lib.clang_Cursor_isFunctionInlined(self))
+ @property
+ @cursor_null_guard
+ def is_constexpr(self) -> bool:
+ """
+ Determine if the cursor is a constexpr declaration.
+ """
+ return bool(conf.lib.clang_Cursor_isConstexpr(self))
+
@cursor_null_guard
def has_attrs(self) -> bool:
"""
@@ -4492,6 +4500,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_constexpr.py b/clang/bindings/python/tests/cindex/test_constexpr.py
new file mode 100644
index 0000000000000..cb3db25929f1c
--- /dev/null
+++ b/clang/bindings/python/tests/cindex/test_constexpr.py
@@ -0,0 +1,54 @@
+import unittest
+from .util import get_cursor, get_tu
+
+class TestConstexpr(unittest.TestCase):
+ def test_is_constexpr(self):
+ source = """
+ constexpr int f1() {
+ constexpr int local_v1 = 1;
+ int local_v2 = 2;
+ return local_v1 + local_v2;
+ }
+ int f2() { return 2; }
+
+ constexpr int v1 = 3;
+ int v2 = 4;
+
+ struct S {
+ static constexpr int m1 = 5;
+ int m2;
+ constexpr int m3() const { return 6; }
+ int m4() const { return 7; }
+ };
+ """
+ tu = get_tu(source, lang="cpp")
+
+ f1 = get_cursor(tu, "f1")
+ f2 = get_cursor(tu, "f2")
+ self.assertIsNotNone(f1)
+ self.assertTrue(f1.is_constexpr)
+ self.assertFalse(f2.is_constexpr)
+
+ v1 = get_cursor(tu, "v1")
+ v2 = get_cursor(tu, "v2")
+ self.assertIsNotNone(v1)
+ self.assertTrue(v1.is_constexpr)
+ self.assertFalse(v2.is_constexpr)
+
+ local_v1 = get_cursor(f1, "local_v1")
+ local_v2 = get_cursor(f1, "local_v2")
+ self.assertIsNotNone(local_v1)
+ self.assertTrue(local_v1.is_constexpr)
+ self.assertFalse(local_v2.is_constexpr)
+
+ S = get_cursor(tu, "S")
+ m1 = get_cursor(S, "m1")
+ m2 = get_cursor(S, "m2")
+ m3 = get_cursor(S, "m3")
+ m4 = get_cursor(S, "m4")
+
+ self.assertIsNotNone(m1)
+ self.assertTrue(m1.is_constexpr)
+ self.assertFalse(m2.is_constexpr)
+ self.assertTrue(m3.is_constexpr)
+ self.assertFalse(m4.is_constexpr)
diff --git a/clang/include/clang-c/Index.h b/clang/include/clang-c/Index.h
index dcf1f4f1b4258..b657fdb7979e2 100644
--- a/clang/include/clang-c/Index.h
+++ b/clang/include/clang-c/Index.h
@@ -3351,6 +3351,12 @@ CINDEX_LINKAGE unsigned clang_Cursor_isMacroBuiltin(CXCursor C);
*/
CINDEX_LINKAGE unsigned clang_Cursor_isFunctionInlined(CXCursor C);
+/**
+ * Determine whether a CXCursor that is a function or variable declaration is
+ * a constexpr declaration.
+ */
+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
diff --git a/clang/tools/libclang/CIndex.cpp b/clang/tools/libclang/CIndex.cpp
index 3ee37ed2dfc27..ba01a119d26a4 100644
--- a/clang/tools/libclang/CIndex.cpp
+++ b/clang/tools/libclang/CIndex.cpp
@@ -4559,6 +4559,15 @@ unsigned clang_Cursor_isFunctionInlined(CXCursor C) {
return FD->isInlined();
}
+unsigned clang_Cursor_isConstexpr(CXCursor C) {
+ const Decl *D = getCursorDecl(C);
+ if (const FunctionDecl *FD = dyn_cast_or_null<FunctionDecl>(D))
+ return FD->isConstexpr();
+ if (const VarDecl *VD = dyn_cast_or_null<VarDecl>(D))
+ return VD->isConstexpr();
+ return false;
+}
+
static StringLiteral *getCFSTR_value(CallExpr *callExpr) {
if (callExpr->getNumArgs() != 1) {
return nullptr;
diff --git a/clang/tools/libclang/libclang.map b/clang/tools/libclang/libclang.map
index 3d9d2e268a611..de22123e1f2a8 100644
--- a/clang/tools/libclang/libclang.map
+++ b/clang/tools/libclang/libclang.map
@@ -87,6 +87,7 @@ LLVM_13 {
clang_Cursor_isDynamicCall;
clang_Cursor_isExternalSymbol;
clang_Cursor_isFunctionInlined;
+ clang_Cursor_isConstexpr;
clang_Cursor_isInlineNamespace;
clang_Cursor_isMacroBuiltin;
clang_Cursor_isMacroFunctionLike;
>From 46aa9189219e347637f7ef6d26e40dea556bb90a Mon Sep 17 00:00:00 2001
From: "Jonathan B. Coe" <jonathanbcoe at gmail.com>
Date: Fri, 27 Mar 2026 11:10:29 +0000
Subject: [PATCH 2/2] Fix python test formatting.
---
clang/bindings/python/tests/cindex/test_constexpr.py | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/clang/bindings/python/tests/cindex/test_constexpr.py b/clang/bindings/python/tests/cindex/test_constexpr.py
index cb3db25929f1c..6b5f3ad3ba9c1 100644
--- a/clang/bindings/python/tests/cindex/test_constexpr.py
+++ b/clang/bindings/python/tests/cindex/test_constexpr.py
@@ -1,6 +1,7 @@
import unittest
from .util import get_cursor, get_tu
+
class TestConstexpr(unittest.TestCase):
def test_is_constexpr(self):
source = """
@@ -22,7 +23,7 @@ def test_is_constexpr(self):
};
"""
tu = get_tu(source, lang="cpp")
-
+
f1 = get_cursor(tu, "f1")
f2 = get_cursor(tu, "f2")
self.assertIsNotNone(f1)
@@ -34,7 +35,7 @@ def test_is_constexpr(self):
self.assertIsNotNone(v1)
self.assertTrue(v1.is_constexpr)
self.assertFalse(v2.is_constexpr)
-
+
local_v1 = get_cursor(f1, "local_v1")
local_v2 = get_cursor(f1, "local_v2")
self.assertIsNotNone(local_v1)
@@ -46,7 +47,7 @@ def test_is_constexpr(self):
m2 = get_cursor(S, "m2")
m3 = get_cursor(S, "m3")
m4 = get_cursor(S, "m4")
-
+
self.assertIsNotNone(m1)
self.assertTrue(m1.is_constexpr)
self.assertFalse(m2.is_constexpr)
More information about the cfe-commits
mailing list