[Lldb-commits] [lldb] b856755 - [lldb] Make the ClassTemplateDecl merging logic in TypeSystemClang respect template parameters

Raphael Isemann via lldb-commits lldb-commits at lists.llvm.org
Tue Jun 15 10:25:26 PDT 2021


Author: Raphael Isemann
Date: 2021-06-15T19:25:07+02:00
New Revision: b8567559cf3872d0cc2a0ed24a171da8b1ff400f

URL: https://github.com/llvm/llvm-project/commit/b8567559cf3872d0cc2a0ed24a171da8b1ff400f
DIFF: https://github.com/llvm/llvm-project/commit/b8567559cf3872d0cc2a0ed24a171da8b1ff400f.diff

LOG: [lldb] Make the ClassTemplateDecl merging logic in TypeSystemClang respect template parameters

DWARF doesn't describe templates itself but only actual template instantiations.
Because of that LLDB has to infer the parameters of the class template
declarations from the actual instantiations when creating the internal Clang AST
from debug info

Because there is no dedicated DIE for the class template, LLDB also creates the
`ClassTemplateDecl` implicitly when parsing a template instantiation. To avoid
creating one ClassTemplateDecls for every instantiation,
`TypeSystemClang::CreateClassTemplateDecl` will check if there is already a
`ClassTemplateDecl` in the requested `DeclContext` and will reuse a found
fitting declaration.

The logic that checks if a found class template fits to an instantiation is
currently just comparing the name of the template. So right now we map
`template<typename T> struct S;` to an instantiation with the values `S<1, 2,
3>` even though they clearly don't belong together.

This causes crashes later on when for example the Itanium mangler's
`TemplateArgManglingInfo::needExactType` method tries to find fitting the class
template parameter that fits to an instantiation value. In the example above it
will try to find the parameter for the value `2` but will just trigger a
boundary check when retrieving the parameter with index 1 from the class
template.

There are two ways we can end up with an instantiation that doesn't fit to a
class template with the same name:

1. We have two TUs with two templates that have the same name and internal
   linkage.
2. A forward declared template instantiation is emitted by GCC and Clang
   without an empty list of parameter values.

This patch makes the check for whether a class template declaration can be
reused more sophisticated by also comparing whether the parameter values can fit
to the found class template. If we can't find a fitting class template we
justcreate a second class template with the fitting parameters.

Fixes rdar://76592821

Reviewed By: kastiglione

Differential Revision: https://reviews.llvm.org/D100662

Added: 
    lldb/test/API/lang/cpp/forward-declared-template-specialization/Makefile
    lldb/test/API/lang/cpp/forward-declared-template-specialization/TestCppForwardDeclaredTemplateSpecialization.py
    lldb/test/API/lang/cpp/forward-declared-template-specialization/main.cpp
    lldb/test/API/lang/cpp/incompatible-class-templates/Makefile
    lldb/test/API/lang/cpp/incompatible-class-templates/TestCppIncompatibleClassTemplates.py
    lldb/test/API/lang/cpp/incompatible-class-templates/main.cpp
    lldb/test/API/lang/cpp/incompatible-class-templates/other.cpp

Modified: 
    lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp
    lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.h
    lldb/unittests/Symbol/TestTypeSystemClang.cpp

Removed: 
    


################################################################################
diff  --git a/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp b/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp
index 3ffc5d2870b8a..306573e06250e 100644
--- a/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp
+++ b/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp
@@ -1357,9 +1357,11 @@ CompilerType TypeSystemClang::CreateRecordType(
 }
 
 namespace {
-  bool IsValueParam(const clang::TemplateArgument &argument) {
-    return argument.getKind() == TemplateArgument::Integral;
-  }
+/// Returns true iff the given TemplateArgument should be represented as an
+/// NonTypeTemplateParmDecl in the AST.
+bool IsValueParam(const clang::TemplateArgument &argument) {
+  return argument.getKind() == TemplateArgument::Integral;
+}
 }
 
 static TemplateParameterList *CreateTemplateParameterList(
@@ -1463,6 +1465,99 @@ void TypeSystemClang::CreateFunctionTemplateSpecializationInfo(
                                                template_args_ptr, nullptr);
 }
 
+/// Returns true if the given template parameter can represent the given value.
+/// For example, `typename T` can represent `int` but not integral values such
+/// as `int I = 3`.
+static bool TemplateParameterAllowsValue(NamedDecl *param,
+                                         const TemplateArgument &value) {
+  if (auto *type_param = llvm::dyn_cast<TemplateTypeParmDecl>(param)) {
+    // Compare the argument kind, i.e. ensure that <typename> != <int>.
+    if (value.getKind() != TemplateArgument::Type)
+      return false;
+  } else if (auto *type_param =
+                 llvm::dyn_cast<NonTypeTemplateParmDecl>(param)) {
+    // Compare the argument kind, i.e. ensure that <typename> != <int>.
+    if (!IsValueParam(value))
+      return false;
+    // Compare the integral type, i.e. ensure that <int> != <char>.
+    if (type_param->getType() != value.getIntegralType())
+      return false;
+  } else {
+    // There is no way to create other parameter decls at the moment, so we
+    // can't reach this case during normal LLDB usage. Log that this happened
+    // and assert.
+    Log *log = lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_EXPRESSIONS);
+    LLDB_LOG(log,
+             "Don't know how to compare template parameter to passed"
+             " value. Decl kind of parameter is: {0}",
+             param->getDeclKindName());
+    lldbassert(false && "Can't compare this TemplateParmDecl subclass");
+    // In release builds just fall back to marking the parameter as not
+    // accepting the value so that we don't try to fit an instantiation to a
+    // template that doesn't fit. E.g., avoid that `S<1>` is being connected to
+    // `template<typename T> struct S;`.
+    return false;
+  }
+  return true;
+}
+
+/// Returns true if the given class template declaration could produce an
+/// instantiation with the specified values.
+/// For example, `<typename T>` allows the arguments `float`, but not for
+/// example `bool, float` or `3` (as an integer parameter value).
+static bool ClassTemplateAllowsToInstantiationArgs(
+    ClassTemplateDecl *class_template_decl,
+    const TypeSystemClang::TemplateParameterInfos &instantiation_values) {
+
+  TemplateParameterList &params = *class_template_decl->getTemplateParameters();
+
+  // Save some work by iterating only once over the found parameters and
+  // calculate the information related to parameter packs.
+
+  // Contains the first pack parameter (or non if there are none).
+  llvm::Optional<NamedDecl *> pack_parameter;
+  // Contains the number of non-pack parameters.
+  size_t non_pack_params = params.size();
+  for (size_t i = 0; i < params.size(); ++i) {
+    NamedDecl *param = params.getParam(i);
+    if (param->isParameterPack()) {
+      pack_parameter = param;
+      non_pack_params = i;
+      break;
+    }
+  }
+
+  // The found template needs to have compatible non-pack template arguments.
+  // E.g., ensure that <typename, typename> != <typename>.
+  // The pack parameters are compared later.
+  if (non_pack_params != instantiation_values.args.size())
+    return false;
+
+  // Ensure that <typename...> != <typename>.
+  if (pack_parameter.hasValue() != instantiation_values.hasParameterPack())
+    return false;
+
+  // Compare the first pack parameter that was found with the first pack
+  // parameter value. The special case of having an empty parameter pack value
+  // always fits to a pack parameter.
+  // E.g., ensure that <int...> != <typename...>.
+  if (pack_parameter && !instantiation_values.packed_args->args.empty() &&
+      !TemplateParameterAllowsValue(
+          *pack_parameter, instantiation_values.packed_args->args.front()))
+    return false;
+
+  // Compare all the non-pack parameters now.
+  // E.g., ensure that <int> != <long>.
+  for (const auto pair : llvm::zip_first(instantiation_values.args, params)) {
+    const TemplateArgument &passed_arg = std::get<0>(pair);
+    NamedDecl *found_param = std::get<1>(pair);
+    if (!TemplateParameterAllowsValue(found_param, passed_arg))
+      return false;
+  }
+
+  return class_template_decl;
+}
+
 ClassTemplateDecl *TypeSystemClang::CreateClassTemplateDecl(
     DeclContext *decl_ctx, OptionalClangModuleID owning_module,
     lldb::AccessType access_type, const char *class_name, int kind,
@@ -1476,12 +1571,22 @@ ClassTemplateDecl *TypeSystemClang::CreateClassTemplateDecl(
   IdentifierInfo &identifier_info = ast.Idents.get(class_name);
   DeclarationName decl_name(&identifier_info);
 
+  // Search the AST for an existing ClassTemplateDecl that could be reused.
   clang::DeclContext::lookup_result result = decl_ctx->lookup(decl_name);
-
   for (NamedDecl *decl : result) {
     class_template_decl = dyn_cast<clang::ClassTemplateDecl>(decl);
-    if (class_template_decl)
-      return class_template_decl;
+    if (!class_template_decl)
+      continue;
+    // The class template has to be able to represents the instantiation
+    // values we received. Without this we might end up putting an instantiation
+    // with arguments such as <int, int> to a template such as:
+    //     template<typename T> struct S;
+    // Connecting the instantiation to an incompatible template could cause
+    // problems later on.
+    if (!ClassTemplateAllowsToInstantiationArgs(class_template_decl,
+                                                template_param_infos))
+      continue;
+    return class_template_decl;
   }
 
   llvm::SmallVector<NamedDecl *, 8> template_param_decls;

diff  --git a/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.h b/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.h
index 1a64a557e0a20..7c056cc7a8e76 100644
--- a/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.h
+++ b/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.h
@@ -328,6 +328,8 @@ class TypeSystemClang : public TypeSystem {
         (!packed_args || !packed_args->packed_args);
     }
 
+    bool hasParameterPack() const { return static_cast<bool>(packed_args); }
+
     llvm::SmallVector<const char *, 2> names;
     llvm::SmallVector<clang::TemplateArgument, 2> args;
     

diff  --git a/lldb/test/API/lang/cpp/forward-declared-template-specialization/Makefile b/lldb/test/API/lang/cpp/forward-declared-template-specialization/Makefile
new file mode 100644
index 0000000000000..99998b20bcb05
--- /dev/null
+++ b/lldb/test/API/lang/cpp/forward-declared-template-specialization/Makefile
@@ -0,0 +1,3 @@
+CXX_SOURCES := main.cpp
+
+include Makefile.rules

diff  --git a/lldb/test/API/lang/cpp/forward-declared-template-specialization/TestCppForwardDeclaredTemplateSpecialization.py b/lldb/test/API/lang/cpp/forward-declared-template-specialization/TestCppForwardDeclaredTemplateSpecialization.py
new file mode 100644
index 0000000000000..86d2bffca98fc
--- /dev/null
+++ b/lldb/test/API/lang/cpp/forward-declared-template-specialization/TestCppForwardDeclaredTemplateSpecialization.py
@@ -0,0 +1,19 @@
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+class TestCase(TestBase):
+
+    mydir = TestBase.compute_mydir(__file__)
+
+    def test(self):
+        """
+        Tests a forward declared template and a normal template in the same
+        executable. GCC/Clang emit very limited debug information for forward
+        declared templates that might trip up LLDB.
+        """
+        self.build()
+        lldbutil.run_to_source_breakpoint(self, "// break here", lldb.SBFileSpec("main.cpp"))
+
+        self.expect_expr("a; b", result_type="Temp<float>")

diff  --git a/lldb/test/API/lang/cpp/forward-declared-template-specialization/main.cpp b/lldb/test/API/lang/cpp/forward-declared-template-specialization/main.cpp
new file mode 100644
index 0000000000000..c6566d3c052e1
--- /dev/null
+++ b/lldb/test/API/lang/cpp/forward-declared-template-specialization/main.cpp
@@ -0,0 +1,16 @@
+// Forward declare a template and a specialization;
+template <typename T> class Temp;
+template <> class Temp<int>;
+
+// Force that debug informatin for the specialization is emitted.
+// Clang and GCC will create debug information that lacks any description
+// of the template argument 'int'.
+Temp<int> *a;
+
+// Define the template and create an implicit instantiation.
+template <typename T> class Temp { int f; };
+Temp<float> b;
+
+int main() {
+  return 0; // break here
+}

diff  --git a/lldb/test/API/lang/cpp/incompatible-class-templates/Makefile b/lldb/test/API/lang/cpp/incompatible-class-templates/Makefile
new file mode 100644
index 0000000000000..020dce7c31d11
--- /dev/null
+++ b/lldb/test/API/lang/cpp/incompatible-class-templates/Makefile
@@ -0,0 +1,3 @@
+CXX_SOURCES := main.cpp other.cpp
+
+include Makefile.rules

diff  --git a/lldb/test/API/lang/cpp/incompatible-class-templates/TestCppIncompatibleClassTemplates.py b/lldb/test/API/lang/cpp/incompatible-class-templates/TestCppIncompatibleClassTemplates.py
new file mode 100644
index 0000000000000..61f87040bd0dc
--- /dev/null
+++ b/lldb/test/API/lang/cpp/incompatible-class-templates/TestCppIncompatibleClassTemplates.py
@@ -0,0 +1,19 @@
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+class TestCase(TestBase):
+
+    mydir = TestBase.compute_mydir(__file__)
+
+    def test(self):
+        """
+        Test debugging a binary that has two templates with the same name
+        but 
diff erent template parameters.
+        """
+        self.build()
+        lldbutil.run_to_source_breakpoint(self, "// break here", lldb.SBFileSpec("main.cpp"))
+
+        # Try using both templates in the same expression. This shouldn't crash.
+        self.expect_expr("Template1.x + Template2.x", result_type="int")

diff  --git a/lldb/test/API/lang/cpp/incompatible-class-templates/main.cpp b/lldb/test/API/lang/cpp/incompatible-class-templates/main.cpp
new file mode 100644
index 0000000000000..e90b96c1dbe2c
--- /dev/null
+++ b/lldb/test/API/lang/cpp/incompatible-class-templates/main.cpp
@@ -0,0 +1,11 @@
+int other();
+
+namespace {
+template <typename T1> struct Temp { int x; };
+// This emits the 'Temp' template in this TU.
+Temp<float> Template1;
+} // namespace
+
+int main() {
+  return Template1.x + other(); // break here
+}

diff  --git a/lldb/test/API/lang/cpp/incompatible-class-templates/other.cpp b/lldb/test/API/lang/cpp/incompatible-class-templates/other.cpp
new file mode 100644
index 0000000000000..b1f903def1325
--- /dev/null
+++ b/lldb/test/API/lang/cpp/incompatible-class-templates/other.cpp
@@ -0,0 +1,7 @@
+namespace {
+template <typename T1, typename T2> struct Temp { int x; };
+// This emits the 'Temp' template from this TU.
+Temp<int, float> Template2;
+} // namespace
+
+int other() { return Template2.x; }

diff  --git a/lldb/unittests/Symbol/TestTypeSystemClang.cpp b/lldb/unittests/Symbol/TestTypeSystemClang.cpp
index 44e72f37d3f01..10f59147dd1b1 100644
--- a/lldb/unittests/Symbol/TestTypeSystemClang.cpp
+++ b/lldb/unittests/Symbol/TestTypeSystemClang.cpp
@@ -515,6 +515,187 @@ TEST_F(TestTypeSystemClang, TemplateArguments) {
   }
 }
 
+class TestCreateClassTemplateDecl : public TestTypeSystemClang {
+protected:
+  /// The class templates created so far by the Expect* functions below.
+  llvm::DenseSet<ClassTemplateDecl *> m_created_templates;
+
+  /// Utility function for creating a class template.
+  ClassTemplateDecl *
+  CreateClassTemplate(const TypeSystemClang::TemplateParameterInfos &infos) {
+    ClassTemplateDecl *decl = m_ast->CreateClassTemplateDecl(
+        m_ast->GetTranslationUnitDecl(), OptionalClangModuleID(), eAccessPublic,
+        "foo", TTK_Struct, infos);
+    return decl;
+  }
+
+  /// Creates a new class template with the given template parameters.
+  /// Asserts that a new ClassTemplateDecl is created.
+  /// \param description The gtest scope string that should describe the input.
+  /// \param infos The template parameters that the class template should have.
+  /// \returns The created ClassTemplateDecl.
+  ClassTemplateDecl *
+  ExpectNewTemplate(std::string description,
+                    const TypeSystemClang::TemplateParameterInfos &infos) {
+    SCOPED_TRACE(description);
+    ClassTemplateDecl *first_template = CreateClassTemplate(infos);
+    // A new template should have been created.
+    EXPECT_FALSE(m_created_templates.contains(first_template))
+        << "Didn't create new class template but reused this existing decl:\n"
+        << ClangUtil::DumpDecl(first_template);
+    m_created_templates.insert(first_template);
+
+    // Creating a new template with the same arguments should always return
+    // the template created above.
+    ClassTemplateDecl *second_template = CreateClassTemplate(infos);
+    EXPECT_EQ(first_template, second_template)
+        << "Second attempt to create class template didn't reuse first decl:\n"
+        << ClangUtil::DumpDecl(first_template) << "\nInstead created/reused:\n"
+        << ClangUtil::DumpDecl(second_template);
+    return first_template;
+  }
+
+  /// Tries to create a new class template but asserts that an existing class
+  /// template in the current AST is reused (in contract so a new class
+  /// template being created).
+  /// \param description The gtest scope string that should describe the input.
+  /// \param infos The template parameters that the class template should have.
+  void
+  ExpectReusedTemplate(std::string description,
+                       const TypeSystemClang::TemplateParameterInfos &infos,
+                       ClassTemplateDecl *expected) {
+    SCOPED_TRACE(description);
+    ClassTemplateDecl *td = CreateClassTemplate(infos);
+    EXPECT_EQ(td, expected)
+        << "Created/reused class template is:\n"
+        << ClangUtil::DumpDecl(td) << "\nExpected to reuse:\n"
+        << ClangUtil::DumpDecl(expected);
+  }
+};
+
+TEST_F(TestCreateClassTemplateDecl, FindExistingTemplates) {
+  // This tests the logic in TypeSystemClang::CreateClassTemplateDecl that
+  // decides whether an existing ClassTemplateDecl in the AST can be reused.
+  // The behaviour should follow the C++ rules for redeclaring templates
+  // (e.g., parameter names can be changed/omitted.)
+
+  // This describes a class template *instantiation* from which we will infer
+  // the structure of the class template.
+  TypeSystemClang::TemplateParameterInfos infos;
+
+  // Test an empty template parameter list: <>
+  ExpectNewTemplate("<>", infos);
+
+  // Test that <typename T> with T = int creates a new template.
+  infos.names = {"T"};
+  infos.args = {TemplateArgument(m_ast->getASTContext().IntTy)};
+  ClassTemplateDecl *single_type_arg = ExpectNewTemplate("<typename T>", infos);
+
+  // Test that changing the parameter name doesn't create a new class template.
+  infos.names = {"A"};
+  ExpectReusedTemplate("<typename A> (A = int)", infos, single_type_arg);
+
+  // Test that changing the used type doesn't create a new class template.
+  infos.args = {TemplateArgument(m_ast->getASTContext().FloatTy)};
+  ExpectReusedTemplate("<typename A> (A = float)", infos, single_type_arg);
+
+  // Test that <typename A, signed char I> creates a new template with A = int
+  // and I = 47;
+  infos.names.push_back("I");
+  infos.args.push_back(TemplateArgument(m_ast->getASTContext(),
+                                        llvm::APSInt(llvm::APInt(8, 47)),
+                                        m_ast->getASTContext().SignedCharTy));
+  ClassTemplateDecl *type_and_char_value =
+      ExpectNewTemplate("<typename A, signed char I> (I = 47)", infos);
+
+  // Change the value of the I parameter to 123. The previously created
+  // class template should still be reused.
+  infos.args.pop_back();
+  infos.args.push_back(TemplateArgument(m_ast->getASTContext(),
+                                        llvm::APSInt(llvm::APInt(8, 123)),
+                                        m_ast->getASTContext().SignedCharTy));
+  ExpectReusedTemplate("<typename A, signed char I> (I = 123)", infos,
+                       type_and_char_value);
+
+  // Change the type of the I parameter to int so we have <typename A, int I>.
+  // The class template from above can't be reused.
+  infos.args.pop_back();
+  infos.args.push_back(TemplateArgument(m_ast->getASTContext(),
+                                        llvm::APSInt(llvm::APInt(32, 47)),
+                                        m_ast->getASTContext().IntTy));
+  ExpectNewTemplate("<typename A, int I> (I = 123)", infos);
+
+  // Test a second type parameter will also cause a new template to be created.
+  // We now have <typename A, int I, typename B>.
+  infos.names.push_back("B");
+  infos.args.push_back(TemplateArgument(m_ast->getASTContext().IntTy));
+  ClassTemplateDecl *type_and_char_value_and_type =
+      ExpectNewTemplate("<typename A, int I, typename B>", infos);
+
+  // Remove all the names from the parameters which shouldn't influence the
+  // way the templates get merged.
+  infos.names = {"", "", ""};
+  ExpectReusedTemplate("<typename, int, typename>", infos,
+                       type_and_char_value_and_type);
+}
+
+TEST_F(TestCreateClassTemplateDecl, FindExistingTemplatesWithParameterPack) {
+  // The same as FindExistingTemplates but for templates with parameter packs.
+
+  TypeSystemClang::TemplateParameterInfos infos;
+  infos.packed_args =
+      std::make_unique<TypeSystemClang::TemplateParameterInfos>();
+  infos.packed_args->names = {"", ""};
+  infos.packed_args->args = {TemplateArgument(m_ast->getASTContext().IntTy),
+                             TemplateArgument(m_ast->getASTContext().IntTy)};
+  ClassTemplateDecl *type_pack =
+      ExpectNewTemplate("<typename ...> (int, int)", infos);
+
+  // Special case: An instantiation for a parameter pack with no values fits
+  // to whatever class template we find. There isn't enough information to
+  // do an actual comparison here.
+  infos.packed_args =
+      std::make_unique<TypeSystemClang::TemplateParameterInfos>();
+  ExpectReusedTemplate("<...> (no values in pack)", infos, type_pack);
+
+  // Change the type content of pack type values.
+  infos.packed_args->names = {"", ""};
+  infos.packed_args->args = {TemplateArgument(m_ast->getASTContext().IntTy),
+                             TemplateArgument(m_ast->getASTContext().LongTy)};
+  ExpectReusedTemplate("<typename ...> (int, long)", infos, type_pack);
+
+  // Change the number of pack values.
+  infos.packed_args->args = {TemplateArgument(m_ast->getASTContext().IntTy)};
+  ExpectReusedTemplate("<typename ...> (int)", infos, type_pack);
+
+  // The names of the pack values shouldn't matter.
+  infos.packed_args->names = {"A", "B"};
+  ExpectReusedTemplate("<typename ...> (int)", infos, type_pack);
+
+  // Changing the kind of template argument will create a new template.
+  infos.packed_args->args = {TemplateArgument(m_ast->getASTContext(),
+                                              llvm::APSInt(llvm::APInt(32, 1)),
+                                              m_ast->getASTContext().IntTy)};
+  ClassTemplateDecl *int_pack = ExpectNewTemplate("<int ...> (int = 1)", infos);
+
+  // Changing the value of integral parameters will not create a new template.
+  infos.packed_args->args = {TemplateArgument(
+      m_ast->getASTContext(), llvm::APSInt(llvm::APInt(32, 123)),
+      m_ast->getASTContext().IntTy)};
+  ExpectReusedTemplate("<int ...> (int = 123)", infos, int_pack);
+
+  // Changing the integral type will create a new template.
+  infos.packed_args->args = {TemplateArgument(m_ast->getASTContext(),
+                                              llvm::APSInt(llvm::APInt(64, 1)),
+                                              m_ast->getASTContext().LongTy)};
+  ExpectNewTemplate("<long ...> (long = 1)", infos);
+
+  // Prependinding a non-pack parameter will create a new template.
+  infos.names = {"T"};
+  infos.args = {TemplateArgument(m_ast->getASTContext().IntTy)};
+  ExpectNewTemplate("<typename T, long...> (T = int, long = 1)", infos);
+}
+
 TEST_F(TestTypeSystemClang, OnlyPackName) {
   TypeSystemClang::TemplateParameterInfos infos;
   infos.pack_name = "A";


        


More information about the lldb-commits mailing list