1 | //===--- JSONNodeDumper.h - Printing of AST nodes to JSON -----------------===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | // |
9 | // This file implements AST dumping of components of individual AST nodes to |
10 | // a JSON. |
11 | // |
12 | //===----------------------------------------------------------------------===// |
13 | |
14 | #ifndef LLVM_CLANG_AST_JSONNODEDUMPER_H |
15 | #define LLVM_CLANG_AST_JSONNODEDUMPER_H |
16 | |
17 | #include "clang/AST/ASTContext.h" |
18 | #include "clang/AST/ASTDumperUtils.h" |
19 | #include "clang/AST/ASTNodeTraverser.h" |
20 | #include "clang/AST/AttrVisitor.h" |
21 | #include "clang/AST/CommentCommandTraits.h" |
22 | #include "clang/AST/CommentVisitor.h" |
23 | #include "clang/AST/ExprConcepts.h" |
24 | #include "clang/AST/ExprCXX.h" |
25 | #include "clang/AST/Mangle.h" |
26 | #include "clang/AST/Type.h" |
27 | #include "llvm/Support/JSON.h" |
28 | |
29 | namespace clang { |
30 | |
31 | class APValue; |
32 | |
33 | class NodeStreamer { |
34 | bool FirstChild = true; |
35 | bool TopLevel = true; |
36 | llvm::SmallVector<std::function<void(bool IsLastChild)>, 32> Pending; |
37 | |
38 | protected: |
39 | llvm::json::OStream JOS; |
40 | |
41 | public: |
42 | /// Add a child of the current node. Calls DoAddChild without arguments |
43 | template <typename Fn> void AddChild(Fn DoAddChild) { |
44 | return AddChild("" , DoAddChild); |
45 | } |
46 | |
47 | /// Add a child of the current node with an optional label. |
48 | /// Calls DoAddChild without arguments. |
49 | template <typename Fn> void AddChild(StringRef Label, Fn DoAddChild) { |
50 | // If we're at the top level, there's nothing interesting to do; just |
51 | // run the dumper. |
52 | if (TopLevel) { |
53 | TopLevel = false; |
54 | JOS.objectBegin(); |
55 | |
56 | DoAddChild(); |
57 | |
58 | while (!Pending.empty()) { |
59 | Pending.back()(true); |
60 | Pending.pop_back(); |
61 | } |
62 | |
63 | JOS.objectEnd(); |
64 | TopLevel = true; |
65 | return; |
66 | } |
67 | |
68 | // We need to capture an owning-string in the lambda because the lambda |
69 | // is invoked in a deferred manner. |
70 | std::string LabelStr(!Label.empty() ? Label : "inner" ); |
71 | bool WasFirstChild = FirstChild; |
72 | auto DumpWithIndent = [=](bool IsLastChild) { |
73 | if (WasFirstChild) { |
74 | JOS.attributeBegin(Key: LabelStr); |
75 | JOS.arrayBegin(); |
76 | } |
77 | |
78 | FirstChild = true; |
79 | unsigned Depth = Pending.size(); |
80 | JOS.objectBegin(); |
81 | |
82 | DoAddChild(); |
83 | |
84 | // If any children are left, they're the last at their nesting level. |
85 | // Dump those ones out now. |
86 | while (Depth < Pending.size()) { |
87 | Pending.back()(true); |
88 | this->Pending.pop_back(); |
89 | } |
90 | |
91 | JOS.objectEnd(); |
92 | |
93 | if (IsLastChild) { |
94 | JOS.arrayEnd(); |
95 | JOS.attributeEnd(); |
96 | } |
97 | }; |
98 | |
99 | if (FirstChild) { |
100 | Pending.push_back(std::move(DumpWithIndent)); |
101 | } else { |
102 | Pending.back()(false); |
103 | Pending.back() = std::move(DumpWithIndent); |
104 | } |
105 | FirstChild = false; |
106 | } |
107 | |
108 | NodeStreamer(raw_ostream &OS) : JOS(OS, 2) {} |
109 | }; |
110 | |
111 | // Dumps AST nodes in JSON format. There is no implied stability for the |
112 | // content or format of the dump between major releases of Clang, other than it |
113 | // being valid JSON output. Further, there is no requirement that the |
114 | // information dumped is a complete representation of the AST, only that the |
115 | // information presented is correct. |
116 | class JSONNodeDumper |
117 | : public ConstAttrVisitor<JSONNodeDumper>, |
118 | public comments::ConstCommentVisitor<JSONNodeDumper, void, |
119 | const comments::FullComment *>, |
120 | public ConstTemplateArgumentVisitor<JSONNodeDumper>, |
121 | public ConstStmtVisitor<JSONNodeDumper>, |
122 | public TypeVisitor<JSONNodeDumper>, |
123 | public ConstDeclVisitor<JSONNodeDumper>, |
124 | public NodeStreamer { |
125 | friend class JSONDumper; |
126 | |
127 | const SourceManager &SM; |
128 | ASTContext& Ctx; |
129 | ASTNameGenerator ASTNameGen; |
130 | PrintingPolicy PrintPolicy; |
131 | const comments::CommandTraits *Traits; |
132 | StringRef LastLocFilename, LastLocPresumedFilename; |
133 | unsigned LastLocLine, LastLocPresumedLine; |
134 | |
135 | using InnerAttrVisitor = ConstAttrVisitor<JSONNodeDumper>; |
136 | using = |
137 | comments::ConstCommentVisitor<JSONNodeDumper, void, |
138 | const comments::FullComment *>; |
139 | using InnerTemplateArgVisitor = ConstTemplateArgumentVisitor<JSONNodeDumper>; |
140 | using InnerStmtVisitor = ConstStmtVisitor<JSONNodeDumper>; |
141 | using InnerTypeVisitor = TypeVisitor<JSONNodeDumper>; |
142 | using InnerDeclVisitor = ConstDeclVisitor<JSONNodeDumper>; |
143 | |
144 | void attributeOnlyIfTrue(StringRef Key, bool Value) { |
145 | if (Value) |
146 | JOS.attribute(Key, Contents: Value); |
147 | } |
148 | |
149 | void writeIncludeStack(PresumedLoc Loc, bool JustFirst = false); |
150 | |
151 | // Writes the attributes of a SourceLocation object without. |
152 | void writeBareSourceLocation(SourceLocation Loc, bool IsSpelling); |
153 | |
154 | // Writes the attributes of a SourceLocation to JSON based on its presumed |
155 | // spelling location. If the given location represents a macro invocation, |
156 | // this outputs two sub-objects: one for the spelling and one for the |
157 | // expansion location. |
158 | void writeSourceLocation(SourceLocation Loc); |
159 | void writeSourceRange(SourceRange R); |
160 | std::string createPointerRepresentation(const void *Ptr); |
161 | llvm::json::Object createQualType(QualType QT, bool Desugar = true); |
162 | llvm::json::Object createBareDeclRef(const Decl *D); |
163 | llvm::json::Object createFPOptions(FPOptionsOverride FPO); |
164 | void writeBareDeclRef(const Decl *D); |
165 | llvm::json::Object createCXXRecordDefinitionData(const CXXRecordDecl *RD); |
166 | llvm::json::Object createCXXBaseSpecifier(const CXXBaseSpecifier &BS); |
167 | std::string createAccessSpecifier(AccessSpecifier AS); |
168 | llvm::json::Array createCastPath(const CastExpr *C); |
169 | |
170 | void writePreviousDeclImpl(...) {} |
171 | |
172 | template <typename T> void writePreviousDeclImpl(const Mergeable<T> *D) { |
173 | const T *First = D->getFirstDecl(); |
174 | if (First != D) |
175 | JOS.attribute(Key: "firstRedecl" , Contents: createPointerRepresentation(Ptr: First)); |
176 | } |
177 | |
178 | template <typename T> void writePreviousDeclImpl(const Redeclarable<T> *D) { |
179 | const T *Prev = D->getPreviousDecl(); |
180 | if (Prev) |
181 | JOS.attribute(Key: "previousDecl" , Contents: createPointerRepresentation(Ptr: Prev)); |
182 | } |
183 | void addPreviousDeclaration(const Decl *D); |
184 | |
185 | StringRef getCommentCommandName(unsigned CommandID) const; |
186 | |
187 | public: |
188 | JSONNodeDumper(raw_ostream &OS, const SourceManager &SrcMgr, ASTContext &Ctx, |
189 | const PrintingPolicy &PrintPolicy, |
190 | const comments::CommandTraits *Traits) |
191 | : NodeStreamer(OS), SM(SrcMgr), Ctx(Ctx), ASTNameGen(Ctx), |
192 | PrintPolicy(PrintPolicy), Traits(Traits), LastLocLine(0), |
193 | LastLocPresumedLine(0) {} |
194 | |
195 | void Visit(const Attr *A); |
196 | void Visit(const Stmt *Node); |
197 | void Visit(const Type *T); |
198 | void Visit(QualType T); |
199 | void Visit(const Decl *D); |
200 | void Visit(TypeLoc TL); |
201 | |
202 | void (const comments::Comment *C, const comments::FullComment *FC); |
203 | void Visit(const TemplateArgument &TA, SourceRange R = {}, |
204 | const Decl *From = nullptr, StringRef Label = {}); |
205 | void Visit(const CXXCtorInitializer *Init); |
206 | void Visit(const OMPClause *C); |
207 | void Visit(const BlockDecl::Capture &C); |
208 | void Visit(const GenericSelectionExpr::ConstAssociation &A); |
209 | void Visit(const concepts::Requirement *R); |
210 | void Visit(const APValue &Value, QualType Ty); |
211 | void Visit(const ConceptReference *); |
212 | |
213 | void VisitAliasAttr(const AliasAttr *AA); |
214 | void VisitCleanupAttr(const CleanupAttr *CA); |
215 | void VisitDeprecatedAttr(const DeprecatedAttr *DA); |
216 | void VisitUnavailableAttr(const UnavailableAttr *UA); |
217 | void VisitSectionAttr(const SectionAttr *SA); |
218 | void VisitVisibilityAttr(const VisibilityAttr *VA); |
219 | void VisitTLSModelAttr(const TLSModelAttr *TA); |
220 | |
221 | void VisitTypedefType(const TypedefType *TT); |
222 | void VisitUsingType(const UsingType *TT); |
223 | void VisitFunctionType(const FunctionType *T); |
224 | void VisitFunctionProtoType(const FunctionProtoType *T); |
225 | void VisitRValueReferenceType(const ReferenceType *RT); |
226 | void VisitArrayType(const ArrayType *AT); |
227 | void VisitConstantArrayType(const ConstantArrayType *CAT); |
228 | void VisitDependentSizedExtVectorType(const DependentSizedExtVectorType *VT); |
229 | void VisitVectorType(const VectorType *VT); |
230 | void VisitUnresolvedUsingType(const UnresolvedUsingType *UUT); |
231 | void VisitUnaryTransformType(const UnaryTransformType *UTT); |
232 | void VisitTagType(const TagType *TT); |
233 | void VisitTemplateTypeParmType(const TemplateTypeParmType *TTPT); |
234 | void VisitSubstTemplateTypeParmType(const SubstTemplateTypeParmType *STTPT); |
235 | void |
236 | VisitSubstTemplateTypeParmPackType(const SubstTemplateTypeParmPackType *T); |
237 | void VisitAutoType(const AutoType *AT); |
238 | void VisitTemplateSpecializationType(const TemplateSpecializationType *TST); |
239 | void VisitInjectedClassNameType(const InjectedClassNameType *ICNT); |
240 | void VisitObjCInterfaceType(const ObjCInterfaceType *OIT); |
241 | void VisitPackExpansionType(const PackExpansionType *PET); |
242 | void VisitElaboratedType(const ElaboratedType *ET); |
243 | void VisitMacroQualifiedType(const MacroQualifiedType *MQT); |
244 | void VisitMemberPointerType(const MemberPointerType *MPT); |
245 | |
246 | void VisitNamedDecl(const NamedDecl *ND); |
247 | void VisitTypedefDecl(const TypedefDecl *TD); |
248 | void VisitTypeAliasDecl(const TypeAliasDecl *TAD); |
249 | void VisitNamespaceDecl(const NamespaceDecl *ND); |
250 | void VisitUsingDirectiveDecl(const UsingDirectiveDecl *UDD); |
251 | void VisitNamespaceAliasDecl(const NamespaceAliasDecl *NAD); |
252 | void VisitUsingDecl(const UsingDecl *UD); |
253 | void VisitUsingEnumDecl(const UsingEnumDecl *UED); |
254 | void VisitUsingShadowDecl(const UsingShadowDecl *USD); |
255 | void VisitVarDecl(const VarDecl *VD); |
256 | void VisitFieldDecl(const FieldDecl *FD); |
257 | void VisitFunctionDecl(const FunctionDecl *FD); |
258 | void VisitEnumDecl(const EnumDecl *ED); |
259 | void VisitEnumConstantDecl(const EnumConstantDecl *ECD); |
260 | void VisitRecordDecl(const RecordDecl *RD); |
261 | void VisitCXXRecordDecl(const CXXRecordDecl *RD); |
262 | void VisitHLSLBufferDecl(const HLSLBufferDecl *D); |
263 | void VisitTemplateTypeParmDecl(const TemplateTypeParmDecl *D); |
264 | void VisitNonTypeTemplateParmDecl(const NonTypeTemplateParmDecl *D); |
265 | void VisitTemplateTemplateParmDecl(const TemplateTemplateParmDecl *D); |
266 | void VisitLinkageSpecDecl(const LinkageSpecDecl *LSD); |
267 | void VisitAccessSpecDecl(const AccessSpecDecl *ASD); |
268 | void VisitFriendDecl(const FriendDecl *FD); |
269 | |
270 | void VisitObjCIvarDecl(const ObjCIvarDecl *D); |
271 | void VisitObjCMethodDecl(const ObjCMethodDecl *D); |
272 | void VisitObjCTypeParamDecl(const ObjCTypeParamDecl *D); |
273 | void VisitObjCCategoryDecl(const ObjCCategoryDecl *D); |
274 | void VisitObjCCategoryImplDecl(const ObjCCategoryImplDecl *D); |
275 | void VisitObjCProtocolDecl(const ObjCProtocolDecl *D); |
276 | void VisitObjCInterfaceDecl(const ObjCInterfaceDecl *D); |
277 | void VisitObjCImplementationDecl(const ObjCImplementationDecl *D); |
278 | void VisitObjCCompatibleAliasDecl(const ObjCCompatibleAliasDecl *D); |
279 | void VisitObjCPropertyDecl(const ObjCPropertyDecl *D); |
280 | void VisitObjCPropertyImplDecl(const ObjCPropertyImplDecl *D); |
281 | void VisitBlockDecl(const BlockDecl *D); |
282 | |
283 | void VisitDeclRefExpr(const DeclRefExpr *DRE); |
284 | void VisitSYCLUniqueStableNameExpr(const SYCLUniqueStableNameExpr *E); |
285 | void VisitPredefinedExpr(const PredefinedExpr *PE); |
286 | void VisitUnaryOperator(const UnaryOperator *UO); |
287 | void VisitBinaryOperator(const BinaryOperator *BO); |
288 | void VisitCompoundAssignOperator(const CompoundAssignOperator *CAO); |
289 | void VisitMemberExpr(const MemberExpr *ME); |
290 | void VisitAtomicExpr(const AtomicExpr *AE); |
291 | void VisitCXXNewExpr(const CXXNewExpr *NE); |
292 | void VisitCXXDeleteExpr(const CXXDeleteExpr *DE); |
293 | void VisitCXXThisExpr(const CXXThisExpr *TE); |
294 | void VisitCastExpr(const CastExpr *CE); |
295 | void VisitImplicitCastExpr(const ImplicitCastExpr *ICE); |
296 | void VisitCallExpr(const CallExpr *CE); |
297 | void VisitUnaryExprOrTypeTraitExpr(const UnaryExprOrTypeTraitExpr *TTE); |
298 | void VisitSizeOfPackExpr(const SizeOfPackExpr *SOPE); |
299 | void VisitUnresolvedLookupExpr(const UnresolvedLookupExpr *ULE); |
300 | void VisitAddrLabelExpr(const AddrLabelExpr *ALE); |
301 | void VisitCXXTypeidExpr(const CXXTypeidExpr *CTE); |
302 | void VisitConstantExpr(const ConstantExpr *CE); |
303 | void VisitInitListExpr(const InitListExpr *ILE); |
304 | void VisitGenericSelectionExpr(const GenericSelectionExpr *GSE); |
305 | void VisitCXXUnresolvedConstructExpr(const CXXUnresolvedConstructExpr *UCE); |
306 | void VisitCXXConstructExpr(const CXXConstructExpr *CE); |
307 | void VisitExprWithCleanups(const ExprWithCleanups *EWC); |
308 | void VisitCXXBindTemporaryExpr(const CXXBindTemporaryExpr *BTE); |
309 | void VisitMaterializeTemporaryExpr(const MaterializeTemporaryExpr *MTE); |
310 | void VisitCXXDependentScopeMemberExpr(const CXXDependentScopeMemberExpr *ME); |
311 | void VisitRequiresExpr(const RequiresExpr *RE); |
312 | |
313 | void VisitObjCEncodeExpr(const ObjCEncodeExpr *OEE); |
314 | void VisitObjCMessageExpr(const ObjCMessageExpr *OME); |
315 | void VisitObjCBoxedExpr(const ObjCBoxedExpr *OBE); |
316 | void VisitObjCSelectorExpr(const ObjCSelectorExpr *OSE); |
317 | void VisitObjCProtocolExpr(const ObjCProtocolExpr *OPE); |
318 | void VisitObjCPropertyRefExpr(const ObjCPropertyRefExpr *OPRE); |
319 | void VisitObjCSubscriptRefExpr(const ObjCSubscriptRefExpr *OSRE); |
320 | void VisitObjCIvarRefExpr(const ObjCIvarRefExpr *OIRE); |
321 | void VisitObjCBoolLiteralExpr(const ObjCBoolLiteralExpr *OBLE); |
322 | |
323 | void VisitIntegerLiteral(const IntegerLiteral *IL); |
324 | void VisitCharacterLiteral(const CharacterLiteral *CL); |
325 | void VisitFixedPointLiteral(const FixedPointLiteral *FPL); |
326 | void VisitFloatingLiteral(const FloatingLiteral *FL); |
327 | void VisitStringLiteral(const StringLiteral *SL); |
328 | void VisitCXXBoolLiteralExpr(const CXXBoolLiteralExpr *BLE); |
329 | |
330 | void VisitIfStmt(const IfStmt *IS); |
331 | void VisitSwitchStmt(const SwitchStmt *SS); |
332 | void VisitCaseStmt(const CaseStmt *CS); |
333 | void VisitLabelStmt(const LabelStmt *LS); |
334 | void VisitGotoStmt(const GotoStmt *GS); |
335 | void VisitWhileStmt(const WhileStmt *WS); |
336 | void VisitObjCAtCatchStmt(const ObjCAtCatchStmt *OACS); |
337 | void VisitCompoundStmt(const CompoundStmt *IS); |
338 | |
339 | void VisitNullTemplateArgument(const TemplateArgument &TA); |
340 | void VisitTypeTemplateArgument(const TemplateArgument &TA); |
341 | void VisitDeclarationTemplateArgument(const TemplateArgument &TA); |
342 | void VisitNullPtrTemplateArgument(const TemplateArgument &TA); |
343 | void VisitIntegralTemplateArgument(const TemplateArgument &TA); |
344 | void VisitTemplateTemplateArgument(const TemplateArgument &TA); |
345 | void VisitTemplateExpansionTemplateArgument(const TemplateArgument &TA); |
346 | void VisitExpressionTemplateArgument(const TemplateArgument &TA); |
347 | void VisitPackTemplateArgument(const TemplateArgument &TA); |
348 | |
349 | void (const comments::TextComment *C, |
350 | const comments::FullComment *); |
351 | void visitInlineCommandComment(const comments::InlineCommandComment *C, |
352 | const comments::FullComment *); |
353 | void (const comments::HTMLStartTagComment *C, |
354 | const comments::FullComment *); |
355 | void (const comments::HTMLEndTagComment *C, |
356 | const comments::FullComment *); |
357 | void visitBlockCommandComment(const comments::BlockCommandComment *C, |
358 | const comments::FullComment *); |
359 | void visitParamCommandComment(const comments::ParamCommandComment *C, |
360 | const comments::FullComment *FC); |
361 | void visitTParamCommandComment(const comments::TParamCommandComment *C, |
362 | const comments::FullComment *FC); |
363 | void (const comments::VerbatimBlockComment *C, |
364 | const comments::FullComment *); |
365 | void |
366 | (const comments::VerbatimBlockLineComment *C, |
367 | const comments::FullComment *); |
368 | void (const comments::VerbatimLineComment *C, |
369 | const comments::FullComment *); |
370 | }; |
371 | |
372 | class JSONDumper : public ASTNodeTraverser<JSONDumper, JSONNodeDumper> { |
373 | JSONNodeDumper NodeDumper; |
374 | |
375 | template <typename SpecializationDecl> |
376 | void writeTemplateDeclSpecialization(const SpecializationDecl *SD, |
377 | bool DumpExplicitInst, |
378 | bool DumpRefOnly) { |
379 | bool DumpedAny = false; |
380 | for (const auto *RedeclWithBadType : SD->redecls()) { |
381 | // FIXME: The redecls() range sometimes has elements of a less-specific |
382 | // type. (In particular, ClassTemplateSpecializationDecl::redecls() gives |
383 | // us TagDecls, and should give CXXRecordDecls). |
384 | const auto *Redecl = dyn_cast<SpecializationDecl>(RedeclWithBadType); |
385 | if (!Redecl) { |
386 | // Found the injected-class-name for a class template. This will be |
387 | // dumped as part of its surrounding class so we don't need to dump it |
388 | // here. |
389 | assert(isa<CXXRecordDecl>(RedeclWithBadType) && |
390 | "expected an injected-class-name" ); |
391 | continue; |
392 | } |
393 | |
394 | switch (Redecl->getTemplateSpecializationKind()) { |
395 | case TSK_ExplicitInstantiationDeclaration: |
396 | case TSK_ExplicitInstantiationDefinition: |
397 | if (!DumpExplicitInst) |
398 | break; |
399 | [[fallthrough]]; |
400 | case TSK_Undeclared: |
401 | case TSK_ImplicitInstantiation: |
402 | if (DumpRefOnly) |
403 | NodeDumper.AddChild([=] { NodeDumper.writeBareDeclRef(D: Redecl); }); |
404 | else |
405 | Visit(Redecl); |
406 | DumpedAny = true; |
407 | break; |
408 | case TSK_ExplicitSpecialization: |
409 | break; |
410 | } |
411 | } |
412 | |
413 | // Ensure we dump at least one decl for each specialization. |
414 | if (!DumpedAny) |
415 | NodeDumper.AddChild([=] { NodeDumper.writeBareDeclRef(D: SD); }); |
416 | } |
417 | |
418 | template <typename TemplateDecl> |
419 | void writeTemplateDecl(const TemplateDecl *TD, bool DumpExplicitInst) { |
420 | // FIXME: it would be nice to dump template parameters and specializations |
421 | // to their own named arrays rather than shoving them into the "inner" |
422 | // array. However, template declarations are currently being handled at the |
423 | // wrong "level" of the traversal hierarchy and so it is difficult to |
424 | // achieve without losing information elsewhere. |
425 | |
426 | dumpTemplateParameters(TPL: TD->getTemplateParameters()); |
427 | |
428 | Visit(TD->getTemplatedDecl()); |
429 | |
430 | for (const auto *Child : TD->specializations()) |
431 | writeTemplateDeclSpecialization(Child, DumpExplicitInst, |
432 | !TD->isCanonicalDecl()); |
433 | } |
434 | |
435 | public: |
436 | JSONDumper(raw_ostream &OS, const SourceManager &SrcMgr, ASTContext &Ctx, |
437 | const PrintingPolicy &PrintPolicy, |
438 | const comments::CommandTraits *Traits) |
439 | : NodeDumper(OS, SrcMgr, Ctx, PrintPolicy, Traits) {} |
440 | |
441 | JSONNodeDumper &doGetNodeDelegate() { return NodeDumper; } |
442 | |
443 | void VisitFunctionTemplateDecl(const FunctionTemplateDecl *FTD) { |
444 | writeTemplateDecl(TD: FTD, DumpExplicitInst: true); |
445 | } |
446 | void VisitClassTemplateDecl(const ClassTemplateDecl *CTD) { |
447 | writeTemplateDecl(TD: CTD, DumpExplicitInst: false); |
448 | } |
449 | void VisitVarTemplateDecl(const VarTemplateDecl *VTD) { |
450 | writeTemplateDecl(TD: VTD, DumpExplicitInst: false); |
451 | } |
452 | }; |
453 | |
454 | } // namespace clang |
455 | |
456 | #endif // LLVM_CLANG_AST_JSONNODEDUMPER_H |
457 | |