https://github.com/fscheidl updated https://github.com/llvm/llvm-project/pull/183504
>From d357e2d674700d972e8cc5ecd1cdb007ae220eb8 Mon Sep 17 00:00:00 2001 From: Fabian Scheidl <[email protected]> Date: Thu, 26 Feb 2026 12:53:14 +0100 Subject: [PATCH] [[libclang] Add functions to query template parameters and fix parameter pack handling --- clang/bindings/python/clang/cindex.py | 26 ++++ .../python/tests/cindex/test_cursor.py | 135 ++++++++++++++++++ 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 | 8 ++ 8 files changed, 357 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 1896a0a9c1c34..9207fd0727063 100644 --- a/clang/bindings/python/clang/cindex.py +++ b/clang/bindings/python/clang/cindex.py @@ -2272,6 +2272,28 @@ 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.""" + 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.""" @@ -4427,6 +4449,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..4adfc244f2582 100644 --- a/clang/bindings/python/tests/cindex/test_cursor.py +++ b/clang/bindings/python/tests/cindex/test_cursor.py @@ -916,6 +916,141 @@ 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") + foo = get_cursor(tu, "Foo") + # Find the specialization. + spec = None + for c in tu.cursor.walk_preorder(): + if c.kind == CursorKind.STRUCT_DECL and c.spelling == "Foo": + if c.get_num_template_arguments() >= 0: + spec = c + break + self.assertIsNotNone(spec) + 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 = conf.lib.clang_Cursor_getTemplateParameter(foo, 5) + 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") + # Find a call to S::method<int> and get the referenced specialization. + method_spec = None + for c in tu.cursor.walk_preorder(): + if c.kind == CursorKind.CALL_EXPR and c.spelling == "method": + method_spec = c.referenced + break + 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 cb1010aee1edd..e6286ab41fde8 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -430,6 +430,10 @@ libclang - Visit constraints of `auto` type to properly visit concept usages (#GH166580) - Visit switch initializer statements (https://bugs.kde.org/show_bug.cgi?id=415537#c2) - Fix crash in clang_getBinaryOperatorKindSpelling and clang_getUnaryOperatorKindSpelling +- 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 --------------- @@ -468,6 +472,8 @@ Python Binding Changes ``CodeCompletionResults.results`` should be changed to directly use ``CodeCompletionResults``: it nows supports ``__len__`` and ``__getitem__``, so it can be used the same as ``CodeCompletionResults.results``. +- 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 203634c80d82a..8d04932f57fcf 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 cb3245756a394..e6e78c8d5ab37 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 17f485e5c78a5..1f39fdc66a5cf 100644 --- a/clang/tools/libclang/CXCursor.cpp +++ b/clang/tools/libclang/CXCursor.cpp @@ -1438,29 +1438,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 { @@ -1485,14 +1496,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(); @@ -1500,26 +1512,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, @@ -1603,6 +1627,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 3d9d2e268a611..1196ba4cd408d 100644 --- a/clang/tools/libclang/libclang.map +++ b/clang/tools/libclang/libclang.map @@ -457,6 +457,14 @@ LLVM_21 { clang_Cursor_isGCCAssemblyVolatile; }; +LLVM_23 { + global: + 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 # version, please update the example to depend on the version you added. # LLVM_X { _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
