[clang] [libclang] Add accessors for template parameters and fix parameter pack handling (PR #183504)
via cfe-commits
cfe-commits at lists.llvm.org
Mon Apr 13 23:53:19 PDT 2026
https://github.com/fscheidl updated https://github.com/llvm/llvm-project/pull/183504
>From 50546d2c0cd9474cf34da269c0f6ebc8e4de6200 Mon Sep 17 00:00:00 2001
From: Fabian Scheidl <fscheidl at apple.com>
Date: Sun, 1 Mar 2026 19:01:25 +0100
Subject: [PATCH 1/5] [[libclang] Add functions to query template parameters
and fix parameter pack handling
---
clang/bindings/python/clang/cindex.py | 29 ++++
.../python/tests/cindex/test_cursor.py | 121 ++++++++++++++++
clang/docs/ReleaseNotes.rst | 6 +
clang/include/clang-c/Index.h | 58 +++++++-
clang/test/Index/template-parameters.cpp | 14 ++
clang/tools/c-index-test/c-index-test.c | 9 +-
clang/tools/libclang/CXCursor.cpp | 135 ++++++++++++++----
clang/tools/libclang/libclang.map | 4 +
8 files changed, 342 insertions(+), 34 deletions(-)
create mode 100644 clang/test/Index/template-parameters.cpp
diff --git a/clang/bindings/python/clang/cindex.py b/clang/bindings/python/clang/cindex.py
index b71f9ed2275e0..83daebb1d18ab 100644
--- a/clang/bindings/python/clang/cindex.py
+++ b/clang/bindings/python/clang/cindex.py
@@ -2277,6 +2277,31 @@ def get_template_argument_unsigned_value(self, num: int) -> int:
"""Returns the value of the indicated arg as an unsigned 64b integer."""
return conf.lib.clang_Cursor_getTemplateArgumentUnsignedValue(self, num) # type: ignore [no-any-return]
+ @cursor_null_guard
+ def get_template_argument_integral_type(self, num: int) -> Type:
+ """Returns the type of an integral template argument at the given index."""
+ return Type.from_result(
+ conf.lib.clang_Cursor_getTemplateArgumentIntegralType(self, num), self
+ )
+
+ @cursor_null_guard
+ def get_num_template_parameters(self) -> int:
+ """Returns the number of template parameters, or -1 if not a template."""
+ return conf.lib.clang_Cursor_getNumTemplateParameters(self) # type: ignore [no-any-return]
+
+ @cursor_null_guard
+ def get_template_parameter(self, num: int) -> Cursor:
+ """Returns the template parameter at the given index.
+
+ If the index is out of range, a null cursor is returned
+ """
+ return conf.lib.clang_Cursor_getTemplateParameter(self, num) # type: ignore [no-any-return]
+
+ @cursor_null_guard
+ def is_template_parameter_pack(self) -> bool:
+ """Returns True if the cursor is a template parameter pack."""
+ return bool(conf.lib.clang_Cursor_isTemplateParameterPack(self))
+
@cursor_null_guard
def get_children(self) -> Iterator[Cursor]:
"""Return an iterator for accessing the children of this cursor."""
@@ -4483,6 +4508,10 @@ def set_property(self, property, value):
("clang_Cursor_getTemplateArgumentType", [Cursor, c_uint], Type),
("clang_Cursor_getTemplateArgumentValue", [Cursor, c_uint], c_longlong),
("clang_Cursor_getTemplateArgumentUnsignedValue", [Cursor, c_uint], c_ulonglong),
+ ("clang_Cursor_getTemplateArgumentIntegralType", [Cursor, c_uint], Type),
+ ("clang_Cursor_getNumTemplateParameters", [Cursor], c_int),
+ ("clang_Cursor_getTemplateParameter", [Cursor, c_uint], Cursor),
+ ("clang_Cursor_isTemplateParameterPack", [Cursor], c_uint),
("clang_getCursorBinaryOperatorKind", [Cursor], c_int),
("clang_Cursor_getBriefCommentText", [Cursor], _CXString),
("clang_Cursor_getRawCommentText", [Cursor], _CXString),
diff --git a/clang/bindings/python/tests/cindex/test_cursor.py b/clang/bindings/python/tests/cindex/test_cursor.py
index 76680e576b307..7304e2702205e 100644
--- a/clang/bindings/python/tests/cindex/test_cursor.py
+++ b/clang/bindings/python/tests/cindex/test_cursor.py
@@ -916,6 +916,127 @@ def test_get_template_argument_unsigned_value(self):
self.assertEqual(foos[1].get_template_argument_unsigned_value(0), 2**32 - 7)
self.assertEqual(foos[1].get_template_argument_unsigned_value(2), True)
+ def test_get_template_argument_integral_type(self):
+ tu = get_tu(TEMPLATE_ARG_TEST, lang="cpp")
+ foos = get_cursors(tu, "foo")
+
+ self.assertEqual(
+ foos[1].get_template_argument_integral_type(0).kind, TypeKind.INT
+ )
+ self.assertEqual(
+ foos[1].get_template_argument_integral_type(1).kind,
+ TypeKind.INVALID,
+ )
+ self.assertEqual(
+ foos[1].get_template_argument_integral_type(2).kind, TypeKind.BOOL
+ )
+
+ def test_get_template_argument_integral_type_pack(self):
+ source = """
+ template<int... Ns> struct Foo {};
+ template class Foo<1, 2, 3>;
+ """
+ tu = get_tu(source, lang="cpp")
+ foos = get_cursors(tu, "Foo")
+ spec = foos[1]
+ self.assertEqual(spec.kind, CursorKind.STRUCT_DECL)
+ self.assertEqual(spec.get_num_template_arguments(), 3)
+ self.assertEqual(spec.get_template_argument_integral_type(0).kind, TypeKind.INT)
+ self.assertEqual(spec.get_template_argument_integral_type(1).kind, TypeKind.INT)
+ self.assertEqual(spec.get_template_argument_integral_type(2).kind, TypeKind.INT)
+
+ def test_get_num_template_parameters(self):
+ source = "template<typename T, int N> void foo(); template<typename... Ts> void bar(); void baz();"
+ tu = get_tu(source, lang="cpp")
+ foo = get_cursor(tu, "foo")
+ bar = get_cursor(tu, "bar")
+ baz = get_cursor(tu, "baz")
+ self.assertIsNotNone(foo)
+ self.assertIsNotNone(bar)
+ self.assertIsNotNone(baz)
+
+ self.assertEqual(foo.get_num_template_parameters(), 2)
+ self.assertEqual(bar.get_num_template_parameters(), 1)
+ self.assertEqual(baz.get_num_template_parameters(), -1)
+
+ def test_get_template_parameter(self):
+ source = "template<typename T, int N> void foo();"
+ tu = get_tu(source, lang="cpp")
+ foo = get_cursor(tu, "foo")
+ self.assertIsNotNone(foo)
+
+ t_param = foo.get_template_parameter(0)
+ n_param = foo.get_template_parameter(1)
+ self.assertEqual(t_param.kind, CursorKind.TEMPLATE_TYPE_PARAMETER)
+ self.assertEqual(t_param.spelling, "T")
+ self.assertEqual(n_param.kind, CursorKind.TEMPLATE_NON_TYPE_PARAMETER)
+ self.assertEqual(n_param.spelling, "N")
+ oob = foo.get_template_parameter(2)
+ self.assertTrue(oob.is_null())
+
+ def test_is_template_parameter_pack(self):
+ # Type parameter pack
+ source = "template<typename T, typename... Ts> void foo();"
+ tu = get_tu(source, lang="cpp")
+ foo = get_cursor(tu, "foo")
+ self.assertIsNotNone(foo)
+ self.assertFalse(foo.get_template_parameter(0).is_template_parameter_pack())
+ self.assertTrue(foo.get_template_parameter(1).is_template_parameter_pack())
+
+ # Non-type parameter pack
+ source = "template<int... Ns> void bar();"
+ tu = get_tu(source, lang="cpp")
+ bar = get_cursor(tu, "bar")
+ self.assertIsNotNone(bar)
+ self.assertTrue(bar.get_template_parameter(0).is_template_parameter_pack())
+
+ # Template template parameter pack
+ source = "template<template<typename> class... Ts> void baz();"
+ tu = get_tu(source, lang="cpp")
+ baz = get_cursor(tu, "baz")
+ self.assertIsNotNone(baz)
+ self.assertTrue(baz.get_template_parameter(0).is_template_parameter_pack())
+
+ def test_get_template_argument_variadic(self):
+ source = """
+ template<typename T, typename... Ts> struct Foo {};
+ Foo<int, float, double> x;
+ Foo<int> y;
+ """
+ tu = get_tu(source, lang="cpp")
+
+ x = get_cursor(tu, "x")
+ self.assertIsNotNone(x)
+ x_type = x.type.get_declaration()
+ self.assertEqual(x_type.get_num_template_arguments(), 3)
+ self.assertEqual(x_type.get_template_argument_type(0).kind, TypeKind.INT)
+ self.assertEqual(x_type.get_template_argument_type(1).kind, TypeKind.FLOAT)
+ self.assertEqual(x_type.get_template_argument_type(2).kind, TypeKind.DOUBLE)
+ self.assertEqual(x_type.get_template_argument_type(3).kind, TypeKind.INVALID)
+
+ # Empty pack: only T=int, pack contributes 0.
+ y = get_cursor(tu, "y")
+ self.assertIsNotNone(y)
+ y_type = y.type.get_declaration()
+ self.assertEqual(y_type.get_num_template_arguments(), 1)
+
+ def test_get_num_template_arguments_method(self):
+ source = """
+ struct S {
+ template<typename U> void method(U) {}
+ };
+ void use() { S().method(42); }
+ """
+ tu = get_tu(source, lang="cpp")
+ methods = get_cursors(tu, "method")
+ call_expr = methods[1]
+ self.assertEqual(call_expr.kind, CursorKind.CALL_EXPR)
+ method_spec = call_expr.referenced
+ self.assertIsNotNone(method_spec)
+ self.assertEqual(method_spec.kind, CursorKind.CXX_METHOD)
+ self.assertEqual(method_spec.get_num_template_arguments(), 1)
+ self.assertEqual(method_spec.get_template_argument_type(0).kind, TypeKind.INT)
+
def test_referenced(self):
tu = get_tu("void foo(); void bar() { foo(); }")
foo = get_cursor(tu, "foo")
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 2da7175b51ea3..063d648972294 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -583,6 +583,10 @@ 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_Cursor_getNumTemplateParameters``, ``clang_Cursor_getTemplateParameter``,
+ ``clang_Cursor_isTemplateParameterPack``, and ``clang_Cursor_getTemplateArgumentIntegralType``.
+- Fixed ``clang_Cursor_getNumTemplateArguments`` and related APIs to work with
+ ``CXCursor_CXXMethod`` cursors and to correctly allow indexing into parameter pack arguments.
Code Completion
---------------
@@ -623,6 +627,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_num_template_parameters``, ``Cursor.get_template_parameter``,
+ ``Cursor.is_template_parameter_pack``, and ``Cursor.get_template_argument_integral_type``.
OpenMP Support
--------------
diff --git a/clang/include/clang-c/Index.h b/clang/include/clang-c/Index.h
index dcf1f4f1b4258..d2f4e6e10dacd 100644
--- a/clang/include/clang-c/Index.h
+++ b/clang/include/clang-c/Index.h
@@ -3267,7 +3267,29 @@ CINDEX_LINKAGE CXType clang_Cursor_getTemplateArgumentType(CXCursor C,
unsigned I);
/**
- * Retrieve the value of an Integral TemplateArgument (of a function
+ * Retrieve the type of an Integral TemplateArgument at a given index.
+ *
+ * For example, for:
+ * template <typename T, int N>
+ * void foo() {}
+ *
+ * template <>
+ * void foo<float, 42>();
+ *
+ * If called with I = 1, the type "int" will be returned (the type of the
+ * integral argument 42). An invalid type is returned if the argument at
+ * index I is not integral, or if the index is out of range.
+ *
+ * \param C a cursor representing a template specialization.
+ * \param I the zero-based index of the template argument.
+ *
+ * \returns the type of the integral template argument, or an invalid type.
+ */
+CINDEX_LINKAGE CXType clang_Cursor_getTemplateArgumentIntegralType(CXCursor C,
+ unsigned I);
+
+/**
+ * Retrieve the value of an Integral TemplateArgument (of a function or class
* decl representing a template specialization) as a signed long long.
*
* It is undefined to call this function on a CXCursor that does not represent a
@@ -3288,7 +3310,7 @@ CINDEX_LINKAGE long long clang_Cursor_getTemplateArgumentValue(CXCursor C,
unsigned I);
/**
- * Retrieve the value of an Integral TemplateArgument (of a function
+ * Retrieve the value of an Integral TemplateArgument (of a function or class
* decl representing a template specialization) as an unsigned long long.
*
* It is undefined to call this function on a CXCursor that does not represent a
@@ -3308,6 +3330,38 @@ CINDEX_LINKAGE long long clang_Cursor_getTemplateArgumentValue(CXCursor C,
CINDEX_LINKAGE unsigned long long
clang_Cursor_getTemplateArgumentUnsignedValue(CXCursor C, unsigned I);
+/**
+ * Retrieve the number of template parameters on a template declaration.
+ *
+ * \param C a cursor representing a class template or function template.
+ *
+ * \returns the number of template parameters, or -1 if the cursor does not
+ * represent a template.
+ */
+CINDEX_LINKAGE int clang_Cursor_getNumTemplateParameters(CXCursor C);
+
+/**
+ * Retrieve a template parameter at the given index.
+ *
+ * \param C a cursor representing a class template or function template.
+ * \param I the zero-based index of the template parameter.
+ *
+ * \returns a cursor for the template parameter, or a null cursor if the
+ * index is out of range or the cursor is not a template.
+ */
+CINDEX_LINKAGE CXCursor clang_Cursor_getTemplateParameter(CXCursor C,
+ unsigned I);
+
+/**
+ * Determine whether a template parameter is a parameter pack.
+ *
+ * \param C a cursor representing a template type parameter, non-type
+ * template parameter, or template template parameter.
+ *
+ * \returns non-zero if the template parameter is a parameter pack.
+ */
+CINDEX_LINKAGE unsigned clang_Cursor_isTemplateParameterPack(CXCursor C);
+
/**
* Determine whether two CXTypes represent the same type.
*
diff --git a/clang/test/Index/template-parameters.cpp b/clang/test/Index/template-parameters.cpp
new file mode 100644
index 0000000000000..b78e850de59e2
--- /dev/null
+++ b/clang/test/Index/template-parameters.cpp
@@ -0,0 +1,14 @@
+// Test template argument pack expansion.
+// RUN: c-index-test -test-load-source all -fno-delayed-template-parsing %s | FileCheck %s
+
+template<typename T, typename... Ts>
+struct Variadic {};
+
+template class Variadic<int, float, double>;
+template class Variadic<int>;
+
+// Pack with 3 args: should report 3 (not 2).
+// CHECK: StructDecl=Variadic:7:16 (Definition) [Specialization of Variadic:5:8] [Template arg 0: kind: 1, type: int] [Template arg 1: kind: 1, type: float] [Template arg 2: kind: 1, type: double]
+
+// Empty pack: should report 1 (just T, pack contributes 0).
+// CHECK: StructDecl=Variadic:8:16 (Definition) [Specialization of Variadic:5:8] [Template arg 0: kind: 1, type: int]
diff --git a/clang/tools/c-index-test/c-index-test.c b/clang/tools/c-index-test/c-index-test.c
index 4b3e105aa7aff..6f71c16f58deb 100644
--- a/clang/tools/c-index-test/c-index-test.c
+++ b/clang/tools/c-index-test/c-index-test.c
@@ -1054,10 +1054,11 @@ static void PrintCursor(CXCursor Cursor, const char *CommentSchemaFile) {
clang_getCString(Name), line, column);
clang_disposeString(Name);
- if (Cursor.kind == CXCursor_FunctionDecl
- || Cursor.kind == CXCursor_StructDecl
- || Cursor.kind == CXCursor_ClassDecl
- || Cursor.kind == CXCursor_ClassTemplatePartialSpecialization) {
+ if (Cursor.kind == CXCursor_FunctionDecl ||
+ Cursor.kind == CXCursor_CXXMethod ||
+ Cursor.kind == CXCursor_StructDecl ||
+ Cursor.kind == CXCursor_ClassDecl ||
+ Cursor.kind == CXCursor_ClassTemplatePartialSpecialization) {
/* Collect the template parameter kinds from the base template. */
int NumTemplateArgs = clang_Cursor_getNumTemplateArguments(Cursor);
int I;
diff --git a/clang/tools/libclang/CXCursor.cpp b/clang/tools/libclang/CXCursor.cpp
index d31d2c0c9bb67..a60b26590a724 100644
--- a/clang/tools/libclang/CXCursor.cpp
+++ b/clang/tools/libclang/CXCursor.cpp
@@ -1439,29 +1439,40 @@ CXCursor clang_Cursor_getArgument(CXCursor C, unsigned i) {
int clang_Cursor_getNumTemplateArguments(CXCursor C) {
CXCursorKind kind = clang_getCursorKind(C);
- if (kind != CXCursor_FunctionDecl && kind != CXCursor_StructDecl &&
- kind != CXCursor_ClassDecl &&
+ if (kind != CXCursor_FunctionDecl && kind != CXCursor_CXXMethod &&
+ kind != CXCursor_StructDecl && kind != CXCursor_ClassDecl &&
kind != CXCursor_ClassTemplatePartialSpecialization) {
return -1;
}
- if (const auto *FD =
- llvm::dyn_cast_if_present<clang::FunctionDecl>(getCursorDecl(C))) {
+ const TemplateArgumentList *TAL = nullptr;
+
+ if (const auto *FD = dyn_cast_if_present<FunctionDecl>(getCursorDecl(C))) {
const FunctionTemplateSpecializationInfo *SpecInfo =
FD->getTemplateSpecializationInfo();
if (!SpecInfo) {
return -1;
}
- return SpecInfo->TemplateArguments->size();
+ TAL = SpecInfo->TemplateArguments;
}
- if (const auto *SD =
- llvm::dyn_cast_if_present<clang::ClassTemplateSpecializationDecl>(
- getCursorDecl(C))) {
- return SD->getTemplateArgs().size();
+ if (!TAL) {
+ if (const auto *SD = dyn_cast_if_present<ClassTemplateSpecializationDecl>(
+ getCursorDecl(C))) {
+ TAL = &SD->getTemplateArgs();
+ }
}
- return -1;
+ if (!TAL)
+ return -1;
+
+ unsigned ArgCount = TAL->size();
+ for (unsigned i = 0; i < TAL->size(); i++) {
+ const TemplateArgument &Arg = TAL->get(i);
+ if (Arg.getKind() == TemplateArgument::Pack)
+ ArgCount += Arg.pack_size() - 1;
+ }
+ return ArgCount;
}
enum CXGetTemplateArgumentStatus {
@@ -1486,14 +1497,15 @@ enum CXGetTemplateArgumentStatus {
static int clang_Cursor_getTemplateArgument(CXCursor C, unsigned I,
TemplateArgument *TA) {
CXCursorKind kind = clang_getCursorKind(C);
- if (kind != CXCursor_FunctionDecl && kind != CXCursor_StructDecl &&
- kind != CXCursor_ClassDecl &&
+ if (kind != CXCursor_FunctionDecl && kind != CXCursor_CXXMethod &&
+ kind != CXCursor_StructDecl && kind != CXCursor_ClassDecl &&
kind != CXCursor_ClassTemplatePartialSpecialization) {
return -1;
}
- if (const auto *FD =
- llvm::dyn_cast_if_present<clang::FunctionDecl>(getCursorDecl(C))) {
+ const TemplateArgumentList *TAL = nullptr;
+
+ if (const auto *FD = dyn_cast_if_present<FunctionDecl>(getCursorDecl(C))) {
const FunctionTemplateSpecializationInfo *SpecInfo =
FD->getTemplateSpecializationInfo();
@@ -1501,26 +1513,38 @@ static int clang_Cursor_getTemplateArgument(CXCursor C, unsigned I,
return CXGetTemplateArgumentStatus_NullTemplSpecInfo;
}
- if (I >= SpecInfo->TemplateArguments->size()) {
- return CXGetTemplateArgumentStatus_InvalidIndex;
- }
-
- *TA = SpecInfo->TemplateArguments->get(I);
- return 0;
+ TAL = SpecInfo->TemplateArguments;
}
- if (const auto *SD =
- llvm::dyn_cast_if_present<clang::ClassTemplateSpecializationDecl>(
- getCursorDecl(C))) {
- if (I >= SD->getTemplateArgs().size()) {
- return CXGetTemplateArgumentStatus_InvalidIndex;
+ if (!TAL) {
+ if (const auto *SD = dyn_cast_if_present<ClassTemplateSpecializationDecl>(
+ getCursorDecl(C))) {
+ TAL = &SD->getTemplateArgs();
}
+ }
- *TA = SD->getTemplateArgs()[I];
- return 0;
+ if (!TAL)
+ return CXGetTemplateArgumentStatus_BadDeclCast;
+
+ unsigned current = 0;
+ for (unsigned i = 0; i < TAL->size(); i++) {
+ const auto &TACand = TAL->get(i);
+ if (TACand.getKind() == TemplateArgument::Pack) {
+ if (I < current + TACand.pack_size()) {
+ *TA = TACand.pack_elements()[I - current];
+ return 0;
+ }
+ current += TACand.pack_size();
+ continue;
+ }
+ if (current == I) {
+ *TA = TACand;
+ return 0;
+ }
+ current++;
}
- return CXGetTemplateArgumentStatus_BadDeclCast;
+ return CXGetTemplateArgumentStatus_InvalidIndex;
}
enum CXTemplateArgumentKind clang_Cursor_getTemplateArgumentKind(CXCursor C,
@@ -1604,6 +1628,61 @@ unsigned long long clang_Cursor_getTemplateArgumentUnsignedValue(CXCursor C,
return TA.getAsIntegral().getZExtValue();
}
+CXType clang_Cursor_getTemplateArgumentIntegralType(CXCursor C, unsigned I) {
+ TemplateArgument TA;
+ if (clang_Cursor_getTemplateArgument(C, I, &TA))
+ return cxtype::MakeCXType(QualType(), getCursorTU(C));
+
+ if (TA.getKind() != TemplateArgument::Integral)
+ return cxtype::MakeCXType(QualType(), getCursorTU(C));
+
+ return cxtype::MakeCXType(TA.getIntegralType(), getCursorTU(C));
+}
+
+int clang_Cursor_getNumTemplateParameters(CXCursor C) {
+ CXCursorKind kind = clang_getCursorKind(C);
+ if (kind != CXCursor_ClassTemplate && kind != CXCursor_FunctionTemplate)
+ return -1;
+
+ const Decl *D = getCursorDecl(C);
+ if (const auto *CTD = dyn_cast_if_present<ClassTemplateDecl>(D))
+ return CTD->getTemplateParameters()->size();
+ if (const auto *FTD = dyn_cast_if_present<FunctionTemplateDecl>(D))
+ return FTD->getTemplateParameters()->size();
+
+ return -1;
+}
+
+CXCursor clang_Cursor_getTemplateParameter(CXCursor C, unsigned I) {
+ CXCursorKind kind = clang_getCursorKind(C);
+ if (kind != CXCursor_ClassTemplate && kind != CXCursor_FunctionTemplate)
+ return clang_getNullCursor();
+
+ const Decl *D = getCursorDecl(C);
+ const TemplateParameterList *TPL = nullptr;
+ if (const auto *CTD = dyn_cast_if_present<ClassTemplateDecl>(D))
+ TPL = CTD->getTemplateParameters();
+ else if (const auto *FTD = dyn_cast_if_present<FunctionTemplateDecl>(D))
+ TPL = FTD->getTemplateParameters();
+
+ if (!TPL || I >= TPL->size())
+ return clang_getNullCursor();
+
+ const NamedDecl *ND = TPL->getParam(I);
+ return MakeCXCursor(ND, getCursorTU(C));
+}
+
+unsigned clang_Cursor_isTemplateParameterPack(CXCursor C) {
+ const Decl *D = getCursorDecl(C);
+ if (const auto *TTPD = dyn_cast_if_present<TemplateTypeParmDecl>(D))
+ return TTPD->isParameterPack();
+ if (const auto *NTTPD = dyn_cast_if_present<NonTypeTemplateParmDecl>(D))
+ return NTTPD->isParameterPack();
+ if (const auto *TTPD = dyn_cast_if_present<TemplateTemplateParmDecl>(D))
+ return TTPD->isParameterPack();
+ return 0;
+}
+
//===----------------------------------------------------------------------===//
// CXCursorSet.
//===----------------------------------------------------------------------===//
diff --git a/clang/tools/libclang/libclang.map b/clang/tools/libclang/libclang.map
index 2691602d432f6..e1fe6064dd730 100644
--- a/clang/tools/libclang/libclang.map
+++ b/clang/tools/libclang/libclang.map
@@ -460,6 +460,10 @@ LLVM_21 {
LLVM_23 {
global:
clang_ModuleCache_prune;
+ clang_Cursor_getNumTemplateParameters;
+ clang_Cursor_getTemplateParameter;
+ clang_Cursor_getTemplateArgumentIntegralType;
+ clang_Cursor_isTemplateParameterPack;
};
# Example of how to add a new symbol version entry. If you do add a new symbol
>From 48042ba8e3b7e21029ca7516b0566bf3599008ec Mon Sep 17 00:00:00 2001
From: Fabian Scheidl <fscheidl at apple.com>
Date: Tue, 7 Apr 2026 17:33:29 +0200
Subject: [PATCH 2/5] [libclang] Rename to getConstantTemplateArgumentType, add
variable template support
- Rename getIntegralType API to getConstantTemplateArgumentType
- Add variable template specialization support
- Fix Python null cursor handling
---
clang/bindings/python/clang/cindex.py | 32 ++++--
.../python/tests/cindex/test_cursor.py | 96 ++++++++++++++--
clang/bindings/python/tests/cindex/util.py | 3 +-
clang/docs/ReleaseNotes.rst | 33 +++++-
clang/include/clang-c/Index.h | 107 +++++++++++++-----
clang/lib/Sema/SemaCodeComplete.cpp | 5 +
clang/test/Index/template-parameters.cpp | 36 ++++++
clang/tools/c-index-test/c-index-test.c | 4 +-
clang/tools/libclang/CIndex.cpp | 4 +
clang/tools/libclang/CIndexCXX.cpp | 22 +++-
clang/tools/libclang/CXCursor.cpp | 86 ++++++++++----
clang/tools/libclang/libclang.map | 2 +-
12 files changed, 344 insertions(+), 86 deletions(-)
diff --git a/clang/bindings/python/clang/cindex.py b/clang/bindings/python/clang/cindex.py
index 83daebb1d18ab..39027d3bbc6f6 100644
--- a/clang/bindings/python/clang/cindex.py
+++ b/clang/bindings/python/clang/cindex.py
@@ -1575,6 +1575,10 @@ def is_unexposed(self):
FRIEND_DECL = 603
# A concept declaration
CONCEPT_DECL = 604
+ # A C++ variable template declaration
+ VAR_TEMPLATE = 605
+ # A C++ variable template partial specialization
+ VAR_TEMPLATE_PARTIAL_SPECIALIZATION = 606
# A code completion overload candidate.
OVERLOAD_CANDIDATE = 700
@@ -2278,24 +2282,32 @@ def get_template_argument_unsigned_value(self, num: int) -> int:
return conf.lib.clang_Cursor_getTemplateArgumentUnsignedValue(self, num) # type: ignore [no-any-return]
@cursor_null_guard
- def get_template_argument_integral_type(self, num: int) -> Type:
- """Returns the type of an integral template argument at the given index."""
+ def get_constant_template_argument_type(self, num: int) -> Type:
+ """Returns the type of a constant template argument at the given index.
+
+ Returns an invalid Type if the argument is not a constant template
+ argument, the index is out of range, or the cursor is not a template
+ specialization.
+ """
return Type.from_result(
- conf.lib.clang_Cursor_getTemplateArgumentIntegralType(self, num), self
+ conf.lib.clang_Cursor_getConstantTemplateArgumentType(self, num), self
)
@cursor_null_guard
def get_num_template_parameters(self) -> int:
- """Returns the number of template parameters, or -1 if not a template."""
+ """Returns the number of template parameters, or -1 if the cursor does
+ not represent a template.
+ """
return conf.lib.clang_Cursor_getNumTemplateParameters(self) # type: ignore [no-any-return]
@cursor_null_guard
- def get_template_parameter(self, num: int) -> Cursor:
- """Returns the template parameter at the given index.
-
- If the index is out of range, a null cursor is returned
+ def get_template_parameter(self, num: int) -> Cursor | None:
+ """Returns the template parameter at the given index, or None if the
+ index is out of range or the cursor is not a template.
"""
- return conf.lib.clang_Cursor_getTemplateParameter(self, num) # type: ignore [no-any-return]
+ return Cursor.from_cursor_result(
+ conf.lib.clang_Cursor_getTemplateParameter(self, num), self
+ )
@cursor_null_guard
def is_template_parameter_pack(self) -> bool:
@@ -4508,7 +4520,7 @@ def set_property(self, property, value):
("clang_Cursor_getTemplateArgumentType", [Cursor, c_uint], Type),
("clang_Cursor_getTemplateArgumentValue", [Cursor, c_uint], c_longlong),
("clang_Cursor_getTemplateArgumentUnsignedValue", [Cursor, c_uint], c_ulonglong),
- ("clang_Cursor_getTemplateArgumentIntegralType", [Cursor, c_uint], Type),
+ ("clang_Cursor_getConstantTemplateArgumentType", [Cursor, c_uint], Type),
("clang_Cursor_getNumTemplateParameters", [Cursor], c_int),
("clang_Cursor_getTemplateParameter", [Cursor, c_uint], Cursor),
("clang_Cursor_isTemplateParameterPack", [Cursor], c_uint),
diff --git a/clang/bindings/python/tests/cindex/test_cursor.py b/clang/bindings/python/tests/cindex/test_cursor.py
index 7304e2702205e..8773dacc3389f 100644
--- a/clang/bindings/python/tests/cindex/test_cursor.py
+++ b/clang/bindings/python/tests/cindex/test_cursor.py
@@ -916,22 +916,40 @@ def test_get_template_argument_unsigned_value(self):
self.assertEqual(foos[1].get_template_argument_unsigned_value(0), 2**32 - 7)
self.assertEqual(foos[1].get_template_argument_unsigned_value(2), True)
- def test_get_template_argument_integral_type(self):
- tu = get_tu(TEMPLATE_ARG_TEST, lang="cpp")
+ def test_get_constant_template_argument_type(self):
+ source = """
+ template<typename T, int N> void foo();
+ template<> void foo<float, -7>();
+ int g;
+ template<int* P> void bar();
+ template<> void bar<&g>();
+ template<int* Q> void baz();
+ template<> void baz<nullptr>();
+ template<float F> void bax();
+ template<> void bax<3.14f>();
+ """
+ tu = get_tu(source, lang="cpp", flags=["-std=c++20"])
foos = get_cursors(tu, "foo")
-
self.assertEqual(
- foos[1].get_template_argument_integral_type(0).kind, TypeKind.INT
+ foos[1].get_constant_template_argument_type(0).kind, TypeKind.INT
+ )
+ self.assertEqual(
+ foos[1].get_constant_template_argument_type(1).kind, TypeKind.INVALID
+ )
+ bars = get_cursors(tu, "bar")
+ self.assertEqual(
+ bars[1].get_constant_template_argument_type(0).kind, TypeKind.POINTER
)
+ bazs = get_cursors(tu, "baz")
self.assertEqual(
- foos[1].get_template_argument_integral_type(1).kind,
- TypeKind.INVALID,
+ bazs[1].get_constant_template_argument_type(0).kind, TypeKind.POINTER
)
+ baxs = get_cursors(tu, "bax")
self.assertEqual(
- foos[1].get_template_argument_integral_type(2).kind, TypeKind.BOOL
+ baxs[1].get_constant_template_argument_type(0).kind, TypeKind.FLOAT
)
- def test_get_template_argument_integral_type_pack(self):
+ def test_get_constant_template_argument_type_pack(self):
source = """
template<int... Ns> struct Foo {};
template class Foo<1, 2, 3>;
@@ -941,9 +959,9 @@ def test_get_template_argument_integral_type_pack(self):
spec = foos[1]
self.assertEqual(spec.kind, CursorKind.STRUCT_DECL)
self.assertEqual(spec.get_num_template_arguments(), 3)
- self.assertEqual(spec.get_template_argument_integral_type(0).kind, TypeKind.INT)
- self.assertEqual(spec.get_template_argument_integral_type(1).kind, TypeKind.INT)
- self.assertEqual(spec.get_template_argument_integral_type(2).kind, TypeKind.INT)
+ self.assertEqual(spec.get_constant_template_argument_type(0).kind, TypeKind.INT)
+ self.assertEqual(spec.get_constant_template_argument_type(1).kind, TypeKind.INT)
+ self.assertEqual(spec.get_constant_template_argument_type(2).kind, TypeKind.INT)
def test_get_num_template_parameters(self):
source = "template<typename T, int N> void foo(); template<typename... Ts> void bar(); void baz();"
@@ -972,7 +990,21 @@ def test_get_template_parameter(self):
self.assertEqual(n_param.kind, CursorKind.TEMPLATE_NON_TYPE_PARAMETER)
self.assertEqual(n_param.spelling, "N")
oob = foo.get_template_parameter(2)
- self.assertTrue(oob.is_null())
+ self.assertIsNone(oob)
+
+ def test_get_template_parameter_variable_template(self):
+ source = "template<typename T, int N> T val = T(N);"
+ tu = get_tu(source, lang="cpp")
+ val = get_cursor(tu, "val")
+ self.assertIsNotNone(val)
+ self.assertEqual(val.kind, CursorKind.VAR_TEMPLATE)
+ self.assertEqual(val.get_num_template_parameters(), 2)
+ t_param = val.get_template_parameter(0)
+ self.assertEqual(t_param.kind, CursorKind.TEMPLATE_TYPE_PARAMETER)
+ self.assertEqual(t_param.spelling, "T")
+ n_param = val.get_template_parameter(1)
+ self.assertEqual(n_param.kind, CursorKind.TEMPLATE_NON_TYPE_PARAMETER)
+ self.assertEqual(n_param.spelling, "N")
def test_is_template_parameter_pack(self):
# Type parameter pack
@@ -1037,6 +1069,46 @@ def test_get_num_template_arguments_method(self):
self.assertEqual(method_spec.get_num_template_arguments(), 1)
self.assertEqual(method_spec.get_template_argument_type(0).kind, TypeKind.INT)
+ def test_get_num_template_arguments_variable_template(self):
+ source = """
+ template<typename T> T pi = T(3.14);
+ template<> int pi<int> = 3;
+ """
+ tu = get_tu(source, lang="cpp")
+ pis = get_cursors(tu, "pi")
+ spec = pis[1]
+ self.assertEqual(spec.get_num_template_arguments(), 1)
+ self.assertEqual(spec.get_template_argument_type(0).kind, TypeKind.INT)
+
+ def test_get_num_template_arguments_variable_template_partial_spec(self):
+ source = """
+ template<typename T> T val = T(0);
+ template<typename U> U val<U*> = U(1);
+ """
+ tu = get_tu(source, lang="cpp")
+ vals = get_cursors(tu, "val")
+ partial = [
+ c for c in vals if c.kind == CursorKind.VAR_TEMPLATE_PARTIAL_SPECIALIZATION
+ ]
+ self.assertEqual(len(partial), 1)
+ self.assertEqual(partial[0].get_num_template_arguments(), 1)
+
+ def test_get_template_argument_kind_variable_template(self):
+ source = """
+ template<typename T, int N> T val = T(N);
+ template<> int val<int, 42> = 42;
+ """
+ tu = get_tu(source, lang="cpp")
+ vals = get_cursors(tu, "val")
+ spec = vals[1]
+ self.assertEqual(spec.get_num_template_arguments(), 2)
+ self.assertEqual(spec.get_template_argument_kind(0), TemplateArgumentKind.TYPE)
+ self.assertEqual(
+ spec.get_template_argument_kind(1), TemplateArgumentKind.INTEGRAL
+ )
+ self.assertEqual(spec.get_template_argument_value(1), 42)
+ self.assertEqual(spec.get_template_argument_unsigned_value(1), 42)
+
def test_referenced(self):
tu = get_tu("void foo(); void bar() { foo(); }")
foo = get_cursor(tu, "foo")
diff --git a/clang/bindings/python/tests/cindex/util.py b/clang/bindings/python/tests/cindex/util.py
index 5e66a9dc82c44..4f227c058e98e 100644
--- a/clang/bindings/python/tests/cindex/util.py
+++ b/clang/bindings/python/tests/cindex/util.py
@@ -18,7 +18,8 @@ def get_tu(source, lang="c", all_warnings=False, flags=[]):
name = "t.c"
if lang == "cpp":
name = "t.cpp"
- args.append("-std=c++11")
+ if not any(f.startswith("-std=") for f in args):
+ args.append("-std=c++11")
elif lang == "objc":
name = "t.m"
elif lang != "c":
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 063d648972294..f461ec0920f55 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -78,7 +78,14 @@ Clang Frontend Potentially Breaking Changes
libraries will need to be recompiled, or used with
(`--no-offload-new-driver`). This option will be removed in the next release.
-
+libclang Potentially Breaking Changes
+-------------------------------------
+- ``clang_Cursor_getNumTemplateArguments`` and related accessor APIs now
+ expand parameter pack arguments into individual arguments, instead of
+ reporting the pack as a single argument.
+- Variable template declarations and partial specializations now produce
+ ``CXCursor_VarTemplate`` and ``CXCursor_VarTemplatePartialSpecialization``
+ cursor kinds, respectively, instead of ``CXCursor_UnexposedDecl``.
Clang Python Bindings Potentially Breaking Changes
--------------------------------------------------
@@ -115,6 +122,12 @@ Clang Python Bindings Potentially Breaking Changes
``UnsavedFile`` is already available to use and existing uses should
be adapted to refer to it instead. ``_CXUnsavedFile`` will be removed in a
future release.
+- ``Cursor.get_num_template_arguments`` and related methods now expand
+ parameter pack arguments into individual arguments, instead of reporting
+ the pack as a single argument.
+- Variable template declarations and partial specializations now produce
+ ``CursorKind.VAR_TEMPLATE`` and ``CursorKind.VAR_TEMPLATE_PARTIAL_SPECIALIZATION``
+ cursor kinds, respectively, instead of ``CursorKind.UNEXPOSED_DECL``.
What's New in Clang |release|?
==============================
@@ -584,9 +597,19 @@ libclang
- Fix crash in clang_getBinaryOperatorKindSpelling and clang_getUnaryOperatorKindSpelling
- The clang_Module_getASTFile API is deprecated and now always returns nullptr
- Added ``clang_Cursor_getNumTemplateParameters``, ``clang_Cursor_getTemplateParameter``,
- ``clang_Cursor_isTemplateParameterPack``, and ``clang_Cursor_getTemplateArgumentIntegralType``.
-- Fixed ``clang_Cursor_getNumTemplateArguments`` and related APIs to work with
- ``CXCursor_CXXMethod`` cursors and to correctly allow indexing into parameter pack arguments.
+ ``clang_Cursor_isTemplateParameterPack``, and ``clang_Cursor_getConstantTemplateArgumentType``.
+- Added ``CXCursor_VarTemplate`` and ``CXCursor_VarTemplatePartialSpecialization`` cursor kinds
+ for variable template declarations and partial specializations, which were previously
+ reported as ``CXCursor_UnexposedDecl``.
+- Extended ``clang_Cursor_getNumTemplateArguments``, ``clang_Cursor_getTemplateArgumentKind``,
+ ``clang_Cursor_getTemplateArgumentType``, ``clang_Cursor_getTemplateArgumentValue``, and
+ ``clang_Cursor_getTemplateArgumentUnsignedValue`` to work with variable template
+ specializations (``CXCursor_VarDecl``) and partial specializations
+ (``CXCursor_VarTemplatePartialSpecialization``).
+- Extended ``clang_Cursor_getNumTemplateParameters`` and ``clang_Cursor_getTemplateParameter``
+ to work with variable templates (``CXCursor_VarTemplate``).
+- Fixed ``clang_Cursor_getNumTemplateArguments`` and related APIs to transparently expand
+ parameter pack arguments and to work with ``CXCursor_CXXMethod`` cursors.
Code Completion
---------------
@@ -628,7 +651,7 @@ Python Binding Changes
- 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_num_template_parameters``, ``Cursor.get_template_parameter``,
- ``Cursor.is_template_parameter_pack``, and ``Cursor.get_template_argument_integral_type``.
+ ``Cursor.is_template_parameter_pack``, and ``Cursor.get_constant_template_argument_type``.
OpenMP Support
--------------
diff --git a/clang/include/clang-c/Index.h b/clang/include/clang-c/Index.h
index d2f4e6e10dacd..041f2ec4a3b20 100644
--- a/clang/include/clang-c/Index.h
+++ b/clang/include/clang-c/Index.h
@@ -2310,9 +2310,17 @@ enum CXCursorKind {
* a concept declaration.
*/
CXCursor_ConceptDecl = 604,
+ /**
+ * a C++ variable template declaration.
+ */
+ CXCursor_VarTemplate = 605,
+ /**
+ * a C++ variable template partial specialization.
+ */
+ CXCursor_VarTemplatePartialSpecialization = 606,
CXCursor_FirstExtraDecl = CXCursor_ModuleImportDecl,
- CXCursor_LastExtraDecl = CXCursor_ConceptDecl,
+ CXCursor_LastExtraDecl = CXCursor_VarTemplatePartialSpecialization,
/**
* A code completion overload candidate.
@@ -3208,11 +3216,11 @@ enum CXTemplateArgumentKind {
};
/**
- * Returns the number of template args of a function, struct, or class decl
- * representing a template specialization.
+ * Returns the number of template args of a function, struct, class, or
+ * variable decl representing a template specialization.
*
- * If the argument cursor cannot be converted into a template function
- * declaration, -1 is returned.
+ * If the argument cursor does not represent a template specialization,
+ * -1 is returned.
*
* For example, for the following declaration and specialization:
* template <typename T, int kInt, bool kBool>
@@ -3222,14 +3230,24 @@ enum CXTemplateArgumentKind {
* void foo<float, -7, true>();
*
* The value 3 would be returned from this call.
+ *
+ * Parameter pack arguments are expanded. For example:
+ * template <typename T, typename... Ts>
+ * struct Bar {};
+ *
+ * Bar<int, float, double> bar;
+ *
+ * The value 3 would be returned (not 2), and each argument is individually
+ * accessible via \c clang_Cursor_getTemplateArgumentKind and related APIs.
*/
CINDEX_LINKAGE int clang_Cursor_getNumTemplateArguments(CXCursor C);
/**
* Retrieve the kind of the I'th template argument of the CXCursor C.
*
- * If the argument CXCursor does not represent a FunctionDecl, StructDecl, or
- * ClassTemplatePartialSpecialization, an invalid template argument kind is
+ * If the argument CXCursor does not represent a FunctionDecl, CXXMethod,
+ * StructDecl, ClassDecl, ClassTemplatePartialSpecialization, VarDecl, or
+ * VarTemplatePartialSpecialization, an invalid template argument kind is
* returned.
*
* For example, for the following declaration and specialization:
@@ -3247,11 +3265,13 @@ clang_Cursor_getTemplateArgumentKind(CXCursor C, unsigned I);
/**
* Retrieve a CXType representing the type of a TemplateArgument of a
- * function decl representing a template specialization.
+ * function, struct, class, or variable decl representing a template
+ * specialization.
*
- * If the argument CXCursor does not represent a FunctionDecl, StructDecl,
- * ClassDecl or ClassTemplatePartialSpecialization whose I'th template argument
- * has a kind of CXTemplateArgKind_Integral, an invalid type is returned.
+ * If the argument CXCursor does not represent a FunctionDecl, CXXMethod,
+ * StructDecl, ClassDecl, ClassTemplatePartialSpecialization, VarDecl, or
+ * VarTemplatePartialSpecialization whose I'th template argument has a kind of
+ * CXTemplateArgKind_Type, an invalid type is returned.
*
* For example, for the following declaration and specialization:
* template <typename T, int kInt, bool kBool>
@@ -3260,16 +3280,20 @@ clang_Cursor_getTemplateArgumentKind(CXCursor C, unsigned I);
* template <>
* void foo<float, -7, true>();
*
- * If called with I = 0, "float", will be returned.
+ * If called with I = 0, "float" will be returned.
* Invalid types will be returned for I == 1 or 2.
*/
CINDEX_LINKAGE CXType clang_Cursor_getTemplateArgumentType(CXCursor C,
unsigned I);
/**
- * Retrieve the type of an Integral TemplateArgument at a given index.
+ * Retrieve the type of a constant template argument at a given index.
*
- * For example, for:
+ * If the argument CXCursor does not represent a FunctionDecl, CXXMethod,
+ * StructDecl, ClassDecl, ClassTemplatePartialSpecialization, VarDecl, or
+ * VarTemplatePartialSpecialization, an invalid type is returned.
+ *
+ * For example, for the following declaration and specialization:
* template <typename T, int N>
* void foo() {}
*
@@ -3277,24 +3301,38 @@ CINDEX_LINKAGE CXType clang_Cursor_getTemplateArgumentType(CXCursor C,
* void foo<float, 42>();
*
* If called with I = 1, the type "int" will be returned (the type of the
- * integral argument 42). An invalid type is returned if the argument at
- * index I is not integral, or if the index is out of range.
+ * constant template argument 42). An invalid type is returned if the argument
+ * at the given index is not a constant template argument, or if the index is
+ * out of range.
+ *
+ * Note that this returns the type of the argument after conversion to the
+ * parameter type. For example:
+ * template <short S>
+ * void bar() {}
+ *
+ * template <>
+ * void bar<12>();
+ *
+ * If called with I = 0, "short" will be returned, not "int".
*
* \param C a cursor representing a template specialization.
* \param I the zero-based index of the template argument.
*
- * \returns the type of the integral template argument, or an invalid type.
+ * \returns the type of the constant template argument, or an invalid type.
*/
-CINDEX_LINKAGE CXType clang_Cursor_getTemplateArgumentIntegralType(CXCursor C,
+CINDEX_LINKAGE CXType clang_Cursor_getConstantTemplateArgumentType(CXCursor C,
unsigned I);
/**
- * Retrieve the value of an Integral TemplateArgument (of a function or class
- * decl representing a template specialization) as a signed long long.
+ * Retrieve the value of an Integral TemplateArgument (of a function, struct,
+ * class, or variable decl representing a template specialization) as a signed
+ * long long.
*
* It is undefined to call this function on a CXCursor that does not represent a
- * FunctionDecl, StructDecl, ClassDecl or ClassTemplatePartialSpecialization
- * whose I'th template argument is not an integral value.
+ * FunctionDecl, CXXMethod, StructDecl, ClassDecl,
+ * ClassTemplatePartialSpecialization, VarDecl, or
+ * VarTemplatePartialSpecialization whose I'th template argument is
+ * not an integral value.
*
* For example, for the following declaration and specialization:
* template <typename T, int kInt, bool kBool>
@@ -3310,12 +3348,15 @@ CINDEX_LINKAGE long long clang_Cursor_getTemplateArgumentValue(CXCursor C,
unsigned I);
/**
- * Retrieve the value of an Integral TemplateArgument (of a function or class
- * decl representing a template specialization) as an unsigned long long.
+ * Retrieve the value of an Integral TemplateArgument (of a function, struct,
+ * class, or variable decl representing a template specialization) as an
+ * unsigned long long.
*
* It is undefined to call this function on a CXCursor that does not represent a
- * FunctionDecl, StructDecl, ClassDecl or ClassTemplatePartialSpecialization or
- * whose I'th template argument is not an integral value.
+ * FunctionDecl, CXXMethod, StructDecl, ClassDecl,
+ * ClassTemplatePartialSpecialization, VarDecl, or
+ * VarTemplatePartialSpecialization whose I'th template argument is
+ * not an integral value.
*
* For example, for the following declaration and specialization:
* template <typename T, int kInt, bool kBool>
@@ -3333,7 +3374,17 @@ clang_Cursor_getTemplateArgumentUnsignedValue(CXCursor C, unsigned I);
/**
* Retrieve the number of template parameters on a template declaration.
*
- * \param C a cursor representing a class template or function template.
+ * A parameter pack counts as a single template parameter. Use
+ * \c clang_Cursor_isTemplateParameterPack to check whether a specific
+ * parameter is a pack.
+ *
+ * For example:
+ * template <typename T, typename... Ts>
+ * struct Foo {};
+ *
+ * The value 2 would be returned (T and Ts).
+ *
+ * \param C a cursor representing a class, function, or variable template.
*
* \returns the number of template parameters, or -1 if the cursor does not
* represent a template.
@@ -3343,7 +3394,7 @@ CINDEX_LINKAGE int clang_Cursor_getNumTemplateParameters(CXCursor C);
/**
* Retrieve a template parameter at the given index.
*
- * \param C a cursor representing a class template or function template.
+ * \param C a cursor representing a class, function, or variable template.
* \param I the zero-based index of the template parameter.
*
* \returns a cursor for the template parameter, or a null cursor if the
diff --git a/clang/lib/Sema/SemaCodeComplete.cpp b/clang/lib/Sema/SemaCodeComplete.cpp
index 0cd9819dc2964..1fac84d4d3de6 100644
--- a/clang/lib/Sema/SemaCodeComplete.cpp
+++ b/clang/lib/Sema/SemaCodeComplete.cpp
@@ -4429,7 +4429,10 @@ CXCursorKind clang::getCursorKindForDecl(const Decl *D) {
case Decl::TypeAliasTemplate:
return CXCursor_TypeAliasTemplateDecl;
case Decl::Var:
+ case Decl::VarTemplateSpecialization:
return CXCursor_VarDecl;
+ case Decl::VarTemplatePartialSpecialization:
+ return CXCursor_VarTemplatePartialSpecialization;
case Decl::Namespace:
return CXCursor_Namespace;
case Decl::NamespaceAlias:
@@ -4444,6 +4447,8 @@ CXCursorKind clang::getCursorKindForDecl(const Decl *D) {
return CXCursor_FunctionTemplate;
case Decl::ClassTemplate:
return CXCursor_ClassTemplate;
+ case Decl::VarTemplate:
+ return CXCursor_VarTemplate;
case Decl::AccessSpec:
return CXCursor_CXXAccessSpecifier;
case Decl::ClassTemplatePartialSpecialization:
diff --git a/clang/test/Index/template-parameters.cpp b/clang/test/Index/template-parameters.cpp
index b78e850de59e2..ac48929612c90 100644
--- a/clang/test/Index/template-parameters.cpp
+++ b/clang/test/Index/template-parameters.cpp
@@ -12,3 +12,39 @@ template class Variadic<int>;
// Empty pack: should report 1 (just T, pack contributes 0).
// CHECK: StructDecl=Variadic:8:16 (Definition) [Specialization of Variadic:5:8] [Template arg 0: kind: 1, type: int]
+
+template<typename T>
+T pi = T(3.14);
+
+template<>
+int pi<int> = 3;
+
+// Variable template declaration: should produce VarTemplate cursor.
+// CHECK: VarTemplate=pi:17:3 (Definition)
+
+// Variable template specialization: should report 1 argument.
+// CHECK: VarDecl=pi:20:5 (Definition) [Specialization of pi:17:3] [Template arg 0: kind: 1, type: int]
+
+template<typename T>
+T tau = T(6.28);
+
+template<>
+float tau<float> = 6.28f;
+
+template<typename U>
+U tau<U*> = U(0);
+
+// Variable template explicit specialization.
+// CHECK: VarDecl=tau:32:7 (Definition) [Specialization of tau:29:3] [Template arg 0: kind: 1, type: float]
+
+// Variable template partial specialization.
+// CHECK: VarTemplatePartialSpecialization=tau:35:3 (Definition) [Specialization of tau:29:3] [Template arg 0: kind: 1, type: type-parameter-0-0 *]
+
+template<int N>
+int ival = N;
+
+template<>
+int ival<42> = 42;
+
+// Variable template with NTTP: should report integral argument.
+// CHECK: VarDecl=ival:47:5 (Definition) [Specialization of ival:44:5] [Template arg 0: kind: 4, intval: 42]
diff --git a/clang/tools/c-index-test/c-index-test.c b/clang/tools/c-index-test/c-index-test.c
index 6f71c16f58deb..5b1d9bd645c94 100644
--- a/clang/tools/c-index-test/c-index-test.c
+++ b/clang/tools/c-index-test/c-index-test.c
@@ -1058,7 +1058,9 @@ static void PrintCursor(CXCursor Cursor, const char *CommentSchemaFile) {
Cursor.kind == CXCursor_CXXMethod ||
Cursor.kind == CXCursor_StructDecl ||
Cursor.kind == CXCursor_ClassDecl ||
- Cursor.kind == CXCursor_ClassTemplatePartialSpecialization) {
+ Cursor.kind == CXCursor_ClassTemplatePartialSpecialization ||
+ Cursor.kind == CXCursor_VarDecl ||
+ Cursor.kind == CXCursor_VarTemplatePartialSpecialization) {
/* Collect the template parameter kinds from the base template. */
int NumTemplateArgs = clang_Cursor_getNumTemplateArguments(Cursor);
int I;
diff --git a/clang/tools/libclang/CIndex.cpp b/clang/tools/libclang/CIndex.cpp
index 3ee37ed2dfc27..4fa9a30b500c6 100644
--- a/clang/tools/libclang/CIndex.cpp
+++ b/clang/tools/libclang/CIndex.cpp
@@ -6481,6 +6481,10 @@ CXString clang_getCursorKindSpelling(enum CXCursorKind Kind) {
return cxstring::createRef("attribute(aligned)");
case CXCursor_ConceptDecl:
return cxstring::createRef("ConceptDecl");
+ case CXCursor_VarTemplate:
+ return cxstring::createRef("VarTemplate");
+ case CXCursor_VarTemplatePartialSpecialization:
+ return cxstring::createRef("VarTemplatePartialSpecialization");
case CXCursor_OpenACCComputeConstruct:
return cxstring::createRef("OpenACCComputeConstruct");
case CXCursor_OpenACCLoopConstruct:
diff --git a/clang/tools/libclang/CIndexCXX.cpp b/clang/tools/libclang/CIndexCXX.cpp
index 605f3ef9a4a62..2ee4189b51109 100644
--- a/clang/tools/libclang/CIndexCXX.cpp
+++ b/clang/tools/libclang/CIndexCXX.cpp
@@ -104,8 +104,9 @@ enum CXCursorKind clang_getTemplateCursorKind(CXCursor C) {
using namespace clang::cxcursor;
switch (C.kind) {
- case CXCursor_ClassTemplate:
+ case CXCursor_ClassTemplate:
case CXCursor_FunctionTemplate:
+ case CXCursor_VarTemplate:
if (const TemplateDecl *Template
= dyn_cast_or_null<TemplateDecl>(getCursorDecl(C)))
return MakeCXCursor(Template->getTemplatedDecl(), getCursorTU(C)).kind;
@@ -128,7 +129,10 @@ enum CXCursorKind clang_getTemplateCursorKind(CXCursor C) {
}
}
break;
-
+
+ case CXCursor_VarTemplatePartialSpecialization:
+ return CXCursor_VarDecl;
+
default:
break;
}
@@ -166,7 +170,19 @@ CXCursor clang_getSpecializedCursorTemplate(CXCursor C) {
if (!Template)
Template = Function->getInstantiatedFromMemberFunction();
} else if (const VarDecl *Var = dyn_cast<VarDecl>(D)) {
- if (Var->isStaticDataMember())
+ if (const VarTemplatePartialSpecializationDecl *PartialSpec
+ = dyn_cast<VarTemplatePartialSpecializationDecl>(Var))
+ Template = PartialSpec->getSpecializedTemplate();
+ else if (const VarTemplateSpecializationDecl *VTSpec
+ = dyn_cast<VarTemplateSpecializationDecl>(Var)) {
+ llvm::PointerUnion<VarTemplateDecl *,
+ VarTemplatePartialSpecializationDecl *> Result
+ = VTSpec->getSpecializedTemplateOrPartial();
+ if (isa<VarTemplateDecl *>(Result))
+ Template = cast<VarTemplateDecl *>(Result);
+ else
+ Template = cast<VarTemplatePartialSpecializationDecl *>(Result);
+ } else if (Var->isStaticDataMember())
Template = Var->getInstantiatedFromStaticDataMember();
} else if (const RedeclarableTemplateDecl *Tmpl
= dyn_cast<RedeclarableTemplateDecl>(D))
diff --git a/clang/tools/libclang/CXCursor.cpp b/clang/tools/libclang/CXCursor.cpp
index a60b26590a724..58df654472cb2 100644
--- a/clang/tools/libclang/CXCursor.cpp
+++ b/clang/tools/libclang/CXCursor.cpp
@@ -1441,13 +1441,16 @@ int clang_Cursor_getNumTemplateArguments(CXCursor C) {
CXCursorKind kind = clang_getCursorKind(C);
if (kind != CXCursor_FunctionDecl && kind != CXCursor_CXXMethod &&
kind != CXCursor_StructDecl && kind != CXCursor_ClassDecl &&
- kind != CXCursor_ClassTemplatePartialSpecialization) {
+ kind != CXCursor_ClassTemplatePartialSpecialization &&
+ kind != CXCursor_VarDecl &&
+ kind != CXCursor_VarTemplatePartialSpecialization) {
return -1;
}
const TemplateArgumentList *TAL = nullptr;
+ const Decl *D = getCursorDecl(C);
- if (const auto *FD = dyn_cast_if_present<FunctionDecl>(getCursorDecl(C))) {
+ if (const auto *FD = dyn_cast_if_present<FunctionDecl>(D)) {
const FunctionTemplateSpecializationInfo *SpecInfo =
FD->getTemplateSpecializationInfo();
if (!SpecInfo) {
@@ -1457,12 +1460,19 @@ int clang_Cursor_getNumTemplateArguments(CXCursor C) {
}
if (!TAL) {
- if (const auto *SD = dyn_cast_if_present<ClassTemplateSpecializationDecl>(
- getCursorDecl(C))) {
+ if (const auto *SD =
+ dyn_cast_if_present<ClassTemplateSpecializationDecl>(D)) {
TAL = &SD->getTemplateArgs();
}
}
+ if (!TAL) {
+ if (const auto *VD =
+ dyn_cast_if_present<VarTemplateSpecializationDecl>(D)) {
+ TAL = &VD->getTemplateArgs();
+ }
+ }
+
if (!TAL)
return -1;
@@ -1479,12 +1489,12 @@ enum CXGetTemplateArgumentStatus {
/** The operation completed successfully */
CXGetTemplateArgumentStatus_Success = 0,
- /** The specified cursor did not represent a FunctionDecl or
- ClassTemplateSpecializationDecl. */
+ /** The specified cursor did not represent a FunctionDecl,
+ ClassTemplateSpecializationDecl, or VarTemplateSpecializationDecl. */
CXGetTemplateArgumentStatus_CursorNotCompatibleDecl = -1,
- /** The specified cursor was not castable to a FunctionDecl or
- ClassTemplateSpecializationDecl. */
+ /** The specified cursor was not castable to a FunctionDecl,
+ ClassTemplateSpecializationDecl, or VarTemplateSpecializationDecl. */
CXGetTemplateArgumentStatus_BadDeclCast = -2,
/** A NULL FunctionTemplateSpecializationInfo was retrieved. */
@@ -1499,13 +1509,16 @@ static int clang_Cursor_getTemplateArgument(CXCursor C, unsigned I,
CXCursorKind kind = clang_getCursorKind(C);
if (kind != CXCursor_FunctionDecl && kind != CXCursor_CXXMethod &&
kind != CXCursor_StructDecl && kind != CXCursor_ClassDecl &&
- kind != CXCursor_ClassTemplatePartialSpecialization) {
+ kind != CXCursor_ClassTemplatePartialSpecialization &&
+ kind != CXCursor_VarDecl &&
+ kind != CXCursor_VarTemplatePartialSpecialization) {
return -1;
}
const TemplateArgumentList *TAL = nullptr;
+ const Decl *D = getCursorDecl(C);
- if (const auto *FD = dyn_cast_if_present<FunctionDecl>(getCursorDecl(C))) {
+ if (const auto *FD = dyn_cast_if_present<FunctionDecl>(D)) {
const FunctionTemplateSpecializationInfo *SpecInfo =
FD->getTemplateSpecializationInfo();
@@ -1517,12 +1530,19 @@ static int clang_Cursor_getTemplateArgument(CXCursor C, unsigned I,
}
if (!TAL) {
- if (const auto *SD = dyn_cast_if_present<ClassTemplateSpecializationDecl>(
- getCursorDecl(C))) {
+ if (const auto *SD =
+ dyn_cast_if_present<ClassTemplateSpecializationDecl>(D)) {
TAL = &SD->getTemplateArgs();
}
}
+ if (!TAL) {
+ if (const auto *VD =
+ dyn_cast_if_present<VarTemplateSpecializationDecl>(D)) {
+ TAL = &VD->getTemplateArgs();
+ }
+ }
+
if (!TAL)
return CXGetTemplateArgumentStatus_BadDeclCast;
@@ -1595,6 +1615,27 @@ CXType clang_Cursor_getTemplateArgumentType(CXCursor C, unsigned I) {
return cxtype::MakeCXType(TA.getAsType(), getCursorTU(C));
}
+CXType clang_Cursor_getConstantTemplateArgumentType(CXCursor C, unsigned I) {
+ TemplateArgument TA;
+ if (clang_Cursor_getTemplateArgument(C, I, &TA) !=
+ CXGetTemplateArgumentStatus_Success) {
+ return cxtype::MakeCXType(QualType(), getCursorTU(C));
+ }
+
+ switch (TA.getKind()) {
+ case TemplateArgument::Integral:
+ return cxtype::MakeCXType(TA.getIntegralType(), getCursorTU(C));
+ case TemplateArgument::Declaration:
+ return cxtype::MakeCXType(TA.getParamTypeForDecl(), getCursorTU(C));
+ case TemplateArgument::NullPtr:
+ return cxtype::MakeCXType(TA.getNullPtrType(), getCursorTU(C));
+ case TemplateArgument::StructuralValue:
+ return cxtype::MakeCXType(TA.getStructuralValueType(), getCursorTU(C));
+ default:
+ return cxtype::MakeCXType(QualType(), getCursorTU(C));
+ }
+}
+
long long clang_Cursor_getTemplateArgumentValue(CXCursor C, unsigned I) {
TemplateArgument TA;
if (clang_Cursor_getTemplateArgument(C, I, &TA) !=
@@ -1628,20 +1669,10 @@ unsigned long long clang_Cursor_getTemplateArgumentUnsignedValue(CXCursor C,
return TA.getAsIntegral().getZExtValue();
}
-CXType clang_Cursor_getTemplateArgumentIntegralType(CXCursor C, unsigned I) {
- TemplateArgument TA;
- if (clang_Cursor_getTemplateArgument(C, I, &TA))
- return cxtype::MakeCXType(QualType(), getCursorTU(C));
-
- if (TA.getKind() != TemplateArgument::Integral)
- return cxtype::MakeCXType(QualType(), getCursorTU(C));
-
- return cxtype::MakeCXType(TA.getIntegralType(), getCursorTU(C));
-}
-
int clang_Cursor_getNumTemplateParameters(CXCursor C) {
CXCursorKind kind = clang_getCursorKind(C);
- if (kind != CXCursor_ClassTemplate && kind != CXCursor_FunctionTemplate)
+ if (kind != CXCursor_ClassTemplate && kind != CXCursor_FunctionTemplate &&
+ kind != CXCursor_VarTemplate)
return -1;
const Decl *D = getCursorDecl(C);
@@ -1649,13 +1680,16 @@ int clang_Cursor_getNumTemplateParameters(CXCursor C) {
return CTD->getTemplateParameters()->size();
if (const auto *FTD = dyn_cast_if_present<FunctionTemplateDecl>(D))
return FTD->getTemplateParameters()->size();
+ if (const auto *VTD = dyn_cast_if_present<VarTemplateDecl>(D))
+ return VTD->getTemplateParameters()->size();
return -1;
}
CXCursor clang_Cursor_getTemplateParameter(CXCursor C, unsigned I) {
CXCursorKind kind = clang_getCursorKind(C);
- if (kind != CXCursor_ClassTemplate && kind != CXCursor_FunctionTemplate)
+ if (kind != CXCursor_ClassTemplate && kind != CXCursor_FunctionTemplate &&
+ kind != CXCursor_VarTemplate)
return clang_getNullCursor();
const Decl *D = getCursorDecl(C);
@@ -1664,6 +1698,8 @@ CXCursor clang_Cursor_getTemplateParameter(CXCursor C, unsigned I) {
TPL = CTD->getTemplateParameters();
else if (const auto *FTD = dyn_cast_if_present<FunctionTemplateDecl>(D))
TPL = FTD->getTemplateParameters();
+ else if (const auto *VTD = dyn_cast_if_present<VarTemplateDecl>(D))
+ TPL = VTD->getTemplateParameters();
if (!TPL || I >= TPL->size())
return clang_getNullCursor();
diff --git a/clang/tools/libclang/libclang.map b/clang/tools/libclang/libclang.map
index e1fe6064dd730..7f837fa3051c7 100644
--- a/clang/tools/libclang/libclang.map
+++ b/clang/tools/libclang/libclang.map
@@ -462,7 +462,7 @@ LLVM_23 {
clang_ModuleCache_prune;
clang_Cursor_getNumTemplateParameters;
clang_Cursor_getTemplateParameter;
- clang_Cursor_getTemplateArgumentIntegralType;
+ clang_Cursor_getConstantTemplateArgumentType;
clang_Cursor_isTemplateParameterPack;
};
>From ce1697aef2284374ff480b10af40ca3670f88042 Mon Sep 17 00:00:00 2001
From: Fabian Scheidl <fscheidl at apple.com>
Date: Wed, 8 Apr 2026 10:49:05 +0200
Subject: [PATCH 3/5] [libclang] Improve getConstantTemplateArgumentType docs
and tests
---
.../python/tests/cindex/test_cursor.py | 18 +++++++++++++--
clang/include/clang-c/Index.h | 23 +++++++------------
clang/tools/libclang/CIndexCXX.cpp | 12 +++++-----
3 files changed, 30 insertions(+), 23 deletions(-)
diff --git a/clang/bindings/python/tests/cindex/test_cursor.py b/clang/bindings/python/tests/cindex/test_cursor.py
index 8773dacc3389f..8f863f3fdfb7d 100644
--- a/clang/bindings/python/tests/cindex/test_cursor.py
+++ b/clang/bindings/python/tests/cindex/test_cursor.py
@@ -927,14 +927,20 @@ def test_get_constant_template_argument_type(self):
template<> void baz<nullptr>();
template<float F> void bax();
template<> void bax<3.14f>();
+ int v[5];
+ template<int b[5]> void arr();
+ template<> void arr<v>();
+ void handler(int);
+ template<void f(int)> void func();
+ template<> void func<handler>();
"""
tu = get_tu(source, lang="cpp", flags=["-std=c++20"])
foos = get_cursors(tu, "foo")
self.assertEqual(
- foos[1].get_constant_template_argument_type(0).kind, TypeKind.INT
+ foos[1].get_constant_template_argument_type(0).kind, TypeKind.INVALID
)
self.assertEqual(
- foos[1].get_constant_template_argument_type(1).kind, TypeKind.INVALID
+ foos[1].get_constant_template_argument_type(1).kind, TypeKind.INT
)
bars = get_cursors(tu, "bar")
self.assertEqual(
@@ -948,6 +954,14 @@ def test_get_constant_template_argument_type(self):
self.assertEqual(
baxs[1].get_constant_template_argument_type(0).kind, TypeKind.FLOAT
)
+ arrs = get_cursors(tu, "arr")
+ self.assertEqual(
+ arrs[1].get_constant_template_argument_type(0).kind, TypeKind.POINTER
+ )
+ funcs = get_cursors(tu, "func")
+ self.assertEqual(
+ funcs[1].get_constant_template_argument_type(0).kind, TypeKind.POINTER
+ )
def test_get_constant_template_argument_type_pack(self):
source = """
diff --git a/clang/include/clang-c/Index.h b/clang/include/clang-c/Index.h
index 041f2ec4a3b20..f7f0fe6b74e81 100644
--- a/clang/include/clang-c/Index.h
+++ b/clang/include/clang-c/Index.h
@@ -3293,27 +3293,20 @@ CINDEX_LINKAGE CXType clang_Cursor_getTemplateArgumentType(CXCursor C,
* StructDecl, ClassDecl, ClassTemplatePartialSpecialization, VarDecl, or
* VarTemplatePartialSpecialization, an invalid type is returned.
*
- * For example, for the following declaration and specialization:
- * template <typename T, int N>
+ * The returned type reflects conversion to the parameter type. For example:
+ * template <typename T, short S>
* void foo() {}
*
* template <>
* void foo<float, 42>();
*
- * If called with I = 1, the type "int" will be returned (the type of the
- * constant template argument 42). An invalid type is returned if the argument
- * at the given index is not a constant template argument, or if the index is
- * out of range.
- *
- * Note that this returns the type of the argument after conversion to the
- * parameter type. For example:
- * template <short S>
- * void bar() {}
- *
- * template <>
- * void bar<12>();
+ * If called with I = 0, an invalid type is returned (T is not a constant
+ * template argument). If called with I = 1, "short" is returned, not "int".
+ * Similarly, a parameter declared as \c int[5] will yield a pointer type
+ * after adjustment.
*
- * If called with I = 0, "short" will be returned, not "int".
+ * For expanded parameter pack arguments, the type of the pack parameter is
+ * returned for each expanded argument.
*
* \param C a cursor representing a template specialization.
* \param I the zero-based index of the template argument.
diff --git a/clang/tools/libclang/CIndexCXX.cpp b/clang/tools/libclang/CIndexCXX.cpp
index 2ee4189b51109..b4c03fc9705d6 100644
--- a/clang/tools/libclang/CIndexCXX.cpp
+++ b/clang/tools/libclang/CIndexCXX.cpp
@@ -170,14 +170,14 @@ CXCursor clang_getSpecializedCursorTemplate(CXCursor C) {
if (!Template)
Template = Function->getInstantiatedFromMemberFunction();
} else if (const VarDecl *Var = dyn_cast<VarDecl>(D)) {
- if (const VarTemplatePartialSpecializationDecl *PartialSpec
- = dyn_cast<VarTemplatePartialSpecializationDecl>(Var))
+ if (const VarTemplatePartialSpecializationDecl *PartialSpec =
+ dyn_cast<VarTemplatePartialSpecializationDecl>(Var))
Template = PartialSpec->getSpecializedTemplate();
- else if (const VarTemplateSpecializationDecl *VTSpec
- = dyn_cast<VarTemplateSpecializationDecl>(Var)) {
+ else if (const VarTemplateSpecializationDecl *VTSpec =
+ dyn_cast<VarTemplateSpecializationDecl>(Var)) {
llvm::PointerUnion<VarTemplateDecl *,
- VarTemplatePartialSpecializationDecl *> Result
- = VTSpec->getSpecializedTemplateOrPartial();
+ VarTemplatePartialSpecializationDecl *>
+ Result = VTSpec->getSpecializedTemplateOrPartial();
if (isa<VarTemplateDecl *>(Result))
Template = cast<VarTemplateDecl *>(Result);
else
>From 8af876461b1545c3d80da722c3ac9f44a42083fd Mon Sep 17 00:00:00 2001
From: Fabian Scheidl <fscheidl at apple.com>
Date: Wed, 8 Apr 2026 16:45:22 +0200
Subject: [PATCH 4/5] [libclang] Fix coding style
---
clang/tools/libclang/CXCursor.cpp | 36 ++++++++++++-------------------
1 file changed, 14 insertions(+), 22 deletions(-)
diff --git a/clang/tools/libclang/CXCursor.cpp b/clang/tools/libclang/CXCursor.cpp
index 58df654472cb2..fcb1576577fd3 100644
--- a/clang/tools/libclang/CXCursor.cpp
+++ b/clang/tools/libclang/CXCursor.cpp
@@ -1463,12 +1463,8 @@ int clang_Cursor_getNumTemplateArguments(CXCursor C) {
if (const auto *SD =
dyn_cast_if_present<ClassTemplateSpecializationDecl>(D)) {
TAL = &SD->getTemplateArgs();
- }
- }
-
- if (!TAL) {
- if (const auto *VD =
- dyn_cast_if_present<VarTemplateSpecializationDecl>(D)) {
+ } else if (const auto *VD =
+ dyn_cast_if_present<VarTemplateSpecializationDecl>(D)) {
TAL = &VD->getTemplateArgs();
}
}
@@ -1477,8 +1473,8 @@ int clang_Cursor_getNumTemplateArguments(CXCursor C) {
return -1;
unsigned ArgCount = TAL->size();
- for (unsigned i = 0; i < TAL->size(); i++) {
- const TemplateArgument &Arg = TAL->get(i);
+ for (unsigned I = 0; I < TAL->size(); ++I) {
+ const TemplateArgument &Arg = TAL->get(I);
if (Arg.getKind() == TemplateArgument::Pack)
ArgCount += Arg.pack_size() - 1;
}
@@ -1533,12 +1529,8 @@ static int clang_Cursor_getTemplateArgument(CXCursor C, unsigned I,
if (const auto *SD =
dyn_cast_if_present<ClassTemplateSpecializationDecl>(D)) {
TAL = &SD->getTemplateArgs();
- }
- }
-
- if (!TAL) {
- if (const auto *VD =
- dyn_cast_if_present<VarTemplateSpecializationDecl>(D)) {
+ } else if (const auto *VD =
+ dyn_cast_if_present<VarTemplateSpecializationDecl>(D)) {
TAL = &VD->getTemplateArgs();
}
}
@@ -1546,22 +1538,22 @@ static int clang_Cursor_getTemplateArgument(CXCursor C, unsigned I,
if (!TAL)
return CXGetTemplateArgumentStatus_BadDeclCast;
- unsigned current = 0;
- for (unsigned i = 0; i < TAL->size(); i++) {
- const auto &TACand = TAL->get(i);
+ unsigned Current = 0;
+ for (unsigned J = 0; J < TAL->size(); ++J) {
+ const auto &TACand = TAL->get(J);
if (TACand.getKind() == TemplateArgument::Pack) {
- if (I < current + TACand.pack_size()) {
- *TA = TACand.pack_elements()[I - current];
+ if (I < Current + TACand.pack_size()) {
+ *TA = TACand.pack_elements()[I - Current];
return 0;
}
- current += TACand.pack_size();
+ Current += TACand.pack_size();
continue;
}
- if (current == I) {
+ if (Current == I) {
*TA = TACand;
return 0;
}
- current++;
+ Current++;
}
return CXGetTemplateArgumentStatus_InvalidIndex;
>From ffec0f7e7a03e6d339ac8fe1418e0aec58a7421f Mon Sep 17 00:00:00 2001
From: Fabian Scheidl <fscheidl at apple.com>
Date: Tue, 14 Apr 2026 08:53:01 +0200
Subject: [PATCH 5/5] Clean up python tests
---
.../python/tests/cindex/test_cursor.py | 29 ++++++++++---------
1 file changed, 15 insertions(+), 14 deletions(-)
diff --git a/clang/bindings/python/tests/cindex/test_cursor.py b/clang/bindings/python/tests/cindex/test_cursor.py
index 8f863f3fdfb7d..379fdd60b19f1 100644
--- a/clang/bindings/python/tests/cindex/test_cursor.py
+++ b/clang/bindings/python/tests/cindex/test_cursor.py
@@ -1021,24 +1021,25 @@ def test_get_template_parameter_variable_template(self):
self.assertEqual(n_param.spelling, "N")
def test_is_template_parameter_pack(self):
- # Type parameter pack
- source = "template<typename T, typename... Ts> void foo();"
+ source = """
+ template<typename T, typename... Ts> void foo();
+ template<int... Ns> void bar();
+ template<template<typename> class... Ts> void baz();
+ """
tu = get_tu(source, lang="cpp")
+
+ # Type parameter pack
foo = get_cursor(tu, "foo")
self.assertIsNotNone(foo)
self.assertFalse(foo.get_template_parameter(0).is_template_parameter_pack())
self.assertTrue(foo.get_template_parameter(1).is_template_parameter_pack())
# Non-type parameter pack
- source = "template<int... Ns> void bar();"
- tu = get_tu(source, lang="cpp")
bar = get_cursor(tu, "bar")
self.assertIsNotNone(bar)
self.assertTrue(bar.get_template_parameter(0).is_template_parameter_pack())
# Template template parameter pack
- source = "template<template<typename> class... Ts> void baz();"
- tu = get_tu(source, lang="cpp")
baz = get_cursor(tu, "baz")
self.assertIsNotNone(baz)
self.assertTrue(baz.get_template_parameter(0).is_template_parameter_pack())
@@ -1053,18 +1054,18 @@ def test_get_template_argument_variadic(self):
x = get_cursor(tu, "x")
self.assertIsNotNone(x)
- x_type = x.type.get_declaration()
- self.assertEqual(x_type.get_num_template_arguments(), 3)
- self.assertEqual(x_type.get_template_argument_type(0).kind, TypeKind.INT)
- self.assertEqual(x_type.get_template_argument_type(1).kind, TypeKind.FLOAT)
- self.assertEqual(x_type.get_template_argument_type(2).kind, TypeKind.DOUBLE)
- self.assertEqual(x_type.get_template_argument_type(3).kind, TypeKind.INVALID)
+ x_decl = x.type.get_declaration()
+ self.assertEqual(x_decl.get_num_template_arguments(), 3)
+ self.assertEqual(x_decl.get_template_argument_type(0).kind, TypeKind.INT)
+ self.assertEqual(x_decl.get_template_argument_type(1).kind, TypeKind.FLOAT)
+ self.assertEqual(x_decl.get_template_argument_type(2).kind, TypeKind.DOUBLE)
+ self.assertEqual(x_decl.get_template_argument_type(3).kind, TypeKind.INVALID)
# Empty pack: only T=int, pack contributes 0.
y = get_cursor(tu, "y")
self.assertIsNotNone(y)
- y_type = y.type.get_declaration()
- self.assertEqual(y_type.get_num_template_arguments(), 1)
+ y_decl = y.type.get_declaration()
+ self.assertEqual(y_decl.get_num_template_arguments(), 1)
def test_get_num_template_arguments_method(self):
source = """
More information about the cfe-commits
mailing list