[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