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 <[email protected]> Date: Sun, 1 Mar 2026 19:01:25 +0100 Subject: [PATCH 1/3] [[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 <[email protected]> Date: Tue, 7 Apr 2026 17:33:29 +0200 Subject: [PATCH 2/3] [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 <[email protected]> Date: Wed, 8 Apr 2026 10:49:05 +0200 Subject: [PATCH 3/3] [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 _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
