1//===-- ClangDiagnosticsEmitter.cpp - Generate Clang diagnostics tables ---===//
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// These tablegen backends emit Clang diagnostics tables.
10//
11//===----------------------------------------------------------------------===//
12
13#include "TableGenBackends.h"
14#include "llvm/ADT/DenseSet.h"
15#include "llvm/ADT/PointerUnion.h"
16#include "llvm/ADT/STLExtras.h"
17#include "llvm/ADT/SmallString.h"
18#include "llvm/ADT/SmallVector.h"
19#include "llvm/ADT/StringExtras.h"
20#include "llvm/ADT/StringMap.h"
21#include "llvm/ADT/StringSwitch.h"
22#include "llvm/ADT/Twine.h"
23#include "llvm/Support/Casting.h"
24#include "llvm/Support/Format.h"
25#include "llvm/TableGen/Error.h"
26#include "llvm/TableGen/Record.h"
27#include "llvm/TableGen/StringToOffsetTable.h"
28#include "llvm/TableGen/TableGenBackend.h"
29#include <algorithm>
30#include <cctype>
31#include <functional>
32#include <map>
33#include <optional>
34#include <set>
35using namespace llvm;
36
37//===----------------------------------------------------------------------===//
38// Diagnostic category computation code.
39//===----------------------------------------------------------------------===//
40
41namespace {
42class DiagGroupParentMap {
43 const RecordKeeper &Records;
44 std::map<const Record *, std::vector<const Record *>> Mapping;
45
46public:
47 DiagGroupParentMap(const RecordKeeper &records) : Records(records) {
48 for (const Record *Group : Records.getAllDerivedDefinitions(ClassName: "DiagGroup"))
49 for (const Record *SubGroup : Group->getValueAsListOfDefs(FieldName: "SubGroups"))
50 Mapping[SubGroup].push_back(x: Group);
51 }
52
53 ArrayRef<const Record *> getParents(const Record *Group) {
54 return Mapping[Group];
55 }
56};
57} // end anonymous namespace.
58
59static StringRef
60getCategoryFromDiagGroup(const Record *Group,
61 DiagGroupParentMap &DiagGroupParents) {
62 // If the DiagGroup has a category, return it.
63 StringRef CatName = Group->getValueAsString(FieldName: "CategoryName");
64 if (!CatName.empty()) return CatName;
65
66 // The diag group may the subgroup of one or more other diagnostic groups,
67 // check these for a category as well.
68 for (const Record *Parent : DiagGroupParents.getParents(Group)) {
69 CatName = getCategoryFromDiagGroup(Group: Parent, DiagGroupParents);
70 if (!CatName.empty()) return CatName;
71 }
72 return "";
73}
74
75/// getDiagnosticCategory - Return the category that the specified diagnostic
76/// lives in.
77static StringRef getDiagnosticCategory(const Record *R,
78 DiagGroupParentMap &DiagGroupParents) {
79 // If the diagnostic is in a group, and that group has a category, use it.
80 if (const auto *Group = dyn_cast<DefInit>(Val: R->getValueInit(FieldName: "Group"))) {
81 // Check the diagnostic's diag group for a category.
82 StringRef CatName =
83 getCategoryFromDiagGroup(Group: Group->getDef(), DiagGroupParents);
84 if (!CatName.empty()) return CatName;
85 }
86
87 // If the diagnostic itself has a category, get it.
88 return R->getValueAsString(FieldName: "CategoryName");
89}
90
91namespace {
92 class DiagCategoryIDMap {
93 const RecordKeeper &Records;
94 StringMap<unsigned> CategoryIDs;
95 std::vector<StringRef> CategoryStrings;
96
97 public:
98 DiagCategoryIDMap(const RecordKeeper &records) : Records(records) {
99 DiagGroupParentMap ParentInfo(Records);
100
101 // The zero'th category is "".
102 CategoryStrings.push_back(x: "");
103 CategoryIDs[""] = 0;
104
105 for (const Record *Diag :
106 Records.getAllDerivedDefinitions(ClassName: "Diagnostic")) {
107 StringRef Category = getDiagnosticCategory(R: Diag, DiagGroupParents&: ParentInfo);
108 if (Category.empty()) continue; // Skip diags with no category.
109
110 unsigned &ID = CategoryIDs[Category];
111 if (ID != 0) continue; // Already seen.
112
113 ID = CategoryStrings.size();
114 CategoryStrings.push_back(x: Category);
115 }
116 }
117
118 unsigned getID(StringRef CategoryString) {
119 return CategoryIDs[CategoryString];
120 }
121
122 typedef std::vector<StringRef>::const_iterator const_iterator;
123 const_iterator begin() const { return CategoryStrings.begin(); }
124 const_iterator end() const { return CategoryStrings.end(); }
125 };
126
127 struct GroupInfo {
128 StringRef GroupName;
129 std::vector<const Record*> DiagsInGroup;
130 std::vector<StringRef> SubGroups;
131 unsigned IDNo = 0;
132
133 SmallVector<const Record *, 1> Defs;
134
135 GroupInfo() = default;
136 };
137} // end anonymous namespace.
138
139static bool diagGroupBeforeByName(const Record *LHS, const Record *RHS) {
140 return LHS->getValueAsString(FieldName: "GroupName") <
141 RHS->getValueAsString(FieldName: "GroupName");
142}
143
144using DiagsInGroupTy = std::map<StringRef, GroupInfo>;
145
146/// Invert the 1-[0/1] mapping of diags to group into a one to many
147/// mapping of groups to diags in the group.
148static void groupDiagnostics(ArrayRef<const Record *> Diags,
149 ArrayRef<const Record *> DiagGroups,
150 DiagsInGroupTy &DiagsInGroup) {
151 for (const Record *R : Diags) {
152 const auto *DI = dyn_cast<DefInit>(Val: R->getValueInit(FieldName: "Group"));
153 if (!DI)
154 continue;
155 assert(R->getValueAsDef("Class")->getName() != "CLASS_NOTE" &&
156 "Note can't be in a DiagGroup");
157 StringRef GroupName = DI->getDef()->getValueAsString(FieldName: "GroupName");
158 DiagsInGroup[GroupName].DiagsInGroup.push_back(x: R);
159 }
160
161 // Add all DiagGroup's to the DiagsInGroup list to make sure we pick up empty
162 // groups (these are warnings that GCC supports that clang never produces).
163 for (const Record *Group : DiagGroups) {
164 GroupInfo &GI = DiagsInGroup[Group->getValueAsString(FieldName: "GroupName")];
165 GI.GroupName = Group->getName();
166 GI.Defs.push_back(Elt: Group);
167
168 for (const Record *SubGroup : Group->getValueAsListOfDefs(FieldName: "SubGroups"))
169 GI.SubGroups.push_back(x: SubGroup->getValueAsString(FieldName: "GroupName"));
170 }
171
172 // Assign unique ID numbers to the groups.
173 for (auto [IdNo, Iter] : enumerate(First&: DiagsInGroup))
174 Iter.second.IDNo = IdNo;
175
176 // Warn if the same group is defined more than once (including implicitly).
177 for (auto &Group : DiagsInGroup) {
178 if (Group.second.Defs.size() == 1 &&
179 (!Group.second.Defs.front()->isAnonymous() ||
180 Group.second.DiagsInGroup.size() <= 1))
181 continue;
182
183 bool First = true;
184 for (const Record *Def : Group.second.Defs) {
185 // Skip implicit definitions from diagnostics; we'll report those
186 // separately below.
187 bool IsImplicit = false;
188 for (const Record *Diag : Group.second.DiagsInGroup) {
189 if (cast<DefInit>(Val: Diag->getValueInit(FieldName: "Group"))->getDef() == Def) {
190 IsImplicit = true;
191 break;
192 }
193 }
194 if (IsImplicit)
195 continue;
196
197 SMLoc Loc = Def->getLoc().front();
198 if (First) {
199 SrcMgr.PrintMessage(Loc, Kind: SourceMgr::DK_Error,
200 Msg: Twine("group '") + Group.first +
201 "' is defined more than once");
202 First = false;
203 } else {
204 SrcMgr.PrintMessage(Loc, Kind: SourceMgr::DK_Note, Msg: "also defined here");
205 }
206 }
207
208 for (const Record *Diag : Group.second.DiagsInGroup) {
209 if (!cast<DefInit>(Val: Diag->getValueInit(FieldName: "Group"))->getDef()->isAnonymous())
210 continue;
211
212 SMLoc Loc = Diag->getLoc().front();
213 if (First) {
214 SrcMgr.PrintMessage(Loc, Kind: SourceMgr::DK_Error,
215 Msg: Twine("group '") + Group.first +
216 "' is implicitly defined more than once");
217 First = false;
218 } else {
219 SrcMgr.PrintMessage(Loc, Kind: SourceMgr::DK_Note,
220 Msg: "also implicitly defined here");
221 }
222 }
223 }
224}
225
226//===----------------------------------------------------------------------===//
227// Infer members of -Wpedantic.
228//===----------------------------------------------------------------------===//
229
230typedef std::vector<const Record *> RecordVec;
231typedef DenseSet<const Record *> RecordSet;
232typedef PointerUnion<RecordVec *, RecordSet *> VecOrSet;
233
234namespace {
235class InferPedantic {
236 typedef DenseMap<const Record *, std::pair<unsigned, std::optional<unsigned>>>
237 GMap;
238
239 DiagGroupParentMap &DiagGroupParents;
240 ArrayRef<const Record *> Diags;
241 const std::vector<const Record *> DiagGroups;
242 DiagsInGroupTy &DiagsInGroup;
243 DenseSet<const Record *> DiagsSet;
244 GMap GroupCount;
245public:
246 InferPedantic(DiagGroupParentMap &DiagGroupParents,
247 ArrayRef<const Record *> Diags,
248 ArrayRef<const Record *> DiagGroups,
249 DiagsInGroupTy &DiagsInGroup)
250 : DiagGroupParents(DiagGroupParents), Diags(Diags),
251 DiagGroups(DiagGroups), DiagsInGroup(DiagsInGroup) {}
252
253 /// Compute the set of diagnostics and groups that are immediately
254 /// in -Wpedantic.
255 void compute(VecOrSet DiagsInPedantic,
256 VecOrSet GroupsInPedantic);
257
258private:
259 /// Determine whether a group is a subgroup of another group.
260 bool isSubGroupOfGroup(const Record *Group, StringRef RootGroupName);
261
262 /// Determine if the diagnostic is an extension.
263 bool isExtension(const Record *Diag);
264
265 /// Determine if the diagnostic is off by default.
266 bool isOffByDefault(const Record *Diag);
267
268 /// Increment the count for a group, and transitively marked
269 /// parent groups when appropriate.
270 void markGroup(const Record *Group);
271
272 /// Return true if the diagnostic is in a pedantic group.
273 bool groupInPedantic(const Record *Group, bool increment = false);
274};
275} // end anonymous namespace
276
277bool InferPedantic::isSubGroupOfGroup(const Record *Group, StringRef GName) {
278 StringRef GroupName = Group->getValueAsString(FieldName: "GroupName");
279 if (GName == GroupName)
280 return true;
281
282 for (const Record *Parent : DiagGroupParents.getParents(Group))
283 if (isSubGroupOfGroup(Group: Parent, GName))
284 return true;
285
286 return false;
287}
288
289/// Determine if the diagnostic is an extension.
290bool InferPedantic::isExtension(const Record *Diag) {
291 return Diag->getValueAsDef(FieldName: "Class")->getName() == "CLASS_EXTENSION";
292}
293
294bool InferPedantic::isOffByDefault(const Record *Diag) {
295 return Diag->getValueAsDef(FieldName: "DefaultSeverity")->getValueAsString(FieldName: "Name") ==
296 "Ignored";
297}
298
299bool InferPedantic::groupInPedantic(const Record *Group, bool increment) {
300 GMap::mapped_type &V = GroupCount[Group];
301 // Lazily compute the threshold value for the group count.
302 if (!V.second) {
303 const GroupInfo &GI = DiagsInGroup[Group->getValueAsString(FieldName: "GroupName")];
304 V.second = GI.SubGroups.size() + GI.DiagsInGroup.size();
305 }
306
307 if (increment)
308 ++V.first;
309
310 // Consider a group in -Wpendatic IFF if has at least one diagnostic
311 // or subgroup AND all of those diagnostics and subgroups are covered
312 // by -Wpedantic via our computation.
313 return V.first != 0 && V.first == *V.second;
314}
315
316void InferPedantic::markGroup(const Record *Group) {
317 // If all the diagnostics and subgroups have been marked as being
318 // covered by -Wpedantic, increment the count of parent groups. Once the
319 // group's count is equal to the number of subgroups and diagnostics in
320 // that group, we can safely add this group to -Wpedantic.
321 if (groupInPedantic(Group, /* increment */ true))
322 for (const Record *Parent : DiagGroupParents.getParents(Group))
323 markGroup(Group: Parent);
324}
325
326void InferPedantic::compute(VecOrSet DiagsInPedantic,
327 VecOrSet GroupsInPedantic) {
328 // All extensions that are not on by default are implicitly in the
329 // "pedantic" group. For those that aren't explicitly included in -Wpedantic,
330 // mark them for consideration to be included in -Wpedantic directly.
331 for (const Record *R : Diags) {
332 if (!isExtension(Diag: R) || !isOffByDefault(Diag: R))
333 continue;
334 DiagsSet.insert(V: R);
335 if (const auto *Group = dyn_cast<DefInit>(Val: R->getValueInit(FieldName: "Group"))) {
336 const Record *GroupRec = Group->getDef();
337 if (!isSubGroupOfGroup(Group: GroupRec, GName: "pedantic")) {
338 markGroup(Group: GroupRec);
339 }
340 }
341 }
342
343 // Compute the set of diagnostics that are directly in -Wpedantic. We
344 // march through Diags a second time to ensure the results are emitted
345 // in deterministic order.
346 for (const Record *R : Diags) {
347 if (!DiagsSet.count(V: R))
348 continue;
349 // Check if the group is implicitly in -Wpedantic. If so,
350 // the diagnostic should not be directly included in the -Wpedantic
351 // diagnostic group.
352 if (const auto *Group = dyn_cast<DefInit>(Val: R->getValueInit(FieldName: "Group")))
353 if (groupInPedantic(Group: Group->getDef()))
354 continue;
355
356 // The diagnostic is not included in a group that is (transitively) in
357 // -Wpedantic. Include it in -Wpedantic directly.
358 if (auto *V = dyn_cast<RecordVec *>(Val&: DiagsInPedantic))
359 V->push_back(x: R);
360 else
361 cast<RecordSet *>(Val&: DiagsInPedantic)->insert(V: R);
362 }
363
364 if (!GroupsInPedantic)
365 return;
366
367 // Compute the set of groups that are directly in -Wpedantic. We
368 // march through the groups to ensure the results are emitted
369 /// in a deterministc order.
370 for (const Record *Group : DiagGroups) {
371 if (!groupInPedantic(Group))
372 continue;
373
374 const std::vector<const Record *> &Parents =
375 DiagGroupParents.getParents(Group);
376 bool AllParentsInPedantic =
377 all_of(Range: Parents, P: [&](const Record *R) { return groupInPedantic(Group: R); });
378 // If all the parents are in -Wpedantic, this means that this diagnostic
379 // group will be indirectly included by -Wpedantic already. In that
380 // case, do not add it directly to -Wpedantic. If the group has no
381 // parents, obviously it should go into -Wpedantic.
382 if (Parents.size() > 0 && AllParentsInPedantic)
383 continue;
384
385 if (auto *V = dyn_cast<RecordVec *>(Val&: GroupsInPedantic))
386 V->push_back(x: Group);
387 else
388 cast<RecordSet *>(Val&: GroupsInPedantic)->insert(V: Group);
389 }
390}
391
392namespace {
393enum PieceKind {
394 MultiPieceClass,
395 TextPieceClass,
396 PlaceholderPieceClass,
397 SelectPieceClass,
398 EnumSelectPieceClass,
399 PluralPieceClass,
400 DiffPieceClass,
401 SubstitutionPieceClass,
402};
403
404enum ModifierType {
405 MT_Unknown,
406 MT_Placeholder,
407 MT_Select,
408 MT_EnumSelect,
409 MT_Sub,
410 MT_Plural,
411 MT_Diff,
412 MT_Ordinal,
413 MT_Human,
414 MT_S,
415 MT_Q,
416 MT_ObjCClass,
417 MT_ObjCInstance,
418 MT_Quoted,
419};
420
421static StringRef getModifierName(ModifierType MT) {
422 switch (MT) {
423 case MT_EnumSelect:
424 case MT_Select:
425 return "select";
426 case MT_Sub:
427 return "sub";
428 case MT_Diff:
429 return "diff";
430 case MT_Plural:
431 return "plural";
432 case MT_Ordinal:
433 return "ordinal";
434 case MT_Human:
435 return "human";
436 case MT_S:
437 return "s";
438 case MT_Q:
439 return "q";
440 case MT_Placeholder:
441 return "";
442 case MT_ObjCClass:
443 return "objcclass";
444 case MT_ObjCInstance:
445 return "objcinstance";
446 case MT_Quoted:
447 return "quoted";
448 case MT_Unknown:
449 llvm_unreachable("invalid modifier type");
450 }
451 // Unhandled case
452 llvm_unreachable("invalid modifier type");
453}
454
455struct Piece {
456 // This type and its derived classes are move-only.
457 Piece(PieceKind Kind) : ClassKind(Kind) {}
458 Piece(Piece const &O) = delete;
459 Piece &operator=(Piece const &) = delete;
460 virtual ~Piece() {}
461
462 PieceKind getPieceClass() const { return ClassKind; }
463 static bool classof(const Piece *) { return true; }
464
465private:
466 PieceKind ClassKind;
467};
468
469struct MultiPiece : Piece {
470 MultiPiece() : Piece(MultiPieceClass) {}
471 MultiPiece(std::vector<Piece *> Pieces)
472 : Piece(MultiPieceClass), Pieces(std::move(Pieces)) {}
473
474 std::vector<Piece *> Pieces;
475
476 static bool classof(const Piece *P) {
477 return P->getPieceClass() == MultiPieceClass;
478 }
479};
480
481struct TextPiece : Piece {
482 StringRef Role;
483 std::string Text;
484 TextPiece(StringRef Text, StringRef Role = "")
485 : Piece(TextPieceClass), Role(Role), Text(Text.str()) {}
486
487 static bool classof(const Piece *P) {
488 return P->getPieceClass() == TextPieceClass;
489 }
490};
491
492struct PlaceholderPiece : Piece {
493 ModifierType Kind;
494 int Index;
495 PlaceholderPiece(ModifierType Kind, int Index)
496 : Piece(PlaceholderPieceClass), Kind(Kind), Index(Index) {}
497
498 static bool classof(const Piece *P) {
499 return P->getPieceClass() == PlaceholderPieceClass;
500 }
501};
502
503struct SelectPiece : Piece {
504protected:
505 SelectPiece(PieceKind Kind, ModifierType ModKind)
506 : Piece(Kind), ModKind(ModKind) {}
507
508public:
509 SelectPiece(ModifierType ModKind) : SelectPiece(SelectPieceClass, ModKind) {}
510
511 ModifierType ModKind;
512 std::vector<Piece *> Options;
513 int Index = 0;
514
515 static bool classof(const Piece *P) {
516 return P->getPieceClass() == SelectPieceClass ||
517 P->getPieceClass() == EnumSelectPieceClass ||
518 P->getPieceClass() == PluralPieceClass;
519 }
520};
521
522struct EnumSelectPiece : SelectPiece {
523 EnumSelectPiece() : SelectPiece(EnumSelectPieceClass, MT_EnumSelect) {}
524
525 StringRef EnumName;
526 std::vector<StringRef> OptionEnumNames;
527
528 static bool classof(const Piece *P) {
529 return P->getPieceClass() == EnumSelectPieceClass;
530 }
531};
532
533struct EnumValuePiece : Piece {
534 ModifierType Kind;
535};
536
537struct PluralPiece : SelectPiece {
538 PluralPiece() : SelectPiece(PluralPieceClass, MT_Plural) {}
539
540 std::vector<Piece *> OptionPrefixes;
541 int Index = 0;
542
543 static bool classof(const Piece *P) {
544 return P->getPieceClass() == PluralPieceClass;
545 }
546};
547
548struct DiffPiece : Piece {
549 DiffPiece() : Piece(DiffPieceClass) {}
550
551 Piece *Parts[4] = {};
552 int Indexes[2] = {};
553
554 static bool classof(const Piece *P) {
555 return P->getPieceClass() == DiffPieceClass;
556 }
557};
558
559struct SubstitutionPiece : Piece {
560 SubstitutionPiece() : Piece(SubstitutionPieceClass) {}
561
562 std::string Name;
563 std::vector<int> Modifiers;
564
565 static bool classof(const Piece *P) {
566 return P->getPieceClass() == SubstitutionPieceClass;
567 }
568};
569
570/// Diagnostic text, parsed into pieces.
571
572
573struct DiagnosticTextBuilder {
574 DiagnosticTextBuilder(DiagnosticTextBuilder const &) = delete;
575 DiagnosticTextBuilder &operator=(DiagnosticTextBuilder const &) = delete;
576
577 DiagnosticTextBuilder(const RecordKeeper &Records) {
578 // Build up the list of substitution records.
579 for (auto *S : Records.getAllDerivedDefinitions(ClassName: "TextSubstitution")) {
580 EvaluatingRecordGuard Guard(&EvaluatingRecord, S);
581 Substitutions.try_emplace(
582 Key: S->getName(), Args: DiagText(*this, S->getValueAsString(FieldName: "Substitution")));
583 }
584
585 // Check that no diagnostic definitions have the same name as a
586 // substitution.
587 for (const Record *Diag : Records.getAllDerivedDefinitions(ClassName: "Diagnostic")) {
588 StringRef Name = Diag->getName();
589 if (Substitutions.count(Key: Name))
590 llvm::PrintFatalError(
591 ErrorLoc: Diag->getLoc(),
592 Msg: "Diagnostic '" + Name +
593 "' has same name as TextSubstitution definition");
594 }
595 }
596
597 std::string buildForDocumentation(StringRef Role, const Record *R);
598 std::string buildForDefinition(const Record *R);
599 llvm::SmallVector<std::pair<
600 std::string, llvm::SmallVector<std::pair<unsigned, std::string>>>>
601 buildForEnum(const Record *R);
602
603 Piece *getSubstitution(SubstitutionPiece *S) const {
604 auto It = Substitutions.find(Key: S->Name);
605 if (It == Substitutions.end())
606 llvm::PrintFatalError(Msg: "Failed to find substitution with name: " +
607 S->Name);
608 return It->second.Root;
609 }
610
611 [[noreturn]] void PrintFatalError(Twine const &Msg) const {
612 assert(EvaluatingRecord && "not evaluating a record?");
613 llvm::PrintFatalError(ErrorLoc: EvaluatingRecord->getLoc(), Msg);
614 }
615
616private:
617 struct DiagText {
618 DiagnosticTextBuilder &Builder;
619 std::vector<Piece *> AllocatedPieces;
620 Piece *Root = nullptr;
621
622 template <class T, class... Args> T *New(Args &&... args) {
623 static_assert(std::is_base_of<Piece, T>::value, "must be piece");
624 T *Mem = new T(std::forward<Args>(args)...);
625 AllocatedPieces.push_back(Mem);
626 return Mem;
627 }
628
629 DiagText(DiagnosticTextBuilder &Builder, StringRef Text)
630 : Builder(Builder), Root(parseDiagText(Text, Stop: StopAt::End)) {}
631
632 enum class StopAt {
633 // Parse until the end of the string.
634 End,
635 // Additionally stop if we hit a non-nested '|' or '}'.
636 PipeOrCloseBrace,
637 // Additionally stop if we hit a non-nested '$'.
638 Dollar,
639 };
640
641 Piece *parseDiagText(StringRef &Text, StopAt Stop);
642 int parseModifier(StringRef &) const;
643
644 public:
645 DiagText(DiagText &&O) noexcept
646 : Builder(O.Builder), AllocatedPieces(std::move(O.AllocatedPieces)),
647 Root(O.Root) {
648 O.Root = nullptr;
649 }
650 // The move assignment operator is defined as deleted pending further
651 // motivation.
652 DiagText &operator=(DiagText &&) = delete;
653
654 // The copy constrcutor and copy assignment operator is defined as deleted
655 // pending further motivation.
656 DiagText(const DiagText &) = delete;
657 DiagText &operator=(const DiagText &) = delete;
658
659 ~DiagText() {
660 for (Piece *P : AllocatedPieces)
661 delete P;
662 }
663 };
664
665private:
666 const Record *EvaluatingRecord = nullptr;
667 struct EvaluatingRecordGuard {
668 EvaluatingRecordGuard(const Record **Dest, const Record *New)
669 : Dest(Dest), Old(*Dest) {
670 *Dest = New;
671 }
672 ~EvaluatingRecordGuard() { *Dest = Old; }
673 const Record **Dest;
674 const Record *Old;
675 };
676
677 StringMap<DiagText> Substitutions;
678};
679
680template <class Derived> struct DiagTextVisitor {
681 using ModifierMappingsType = std::optional<std::vector<int>>;
682
683private:
684 Derived &getDerived() { return static_cast<Derived &>(*this); }
685
686public:
687 std::vector<int>
688 getSubstitutionMappings(SubstitutionPiece *P,
689 const ModifierMappingsType &Mappings) const {
690 std::vector<int> NewMappings;
691 for (int Idx : P->Modifiers)
692 NewMappings.push_back(mapIndex(Idx, Mappings));
693 return NewMappings;
694 }
695
696 struct SubstitutionContext {
697 SubstitutionContext(DiagTextVisitor &Visitor, SubstitutionPiece *P)
698 : Visitor(Visitor) {
699 Substitution = Visitor.Builder.getSubstitution(S: P);
700 OldMappings = std::move(Visitor.ModifierMappings);
701 std::vector<int> NewMappings =
702 Visitor.getSubstitutionMappings(P, Mappings: OldMappings);
703 Visitor.ModifierMappings = std::move(NewMappings);
704 }
705
706 ~SubstitutionContext() {
707 Visitor.ModifierMappings = std::move(OldMappings);
708 }
709
710 private:
711 DiagTextVisitor &Visitor;
712 std::optional<std::vector<int>> OldMappings;
713
714 public:
715 Piece *Substitution;
716 };
717
718public:
719 DiagTextVisitor(DiagnosticTextBuilder &Builder) : Builder(Builder) {}
720
721 void Visit(Piece *P) {
722 switch (P->getPieceClass()) {
723#define CASE(T) \
724 case T##PieceClass: \
725 return getDerived().Visit##T(static_cast<T##Piece *>(P))
726 CASE(Multi);
727 CASE(Text);
728 CASE(Placeholder);
729 CASE(Select);
730 CASE(EnumSelect);
731 CASE(Plural);
732 CASE(Diff);
733 CASE(Substitution);
734#undef CASE
735 }
736 }
737
738 void VisitSubstitution(SubstitutionPiece *P) {
739 SubstitutionContext Guard(*this, P);
740 Visit(P: Guard.Substitution);
741 }
742
743 int mapIndex(int Idx,
744 ModifierMappingsType const &ModifierMappings) const {
745 if (!ModifierMappings)
746 return Idx;
747 if (ModifierMappings->size() <= static_cast<unsigned>(Idx))
748 Builder.PrintFatalError(Msg: "Modifier value '" + std::to_string(val: Idx) +
749 "' is not valid for this mapping (has " +
750 std::to_string(val: ModifierMappings->size()) +
751 " mappings)");
752 return (*ModifierMappings)[Idx];
753 }
754
755 int mapIndex(int Idx) const {
756 return mapIndex(Idx, ModifierMappings);
757 }
758
759protected:
760 DiagnosticTextBuilder &Builder;
761 ModifierMappingsType ModifierMappings;
762};
763
764/// Markers written in front of a table cell. Nested tables are distinguished
765/// by indentation rather than by fence length: openCell indents the
766/// continuation lines of a cell by the width of the marker written here, which
767/// puts an inner fence exactly one marker to the right of its parent's. The
768/// two markers must therefore stay the same width.
769constexpr StringRef RowMarker = "* - ";
770constexpr StringRef ColumnMarker = " - ";
771static_assert(RowMarker.size() == ColumnMarker.size());
772
773struct DiagTextDocPrinter : DiagTextVisitor<DiagTextDocPrinter> {
774 using BaseTy = DiagTextVisitor<DiagTextDocPrinter>;
775 DiagTextDocPrinter(DiagnosticTextBuilder &Builder, std::string &Result)
776 : BaseTy(Builder), Result(Result) {}
777
778 bool containsTablePiece(Piece *P) const {
779 if (isa<SelectPiece, DiffPiece>(Val: P))
780 return true;
781 if (auto *Multi = dyn_cast<MultiPiece>(Val: P))
782 return any_of(Range&: Multi->Pieces,
783 P: [this](Piece *Child) { return containsTablePiece(P: Child); });
784 if (auto *Substitution = dyn_cast<SubstitutionPiece>(Val: P))
785 return containsTablePiece(P: Builder.getSubstitution(S: Substitution));
786 return false;
787 }
788
789 /// Append \p P without letting it open a table of its own: a MultiPiece is
790 /// spliced in piece by piece and a substitution is replaced by its
791 /// expansion. Only pieces that contain no table belong here.
792 void appendInline(Piece *P) {
793 assert(!containsTablePiece(P));
794 if (auto *Multi = dyn_cast<MultiPiece>(Val: P)) {
795 for (Piece *Child : Multi->Pieces)
796 appendInline(P: Child);
797 } else if (auto *Substitution = dyn_cast<SubstitutionPiece>(Val: P)) {
798 SubstitutionContext Guard(*this, Substitution);
799 appendInline(P: Guard.Substitution);
800 } else {
801 Visit(P);
802 }
803 }
804
805 /// Append \p P as the body of a cell, using a table for it only if it needs
806 /// one.
807 void appendCellBody(Piece *P) {
808 if (containsTablePiece(P))
809 Visit(P);
810 else
811 appendInline(P);
812 }
813
814 void VisitSubstitution(SubstitutionPiece *P) {
815 SubstitutionContext Guard(*this, P);
816 appendCellBody(P: Guard.Substitution);
817 }
818
819 /// Start a new line, indenting it to the depth of the enclosing cells. A
820 /// blank line is left blank rather than padded out with spaces.
821 void newLine(bool Blank = false) {
822 Result += '\n';
823 if (!Blank)
824 Result.append(n: Indent, c: ' ');
825 }
826
827 /// The cells of the table currently being written. Cells are written
828 /// straight into \p Result, so the only state a table needs is how many
829 /// cells it has so far and, while a cell is open, where in \p Result that
830 /// cell began.
831 struct TableState {
832 /// Whether every cell starts a row of its own, rather than the first cell
833 /// starting a row that the rest extend.
834 bool OnePiecePerRow;
835 unsigned NumCells = 0;
836 bool CellOpen = false;
837 /// Offsets of the open cell's marker and of the body following it.
838 size_t MarkerStart = 0;
839 size_t BodyStart = 0;
840 };
841
842 void startTable() {
843 Result += ":::{list-table}";
844 newLine();
845 Result += ":widths: auto";
846 newLine(/*Blank=*/true);
847 }
848
849 void endTable() {
850 newLine();
851 Result += ":::";
852 }
853
854 /// Open a cell, unless one is already open, by writing its marker. The
855 /// body's continuation lines line up just past the marker.
856 void openCell(TableState &T) {
857 if (T.CellOpen)
858 return;
859 T.MarkerStart = Result.size();
860 newLine();
861 Result += (T.OnePiecePerRow || T.NumCells == 0) ? RowMarker : ColumnMarker;
862 T.BodyStart = Result.size();
863 T.CellOpen = true;
864 Indent += RowMarker.size();
865 }
866
867 /// Close the open cell. A cell whose body turned out to be empty is dropped
868 /// entirely when \p DropIfEmpty, and otherwise keeps its marker, minus the
869 /// trailing space that would be left dangling.
870 void closeCell(TableState &T, bool DropIfEmpty) {
871 assert(T.CellOpen);
872 T.CellOpen = false;
873 Indent -= RowMarker.size();
874 if (Result.size() != T.BodyStart) {
875 ++T.NumCells;
876 } else if (DropIfEmpty) {
877 Result.resize(n: T.MarkerStart);
878 } else {
879 Result.pop_back();
880 ++T.NumCells;
881 }
882 }
883
884 /// Distribute \p Pieces over the cells of the table \p T is writing. A cell
885 /// is either inline text or a nested table, never a mixture, so inline
886 /// pieces extend the open cell while a piece that needs a table of its own
887 /// closes it and takes a cell to itself.
888 void appendPiecesToCells(ArrayRef<Piece *> Pieces, TableState &T) {
889 for (Piece *Child : Pieces) {
890 if (auto *Substitution = dyn_cast<SubstitutionPiece>(Val: Child);
891 Substitution &&
892 isa<MultiPiece>(Val: Builder.getSubstitution(S: Substitution))) {
893 SubstitutionContext Guard(*this, Substitution);
894 appendPiecesToCells(Pieces: cast<MultiPiece>(Val: Guard.Substitution)->Pieces, T);
895 continue;
896 }
897 if (!containsTablePiece(P: Child)) {
898 openCell(T);
899 appendInline(P: Child);
900 continue;
901 }
902 if (T.CellOpen)
903 closeCell(T, /*DropIfEmpty=*/true);
904 openCell(T);
905 Visit(P: Child);
906 closeCell(T, /*DropIfEmpty=*/true);
907 }
908 }
909
910 void VisitMulti(MultiPiece *P) {
911 if (P->Pieces.empty())
912 return;
913 if (P->Pieces.size() == 1 && containsTablePiece(P: P->Pieces.front())) {
914 Visit(P: P->Pieces.front());
915 return;
916 }
917
918 startTable();
919 TableState T{/*OnePiecePerRow=*/false};
920 appendPiecesToCells(Pieces: P->Pieces, T);
921 if (T.CellOpen)
922 closeCell(T, /*DropIfEmpty=*/true);
923 endTable();
924 }
925
926 void VisitText(TextPiece *P) {
927 StringRef Text = P->Text;
928 while (Text.consume_front(Prefix: " "))
929 Result += "&nbsp;";
930
931 unsigned TrailingSpaces = 0;
932 while (Text.consume_back(Suffix: " "))
933 ++TrailingSpaces;
934
935 bool HasText = !Text.empty();
936 if (HasText && !P->Role.empty()) {
937 Result += "{";
938 Result += P->Role;
939 Result += "}`";
940 }
941 for (char C : Text) {
942 if (C == '`')
943 Result += "&#96;";
944 else {
945 if (C == '\\')
946 Result += '\\';
947 Result += C;
948 }
949 }
950 if (HasText && !P->Role.empty())
951 Result += '`';
952 for (unsigned I = 0; I != TrailingSpaces; ++I)
953 Result += "&nbsp;";
954 }
955
956 void VisitPlaceholder(PlaceholderPiece *P) {
957 // Use a role rather than plain `*A*` emphasis: two adjacent placeholders
958 // would render as `*A**B*`, which CommonMark parses as a single emphasis
959 // run containing literal asterisks.
960 Result += "{placeholder}`";
961 Result += char('A' + mapIndex(Idx: P->Index));
962 Result += '`';
963 }
964
965 void VisitSelect(SelectPiece *P) {
966 startTable();
967 TableState T{/*OnePiecePerRow=*/true};
968 for (Piece *Option : P->Options) {
969 openCell(T);
970 appendCellBody(P: Option);
971 closeCell(T, /*DropIfEmpty=*/false);
972 }
973 endTable();
974 }
975
976 void VisitEnumSelect(EnumSelectPiece *P) {
977 // Document this as if it were a 'select', which properly prints all of the
978 // options correctly in a readable/reasonable manner. There isn't really
979 // anything valuable we could add to readers here.
980 VisitSelect(P);
981 }
982
983 void VisitPlural(PluralPiece *P) { VisitSelect(P); }
984
985 void VisitDiff(DiffPiece *P) {
986 // Render %diff{a $ b $ c|d}e,f as %select{a %e b %f c|d}.
987 PlaceholderPiece E(MT_Placeholder, P->Indexes[0]);
988 PlaceholderPiece F(MT_Placeholder, P->Indexes[1]);
989
990 MultiPiece FirstOption;
991 FirstOption.Pieces.push_back(x: P->Parts[0]);
992 FirstOption.Pieces.push_back(x: &E);
993 FirstOption.Pieces.push_back(x: P->Parts[1]);
994 FirstOption.Pieces.push_back(x: &F);
995 FirstOption.Pieces.push_back(x: P->Parts[2]);
996
997 SelectPiece Select(MT_Diff);
998 Select.Options.push_back(x: &FirstOption);
999 Select.Options.push_back(x: P->Parts[3]);
1000
1001 VisitSelect(P: &Select);
1002 }
1003
1004 std::string &Result;
1005 /// Number of spaces every new line is indented by, one marker per enclosing
1006 /// cell.
1007 unsigned Indent = 0;
1008};
1009
1010struct DiagEnumPrinter : DiagTextVisitor<DiagEnumPrinter> {
1011public:
1012 using BaseTy = DiagTextVisitor<DiagEnumPrinter>;
1013 using EnumeratorItem = std::pair<unsigned, std::string>;
1014 using EnumeratorList = llvm::SmallVector<EnumeratorItem>;
1015 using ResultTy = llvm::SmallVector<std::pair<std::string, EnumeratorList>>;
1016
1017 DiagEnumPrinter(DiagnosticTextBuilder &Builder, ResultTy &Result)
1018 : BaseTy(Builder), Result(Result) {}
1019
1020 ResultTy &Result;
1021
1022 void VisitMulti(MultiPiece *P) {
1023 for (auto *Child : P->Pieces)
1024 Visit(P: Child);
1025 }
1026 void VisitText(TextPiece *P) {}
1027 void VisitPlaceholder(PlaceholderPiece *P) {}
1028 void VisitDiff(DiffPiece *P) {}
1029 void VisitSelect(SelectPiece *P) {
1030 for (auto *D : P->Options)
1031 Visit(P: D);
1032 }
1033 void VisitPlural(PluralPiece *P) { VisitSelect(P); }
1034 void VisitEnumSelect(EnumSelectPiece *P) {
1035 assert(P->Options.size() == P->OptionEnumNames.size());
1036
1037 if (!P->EnumName.empty()) {
1038 EnumeratorList List;
1039
1040 for (const auto &Tup : llvm::enumerate(First&: P->OptionEnumNames))
1041 if (!Tup.value().empty())
1042 List.emplace_back(Args: Tup.index(), Args&: Tup.value());
1043
1044 Result.emplace_back(Args&: P->EnumName, Args&: List);
1045 }
1046
1047 VisitSelect(P);
1048 }
1049};
1050
1051struct DiagTextPrinter : DiagTextVisitor<DiagTextPrinter> {
1052public:
1053 using BaseTy = DiagTextVisitor<DiagTextPrinter>;
1054 DiagTextPrinter(DiagnosticTextBuilder &Builder, std::string &Result)
1055 : BaseTy(Builder), Result(Result) {}
1056
1057 void VisitMulti(MultiPiece *P) {
1058 for (auto *Child : P->Pieces)
1059 Visit(P: Child);
1060 }
1061 void VisitText(TextPiece *P) { Result += P->Text; }
1062 void VisitPlaceholder(PlaceholderPiece *P) {
1063 Result += "%";
1064 Result += getModifierName(MT: P->Kind);
1065 addInt(Val: mapIndex(Idx: P->Index));
1066 }
1067 void VisitSelect(SelectPiece *P) {
1068 Result += "%";
1069 Result += getModifierName(MT: P->ModKind);
1070 if (P->ModKind == MT_Select || P->ModKind == MT_EnumSelect) {
1071 Result += "{";
1072 for (auto *D : P->Options) {
1073 Visit(P: D);
1074 Result += '|';
1075 }
1076 if (!P->Options.empty())
1077 Result.erase(position: --Result.end());
1078 Result += '}';
1079 }
1080 addInt(Val: mapIndex(Idx: P->Index));
1081 }
1082
1083 void VisitPlural(PluralPiece *P) {
1084 Result += "%plural{";
1085 assert(P->Options.size() == P->OptionPrefixes.size());
1086 for (const auto [Prefix, Option] :
1087 zip_equal(t&: P->OptionPrefixes, u&: P->Options)) {
1088 if (Prefix)
1089 Visit(P: Prefix);
1090 Visit(P: Option);
1091 Result += "|";
1092 }
1093 if (!P->Options.empty())
1094 Result.erase(position: --Result.end());
1095 Result += '}';
1096 addInt(Val: mapIndex(Idx: P->Index));
1097 }
1098
1099 void VisitEnumSelect(EnumSelectPiece *P) {
1100 // Print as if we are a 'select', which will result in the compiler just
1101 // treating this like a normal select. This way we don't have to do any
1102 // special work for the compiler to consume these.
1103 VisitSelect(P);
1104 }
1105
1106 void VisitDiff(DiffPiece *P) {
1107 Result += "%diff{";
1108 Visit(P: P->Parts[0]);
1109 Result += "$";
1110 Visit(P: P->Parts[1]);
1111 Result += "$";
1112 Visit(P: P->Parts[2]);
1113 Result += "|";
1114 Visit(P: P->Parts[3]);
1115 Result += "}";
1116 addInt(Val: mapIndex(Idx: P->Indexes[0]));
1117 Result += ",";
1118 addInt(Val: mapIndex(Idx: P->Indexes[1]));
1119 }
1120
1121 void addInt(int Val) { Result += std::to_string(val: Val); }
1122
1123 std::string &Result;
1124};
1125
1126int DiagnosticTextBuilder::DiagText::parseModifier(StringRef &Text) const {
1127 if (Text.empty() || !isdigit(Text[0]))
1128 Builder.PrintFatalError(Msg: "expected modifier in diagnostic");
1129 int Val = 0;
1130 do {
1131 Val *= 10;
1132 Val += Text[0] - '0';
1133 Text = Text.drop_front();
1134 } while (!Text.empty() && isdigit(Text[0]));
1135 return Val;
1136}
1137
1138Piece *DiagnosticTextBuilder::DiagText::parseDiagText(StringRef &Text,
1139 StopAt Stop) {
1140 std::vector<Piece *> Parsed;
1141
1142 constexpr StringLiteral StopSets[] = {"%", "%|}", "%|}$"};
1143 StringRef StopSet = StopSets[static_cast<int>(Stop)];
1144
1145 while (!Text.empty()) {
1146 size_t End = (size_t)-2;
1147 do
1148 End = Text.find_first_of(Chars: StopSet, From: End + 2);
1149 while (
1150 End < Text.size() - 1 && Text[End] == '%' &&
1151 (Text[End + 1] == '%' || Text[End + 1] == '|' || Text[End + 1] == '$'));
1152
1153 if (End) {
1154 Parsed.push_back(x: New<TextPiece>(args: Text.slice(Start: 0, End), args: "diagtext"));
1155 Text = Text.substr(Start: End);
1156 if (Text.empty())
1157 break;
1158 }
1159
1160 if (Text[0] == '|' || Text[0] == '}' || Text[0] == '$')
1161 break;
1162
1163 // Drop the '%'.
1164 Text = Text.drop_front();
1165
1166 // Extract the (optional) modifier.
1167 size_t ModLength = Text.find_first_of(Chars: "0123456789<{");
1168 StringRef Modifier = Text.slice(Start: 0, End: ModLength);
1169 Text = Text.substr(Start: ModLength);
1170 ModifierType ModType = StringSwitch<ModifierType>{Modifier}
1171 .Case(S: "select", Value: MT_Select)
1172 .Case(S: "enum_select", Value: MT_EnumSelect)
1173 .Case(S: "sub", Value: MT_Sub)
1174 .Case(S: "diff", Value: MT_Diff)
1175 .Case(S: "plural", Value: MT_Plural)
1176 .Case(S: "s", Value: MT_S)
1177 .Case(S: "ordinal", Value: MT_Ordinal)
1178 .Case(S: "human", Value: MT_Human)
1179 .Case(S: "q", Value: MT_Q)
1180 .Case(S: "objcclass", Value: MT_ObjCClass)
1181 .Case(S: "objcinstance", Value: MT_ObjCInstance)
1182 .Case(S: "quoted", Value: MT_Quoted)
1183 .Case(S: "", Value: MT_Placeholder)
1184 .Default(Value: MT_Unknown);
1185
1186 auto ExpectAndConsume = [&](StringRef Prefix) {
1187 if (!Text.consume_front(Prefix))
1188 Builder.PrintFatalError(Msg: "expected '" + Prefix + "' while parsing %" +
1189 Modifier);
1190 };
1191
1192 if (ModType != MT_EnumSelect && Text[0] == '<')
1193 Builder.PrintFatalError(Msg: "modifier '<' syntax not valid with %" +
1194 Modifier);
1195
1196 switch (ModType) {
1197 case MT_Unknown:
1198 Builder.PrintFatalError(Msg: "Unknown modifier type: " + Modifier);
1199 case MT_Select: {
1200 SelectPiece *Select = New<SelectPiece>(args: MT_Select);
1201 do {
1202 Text = Text.drop_front(); // '{' or '|'
1203 Select->Options.push_back(
1204 x: parseDiagText(Text, Stop: StopAt::PipeOrCloseBrace));
1205 assert(!Text.empty() && "malformed %select");
1206 } while (Text.front() == '|');
1207 ExpectAndConsume("}");
1208 Select->Index = parseModifier(Text);
1209 Parsed.push_back(x: Select);
1210 continue;
1211 }
1212 case MT_EnumSelect: {
1213 EnumSelectPiece *EnumSelect = New<EnumSelectPiece>();
1214 if (Text[0] != '<')
1215 Builder.PrintFatalError(Msg: "expected '<' after " + Modifier);
1216
1217 Text = Text.drop_front(); // Drop '<'
1218 size_t EnumNameLen = Text.find_first_of(C: '>');
1219 EnumSelect->EnumName = Text.slice(Start: 0, End: EnumNameLen);
1220 Text = Text.substr(Start: EnumNameLen);
1221 ExpectAndConsume(">");
1222
1223 if (Text[0] != '{')
1224 Builder.PrintFatalError(Msg: "expected '{' after " + Modifier);
1225
1226 do {
1227 Text = Text.drop_front(); // '{' or '|'
1228
1229 bool BracketsRequired = false;
1230 if (Text[0] == '%') {
1231 BracketsRequired = true;
1232 Text = Text.drop_front(); // '%'
1233 size_t OptionNameLen = Text.find_first_of(Chars: "{");
1234 EnumSelect->OptionEnumNames.push_back(x: Text.slice(Start: 0, End: OptionNameLen));
1235 Text = Text.substr(Start: OptionNameLen);
1236 } else {
1237 EnumSelect->OptionEnumNames.push_back(x: {});
1238 }
1239
1240 if (BracketsRequired)
1241 ExpectAndConsume("{");
1242 else if (Text.front() == '{') {
1243 Text = Text.drop_front();
1244 BracketsRequired = true;
1245 }
1246
1247 EnumSelect->Options.push_back(
1248 x: parseDiagText(Text, Stop: StopAt::PipeOrCloseBrace));
1249
1250 if (BracketsRequired)
1251 ExpectAndConsume("}");
1252
1253 assert(!Text.empty() && "malformed %select");
1254 } while (Text.front() == '|');
1255
1256 ExpectAndConsume("}");
1257 EnumSelect->Index = parseModifier(Text);
1258 Parsed.push_back(x: EnumSelect);
1259 continue;
1260 }
1261 case MT_Plural: {
1262 PluralPiece *Plural = New<PluralPiece>();
1263 do {
1264 Text = Text.drop_front(); // '{' or '|'
1265 size_t End = Text.find_first_of(C: ':');
1266 if (End == StringRef::npos)
1267 Builder.PrintFatalError(Msg: "expected ':' while parsing %plural");
1268 ++End;
1269 assert(!Text.empty());
1270 Plural->OptionPrefixes.push_back(
1271 x: New<TextPiece>(args: Text.slice(Start: 0, End), args: "diagtext"));
1272 Text = Text.substr(Start: End);
1273 Plural->Options.push_back(
1274 x: parseDiagText(Text, Stop: StopAt::PipeOrCloseBrace));
1275 assert(!Text.empty() && "malformed %plural");
1276 } while (Text.front() == '|');
1277 ExpectAndConsume("}");
1278 Plural->Index = parseModifier(Text);
1279 Parsed.push_back(x: Plural);
1280 continue;
1281 }
1282 case MT_Sub: {
1283 SubstitutionPiece *Sub = New<SubstitutionPiece>();
1284 ExpectAndConsume("{");
1285 size_t NameSize = Text.find_first_of(C: '}');
1286 assert(NameSize != size_t(-1) && "failed to find the end of the name");
1287 assert(NameSize != 0 && "empty name?");
1288 Sub->Name = Text.substr(Start: 0, N: NameSize).str();
1289 Text = Text.drop_front(N: NameSize);
1290 ExpectAndConsume("}");
1291 if (!Text.empty()) {
1292 while (true) {
1293 if (!isdigit(Text[0]))
1294 break;
1295 Sub->Modifiers.push_back(x: parseModifier(Text));
1296 if (!Text.consume_front(Prefix: ","))
1297 break;
1298 assert(!Text.empty() && isdigit(Text[0]) &&
1299 "expected another modifier");
1300 }
1301 }
1302 Parsed.push_back(x: Sub);
1303 continue;
1304 }
1305 case MT_Diff: {
1306 DiffPiece *Diff = New<DiffPiece>();
1307 ExpectAndConsume("{");
1308 Diff->Parts[0] = parseDiagText(Text, Stop: StopAt::Dollar);
1309 ExpectAndConsume("$");
1310 Diff->Parts[1] = parseDiagText(Text, Stop: StopAt::Dollar);
1311 ExpectAndConsume("$");
1312 Diff->Parts[2] = parseDiagText(Text, Stop: StopAt::PipeOrCloseBrace);
1313 ExpectAndConsume("|");
1314 Diff->Parts[3] = parseDiagText(Text, Stop: StopAt::PipeOrCloseBrace);
1315 ExpectAndConsume("}");
1316 Diff->Indexes[0] = parseModifier(Text);
1317 ExpectAndConsume(",");
1318 Diff->Indexes[1] = parseModifier(Text);
1319 Parsed.push_back(x: Diff);
1320 continue;
1321 }
1322 case MT_S: {
1323 SelectPiece *Select = New<SelectPiece>(args&: ModType);
1324 Select->Options.push_back(x: New<TextPiece>(args: ""));
1325 Select->Options.push_back(x: New<TextPiece>(args: "s", args: "diagtext"));
1326 Select->Index = parseModifier(Text);
1327 Parsed.push_back(x: Select);
1328 continue;
1329 }
1330 case MT_Q:
1331 case MT_Placeholder:
1332 case MT_ObjCClass:
1333 case MT_ObjCInstance:
1334 case MT_Quoted:
1335 case MT_Ordinal:
1336 case MT_Human: {
1337 Parsed.push_back(x: New<PlaceholderPiece>(args&: ModType, args: parseModifier(Text)));
1338 continue;
1339 }
1340 }
1341 }
1342
1343 return New<MultiPiece>(args&: Parsed);
1344}
1345
1346std::string DiagnosticTextBuilder::buildForDocumentation(StringRef Severity,
1347 const Record *R) {
1348 EvaluatingRecordGuard Guard(&EvaluatingRecord, R);
1349 StringRef Text = R->getValueAsString(FieldName: "Summary");
1350
1351 DiagText D(*this, Text);
1352 TextPiece *Prefix = D.New<TextPiece>(args&: Severity, args&: Severity);
1353 Prefix->Text += ": ";
1354 auto *MP = dyn_cast<MultiPiece>(Val: D.Root);
1355 if (!MP) {
1356 MP = D.New<MultiPiece>();
1357 MP->Pieces.push_back(x: D.Root);
1358 D.Root = MP;
1359 }
1360 MP->Pieces.insert(position: MP->Pieces.begin(), x: Prefix);
1361 std::string Result;
1362 DiagTextDocPrinter{*this, Result}.Visit(P: D.Root);
1363 // The printer indents the line it is on rather than the line it just wrote,
1364 // so it leaves the last line unterminated.
1365 Result += '\n';
1366 return Result;
1367}
1368
1369DiagEnumPrinter::ResultTy DiagnosticTextBuilder::buildForEnum(const Record *R) {
1370 EvaluatingRecordGuard Guard(&EvaluatingRecord, R);
1371 StringRef Text = R->getValueAsString(FieldName: "Summary");
1372 DiagText D(*this, Text);
1373 DiagEnumPrinter::ResultTy Result;
1374 DiagEnumPrinter{*this, Result}.Visit(P: D.Root);
1375 return Result;
1376}
1377
1378std::string DiagnosticTextBuilder::buildForDefinition(const Record *R) {
1379 EvaluatingRecordGuard Guard(&EvaluatingRecord, R);
1380 StringRef Text = R->getValueAsString(FieldName: "Summary");
1381 DiagText D(*this, Text);
1382 std::string Result;
1383 DiagTextPrinter{*this, Result}.Visit(P: D.Root);
1384 return Result;
1385}
1386
1387} // namespace
1388
1389//===----------------------------------------------------------------------===//
1390// Warning Tables (.inc file) generation.
1391//===----------------------------------------------------------------------===//
1392
1393static bool isError(const Record &Diag) {
1394 return Diag.getValueAsDef(FieldName: "Class")->getName() == "CLASS_ERROR";
1395}
1396
1397static bool isRemark(const Record &Diag) {
1398 return Diag.getValueAsDef(FieldName: "Class")->getName() == "CLASS_REMARK";
1399}
1400
1401// Presumes the text has been split at the first whitespace or hyphen.
1402static bool isExemptAtStart(StringRef Text) {
1403 // Fast path, the first character is lowercase or not alphanumeric.
1404 if (Text.empty() || isLower(C: Text[0]) || !isAlnum(C: Text[0]))
1405 return true;
1406
1407 // If the text is all uppercase (or numbers, +, or _), then we assume it's an
1408 // acronym and that's allowed. This covers cases like ISO, C23, C++14, and
1409 // OBJECT_MODE. However, if there's only a single letter other than "C", we
1410 // do not exempt it so that we catch a case like "A really bad idea" while
1411 // still allowing a case like "C does not allow...".
1412 if (all_of(Range&: Text, P: [](char C) {
1413 return isUpper(C) || isDigit(C) || C == '+' || C == '_';
1414 }))
1415 return Text.size() > 1 || Text[0] == 'C';
1416
1417 // Otherwise, there are a few other exemptions.
1418 return StringSwitch<bool>(Text)
1419 .Case(S: "AddressSanitizer", Value: true)
1420 .Case(S: "CFString", Value: true)
1421 .Case(S: "Clang", Value: true)
1422 .Case(S: "Fuchsia", Value: true)
1423 .Case(S: "GNUstep", Value: true)
1424 .Case(S: "IBOutletCollection", Value: true)
1425 .Case(S: "Itanium", Value: true)
1426 .Case(S: "Microsoft", Value: true)
1427 .Case(S: "Neon", Value: true)
1428 .StartsWith(S: "NSInvocation", Value: true) // NSInvocation, NSInvocation's
1429 .Case(S: "Objective", Value: true) // Objective-C (hyphen is a word boundary)
1430 .Case(S: "OpenACC", Value: true)
1431 .Case(S: "OpenCL", Value: true)
1432 .Case(S: "OpenMP", Value: true)
1433 .Case(S: "Pascal", Value: true)
1434 .Case(S: "Swift", Value: true)
1435 .Case(S: "Unicode", Value: true)
1436 .Case(S: "Vulkan", Value: true)
1437 .Case(S: "WebAssembly", Value: true)
1438 .Default(Value: false);
1439}
1440
1441// Does not presume the text has been split at all.
1442static bool isExemptAtEnd(StringRef Text) {
1443 // Rather than come up with a list of characters that are allowed, we go the
1444 // other way and look only for characters that are not allowed.
1445 switch (Text.back()) {
1446 default:
1447 return true;
1448 case '?':
1449 // Explicitly allowed to support "; did you mean?".
1450 return true;
1451 case '.':
1452 case '!':
1453 return false;
1454 }
1455}
1456
1457static void verifyDiagnosticWording(const Record &Diag) {
1458 StringRef FullDiagText = Diag.getValueAsString(FieldName: "Summary");
1459
1460 auto DiagnoseStart = [&](StringRef Text) {
1461 // Verify that the text does not start with a capital letter, except for
1462 // special cases that are exempt like ISO and C++. Find the first word
1463 // by looking for a word breaking character.
1464 char Separators[] = {' ', '-', ',', '}'};
1465 auto Iter = std::find_first_of(
1466 first1: Text.begin(), last1: Text.end(), first2: std::begin(arr&: Separators), last2: std::end(arr&: Separators));
1467
1468 StringRef First = Text.substr(Start: 0, N: Iter - Text.begin());
1469 if (!isExemptAtStart(Text: First)) {
1470 PrintError(Rec: &Diag,
1471 Msg: "Diagnostics should not start with a capital letter; '" +
1472 First + "' is invalid");
1473 }
1474 };
1475
1476 auto DiagnoseEnd = [&](StringRef Text) {
1477 // Verify that the text does not end with punctuation like '.' or '!'.
1478 if (!isExemptAtEnd(Text)) {
1479 PrintError(Rec: &Diag, Msg: "Diagnostics should not end with punctuation; '" +
1480 Text.substr(Start: Text.size() - 1, N: 1) + "' is invalid");
1481 }
1482 };
1483
1484 // If the diagnostic starts with %select, look through it to see whether any
1485 // of the options will cause a problem.
1486 if (FullDiagText.starts_with(Prefix: "%select{")) {
1487 // Do a balanced delimiter scan from the start of the text to find the
1488 // closing '}', skipping intermediary {} pairs.
1489
1490 size_t BraceCount = 1;
1491 constexpr size_t PercentSelectBraceLen = sizeof("%select{") - 1;
1492 auto Iter = FullDiagText.begin() + PercentSelectBraceLen;
1493 for (auto End = FullDiagText.end(); Iter != End; ++Iter) {
1494 char Ch = *Iter;
1495 if (Ch == '{')
1496 ++BraceCount;
1497 else if (Ch == '}')
1498 --BraceCount;
1499 if (!BraceCount)
1500 break;
1501 }
1502 // Defending against a malformed diagnostic string.
1503 if (BraceCount != 0)
1504 return;
1505
1506 StringRef SelectText =
1507 FullDiagText.substr(Start: PercentSelectBraceLen, N: Iter - FullDiagText.begin() -
1508 PercentSelectBraceLen);
1509 SmallVector<StringRef, 4> SelectPieces;
1510 SelectText.split(A&: SelectPieces, Separator: '|');
1511
1512 // Walk over all of the individual pieces of select text to see if any of
1513 // them start with an invalid character. If any of the select pieces is
1514 // empty, we need to look at the first word after the %select to see
1515 // whether that is invalid or not. If all of the pieces are fine, then we
1516 // don't need to check anything else about the start of the diagnostic.
1517 bool CheckSecondWord = false;
1518 for (StringRef Piece : SelectPieces) {
1519 if (Piece.empty())
1520 CheckSecondWord = true;
1521 else
1522 DiagnoseStart(Piece);
1523 }
1524
1525 if (CheckSecondWord) {
1526 // There was an empty select piece, so we need to check the second
1527 // word. This catches situations like '%select{|fine}0 Not okay'. Add
1528 // two to account for the closing curly brace and the number after it.
1529 StringRef AfterSelect =
1530 FullDiagText.substr(Start: Iter - FullDiagText.begin() + 2).ltrim();
1531 DiagnoseStart(AfterSelect);
1532 }
1533 } else {
1534 // If the start of the diagnostic is not %select, we can check the first
1535 // word and be done with it.
1536 DiagnoseStart(FullDiagText);
1537 }
1538
1539 // If the last character in the diagnostic is a number preceded by a }, scan
1540 // backwards to see if this is for a %select{...}0. If it is, we need to look
1541 // at each piece to see whether it ends in punctuation or not.
1542 bool StillNeedToDiagEnd = true;
1543 if (isDigit(C: FullDiagText.back()) && *(FullDiagText.end() - 2) == '}') {
1544 // Scan backwards to find the opening curly brace.
1545 size_t BraceCount = 1;
1546 auto Iter = FullDiagText.end() - sizeof("}0");
1547 for (auto End = FullDiagText.begin(); Iter != End; --Iter) {
1548 char Ch = *Iter;
1549 if (Ch == '}')
1550 ++BraceCount;
1551 else if (Ch == '{')
1552 --BraceCount;
1553 if (!BraceCount)
1554 break;
1555 }
1556 // Defending against a malformed diagnostic string.
1557 if (BraceCount != 0)
1558 return;
1559
1560 // Continue the backwards scan to find the word before the '{' to see if it
1561 // is 'select'.
1562 constexpr size_t SelectLen = sizeof("select") - 1;
1563 bool IsSelect =
1564 (FullDiagText.substr(Start: Iter - SelectLen - FullDiagText.begin(),
1565 N: SelectLen) == "select");
1566 if (IsSelect) {
1567 // Gather the content between the {} for the select in question so we can
1568 // split it into pieces.
1569 StillNeedToDiagEnd = false; // No longer need to handle the end.
1570 StringRef SelectText =
1571 FullDiagText.substr(Start: Iter - FullDiagText.begin() + /*{*/ 1,
1572 N: FullDiagText.end() - Iter - /*pos before }0*/ 3);
1573 SmallVector<StringRef, 4> SelectPieces;
1574 SelectText.split(A&: SelectPieces, Separator: '|');
1575 for (StringRef Piece : SelectPieces) {
1576 // Not worrying about a situation like: "this is bar. %select{foo|}0".
1577 if (!Piece.empty())
1578 DiagnoseEnd(Piece);
1579 }
1580 }
1581 }
1582
1583 // If we didn't already cover the diagnostic because of a %select, handle it
1584 // now.
1585 if (StillNeedToDiagEnd)
1586 DiagnoseEnd(FullDiagText);
1587
1588 // FIXME: This could also be improved by looking for instances of clang or
1589 // gcc in the diagnostic and recommend Clang or GCC instead. However, this
1590 // runs into odd situations like [[clang::warn_unused_result]],
1591 // #pragma clang, or --unwindlib=libgcc.
1592}
1593
1594/// ClangDiagsCompatIDsEmitter - Emit a set of 'compatibility diagnostic ids'
1595/// that map to a set of 2 regular diagnostic ids each and which are used to
1596/// simplify emitting compatibility warnings.
1597void clang::EmitClangDiagsCompatIDs(const llvm::RecordKeeper &Records,
1598 llvm::raw_ostream &OS,
1599 const std::string &Component) {
1600 ArrayRef<const Record *> Ids =
1601 Records.getAllDerivedDefinitions(ClassName: "CompatWarningId");
1602
1603 StringRef PrevComponent = "";
1604 for (auto [I, R] : enumerate(First: make_pointee_range(Range&: Ids))) {
1605 StringRef DiagComponent = R.getValueAsString(FieldName: "Component");
1606 if (!Component.empty() && Component != DiagComponent)
1607 continue;
1608
1609 StringRef CompatDiagName = R.getValueAsString(FieldName: "Name");
1610 StringRef Diag = R.getValueAsString(FieldName: "Diag");
1611 StringRef DiagPre = R.getValueAsString(FieldName: "DiagPre");
1612 int64_t CXXStdVer = R.getValueAsInt(FieldName: "Std");
1613
1614 // We don't want to create empty enums since some compilers (including
1615 // Clang) warn about that, so these macros are used to avoid having to
1616 // unconditionally write 'enum {' and '};' in the headers.
1617 if (PrevComponent != DiagComponent) {
1618 if (!PrevComponent.empty())
1619 OS << "DIAG_COMPAT_IDS_END()\n";
1620 OS << "DIAG_COMPAT_IDS_BEGIN()\n";
1621 PrevComponent = DiagComponent;
1622 }
1623
1624 // FIXME: We sometimes define multiple compat diagnostics with the same
1625 // name, e.g. 'constexpr_body_invalid_stmt' exists for C++14/20/23. It would
1626 // be nice if we could combine all of them into a single compatibility diag
1627 // id.
1628 OS << "DIAG_COMPAT_ID(" << I << ",";
1629 OS << CompatDiagName << "," << CXXStdVer << "," << Diag << "," << DiagPre;
1630 OS << ")\n";
1631 }
1632
1633 if (!PrevComponent.empty())
1634 OS << "DIAG_COMPAT_IDS_END()\n";
1635}
1636
1637/// ClangDiagsIntefaceEmitter - Emit the diagnostics interface header for
1638/// a Clang component.
1639void clang::EmitClangDiagsInterface(llvm::raw_ostream &OS,
1640 const std::string &Component) {
1641 if (Component.empty())
1642 PrintFatalError(Msg: "'-gen-clang-diags-iface' requires a component name");
1643
1644 std::string ComponentUpper = StringRef(Component).upper();
1645 const char *Comp = Component.c_str();
1646 const char *Upper = ComponentUpper.c_str();
1647
1648 OS << llvm::format(Fmt: R"c++(
1649namespace clang {
1650namespace diag {
1651enum {
1652#define DIAG(ENUM, FLAGS, DEFAULT_MAPPING, DESC, GROUP, SFINAE, NOWERROR, \
1653 SHOWINSYSHEADER, SHOWINSYSMACRO, DEFERRABLE, CATEGORY, STABLE_ID, \
1654 LEGACY_STABLE_IDS) \
1655 ENUM,
1656#define %sSTART
1657#include "clang/Basic/Diagnostic%sKinds.inc"
1658#undef DIAG
1659 NUM_BUILTIN_%s_DIAGNOSTICS
1660};
1661
1662#define DIAG_ENUM(ENUM_NAME) \
1663 namespace ENUM_NAME { \
1664 enum {
1665#define DIAG_ENUM_ITEM(IDX, NAME) NAME = IDX,
1666#define DIAG_ENUM_END() \
1667 } \
1668 ; \
1669 }
1670#include "clang/Basic/Diagnostic%sEnums.inc"
1671#undef DIAG_ENUM_END
1672#undef DIAG_ENUM_ITEM
1673#undef DIAG_ENUM
1674} // end namespace diag
1675
1676namespace diag_compat {
1677#define DIAG_COMPAT_IDS_BEGIN() enum {
1678#define DIAG_COMPAT_IDS_END() \
1679 } \
1680 ;
1681#define DIAG_COMPAT_ID(IDX, NAME, ...) NAME = IDX,
1682#include "clang/Basic/Diagnostic%sCompatIDs.inc"
1683#undef DIAG_COMPAT_ID
1684#undef DIAG_COMPAT_IDS_BEGIN
1685#undef DIAG_COMPAT_IDS_END
1686} // end namespace diag_compat
1687} // end namespace clang
1688)c++",
1689 Vals: Upper, Vals: Comp, Vals: Upper, Vals: Comp, Vals: Comp);
1690}
1691
1692/// ClangDiagsEnumsEmitter - The top-level class emits .def files containing
1693/// declarations of Clang diagnostic enums for selects.
1694void clang::EmitClangDiagsEnums(const RecordKeeper &Records, raw_ostream &OS,
1695 const std::string &Component) {
1696 DiagnosticTextBuilder DiagTextBuilder(Records);
1697 ArrayRef<const Record *> Diags =
1698 Records.getAllDerivedDefinitions(ClassName: "Diagnostic");
1699
1700 llvm::SmallVector<std::pair<const Record *, std::string>> EnumerationNames;
1701
1702 for (const Record &R : make_pointee_range(Range&: Diags)) {
1703 DiagEnumPrinter::ResultTy Enums = DiagTextBuilder.buildForEnum(R: &R);
1704
1705 for (auto &Enumeration : Enums) {
1706 bool ShouldPrint =
1707 Component.empty() || Component == R.getValueAsString(FieldName: "Component");
1708
1709 auto PreviousByName = llvm::find_if(Range&: EnumerationNames, P: [&](auto &Prev) {
1710 return Prev.second == Enumeration.first;
1711 });
1712
1713 if (PreviousByName != EnumerationNames.end()) {
1714 PrintError(Rec: &R,
1715 Msg: "Duplicate enumeration name '" + Enumeration.first + "'");
1716 PrintNote(NoteLoc: PreviousByName->first->getLoc(),
1717 Msg: "Previous diagnostic is here");
1718 }
1719
1720 EnumerationNames.emplace_back(Args: &R, Args&: Enumeration.first);
1721
1722 if (ShouldPrint)
1723 OS << "DIAG_ENUM(" << Enumeration.first << ")\n";
1724
1725 llvm::SmallVector<std::string> EnumeratorNames;
1726 for (auto &Enumerator : Enumeration.second) {
1727 if (llvm::is_contained(Range&: EnumeratorNames, Element: Enumerator.second))
1728 PrintError(Rec: &R,
1729 Msg: "Duplicate enumerator name '" + Enumerator.second + "'");
1730 EnumeratorNames.push_back(Elt: Enumerator.second);
1731
1732 if (ShouldPrint)
1733 OS << "DIAG_ENUM_ITEM(" << Enumerator.first << ", "
1734 << Enumerator.second << ")\n";
1735 }
1736 if (ShouldPrint)
1737 OS << "DIAG_ENUM_END()\n";
1738 }
1739 }
1740}
1741
1742//===----------------------------------------------------------------------===//
1743// Stable ID Tables generation
1744//===----------------------------------------------------------------------===//
1745
1746namespace {
1747
1748/// Holds the string table for all Stable IDs, plus the arrays of legacy Stable
1749/// IDs for renamed diagnostics.
1750class DiagStableIDsMap {
1751 StringToOffsetTable StableIDs;
1752 std::vector<uint32_t> LegacyStableIDs;
1753 llvm::StringMap<uint32_t> LegacyStableIDsStartOffsets;
1754
1755public:
1756 DiagStableIDsMap(const RecordKeeper &Records) {
1757 LegacyStableIDs.push_back(x: 0); // Empty array at offset 0
1758
1759 for (const Record *Diag : Records.getAllDerivedDefinitions(ClassName: "Diagnostic")) {
1760 StringRef StableID = getStableID(R: *Diag);
1761 // Memoize the Stable ID
1762 StableIDs.GetOrAddStringOffset(Str: StableID);
1763
1764 auto LegacyIDList = Diag->getValueAsListOfStrings(FieldName: "LegacyStableIds");
1765 if (!LegacyIDList.empty()) {
1766 // Memoize any Legacy Stable IDs, and list their offsets in an array.
1767 size_t StartOffset = LegacyStableIDs.size();
1768 LegacyStableIDsStartOffsets.insert(
1769 KV: std::make_pair(x: Diag->getName(), y&: StartOffset));
1770 for (const auto LegacyID : LegacyIDList) {
1771 unsigned Offset = StableIDs.GetOrAddStringOffset(Str: LegacyID);
1772 LegacyStableIDs.push_back(x: Offset);
1773 }
1774 LegacyStableIDs.push_back(x: 0); // Terminate the array.
1775 }
1776 }
1777 }
1778
1779 /// Gets the string table offset of the Stable ID for the specified Diagnostic
1780 /// record.
1781 uint32_t getStableIDOffset(const Record &R) const {
1782 return StableIDs.GetStringOffset(Str: getStableID(R)).value();
1783 }
1784
1785 /// Gets the offset in the DiagLegacyStableIDs array of the first element of
1786 /// the diagnostic's list of legacy Stable IDs.
1787 uint32_t getLegacyStableIDsStartOffset(StringRef Name) const {
1788 // `lookup()` will return zero if not found, which is exactly what we want
1789 // anyway.
1790 return LegacyStableIDsStartOffsets.lookup(Key: Name);
1791 }
1792
1793 /// Emit diagnostic stable ID arrays and related data structures.
1794 ///
1795 /// This creates the table of stable IDs, plus the array of arrays of old
1796 /// stable IDs.
1797 ///
1798 /// \code
1799 /// #ifdef GET_DIAG_STABLE_ID_ARRAYS
1800 /// static const int32_t DiagOldStableIds[];
1801 /// static constexpr llvm::StringTable DiagStableIds;
1802 /// #endif
1803 /// \endcode
1804 void emit(raw_ostream &OS) const {
1805 OS << "\n#ifdef GET_DIAG_STABLE_ID_ARRAYS\n";
1806 emitStableIDs(OS);
1807 emitLegacyStableIDs(OS);
1808 OS << "#endif // GET_DIAG_STABLE_ID_ARRAYS\n\n";
1809 }
1810
1811private:
1812 /// Gets the Stable ID for the specified Diagnostic record.
1813 /// The Stable ID can be explicitly specified via the "StableId"
1814 /// property. If not specified explicitly, the Stable ID defaults
1815 /// to the name of the diagnostic.
1816 static StringRef getStableID(const Record &R) {
1817 StringRef StableID = R.getValueAsString(FieldName: "StableId");
1818 return StableID.empty() ? R.getName() : StableID;
1819 }
1820
1821 /// Emit a list of stable IDs, used by both the "StableId" and
1822 /// "LegacyStableIds" properties.
1823 ///
1824 /// This creates an `llvm::StringTable` of all the stable ids in use.
1825 void emitStableIDs(raw_ostream &OS) const {
1826 StableIDs.EmitStringTableDef(OS, Name: "DiagStableIDs");
1827 OS << "\n";
1828 }
1829
1830 /// Emit the array of legacy stable IDs for diagnostics.
1831 ///
1832 /// The array of stable IDs contains for each diagnostic a list of its legacy
1833 /// stable IDs. The individual lists are separated by '0'. Diagnostics with
1834 /// no legacy stable IDs are skipped.
1835 ///
1836 /// \code
1837 /// static const uint16_t DiagOldStableIds[] = {
1838 /// /* Empty */ 0,
1839 /// /* Diag0 */ 142, 0,
1840 /// /* Diag13 */ 265, 322, 399, 0
1841 /// }
1842 /// \endcode
1843 ///
1844 void emitLegacyStableIDs(raw_ostream &OS) const {
1845 OS << "static const uint32_t DiagLegacyStableIDs[] = {\n";
1846
1847 bool StartOfLine = true;
1848 for (auto Offset : LegacyStableIDs) {
1849 if (StartOfLine) {
1850 OS << " ";
1851 StartOfLine = false;
1852 }
1853 OS << Offset << ",";
1854 if (Offset > 0) {
1855 OS << " ";
1856 } else {
1857 OS << "\n";
1858 StartOfLine = true;
1859 }
1860 }
1861 OS << "};\n\n";
1862 }
1863};
1864} // namespace
1865
1866/// Emit the definitions of the Stable ID and old Stable ID tables.
1867void clang::EmitClangDiagsStableIDs(const RecordKeeper &Records,
1868 raw_ostream &OS) {
1869 DiagStableIDsMap StableIDs(Records);
1870 StableIDs.emit(OS);
1871}
1872
1873/// ClangDiagsDefsEmitter - The top-level class emits .def files containing
1874/// declarations of Clang diagnostics.
1875void clang::EmitClangDiagsDefs(const RecordKeeper &Records, raw_ostream &OS,
1876 const std::string &Component) {
1877 // Write the #if guard
1878 if (!Component.empty()) {
1879 std::string ComponentName = StringRef(Component).upper();
1880 OS << "#ifdef " << ComponentName << "START\n";
1881 OS << "__" << ComponentName << "START = DIAG_START_" << ComponentName
1882 << ",\n";
1883 OS << "#undef " << ComponentName << "START\n";
1884 OS << "#endif\n\n";
1885 }
1886
1887 DiagnosticTextBuilder DiagTextBuilder(Records);
1888
1889 ArrayRef<const Record *> Diags =
1890 Records.getAllDerivedDefinitions(ClassName: "Diagnostic");
1891
1892 ArrayRef<const Record *> DiagGroups =
1893 Records.getAllDerivedDefinitions(ClassName: "DiagGroup");
1894
1895 DiagsInGroupTy DiagsInGroup;
1896 groupDiagnostics(Diags, DiagGroups, DiagsInGroup);
1897
1898 DiagCategoryIDMap CategoryIDs(Records);
1899 DiagGroupParentMap DGParentMap(Records);
1900 DiagStableIDsMap StableIDs(Records);
1901
1902 // Compute the set of diagnostics that are in -Wpedantic.
1903 RecordSet DiagsInPedantic;
1904 InferPedantic inferPedantic(DGParentMap, Diags, DiagGroups, DiagsInGroup);
1905 inferPedantic.compute(DiagsInPedantic: &DiagsInPedantic, GroupsInPedantic: (RecordVec*)nullptr);
1906
1907 for (const Record &R : make_pointee_range(Range&: Diags)) {
1908 // Check if this is an error that is accidentally in a warning
1909 // group.
1910 if (isError(Diag: R)) {
1911 if (const auto *Group = dyn_cast<DefInit>(Val: R.getValueInit(FieldName: "Group"))) {
1912 const Record *GroupRec = Group->getDef();
1913 StringRef GroupName = GroupRec->getValueAsString(FieldName: "GroupName");
1914 PrintFatalError(ErrorLoc: R.getLoc(), Msg: "Error " + R.getName() +
1915 " cannot be in a warning group [" + GroupName + "]");
1916 }
1917 }
1918
1919 // Check that all remarks have an associated diagnostic group.
1920 if (isRemark(Diag: R)) {
1921 if (!isa<DefInit>(Val: R.getValueInit(FieldName: "Group"))) {
1922 PrintFatalError(ErrorLoc: R.getLoc(), Msg: "Error " + R.getName() +
1923 " not in any diagnostic group");
1924 }
1925 }
1926
1927 // Filter by component.
1928 if (!Component.empty() && Component != R.getValueAsString(FieldName: "Component"))
1929 continue;
1930
1931 // Validate diagnostic wording for common issues.
1932 verifyDiagnosticWording(Diag: R);
1933
1934 OS << "DIAG(" << R.getName() << ", ";
1935 OS << R.getValueAsDef(FieldName: "Class")->getName();
1936 OS << ", (unsigned)diag::Severity::"
1937 << R.getValueAsDef(FieldName: "DefaultSeverity")->getValueAsString(FieldName: "Name");
1938
1939 // Description string.
1940 OS << ", \"";
1941 OS.write_escaped(Str: DiagTextBuilder.buildForDefinition(R: &R)) << '"';
1942
1943 // Warning group associated with the diagnostic. This is stored as an index
1944 // into the alphabetically sorted warning group table.
1945 if (const auto *DI = dyn_cast<DefInit>(Val: R.getValueInit(FieldName: "Group"))) {
1946 auto I = DiagsInGroup.find(x: DI->getDef()->getValueAsString(FieldName: "GroupName"));
1947 assert(I != DiagsInGroup.end());
1948 OS << ", " << I->second.IDNo;
1949 } else if (DiagsInPedantic.count(V: &R)) {
1950 auto I = DiagsInGroup.find(x: "pedantic");
1951 assert(I != DiagsInGroup.end() && "pedantic group not defined");
1952 OS << ", " << I->second.IDNo;
1953 } else {
1954 OS << ", 0";
1955 }
1956
1957 // SFINAE response.
1958 OS << ", " << R.getValueAsDef(FieldName: "SFINAE")->getName();
1959
1960 // Default warning has no Werror bit.
1961 if (R.getValueAsBit(FieldName: "WarningNoWerror"))
1962 OS << ", true";
1963 else
1964 OS << ", false";
1965
1966 if (R.getValueAsBit(FieldName: "ShowInSystemHeader"))
1967 OS << ", true";
1968 else
1969 OS << ", false";
1970
1971 if (R.getValueAsBit(FieldName: "ShowInSystemMacro"))
1972 OS << ", true";
1973 else
1974 OS << ", false";
1975
1976 if (R.getValueAsBit(FieldName: "Deferrable"))
1977 OS << ", true";
1978 else
1979 OS << ", false";
1980
1981 // Category number.
1982 OS << ", " << CategoryIDs.getID(CategoryString: getDiagnosticCategory(R: &R, DiagGroupParents&: DGParentMap));
1983
1984 // Stable ID.
1985 uint32_t StableIDOffset = StableIDs.getStableIDOffset(R);
1986 OS << ", " << StableIDOffset;
1987
1988 // Previous Stable IDs.
1989 uint32_t LegacyStableIDsStartOffset =
1990 StableIDs.getLegacyStableIDsStartOffset(Name: R.getName());
1991 OS << ", " << LegacyStableIDsStartOffset;
1992
1993 OS << ")\n";
1994 }
1995}
1996
1997//===----------------------------------------------------------------------===//
1998// Warning Group Tables generation
1999//===----------------------------------------------------------------------===//
2000
2001static std::string getDiagCategoryEnum(StringRef name) {
2002 if (name.empty())
2003 return "DiagCat_None";
2004 SmallString<256> enumName = StringRef("DiagCat_");
2005 for (char C : name)
2006 enumName += isalnum(C) ? C : '_';
2007 return std::string(enumName);
2008}
2009
2010/// Emit the array of diagnostic subgroups.
2011///
2012/// The array of diagnostic subgroups contains for each group a list of its
2013/// subgroups. The individual lists are separated by '-1'. Groups with no
2014/// subgroups are skipped.
2015///
2016/// \code
2017/// static const int16_t DiagSubGroups[] = {
2018/// /* Empty */ -1,
2019/// /* DiagSubGroup0 */ 142, -1,
2020/// /* DiagSubGroup13 */ 265, 322, 399, -1
2021/// }
2022/// \endcode
2023///
2024static void emitDiagSubGroups(DiagsInGroupTy &DiagsInGroup,
2025 RecordVec &GroupsInPedantic, raw_ostream &OS) {
2026 OS << "static const int16_t DiagSubGroups[] = {\n"
2027 << " /* Empty */ -1,\n";
2028 for (auto const &[Name, Group] : DiagsInGroup) {
2029 const bool IsPedantic = Name == "pedantic";
2030 const std::vector<StringRef> &SubGroups = Group.SubGroups;
2031 if (!SubGroups.empty() || (IsPedantic && !GroupsInPedantic.empty())) {
2032 OS << " /* DiagSubGroup" << Group.IDNo << " */ ";
2033 for (StringRef SubGroup : SubGroups) {
2034 auto RI = DiagsInGroup.find(x: SubGroup);
2035 assert(RI != DiagsInGroup.end() && "Referenced without existing?");
2036 OS << RI->second.IDNo << ", ";
2037 }
2038 // Emit the groups implicitly in "pedantic".
2039 if (IsPedantic) {
2040 for (auto const &Group : GroupsInPedantic) {
2041 StringRef GroupName = Group->getValueAsString(FieldName: "GroupName");
2042 auto RI = DiagsInGroup.find(x: GroupName);
2043 assert(RI != DiagsInGroup.end() && "Referenced without existing?");
2044 OS << RI->second.IDNo << ", ";
2045 }
2046 }
2047
2048 OS << "-1,\n";
2049 }
2050 }
2051 OS << "};\n\n";
2052}
2053
2054/// Emit the list of diagnostic arrays.
2055///
2056/// This data structure is a large array that contains itself arrays of varying
2057/// size. Each array represents a list of diagnostics. The different arrays are
2058/// separated by the value '-1'.
2059///
2060/// \code
2061/// static const int16_t DiagArrays[] = {
2062/// /* Empty */ -1,
2063/// /* DiagArray1 */ diag::warn_pragma_message,
2064/// -1,
2065/// /* DiagArray2 */ diag::warn_abs_too_small,
2066/// diag::warn_unsigned_abs,
2067/// diag::warn_wrong_absolute_value_type,
2068/// -1
2069/// };
2070/// \endcode
2071///
2072static void emitDiagArrays(DiagsInGroupTy &DiagsInGroup,
2073 RecordVec &DiagsInPedantic, raw_ostream &OS) {
2074 OS << "static const int16_t DiagArrays[] = {\n"
2075 << " /* Empty */ -1,\n";
2076 for (const auto &[Name, Group] : DiagsInGroup) {
2077 const bool IsPedantic = Name == "pedantic";
2078
2079 const std::vector<const Record *> &V = Group.DiagsInGroup;
2080 if (!V.empty() || (IsPedantic && !DiagsInPedantic.empty())) {
2081 OS << " /* DiagArray" << Group.IDNo << " */ ";
2082 for (auto *Record : V)
2083 OS << "diag::" << Record->getName() << ", ";
2084 // Emit the diagnostics implicitly in "pedantic".
2085 if (IsPedantic) {
2086 for (auto const &Diag : DiagsInPedantic)
2087 OS << "diag::" << Diag->getName() << ", ";
2088 }
2089 OS << "-1,\n";
2090 }
2091 }
2092 OS << "};\n\n";
2093}
2094
2095/// Emit a list of group names.
2096///
2097/// This creates an `llvm::StringTable` of all the diagnostic group names.
2098static void emitDiagGroupNames(const StringToOffsetTable &GroupNames,
2099 raw_ostream &OS) {
2100 GroupNames.EmitStringTableDef(OS, Name: "DiagGroupNames");
2101 OS << "\n";
2102}
2103
2104/// Emit diagnostic arrays and related data structures.
2105///
2106/// This creates the actual diagnostic array, an array of diagnostic subgroups
2107/// and an array of subgroup names.
2108///
2109/// \code
2110/// #ifdef GET_DIAG_ARRAYS
2111/// static const int16_t DiagArrays[];
2112/// static const int16_t DiagSubGroups[];
2113/// static constexpr llvm::StringTable DiagGroupNames;
2114/// #endif
2115/// \endcode
2116static void emitAllDiagArrays(DiagsInGroupTy &DiagsInGroup,
2117 RecordVec &DiagsInPedantic,
2118 RecordVec &GroupsInPedantic,
2119 const StringToOffsetTable &GroupNames,
2120 raw_ostream &OS) {
2121 OS << "\n#ifdef GET_DIAG_ARRAYS\n";
2122 emitDiagArrays(DiagsInGroup, DiagsInPedantic, OS);
2123 emitDiagSubGroups(DiagsInGroup, GroupsInPedantic, OS);
2124 emitDiagGroupNames(GroupNames, OS);
2125 OS << "#endif // GET_DIAG_ARRAYS\n\n";
2126}
2127
2128/// Emit diagnostic table.
2129///
2130/// The table is sorted by the name of the diagnostic group. Each element
2131/// consists of the name of the diagnostic group (given as offset in the
2132/// group name table), a reference to a list of diagnostics (optional) and a
2133/// reference to a set of subgroups (optional).
2134///
2135/// \code
2136/// #ifdef GET_DIAG_TABLE
2137/// {/* abi */ 159, /* DiagArray11 */ 19, /* Empty */ 0},
2138/// {/* aggregate-return */ 180, /* Empty */ 0, /* Empty */ 0},
2139/// {/* all */ 197, /* Empty */ 0, /* DiagSubGroup13 */ 3},
2140/// {/* deprecated */ 1981,/* DiagArray1 */ 348, /* DiagSubGroup3 */ 9},
2141/// #endif
2142/// \endcode
2143static void emitDiagTable(DiagsInGroupTy &DiagsInGroup,
2144 RecordVec &DiagsInPedantic,
2145 RecordVec &GroupsInPedantic,
2146 const StringToOffsetTable &GroupNames,
2147 raw_ostream &OS) {
2148 unsigned MaxLen = 0;
2149
2150 for (auto const &I: DiagsInGroup)
2151 MaxLen = std::max(a: MaxLen, b: (unsigned)I.first.size());
2152
2153 OS << "\n#ifdef DIAG_ENTRY\n";
2154 unsigned SubGroupIndex = 1, DiagArrayIndex = 1;
2155 for (auto const &[Name, GroupInfo] : DiagsInGroup) {
2156 // Group option string.
2157 OS << "DIAG_ENTRY(";
2158 OS << GroupInfo.GroupName << " /* ";
2159
2160 if (Name.find_first_not_of(Chars: "abcdefghijklmnopqrstuvwxyz"
2161 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
2162 "0123456789!@#$%^*-+=:?") != std::string::npos)
2163 PrintFatalError(Msg: "Invalid character in diagnostic group '" + Name + "'");
2164 OS << Name << " */, ";
2165 OS << *GroupNames.GetStringOffset(Str: Name) << ", ";
2166
2167 // Special handling for 'pedantic'.
2168 const bool IsPedantic = Name == "pedantic";
2169
2170 // Diagnostics in the group.
2171 const std::vector<const Record *> &V = GroupInfo.DiagsInGroup;
2172 const bool hasDiags =
2173 !V.empty() || (IsPedantic && !DiagsInPedantic.empty());
2174 if (hasDiags) {
2175 OS << "/* DiagArray" << GroupInfo.IDNo << " */ " << DiagArrayIndex
2176 << ", ";
2177 if (IsPedantic)
2178 DiagArrayIndex += DiagsInPedantic.size();
2179 DiagArrayIndex += V.size() + 1;
2180 } else {
2181 OS << "0, ";
2182 }
2183
2184 // Subgroups.
2185 const std::vector<StringRef> &SubGroups = GroupInfo.SubGroups;
2186 const bool hasSubGroups =
2187 !SubGroups.empty() || (IsPedantic && !GroupsInPedantic.empty());
2188 if (hasSubGroups) {
2189 OS << "/* DiagSubGroup" << GroupInfo.IDNo << " */ " << SubGroupIndex
2190 << ", ";
2191 if (IsPedantic)
2192 SubGroupIndex += GroupsInPedantic.size();
2193 SubGroupIndex += SubGroups.size() + 1;
2194 } else {
2195 OS << "0, ";
2196 }
2197
2198 std::string Documentation = GroupInfo.Defs.back()
2199 ->getValue(Name: "Documentation")
2200 ->getValue()
2201 ->getAsUnquotedString();
2202
2203 OS << "R\"(" << StringRef(Documentation).trim() << ")\"";
2204
2205 OS << ")\n";
2206 }
2207 OS << "#endif // DIAG_ENTRY\n\n";
2208}
2209
2210/// Emit the table of diagnostic categories.
2211///
2212/// The table has the form of macro calls that have two parameters. The
2213/// category's name as well as an enum that represents the category. The
2214/// table can be used by defining the macro 'CATEGORY' and including this
2215/// table right after.
2216///
2217/// \code
2218/// #ifdef GET_CATEGORY_TABLE
2219/// CATEGORY("Semantic Issue", DiagCat_Semantic_Issue)
2220/// CATEGORY("Lambda Issue", DiagCat_Lambda_Issue)
2221/// #endif
2222/// \endcode
2223static void emitCategoryTable(const RecordKeeper &Records, raw_ostream &OS) {
2224 DiagCategoryIDMap CategoriesByID(Records);
2225 OS << "\n#ifdef GET_CATEGORY_TABLE\n";
2226 for (auto const &C : CategoriesByID)
2227 OS << "CATEGORY(\"" << C << "\", " << getDiagCategoryEnum(name: C) << ")\n";
2228 OS << "#endif // GET_CATEGORY_TABLE\n\n";
2229}
2230
2231void clang::EmitClangDiagGroups(const RecordKeeper &Records, raw_ostream &OS) {
2232 // Compute a mapping from a DiagGroup to all of its parents.
2233 DiagGroupParentMap DGParentMap(Records);
2234
2235 ArrayRef<const Record *> Diags =
2236 Records.getAllDerivedDefinitions(ClassName: "Diagnostic");
2237
2238 ArrayRef<const Record *> DiagGroups =
2239 Records.getAllDerivedDefinitions(ClassName: "DiagGroup");
2240
2241 DiagsInGroupTy DiagsInGroup;
2242 groupDiagnostics(Diags, DiagGroups, DiagsInGroup);
2243
2244 // All extensions are implicitly in the "pedantic" group. Record the
2245 // implicit set of groups in the "pedantic" group, and use this information
2246 // later when emitting the group information for Pedantic.
2247 RecordVec DiagsInPedantic;
2248 RecordVec GroupsInPedantic;
2249 InferPedantic inferPedantic(DGParentMap, Diags, DiagGroups, DiagsInGroup);
2250 inferPedantic.compute(DiagsInPedantic: &DiagsInPedantic, GroupsInPedantic: &GroupsInPedantic);
2251
2252 StringToOffsetTable GroupNames;
2253 for (const auto &[Name, Group] : DiagsInGroup) {
2254 GroupNames.GetOrAddStringOffset(Str: Name);
2255 }
2256
2257 emitAllDiagArrays(DiagsInGroup, DiagsInPedantic, GroupsInPedantic, GroupNames,
2258 OS);
2259 emitDiagTable(DiagsInGroup, DiagsInPedantic, GroupsInPedantic, GroupNames,
2260 OS);
2261 emitCategoryTable(Records, OS);
2262}
2263
2264//===----------------------------------------------------------------------===//
2265// Diagnostic name index generation
2266//===----------------------------------------------------------------------===//
2267
2268void clang::EmitClangDiagsIndexName(const RecordKeeper &Records,
2269 raw_ostream &OS) {
2270 std::vector<const Record *> Diags =
2271 Records.getAllDerivedDefinitions(ClassName: "Diagnostic");
2272
2273 sort(C&: Diags, Comp: [](const Record *LHS, const Record *RHS) {
2274 return LHS->getName() < RHS->getName();
2275 });
2276
2277 for (const Record *Elem : Diags)
2278 OS << "DIAG_NAME_INDEX(" << Elem->getName() << ")\n";
2279}
2280
2281//===----------------------------------------------------------------------===//
2282// Diagnostic documentation generation
2283//===----------------------------------------------------------------------===//
2284
2285namespace docs {
2286namespace {
2287
2288bool isRemarkGroup(const Record *DiagGroup,
2289 const DiagsInGroupTy &DiagsInGroup) {
2290 bool AnyRemarks = false, AnyNonRemarks = false;
2291
2292 std::function<void(StringRef)> Visit = [&](StringRef GroupName) {
2293 auto &GroupInfo = DiagsInGroup.find(x: GroupName)->second;
2294 for (const Record *Diag : GroupInfo.DiagsInGroup)
2295 (isRemark(Diag: *Diag) ? AnyRemarks : AnyNonRemarks) = true;
2296 for (StringRef Name : GroupInfo.SubGroups)
2297 Visit(Name);
2298 };
2299 Visit(DiagGroup->getValueAsString(FieldName: "GroupName"));
2300
2301 if (AnyRemarks && AnyNonRemarks)
2302 PrintFatalError(
2303 ErrorLoc: DiagGroup->getLoc(),
2304 Msg: "Diagnostic group contains both remark and non-remark diagnostics");
2305 return AnyRemarks;
2306}
2307
2308std::string getDefaultSeverity(const Record *Diag) {
2309 return std::string(
2310 Diag->getValueAsDef(FieldName: "DefaultSeverity")->getValueAsString(FieldName: "Name"));
2311}
2312
2313std::set<std::string> getDefaultSeverities(const Record *DiagGroup,
2314 const DiagsInGroupTy &DiagsInGroup) {
2315 std::set<std::string> States;
2316
2317 std::function<void(StringRef)> Visit = [&](StringRef GroupName) {
2318 auto &GroupInfo = DiagsInGroup.find(x: GroupName)->second;
2319 for (const Record *Diag : GroupInfo.DiagsInGroup)
2320 States.insert(x: getDefaultSeverity(Diag));
2321 for (const auto &Name : GroupInfo.SubGroups)
2322 Visit(Name);
2323 };
2324 Visit(DiagGroup->getValueAsString(FieldName: "GroupName"));
2325 return States;
2326}
2327
2328/// Write the heading for a diagnostic flag, preceded by an explicit
2329/// cross-reference target. Naming the target keeps intra-page links working
2330/// without depending on how Sphinx slugifies heading text, which is a docutils
2331/// implementation detail that LLVM additionally overrides via
2332/// `myst_heading_slug_func`.
2333void writeHeader(StringRef Prefix, StringRef GroupName, raw_ostream &OS) {
2334 OS << "(" << Prefix << GroupName << ")=\n\n### " << Prefix << GroupName
2335 << "\n\n";
2336}
2337
2338void writeDiagnosticText(DiagnosticTextBuilder &Builder, const Record *R,
2339 StringRef Role, raw_ostream &OS) {
2340 StringRef Text = R->getValueAsString(FieldName: "Summary");
2341 if (Text == "%0")
2342 OS << "The text of this diagnostic is not controlled by Clang.\n\n";
2343 else
2344 OS << Builder.buildForDocumentation(Severity: Role, R) << '\n';
2345}
2346
2347void writeDocumentation(StringRef Documentation, raw_ostream &OS) {
2348 SmallVector<StringRef> Lines;
2349 Documentation.trim(Chars: "\n").split(A&: Lines, Separator: '\n');
2350
2351 size_t Indent = StringRef::npos;
2352 for (StringRef Line : Lines)
2353 if (size_t I = Line.find_first_not_of(Chars: " \t"); I != StringRef::npos)
2354 Indent = std::min(a: Indent, b: I);
2355
2356 for (StringRef Line : Lines)
2357 OS << Line.drop_front(N: std::min(a: Indent, b: Line.size())) << '\n';
2358}
2359
2360} // namespace
2361} // namespace docs
2362
2363void clang::EmitClangDiagDocs(const RecordKeeper &Records, raw_ostream &OS) {
2364 using namespace docs;
2365
2366 // Get the documentation introduction paragraph.
2367 const Record *Documentation = Records.getDef(Name: "GlobalDocumentation");
2368 if (!Documentation) {
2369 PrintFatalError(Msg: "The Documentation top-level definition is missing, "
2370 "no documentation will be generated.");
2371 return;
2372 }
2373
2374 OS << Documentation->getValueAsString(FieldName: "Intro") << "\n";
2375
2376 DiagnosticTextBuilder Builder(Records);
2377
2378 ArrayRef<const Record *> Diags =
2379 Records.getAllDerivedDefinitions(ClassName: "Diagnostic");
2380
2381 std::vector<const Record *> DiagGroups =
2382 Records.getAllDerivedDefinitions(ClassName: "DiagGroup");
2383 sort(C&: DiagGroups, Comp: diagGroupBeforeByName);
2384
2385 DiagGroupParentMap DGParentMap(Records);
2386
2387 DiagsInGroupTy DiagsInGroup;
2388 groupDiagnostics(Diags, DiagGroups, DiagsInGroup);
2389
2390 // Compute the set of diagnostics that are in -Wpedantic.
2391 {
2392 // Collect into vectors rather than sets: InferPedantic fills a vector by
2393 // marching the records in source order, so the result is deterministic
2394 // without a sort. A set would have to be sorted back into order, and
2395 // source location alone is not a total order because every record from a
2396 // multiclass reports the location of the `def` inside it.
2397 RecordVec DiagsInPedantic;
2398 RecordVec GroupsInPedantic;
2399 InferPedantic inferPedantic(DGParentMap, Diags, DiagGroups, DiagsInGroup);
2400 inferPedantic.compute(DiagsInPedantic: &DiagsInPedantic, GroupsInPedantic: &GroupsInPedantic);
2401 auto &PedDiags = DiagsInGroup["pedantic"];
2402 PedDiags.DiagsInGroup.insert(position: PedDiags.DiagsInGroup.end(),
2403 first: DiagsInPedantic.begin(),
2404 last: DiagsInPedantic.end());
2405 for (auto *Group : GroupsInPedantic)
2406 PedDiags.SubGroups.push_back(x: Group->getValueAsString(FieldName: "GroupName"));
2407 }
2408
2409 // FIXME: Write diagnostic categories and link to diagnostic groups in each.
2410
2411 // Write out the diagnostic groups.
2412 for (const Record *G : DiagGroups) {
2413 bool IsRemarkGroup = isRemarkGroup(DiagGroup: G, DiagsInGroup);
2414 StringRef GroupName = G->getValueAsString(FieldName: "GroupName");
2415 StringRef Prefix = IsRemarkGroup ? "-R" : "-W";
2416 auto &GroupInfo = DiagsInGroup[GroupName];
2417 bool IsSynonym =
2418 GroupInfo.DiagsInGroup.empty() && GroupInfo.SubGroups.size() == 1;
2419
2420 writeHeader(Prefix, GroupName, OS);
2421
2422 if (!IsSynonym) {
2423 // FIXME: Ideally, all the diagnostics in a group should have the same
2424 // default state, but that is not currently the case.
2425 auto DefaultSeverities = getDefaultSeverities(DiagGroup: G, DiagsInGroup);
2426 if (!DefaultSeverities.empty() && !DefaultSeverities.count(x: "Ignored")) {
2427 bool AnyNonErrors = DefaultSeverities.count(x: "Warning") ||
2428 DefaultSeverities.count(x: "Remark");
2429 if (!AnyNonErrors)
2430 OS << "This diagnostic is an error by default, but the flag `-Wno-"
2431 << GroupName << "` can be used to disable the error.\n\n";
2432 else
2433 OS << "This diagnostic is enabled by default.\n\n";
2434 } else if (DefaultSeverities.size() > 1) {
2435 OS << "Some of the diagnostics controlled by this flag are enabled "
2436 << "by default.\n\n";
2437 }
2438 }
2439
2440 if (!GroupInfo.SubGroups.empty()) {
2441 if (IsSynonym)
2442 OS << "Synonym for ";
2443 else if (GroupInfo.DiagsInGroup.empty())
2444 OS << "Controls ";
2445 else
2446 OS << "Also controls ";
2447
2448 sort(C&: GroupInfo.SubGroups);
2449 ListSeparator LS;
2450 // writeHeader emits an explicit target named after the flag, so a `{ref}`
2451 // with no explicit title links to it and renders as the flag name.
2452 for (StringRef Name : GroupInfo.SubGroups)
2453 OS << LS << "{ref}`" << Prefix << Name << "`";
2454 OS << ".\n\n";
2455 }
2456
2457 if (!GroupInfo.DiagsInGroup.empty()) {
2458 OS << "**Diagnostic text:**\n\n";
2459 for (const Record *D : GroupInfo.DiagsInGroup) {
2460 auto Severity = getDefaultSeverity(Diag: D);
2461 Severity[0] = tolower(c: Severity[0]);
2462 if (Severity == "ignored")
2463 Severity = IsRemarkGroup ? "remark" : "warning";
2464
2465 writeDiagnosticText(Builder, R: D, Role: Severity, OS);
2466 }
2467 }
2468
2469 auto Doc = G->getValueAsString(FieldName: "Documentation");
2470 if (!Doc.empty())
2471 writeDocumentation(Documentation: Doc, OS);
2472 else if (GroupInfo.SubGroups.empty() && GroupInfo.DiagsInGroup.empty())
2473 OS << "This diagnostic flag exists for GCC compatibility, and has no "
2474 "effect in Clang.\n";
2475 OS << "\n";
2476 }
2477}
2478