clang-tools 24.0.0git
UseNullptrCheck.cpp
Go to the documentation of this file.
1//===----------------------------------------------------------------------===//
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#include "UseNullptrCheck.h"
10#include "../utils/Matchers.h"
12#include "clang/AST/ASTContext.h"
13#include "clang/AST/RecursiveASTVisitor.h"
14#include "clang/ASTMatchers/ASTMatchFinder.h"
15#include "clang/Lex/Lexer.h"
16
17using namespace clang;
18using namespace clang::ast_matchers;
19using namespace llvm;
20
21namespace clang::tidy::modernize {
22namespace {
23
24AST_MATCHER(Type, sugaredNullptrType) {
25 const Type *DesugaredType = Node.getUnqualifiedDesugaredType();
26 if (const auto *BT = dyn_cast<BuiltinType>(DesugaredType))
27 return BT->getKind() == BuiltinType::NullPtr;
28 return false;
29}
30
31AST_MATCHER(DecltypeType, decltypeTypeNullptrLiteral) {
32 if (const Expr *E = Node.getUnderlyingExpr())
33 return isa<CXXNullPtrLiteralExpr>(E->IgnoreParens());
34 return false;
35}
36
37} // namespace
38
39static constexpr char CastSequence[] = "sequence";
40
41/// Create a matcher that finds implicit casts as well as the head of a
42/// sequence of zero or more nested explicit casts that have an implicit cast
43/// to null within.
44/// Finding sequences of explicit casts is necessary so that an entire sequence
45/// can be replaced instead of just the inner-most implicit cast.
46///
47/// TODO/NOTE: The second "anyOf" below discards matches on a substituted type,
48/// since we don't know if that would _always_ be a pointer type for all other
49/// specializations, unless the expression was "__null", in which case we assume
50/// that all specializations are expected to be for pointer types. Ideally this
51/// would check for the "NULL" macro instead, but that'd be harder to express.
52/// In practice, "NULL" is often defined as "__null", and this is a useful
53/// condition.
54void UseNullptrCheck::registerMatchers(MatchFinder *Finder) {
55 auto ImplicitCastToNull = implicitCastExpr(
56 anyOf(hasCastKind(CK_NullToPointer), hasCastKind(CK_NullToMemberPointer)),
57 anyOf(hasSourceExpression(gnuNullExpr()),
58 unless(hasImplicitDestinationType(
59 qualType(substTemplateTypeParmType())))),
60 unless(hasSourceExpression(hasType(sugaredNullptrType()))),
61 unless(hasImplicitDestinationType(
62 qualType(matchers::matchesAnyListedTypeName(IgnoredTypes)))));
63
64 const auto IsOrHasDescendant = [](const auto &InnerMatcher) {
65 return anyOf(InnerMatcher, hasDescendant(InnerMatcher));
66 };
67
68 Finder->addMatcher(
69 castExpr(anyOf(ImplicitCastToNull,
70 explicitCastExpr(hasDescendant(ImplicitCastToNull))),
71 unless(hasAncestor(explicitCastExpr())),
72 unless(hasAncestor(cxxRewrittenBinaryOperator())))
73 .bind(CastSequence),
74 this);
75
76 Finder->addMatcher(
77 cxxRewrittenBinaryOperator(
78 // Match rewritten operators, but verify (in the check method)
79 // that if an implicit cast is found, it is not from another
80 // nested rewritten operator.
81 expr().bind("matchBinopOperands"),
82 hasEitherOperand(IsOrHasDescendant(
83 implicitCastExpr(ImplicitCastToNull,
84 hasAncestor(cxxRewrittenBinaryOperator().bind(
85 "checkBinopOperands")))
86 .bind(CastSequence))),
87 // Skip defaulted comparison operators.
88 unless(hasAncestor(functionDecl(isDefaulted())))),
89 this);
90
91 if (UseNullptrT)
92 Finder->addMatcher(typeLoc(loc(decltypeType(decltypeTypeNullptrLiteral())))
93 .bind("matchDecltypeNullptr"),
94 this);
95}
96
97static bool isReplaceableRange(SourceLocation StartLoc, SourceLocation EndLoc,
98 const SourceManager &SM) {
99 return SM.isWrittenInSameFile(StartLoc, EndLoc);
100}
101
102/// Replaces the provided range with the text "nullptr", but only if
103/// the start and end location are both in main file.
104/// Returns true if and only if a replacement was made.
105static void replaceWithNullptr(ClangTidyCheck &Check, const SourceManager &SM,
106 SourceLocation StartLoc, SourceLocation EndLoc) {
107 const CharSourceRange Range(SourceRange(StartLoc, EndLoc), true);
108 // Add a space if nullptr follows an alphanumeric character. This happens
109 // whenever there is an c-style explicit cast to nullptr not surrounded by
110 // parentheses and right beside a return statement.
111 const SourceLocation PreviousLocation = StartLoc.getLocWithOffset(-1);
112 const bool NeedsSpace =
113 isAlphanumeric(*SM.getCharacterData(PreviousLocation));
114 Check.diag(Range.getBegin(), "use nullptr") << FixItHint::CreateReplacement(
115 Range, NeedsSpace ? " nullptr" : "nullptr");
116}
117
118/// Returns the name of the outermost macro.
119///
120/// Given
121/// \code
122/// #define MY_NULL NULL
123/// \endcode
124/// If \p Loc points to NULL, this function will return the name MY_NULL.
125static StringRef getOutermostMacroName(SourceLocation Loc,
126 const SourceManager &SM,
127 const LangOptions &LO) {
128 assert(Loc.isMacroID());
129 SourceLocation OutermostMacroLoc;
130
131 while (Loc.isMacroID()) {
132 OutermostMacroLoc = Loc;
133 Loc = SM.getImmediateMacroCallerLoc(Loc);
134 }
135
136 return Lexer::getImmediateMacroName(OutermostMacroLoc, SM, LO);
137}
138
139namespace {
140
141/// RecursiveASTVisitor for ensuring all nodes rooted at a given AST
142/// subtree that have file-level source locations corresponding to a macro
143/// argument have implicit NullTo(Member)Pointer nodes as ancestors.
144class MacroArgUsageVisitor : public RecursiveASTVisitor<MacroArgUsageVisitor> {
145public:
146 MacroArgUsageVisitor(SourceLocation CastLoc, const SourceManager &SM)
147 : CastLoc(CastLoc), SM(SM) {
148 assert(CastLoc.isFileID());
149 }
150
151 bool TraverseStmt(Stmt *S) {
152 const bool VisitedPreviously = Visited;
153
154 if (!RecursiveASTVisitor<MacroArgUsageVisitor>::TraverseStmt(S))
155 return false;
156
157 // The point at which VisitedPreviously is false and Visited is true is the
158 // root of a subtree containing nodes whose locations match CastLoc. It's
159 // at this point we test that the Implicit NullTo(Member)Pointer cast was
160 // found or not.
161 if (!VisitedPreviously) {
162 if (Visited && !CastFound) {
163 // Found nodes with matching SourceLocations but didn't come across a
164 // cast. This is an invalid macro arg use. Can stop traversal
165 // completely now.
166 InvalidFound = true;
167 return false;
168 }
169 // Reset state as we unwind back up the tree.
170 CastFound = false;
171 Visited = false;
172 }
173 return true;
174 }
175
176 bool VisitStmt(Stmt *S) {
177 if (SM.getFileLoc(S->getBeginLoc()) != CastLoc)
178 return true;
179 Visited = true;
180
181 const ImplicitCastExpr *Cast = dyn_cast<ImplicitCastExpr>(S);
182 if (Cast && (Cast->getCastKind() == CK_NullToPointer ||
183 Cast->getCastKind() == CK_NullToMemberPointer))
184 CastFound = true;
185
186 return true;
187 }
188
189 bool TraverseInitListExpr(InitListExpr *S) {
190 // Only go through the semantic form of the InitListExpr, because
191 // ImplicitCast might not appear in the syntactic form, and this results in
192 // finding usages of the macro argument that don't have a ImplicitCast as an
193 // ancestor (thus invalidating the replacement) when they actually have.
194 return RecursiveASTVisitor<MacroArgUsageVisitor>::
195 TraverseSynOrSemInitListExpr(
196 S->isSemanticForm() ? S : S->getSemanticForm());
197 }
198
199 bool foundInvalid() const { return InvalidFound; }
200
201private:
202 SourceLocation CastLoc;
203 const SourceManager &SM;
204
205 bool Visited = false;
206 bool CastFound = false;
207 bool InvalidFound = false;
208};
209
210/// Looks for implicit casts as well as sequences of 0 or more explicit
211/// casts with an implicit null-to-pointer cast within.
212///
213/// The matcher this visitor is used with will find a single implicit cast or a
214/// top-most explicit cast (i.e. it has no explicit casts as an ancestor) where
215/// an implicit cast is nested within. However, there is no guarantee that only
216/// explicit casts exist between the found top-most explicit cast and the
217/// possibly more than one nested implicit cast. This visitor finds all cast
218/// sequences with an implicit cast to null within and creates a replacement
219/// leaving the outermost explicit cast unchanged to avoid introducing
220/// ambiguities.
221class CastSequenceVisitor : public RecursiveASTVisitor<CastSequenceVisitor> {
222public:
223 CastSequenceVisitor(ASTContext &Context, ArrayRef<StringRef> NullMacros,
224 ClangTidyCheck &Check)
225 : SM(Context.getSourceManager()), Context(Context),
226 NullMacros(NullMacros), Check(Check) {}
227
228 bool TraverseStmt(Stmt *S) {
229 // Stop traversing down the tree if requested.
230 if (PruneSubtree) {
231 PruneSubtree = false;
232 return true;
233 }
234 return RecursiveASTVisitor<CastSequenceVisitor>::TraverseStmt(S);
235 }
236
237 // Only VisitStmt is overridden as we shouldn't find other base AST types
238 // within a cast expression.
239 bool VisitStmt(Stmt *S) {
240 auto *C = dyn_cast<CastExpr>(S);
241 // Catch the castExpr inside cxxDefaultArgExpr.
242 if (auto *E = dyn_cast<CXXDefaultArgExpr>(S)) {
243 C = dyn_cast<CastExpr>(E->getExpr());
244 FirstSubExpr = nullptr;
245 }
246 if (!C) {
247 FirstSubExpr = nullptr;
248 return true;
249 }
250
251 auto *CastSubExpr = C->getSubExpr()->IgnoreParens();
252 // Ignore cast expressions which cast nullptr literal.
253 if (isa<CXXNullPtrLiteralExpr>(CastSubExpr))
254 return true;
255
256 if (!FirstSubExpr)
257 FirstSubExpr = CastSubExpr;
258
259 if (C->getCastKind() != CK_NullToPointer &&
260 C->getCastKind() != CK_NullToMemberPointer) {
261 return true;
262 }
263
264 SourceLocation StartLoc = FirstSubExpr->getBeginLoc();
265 SourceLocation EndLoc = FirstSubExpr->getEndLoc();
266
267 // If the location comes from a macro arg expansion, *all* uses of that
268 // arg must be checked to result in NullTo(Member)Pointer casts.
269 //
270 // If the location comes from a macro body expansion, check to see if its
271 // coming from one of the allowed 'NULL' macros.
272 if (SM.isMacroArgExpansion(StartLoc) && SM.isMacroArgExpansion(EndLoc)) {
273 const SourceLocation FileLocStart = SM.getFileLoc(StartLoc),
274 FileLocEnd = SM.getFileLoc(EndLoc);
275 SourceLocation ImmediateMacroArgLoc, MacroLoc;
276 // Skip NULL macros used in macro.
277 if (!getMacroAndArgLocations(StartLoc, ImmediateMacroArgLoc, MacroLoc) ||
278 ImmediateMacroArgLoc != FileLocStart)
279 return skipSubTree();
280
281 if (isReplaceableRange(FileLocStart, FileLocEnd, SM) &&
282 allArgUsesValid(C)) {
283 replaceWithNullptr(Check, SM, FileLocStart, FileLocEnd);
284 }
285 return true;
286 }
287
288 if (SM.isMacroBodyExpansion(StartLoc) && SM.isMacroBodyExpansion(EndLoc)) {
289 const StringRef OutermostMacroName =
290 getOutermostMacroName(StartLoc, SM, Context.getLangOpts());
291
292 // Check to see if the user wants to replace the macro being expanded.
293 if (!llvm::is_contained(NullMacros, OutermostMacroName))
294 return skipSubTree();
295
296 StartLoc = SM.getFileLoc(StartLoc);
297 EndLoc = SM.getFileLoc(EndLoc);
298 }
299
300 if (!isReplaceableRange(StartLoc, EndLoc, SM))
301 return skipSubTree();
302 replaceWithNullptr(Check, SM, StartLoc, EndLoc);
303
304 return true;
305 }
306
307private:
308 bool skipSubTree() {
309 PruneSubtree = true;
310 return true;
311 }
312
313 /// Tests that all expansions of a macro arg, one of which expands to
314 /// result in \p CE, yield NullTo(Member)Pointer casts.
315 bool allArgUsesValid(const CastExpr *CE) {
316 const SourceLocation CastLoc = CE->getBeginLoc();
317
318 // Step 1: Get location of macro arg and location of the macro the arg was
319 // provided to.
320 SourceLocation ArgLoc, MacroLoc;
321 if (!getMacroAndArgLocations(CastLoc, ArgLoc, MacroLoc))
322 return false;
323
324 // Step 2: Find the first ancestor that doesn't expand from this macro.
325 DynTypedNode ContainingAncestor;
326 if (!findContainingAncestor(DynTypedNode::create<Stmt>(*CE), MacroLoc,
327 ContainingAncestor))
328 return false;
329
330 // Step 3:
331 // Visit children of this containing parent looking for the least-descended
332 // nodes of the containing parent which are macro arg expansions that expand
333 // from the given arg location.
334 // Visitor needs: arg loc.
335 MacroArgUsageVisitor ArgUsageVisitor(SM.getFileLoc(CastLoc), SM);
336 if (const auto *D = ContainingAncestor.get<Decl>())
337 ArgUsageVisitor.TraverseDecl(const_cast<Decl *>(D));
338 else if (const auto *S = ContainingAncestor.get<Stmt>())
339 ArgUsageVisitor.TraverseStmt(const_cast<Stmt *>(S));
340 else
341 llvm_unreachable("Unhandled ContainingAncestor node type");
342
343 return !ArgUsageVisitor.foundInvalid();
344 }
345
346 /// Given the SourceLocation for a macro arg expansion, finds the
347 /// non-macro SourceLocation of the macro the arg was passed to and the
348 /// non-macro SourceLocation of the argument in the arg list to that macro.
349 /// These results are returned via \c MacroLoc and \c ArgLoc respectively.
350 /// These values are undefined if the return value is false.
351 ///
352 /// \returns false if one of the returned SourceLocations would be a
353 /// SourceLocation pointing within the definition of another macro.
354 bool getMacroAndArgLocations(SourceLocation Loc, SourceLocation &ArgLoc,
355 SourceLocation &MacroLoc) {
356 assert(Loc.isMacroID() && "Only reasonable to call this on macros");
357
358 ArgLoc = Loc;
359
360 // Find the location of the immediate macro expansion.
361 while (true) {
362 const std::pair<FileID, unsigned> LocInfo = SM.getDecomposedLoc(ArgLoc);
363 const SrcMgr::SLocEntry *E = &SM.getSLocEntry(LocInfo.first);
364 const SrcMgr::ExpansionInfo &Expansion = E->getExpansion();
365
366 const SourceLocation OldArgLoc = ArgLoc;
367 ArgLoc = Expansion.getExpansionLocStart();
368 if (!Expansion.isMacroArgExpansion()) {
369 if (!MacroLoc.isFileID())
370 return false;
371
372 const StringRef Name =
373 Lexer::getImmediateMacroName(OldArgLoc, SM, Context.getLangOpts());
374 return llvm::is_contained(NullMacros, Name);
375 }
376
377 MacroLoc = SM.getExpansionRange(ArgLoc).getBegin();
378
379 ArgLoc = Expansion.getSpellingLoc().getLocWithOffset(LocInfo.second);
380 if (ArgLoc.isFileID())
381 return true;
382
383 // If spelling location resides in the same FileID as macro expansion
384 // location, it means there is no inner macro.
385 const FileID MacroFID = SM.getFileID(MacroLoc);
386 if (SM.isInFileID(ArgLoc, MacroFID)) {
387 // Don't transform this case. If the characters that caused the
388 // null-conversion come from within a macro, they can't be changed.
389 return false;
390 }
391 }
392
393 llvm_unreachable("getMacroAndArgLocations");
394 }
395
396 /// Tests if TestMacroLoc is found while recursively unravelling
397 /// expansions starting at TestLoc. TestMacroLoc.isFileID() must be true.
398 /// Implementation is very similar to getMacroAndArgLocations() except in this
399 /// case, it's not assumed that TestLoc is expanded from a macro argument.
400 /// While unravelling expansions macro arguments are handled as with
401 /// getMacroAndArgLocations() but in this function macro body expansions are
402 /// also handled.
403 ///
404 /// False means either:
405 /// - TestLoc is not from a macro expansion.
406 /// - TestLoc is from a different macro expansion.
407 bool expandsFrom(SourceLocation TestLoc, SourceLocation TestMacroLoc) {
408 if (TestLoc.isFileID())
409 return false;
410
411 SourceLocation Loc = TestLoc, MacroLoc;
412
413 while (true) {
414 const std::pair<FileID, unsigned> LocInfo = SM.getDecomposedLoc(Loc);
415 const SrcMgr::SLocEntry *E = &SM.getSLocEntry(LocInfo.first);
416 const SrcMgr::ExpansionInfo &Expansion = E->getExpansion();
417
418 Loc = Expansion.getExpansionLocStart();
419
420 if (!Expansion.isMacroArgExpansion()) {
421 if (Loc.isFileID())
422 return Loc == TestMacroLoc;
423 // Since Loc is still a macro ID and it's not an argument expansion, we
424 // don't need to do the work of handling an argument expansion. Simply
425 // keep recursively expanding until we hit a FileID or a macro arg
426 // expansion or a macro arg expansion.
427 continue;
428 }
429
430 MacroLoc = SM.getImmediateExpansionRange(Loc).getBegin();
431 if (MacroLoc.isFileID() && MacroLoc == TestMacroLoc) {
432 // Match made.
433 return true;
434 }
435
436 Loc = Expansion.getSpellingLoc().getLocWithOffset(LocInfo.second);
437 if (Loc.isFileID()) {
438 // If we made it this far without finding a match, there is no match to
439 // be made.
440 return false;
441 }
442 }
443
444 llvm_unreachable("expandsFrom");
445 }
446
447 /// Given a starting point \c Start in the AST, find an ancestor that
448 /// doesn't expand from the macro called at file location \c MacroLoc.
449 ///
450 /// \pre MacroLoc.isFileID()
451 /// \returns true if such an ancestor was found, false otherwise.
452 bool findContainingAncestor(DynTypedNode Start, SourceLocation MacroLoc,
453 DynTypedNode &Result) {
454 // Below we're only following the first parent back up the AST. This should
455 // be fine since for the statements we care about there should only be one
456 // parent, except for the case specified below.
457
458 assert(MacroLoc.isFileID());
459
460 while (true) {
461 const auto &Parents = Context.getParents(Start);
462 if (Parents.empty())
463 return false;
464 if (Parents.size() > 1) {
465 // If there are more than one parents, don't do the replacement unless
466 // they are InitListsExpr (semantic and syntactic form). In this case we
467 // can choose any one here, and the ASTVisitor will take care of
468 // traversing the right one.
469 for (const auto &Parent : Parents)
470 if (!Parent.get<InitListExpr>())
471 return false;
472 }
473
474 const DynTypedNode &Parent = Parents[0];
475
476 SourceLocation Loc;
477 if (const auto *D = Parent.get<Decl>())
478 Loc = D->getBeginLoc();
479 else if (const auto *S = Parent.get<Stmt>())
480 Loc = S->getBeginLoc();
481
482 // TypeLoc and NestedNameSpecifierLoc are members of the parent map. Skip
483 // them and keep going up.
484 if (Loc.isValid() && !expandsFrom(Loc, MacroLoc)) {
485 Result = Parent;
486 return true;
487 }
488
489 Start = Parent;
490 }
491
492 llvm_unreachable("findContainingAncestor");
493 }
494
495 SourceManager &SM;
496 ASTContext &Context;
497 ArrayRef<StringRef> NullMacros;
498 ClangTidyCheck &Check;
499 Expr *FirstSubExpr = nullptr;
500 bool PruneSubtree = false;
501};
502
503} // namespace
504
506 : ClangTidyCheck(Name, Context),
507 NullMacrosStr(Options.get("NullMacros", "NULL")),
508 IgnoredTypes(utils::options::parseStringList(Options.get(
509 "IgnoredTypes", "_CmpUnspecifiedParam;^std::__cmp_cat::__unspec"))),
510 UseNullptrT(Options.get("UseNullptrT", true)),
511 IncludeInserter(Options.getLocalOrGlobal("IncludeStyle",
512 utils::IncludeSorter::IS_LLVM),
513 areDiagsSelfContained()) {
514 NullMacrosStr.split(NullMacros, ",");
515}
516
517void UseNullptrCheck::registerPPCallbacks(const SourceManager &SM,
518 Preprocessor *PP,
519 Preprocessor *ModuleExpanderPP) {
520 IncludeInserter.registerPreprocessor(PP);
521}
522
524 Options.store(Opts, "NullMacros", NullMacrosStr);
525 Options.store(Opts, "IgnoredTypes",
527 Options.store(Opts, "IncludeStyle", IncludeInserter.getStyle());
528 Options.store(Opts, "UseNullptrT", UseNullptrT);
529}
530
531void UseNullptrCheck::check(const MatchFinder::MatchResult &Result) {
532 if (const auto *MatchedTypeLoc =
533 Result.Nodes.getNodeAs<TypeLoc>("matchDecltypeNullptr")) {
534 diag(MatchedTypeLoc->getBeginLoc(), "use std::nullptr_t instead")
535 << IncludeInserter.createIncludeInsertion(
536 Result.SourceManager->getFileID(MatchedTypeLoc->getBeginLoc()),
537 "<cstddef>")
538 << FixItHint::CreateReplacement(MatchedTypeLoc->getSourceRange(),
539 "std::nullptr_t");
540 return;
541 }
542
543 const auto *NullCast = Result.Nodes.getNodeAs<CastExpr>(CastSequence);
544 assert(NullCast && "Bad Callback. No node provided");
545
546 if (Result.Nodes.getNodeAs<CXXRewrittenBinaryOperator>(
547 "matchBinopOperands") !=
548 Result.Nodes.getNodeAs<CXXRewrittenBinaryOperator>("checkBinopOperands"))
549 return;
550
551 // Given an implicit null-ptr cast or an explicit cast with an implicit
552 // null-to-pointer cast within use CastSequenceVisitor to identify sequences
553 // of explicit casts that can be converted into 'nullptr'.
554 CastSequenceVisitor(*Result.Context, NullMacros, *this)
555 .TraverseStmt(const_cast<CastExpr *>(NullCast));
556}
557
558} // namespace clang::tidy::modernize
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
UseNullptrCheck(StringRef Name, ClangTidyContext *Context)
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Create a matcher that finds implicit casts as well as the head of a sequence of zero or more nested e...
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) override
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
@ Type
An inlay hint that for a type annotation.
Definition Protocol.h:1745
inline ::clang::ast_matchers::internal::Matcher< QualType > matchesAnyListedTypeName(llvm::ArrayRef< StringRef > NameList, bool CanonicalTypes)
AST_MATCHER(BinaryOperator, isRelationalOperator)
static StringRef getOutermostMacroName(SourceLocation Loc, const SourceManager &SM, const LangOptions &LO)
Returns the name of the outermost macro.
static constexpr char CastSequence[]
static void replaceWithNullptr(ClangTidyCheck &Check, const SourceManager &SM, SourceLocation StartLoc, SourceLocation EndLoc)
Replaces the provided range with the text "nullptr", but only if the start and end location are both ...
static bool isReplaceableRange(SourceLocation StartLoc, SourceLocation EndLoc, const SourceManager &SM)
std::string serializeStringList(ArrayRef< StringRef > Strings)
Serialize a sequence of names that can be parsed by parseStringList.
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Some operations such as code completion produce a set of candidates.
Definition Generators.h:150
llvm::StringMap< ClangTidyValue > OptionMap