1e5dd7070Spatrick //===--- Comment.cpp - Comment AST node implementation --------------------===//
2e5dd7070Spatrick //
3e5dd7070Spatrick // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4e5dd7070Spatrick // See https://llvm.org/LICENSE.txt for license information.
5e5dd7070Spatrick // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6e5dd7070Spatrick //
7e5dd7070Spatrick //===----------------------------------------------------------------------===//
8e5dd7070Spatrick
9e5dd7070Spatrick #include "clang/AST/Comment.h"
10e5dd7070Spatrick #include "clang/AST/ASTContext.h"
11e5dd7070Spatrick #include "clang/AST/Decl.h"
12e5dd7070Spatrick #include "clang/AST/DeclObjC.h"
13e5dd7070Spatrick #include "clang/AST/DeclTemplate.h"
14e5dd7070Spatrick #include "clang/Basic/CharInfo.h"
15e5dd7070Spatrick #include "llvm/Support/ErrorHandling.h"
16e5dd7070Spatrick #include <type_traits>
17e5dd7070Spatrick
18e5dd7070Spatrick namespace clang {
19e5dd7070Spatrick namespace comments {
20e5dd7070Spatrick
21e5dd7070Spatrick // Check that no comment class has a non-trival destructor. They are allocated
22e5dd7070Spatrick // with a BumpPtrAllocator and therefore their destructor is not executed.
23e5dd7070Spatrick #define ABSTRACT_COMMENT(COMMENT)
24e5dd7070Spatrick #define COMMENT(CLASS, PARENT) \
25e5dd7070Spatrick static_assert(std::is_trivially_destructible<CLASS>::value, \
26e5dd7070Spatrick #CLASS " should be trivially destructible!");
27e5dd7070Spatrick #include "clang/AST/CommentNodes.inc"
28e5dd7070Spatrick #undef COMMENT
29e5dd7070Spatrick #undef ABSTRACT_COMMENT
30e5dd7070Spatrick
31e5dd7070Spatrick // DeclInfo is also allocated with a BumpPtrAllocator.
32*12c85518Srobert static_assert(std::is_trivially_destructible_v<DeclInfo>,
33e5dd7070Spatrick "DeclInfo should be trivially destructible!");
34e5dd7070Spatrick
getCommentKindName() const35e5dd7070Spatrick const char *Comment::getCommentKindName() const {
36e5dd7070Spatrick switch (getCommentKind()) {
37e5dd7070Spatrick case NoCommentKind: return "NoCommentKind";
38e5dd7070Spatrick #define ABSTRACT_COMMENT(COMMENT)
39e5dd7070Spatrick #define COMMENT(CLASS, PARENT) \
40e5dd7070Spatrick case CLASS##Kind: \
41e5dd7070Spatrick return #CLASS;
42e5dd7070Spatrick #include "clang/AST/CommentNodes.inc"
43e5dd7070Spatrick #undef COMMENT
44e5dd7070Spatrick #undef ABSTRACT_COMMENT
45e5dd7070Spatrick }
46e5dd7070Spatrick llvm_unreachable("Unknown comment kind!");
47e5dd7070Spatrick }
48e5dd7070Spatrick
49e5dd7070Spatrick namespace {
50e5dd7070Spatrick struct good {};
51e5dd7070Spatrick struct bad {};
52e5dd7070Spatrick
53e5dd7070Spatrick template <typename T>
implements_child_begin_end(Comment::child_iterator (T::*)()const)54e5dd7070Spatrick good implements_child_begin_end(Comment::child_iterator (T::*)() const) {
55e5dd7070Spatrick return good();
56e5dd7070Spatrick }
57e5dd7070Spatrick
58e5dd7070Spatrick LLVM_ATTRIBUTE_UNUSED
implements_child_begin_end(Comment::child_iterator (Comment::*)()const)59e5dd7070Spatrick static inline bad implements_child_begin_end(
60e5dd7070Spatrick Comment::child_iterator (Comment::*)() const) {
61e5dd7070Spatrick return bad();
62e5dd7070Spatrick }
63e5dd7070Spatrick
64e5dd7070Spatrick #define ASSERT_IMPLEMENTS_child_begin(function) \
65e5dd7070Spatrick (void) good(implements_child_begin_end(function))
66e5dd7070Spatrick
67e5dd7070Spatrick LLVM_ATTRIBUTE_UNUSED
CheckCommentASTNodes()68e5dd7070Spatrick static inline void CheckCommentASTNodes() {
69e5dd7070Spatrick #define ABSTRACT_COMMENT(COMMENT)
70e5dd7070Spatrick #define COMMENT(CLASS, PARENT) \
71e5dd7070Spatrick ASSERT_IMPLEMENTS_child_begin(&CLASS::child_begin); \
72e5dd7070Spatrick ASSERT_IMPLEMENTS_child_begin(&CLASS::child_end);
73e5dd7070Spatrick #include "clang/AST/CommentNodes.inc"
74e5dd7070Spatrick #undef COMMENT
75e5dd7070Spatrick #undef ABSTRACT_COMMENT
76e5dd7070Spatrick }
77e5dd7070Spatrick
78e5dd7070Spatrick #undef ASSERT_IMPLEMENTS_child_begin
79e5dd7070Spatrick
80e5dd7070Spatrick } // end unnamed namespace
81e5dd7070Spatrick
child_begin() const82e5dd7070Spatrick Comment::child_iterator Comment::child_begin() const {
83e5dd7070Spatrick switch (getCommentKind()) {
84e5dd7070Spatrick case NoCommentKind: llvm_unreachable("comment without a kind");
85e5dd7070Spatrick #define ABSTRACT_COMMENT(COMMENT)
86e5dd7070Spatrick #define COMMENT(CLASS, PARENT) \
87e5dd7070Spatrick case CLASS##Kind: \
88e5dd7070Spatrick return static_cast<const CLASS *>(this)->child_begin();
89e5dd7070Spatrick #include "clang/AST/CommentNodes.inc"
90e5dd7070Spatrick #undef COMMENT
91e5dd7070Spatrick #undef ABSTRACT_COMMENT
92e5dd7070Spatrick }
93e5dd7070Spatrick llvm_unreachable("Unknown comment kind!");
94e5dd7070Spatrick }
95e5dd7070Spatrick
child_end() const96e5dd7070Spatrick Comment::child_iterator Comment::child_end() const {
97e5dd7070Spatrick switch (getCommentKind()) {
98e5dd7070Spatrick case NoCommentKind: llvm_unreachable("comment without a kind");
99e5dd7070Spatrick #define ABSTRACT_COMMENT(COMMENT)
100e5dd7070Spatrick #define COMMENT(CLASS, PARENT) \
101e5dd7070Spatrick case CLASS##Kind: \
102e5dd7070Spatrick return static_cast<const CLASS *>(this)->child_end();
103e5dd7070Spatrick #include "clang/AST/CommentNodes.inc"
104e5dd7070Spatrick #undef COMMENT
105e5dd7070Spatrick #undef ABSTRACT_COMMENT
106e5dd7070Spatrick }
107e5dd7070Spatrick llvm_unreachable("Unknown comment kind!");
108e5dd7070Spatrick }
109e5dd7070Spatrick
isWhitespaceNoCache() const110e5dd7070Spatrick bool TextComment::isWhitespaceNoCache() const {
111*12c85518Srobert return llvm::all_of(Text, clang::isWhitespace);
112e5dd7070Spatrick }
113e5dd7070Spatrick
isWhitespaceNoCache() const114e5dd7070Spatrick bool ParagraphComment::isWhitespaceNoCache() const {
115e5dd7070Spatrick for (child_iterator I = child_begin(), E = child_end(); I != E; ++I) {
116e5dd7070Spatrick if (const TextComment *TC = dyn_cast<TextComment>(*I)) {
117e5dd7070Spatrick if (!TC->isWhitespace())
118e5dd7070Spatrick return false;
119e5dd7070Spatrick } else
120e5dd7070Spatrick return false;
121e5dd7070Spatrick }
122e5dd7070Spatrick return true;
123e5dd7070Spatrick }
124e5dd7070Spatrick
lookThroughTypedefOrTypeAliasLocs(TypeLoc & SrcTL)125e5dd7070Spatrick static TypeLoc lookThroughTypedefOrTypeAliasLocs(TypeLoc &SrcTL) {
126e5dd7070Spatrick TypeLoc TL = SrcTL.IgnoreParens();
127e5dd7070Spatrick
128e5dd7070Spatrick // Look through attribute types.
129e5dd7070Spatrick if (AttributedTypeLoc AttributeTL = TL.getAs<AttributedTypeLoc>())
130e5dd7070Spatrick return AttributeTL.getModifiedLoc();
131e5dd7070Spatrick // Look through qualified types.
132e5dd7070Spatrick if (QualifiedTypeLoc QualifiedTL = TL.getAs<QualifiedTypeLoc>())
133e5dd7070Spatrick return QualifiedTL.getUnqualifiedLoc();
134e5dd7070Spatrick // Look through pointer types.
135e5dd7070Spatrick if (PointerTypeLoc PointerTL = TL.getAs<PointerTypeLoc>())
136e5dd7070Spatrick return PointerTL.getPointeeLoc().getUnqualifiedLoc();
137e5dd7070Spatrick // Look through reference types.
138e5dd7070Spatrick if (ReferenceTypeLoc ReferenceTL = TL.getAs<ReferenceTypeLoc>())
139e5dd7070Spatrick return ReferenceTL.getPointeeLoc().getUnqualifiedLoc();
140e5dd7070Spatrick // Look through adjusted types.
141e5dd7070Spatrick if (AdjustedTypeLoc ATL = TL.getAs<AdjustedTypeLoc>())
142e5dd7070Spatrick return ATL.getOriginalLoc();
143e5dd7070Spatrick if (BlockPointerTypeLoc BlockPointerTL = TL.getAs<BlockPointerTypeLoc>())
144e5dd7070Spatrick return BlockPointerTL.getPointeeLoc().getUnqualifiedLoc();
145e5dd7070Spatrick if (MemberPointerTypeLoc MemberPointerTL = TL.getAs<MemberPointerTypeLoc>())
146e5dd7070Spatrick return MemberPointerTL.getPointeeLoc().getUnqualifiedLoc();
147e5dd7070Spatrick if (ElaboratedTypeLoc ETL = TL.getAs<ElaboratedTypeLoc>())
148e5dd7070Spatrick return ETL.getNamedTypeLoc();
149e5dd7070Spatrick
150e5dd7070Spatrick return TL;
151e5dd7070Spatrick }
152e5dd7070Spatrick
getFunctionTypeLoc(TypeLoc TL,FunctionTypeLoc & ResFTL)153e5dd7070Spatrick static bool getFunctionTypeLoc(TypeLoc TL, FunctionTypeLoc &ResFTL) {
154e5dd7070Spatrick TypeLoc PrevTL;
155e5dd7070Spatrick while (PrevTL != TL) {
156e5dd7070Spatrick PrevTL = TL;
157e5dd7070Spatrick TL = lookThroughTypedefOrTypeAliasLocs(TL);
158e5dd7070Spatrick }
159e5dd7070Spatrick
160e5dd7070Spatrick if (FunctionTypeLoc FTL = TL.getAs<FunctionTypeLoc>()) {
161e5dd7070Spatrick ResFTL = FTL;
162e5dd7070Spatrick return true;
163e5dd7070Spatrick }
164e5dd7070Spatrick
165e5dd7070Spatrick if (TemplateSpecializationTypeLoc STL =
166e5dd7070Spatrick TL.getAs<TemplateSpecializationTypeLoc>()) {
167e5dd7070Spatrick // If we have a typedef to a template specialization with exactly one
168e5dd7070Spatrick // template argument of a function type, this looks like std::function,
169e5dd7070Spatrick // boost::function, or other function wrapper. Treat these typedefs as
170e5dd7070Spatrick // functions.
171e5dd7070Spatrick if (STL.getNumArgs() != 1)
172e5dd7070Spatrick return false;
173e5dd7070Spatrick TemplateArgumentLoc MaybeFunction = STL.getArgLoc(0);
174e5dd7070Spatrick if (MaybeFunction.getArgument().getKind() != TemplateArgument::Type)
175e5dd7070Spatrick return false;
176e5dd7070Spatrick TypeSourceInfo *MaybeFunctionTSI = MaybeFunction.getTypeSourceInfo();
177e5dd7070Spatrick TypeLoc TL = MaybeFunctionTSI->getTypeLoc().getUnqualifiedLoc();
178e5dd7070Spatrick if (FunctionTypeLoc FTL = TL.getAs<FunctionTypeLoc>()) {
179e5dd7070Spatrick ResFTL = FTL;
180e5dd7070Spatrick return true;
181e5dd7070Spatrick }
182e5dd7070Spatrick }
183e5dd7070Spatrick
184e5dd7070Spatrick return false;
185e5dd7070Spatrick }
186e5dd7070Spatrick
getDirectionAsString(PassDirection D)187e5dd7070Spatrick const char *ParamCommandComment::getDirectionAsString(PassDirection D) {
188e5dd7070Spatrick switch (D) {
189e5dd7070Spatrick case ParamCommandComment::In:
190e5dd7070Spatrick return "[in]";
191e5dd7070Spatrick case ParamCommandComment::Out:
192e5dd7070Spatrick return "[out]";
193e5dd7070Spatrick case ParamCommandComment::InOut:
194e5dd7070Spatrick return "[in,out]";
195e5dd7070Spatrick }
196e5dd7070Spatrick llvm_unreachable("unknown PassDirection");
197e5dd7070Spatrick }
198e5dd7070Spatrick
fill()199e5dd7070Spatrick void DeclInfo::fill() {
200e5dd7070Spatrick assert(!IsFilled);
201e5dd7070Spatrick
202e5dd7070Spatrick // Set defaults.
203e5dd7070Spatrick Kind = OtherKind;
204e5dd7070Spatrick TemplateKind = NotTemplate;
205e5dd7070Spatrick IsObjCMethod = false;
206e5dd7070Spatrick IsInstanceMethod = false;
207e5dd7070Spatrick IsClassMethod = false;
208*12c85518Srobert IsVariadic = false;
209*12c85518Srobert ParamVars = std::nullopt;
210e5dd7070Spatrick TemplateParameters = nullptr;
211e5dd7070Spatrick
212e5dd7070Spatrick if (!CommentDecl) {
213e5dd7070Spatrick // If there is no declaration, the defaults is our only guess.
214e5dd7070Spatrick IsFilled = true;
215e5dd7070Spatrick return;
216e5dd7070Spatrick }
217e5dd7070Spatrick CurrentDecl = CommentDecl;
218e5dd7070Spatrick
219e5dd7070Spatrick Decl::Kind K = CommentDecl->getKind();
220*12c85518Srobert const TypeSourceInfo *TSI = nullptr;
221e5dd7070Spatrick switch (K) {
222e5dd7070Spatrick default:
223e5dd7070Spatrick // Defaults are should be good for declarations we don't handle explicitly.
224e5dd7070Spatrick break;
225e5dd7070Spatrick case Decl::Function:
226e5dd7070Spatrick case Decl::CXXMethod:
227e5dd7070Spatrick case Decl::CXXConstructor:
228e5dd7070Spatrick case Decl::CXXDestructor:
229e5dd7070Spatrick case Decl::CXXConversion: {
230e5dd7070Spatrick const FunctionDecl *FD = cast<FunctionDecl>(CommentDecl);
231e5dd7070Spatrick Kind = FunctionKind;
232e5dd7070Spatrick ParamVars = FD->parameters();
233e5dd7070Spatrick ReturnType = FD->getReturnType();
234e5dd7070Spatrick unsigned NumLists = FD->getNumTemplateParameterLists();
235e5dd7070Spatrick if (NumLists != 0) {
236e5dd7070Spatrick TemplateKind = TemplateSpecialization;
237e5dd7070Spatrick TemplateParameters =
238e5dd7070Spatrick FD->getTemplateParameterList(NumLists - 1);
239e5dd7070Spatrick }
240e5dd7070Spatrick
241e5dd7070Spatrick if (K == Decl::CXXMethod || K == Decl::CXXConstructor ||
242e5dd7070Spatrick K == Decl::CXXDestructor || K == Decl::CXXConversion) {
243e5dd7070Spatrick const CXXMethodDecl *MD = cast<CXXMethodDecl>(CommentDecl);
244e5dd7070Spatrick IsInstanceMethod = MD->isInstance();
245e5dd7070Spatrick IsClassMethod = !IsInstanceMethod;
246e5dd7070Spatrick }
247*12c85518Srobert IsVariadic = FD->isVariadic();
248*12c85518Srobert assert(involvesFunctionType());
249e5dd7070Spatrick break;
250e5dd7070Spatrick }
251e5dd7070Spatrick case Decl::ObjCMethod: {
252e5dd7070Spatrick const ObjCMethodDecl *MD = cast<ObjCMethodDecl>(CommentDecl);
253e5dd7070Spatrick Kind = FunctionKind;
254e5dd7070Spatrick ParamVars = MD->parameters();
255e5dd7070Spatrick ReturnType = MD->getReturnType();
256e5dd7070Spatrick IsObjCMethod = true;
257e5dd7070Spatrick IsInstanceMethod = MD->isInstanceMethod();
258e5dd7070Spatrick IsClassMethod = !IsInstanceMethod;
259*12c85518Srobert IsVariadic = MD->isVariadic();
260*12c85518Srobert assert(involvesFunctionType());
261e5dd7070Spatrick break;
262e5dd7070Spatrick }
263e5dd7070Spatrick case Decl::FunctionTemplate: {
264e5dd7070Spatrick const FunctionTemplateDecl *FTD = cast<FunctionTemplateDecl>(CommentDecl);
265e5dd7070Spatrick Kind = FunctionKind;
266e5dd7070Spatrick TemplateKind = Template;
267e5dd7070Spatrick const FunctionDecl *FD = FTD->getTemplatedDecl();
268e5dd7070Spatrick ParamVars = FD->parameters();
269e5dd7070Spatrick ReturnType = FD->getReturnType();
270e5dd7070Spatrick TemplateParameters = FTD->getTemplateParameters();
271*12c85518Srobert IsVariadic = FD->isVariadic();
272*12c85518Srobert assert(involvesFunctionType());
273e5dd7070Spatrick break;
274e5dd7070Spatrick }
275e5dd7070Spatrick case Decl::ClassTemplate: {
276e5dd7070Spatrick const ClassTemplateDecl *CTD = cast<ClassTemplateDecl>(CommentDecl);
277e5dd7070Spatrick Kind = ClassKind;
278e5dd7070Spatrick TemplateKind = Template;
279e5dd7070Spatrick TemplateParameters = CTD->getTemplateParameters();
280e5dd7070Spatrick break;
281e5dd7070Spatrick }
282e5dd7070Spatrick case Decl::ClassTemplatePartialSpecialization: {
283e5dd7070Spatrick const ClassTemplatePartialSpecializationDecl *CTPSD =
284e5dd7070Spatrick cast<ClassTemplatePartialSpecializationDecl>(CommentDecl);
285e5dd7070Spatrick Kind = ClassKind;
286e5dd7070Spatrick TemplateKind = TemplatePartialSpecialization;
287e5dd7070Spatrick TemplateParameters = CTPSD->getTemplateParameters();
288e5dd7070Spatrick break;
289e5dd7070Spatrick }
290e5dd7070Spatrick case Decl::ClassTemplateSpecialization:
291e5dd7070Spatrick Kind = ClassKind;
292e5dd7070Spatrick TemplateKind = TemplateSpecialization;
293e5dd7070Spatrick break;
294e5dd7070Spatrick case Decl::Record:
295e5dd7070Spatrick case Decl::CXXRecord:
296e5dd7070Spatrick Kind = ClassKind;
297e5dd7070Spatrick break;
298e5dd7070Spatrick case Decl::Var:
299*12c85518Srobert if (const VarTemplateDecl *VTD =
300*12c85518Srobert cast<VarDecl>(CommentDecl)->getDescribedVarTemplate()) {
301*12c85518Srobert TemplateKind = TemplateSpecialization;
302*12c85518Srobert TemplateParameters = VTD->getTemplateParameters();
303*12c85518Srobert }
304*12c85518Srobert [[fallthrough]];
305e5dd7070Spatrick case Decl::Field:
306e5dd7070Spatrick case Decl::EnumConstant:
307e5dd7070Spatrick case Decl::ObjCIvar:
308e5dd7070Spatrick case Decl::ObjCAtDefsField:
309*12c85518Srobert case Decl::ObjCProperty:
310e5dd7070Spatrick if (const auto *VD = dyn_cast<DeclaratorDecl>(CommentDecl))
311e5dd7070Spatrick TSI = VD->getTypeSourceInfo();
312e5dd7070Spatrick else if (const auto *PD = dyn_cast<ObjCPropertyDecl>(CommentDecl))
313e5dd7070Spatrick TSI = PD->getTypeSourceInfo();
314e5dd7070Spatrick Kind = VariableKind;
315e5dd7070Spatrick break;
316*12c85518Srobert case Decl::VarTemplate: {
317*12c85518Srobert const VarTemplateDecl *VTD = cast<VarTemplateDecl>(CommentDecl);
318*12c85518Srobert Kind = VariableKind;
319*12c85518Srobert TemplateKind = Template;
320*12c85518Srobert TemplateParameters = VTD->getTemplateParameters();
321*12c85518Srobert if (const VarDecl *VD = VTD->getTemplatedDecl())
322*12c85518Srobert TSI = VD->getTypeSourceInfo();
323*12c85518Srobert break;
324e5dd7070Spatrick }
325e5dd7070Spatrick case Decl::Namespace:
326e5dd7070Spatrick Kind = NamespaceKind;
327e5dd7070Spatrick break;
328e5dd7070Spatrick case Decl::TypeAlias:
329*12c85518Srobert case Decl::Typedef:
330e5dd7070Spatrick Kind = TypedefKind;
331*12c85518Srobert TSI = cast<TypedefNameDecl>(CommentDecl)->getTypeSourceInfo();
332e5dd7070Spatrick break;
333e5dd7070Spatrick case Decl::TypeAliasTemplate: {
334e5dd7070Spatrick const TypeAliasTemplateDecl *TAT = cast<TypeAliasTemplateDecl>(CommentDecl);
335e5dd7070Spatrick Kind = TypedefKind;
336e5dd7070Spatrick TemplateKind = Template;
337e5dd7070Spatrick TemplateParameters = TAT->getTemplateParameters();
338*12c85518Srobert if (TypeAliasDecl *TAD = TAT->getTemplatedDecl())
339*12c85518Srobert TSI = TAD->getTypeSourceInfo();
340e5dd7070Spatrick break;
341e5dd7070Spatrick }
342e5dd7070Spatrick case Decl::Enum:
343e5dd7070Spatrick Kind = EnumKind;
344e5dd7070Spatrick break;
345e5dd7070Spatrick }
346e5dd7070Spatrick
347*12c85518Srobert // If the type is a typedef / using to something we consider a function,
348*12c85518Srobert // extract arguments and return type.
349*12c85518Srobert if (TSI) {
350*12c85518Srobert TypeLoc TL = TSI->getTypeLoc().getUnqualifiedLoc();
351*12c85518Srobert FunctionTypeLoc FTL;
352*12c85518Srobert if (getFunctionTypeLoc(TL, FTL)) {
353*12c85518Srobert ParamVars = FTL.getParams();
354*12c85518Srobert ReturnType = FTL.getReturnLoc().getType();
355*12c85518Srobert if (const auto *FPT = dyn_cast<FunctionProtoType>(FTL.getTypePtr()))
356*12c85518Srobert IsVariadic = FPT->isVariadic();
357*12c85518Srobert assert(involvesFunctionType());
358*12c85518Srobert }
359*12c85518Srobert }
360*12c85518Srobert
361e5dd7070Spatrick IsFilled = true;
362e5dd7070Spatrick }
363e5dd7070Spatrick
getParamName(const FullComment * FC) const364e5dd7070Spatrick StringRef ParamCommandComment::getParamName(const FullComment *FC) const {
365e5dd7070Spatrick assert(isParamIndexValid());
366e5dd7070Spatrick if (isVarArgParam())
367e5dd7070Spatrick return "...";
368e5dd7070Spatrick return FC->getDeclInfo()->ParamVars[getParamIndex()]->getName();
369e5dd7070Spatrick }
370e5dd7070Spatrick
getParamName(const FullComment * FC) const371e5dd7070Spatrick StringRef TParamCommandComment::getParamName(const FullComment *FC) const {
372e5dd7070Spatrick assert(isPositionValid());
373e5dd7070Spatrick const TemplateParameterList *TPL = FC->getDeclInfo()->TemplateParameters;
374e5dd7070Spatrick for (unsigned i = 0, e = getDepth(); i != e; ++i) {
375e5dd7070Spatrick assert(TPL && "Unknown TemplateParameterList");
376e5dd7070Spatrick if (i == e - 1)
377e5dd7070Spatrick return TPL->getParam(getIndex(i))->getName();
378e5dd7070Spatrick const NamedDecl *Param = TPL->getParam(getIndex(i));
379e5dd7070Spatrick if (auto *TTP = dyn_cast<TemplateTemplateParmDecl>(Param))
380e5dd7070Spatrick TPL = TTP->getTemplateParameters();
381e5dd7070Spatrick }
382e5dd7070Spatrick return "";
383e5dd7070Spatrick }
384e5dd7070Spatrick
385e5dd7070Spatrick } // end namespace comments
386e5dd7070Spatrick } // end namespace clang
387e5dd7070Spatrick
388