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 RecordKeeper &Records) const {
1805 OS << "\n#ifdef GET_DIAG_STABLE_ID_ARRAYS\n";
1806 emitStableIDs(OS);
1807 emitLegacyStableIDs(OS);
1808 emitDescriptions(OS, Records);
1809 OS << "#endif // GET_DIAG_STABLE_ID_ARRAYS\n\n";
1810 }
1811
1812private:
1813 /// Gets the Stable ID for the specified Diagnostic record.
1814 /// The Stable ID can be explicitly specified via the "StableId"
1815 /// property. If not specified explicitly, the Stable ID defaults
1816 /// to the name of the diagnostic.
1817 static StringRef getStableID(const Record &R) {
1818 StringRef StableID = R.getValueAsString(FieldName: "StableId");
1819 return StableID.empty() ? R.getName() : StableID;
1820 }
1821
1822 /// Emit a list of stable IDs, used by both the "StableId" and
1823 /// "LegacyStableIds" properties.
1824 ///
1825 /// This creates an `llvm::StringTable` of all the stable ids in use.
1826 void emitStableIDs(raw_ostream &OS) const {
1827 StableIDs.EmitStringTableDef(OS, Name: "DiagStableIDs");
1828 OS << "\n";
1829 }
1830
1831 /// Emit the array of legacy stable IDs for diagnostics.
1832 ///
1833 /// The array of stable IDs contains for each diagnostic a list of its legacy
1834 /// stable IDs. The individual lists are separated by '0'. Diagnostics with
1835 /// no legacy stable IDs are skipped.
1836 ///
1837 /// \code
1838 /// static const uint16_t DiagOldStableIds[] = {
1839 /// /* Empty */ 0,
1840 /// /* Diag0 */ 142, 0,
1841 /// /* Diag13 */ 265, 322, 399, 0
1842 /// }
1843 /// \endcode
1844 ///
1845 void emitLegacyStableIDs(raw_ostream &OS) const {
1846 OS << "static const uint32_t DiagLegacyStableIDs[] = {\n";
1847
1848 bool StartOfLine = true;
1849 for (auto Offset : LegacyStableIDs) {
1850 if (StartOfLine) {
1851 OS << " ";
1852 StartOfLine = false;
1853 }
1854 OS << Offset << ",";
1855 if (Offset > 0) {
1856 OS << " ";
1857 } else {
1858 OS << "\n";
1859 StartOfLine = true;
1860 }
1861 }
1862 OS << "};\n\n";
1863 }
1864
1865 static void emitDescriptions(raw_ostream &OS, const RecordKeeper &Records) {
1866 DiagnosticTextBuilder DiagTextBuilder(Records);
1867 StringToOffsetTable Descriptions;
1868
1869 OS << "enum {\n";
1870 for (const Record &R :
1871 make_pointee_range(Range: Records.getAllDerivedDefinitions(ClassName: "Diagnostic"))) {
1872 std::string Description = DiagTextBuilder.buildForDefinition(R: &R);
1873 if (Description.size() >= (1U << 12))
1874 PrintFatalError(ErrorLoc: R.getLoc(),
1875 Msg: "diagnostic description exceeds 12-bit length limit");
1876
1877 unsigned Offset = Descriptions.GetOrAddStringOffset(Str: Description);
1878 OS << " DIAG_DESC_OFFSET_" << R.getName() << " = " << Offset << ",\n";
1879 }
1880 OS << "};\n\n";
1881
1882 if (Descriptions.size() >= (1U << 20))
1883 PrintFatalError(Msg: "diagnostic descriptions exceed 20-bit offset limit");
1884 Descriptions.EmitStringTableStorageDef(OS, Name: "StaticDiagInfoDescriptions");
1885 }
1886};
1887} // namespace
1888
1889/// Emit the definitions of the Stable ID and old Stable ID tables.
1890void clang::EmitClangDiagsStableIDs(const RecordKeeper &Records,
1891 raw_ostream &OS) {
1892 DiagStableIDsMap StableIDs(Records);
1893 StableIDs.emit(OS, Records);
1894}
1895
1896/// ClangDiagsDefsEmitter - The top-level class emits .def files containing
1897/// declarations of Clang diagnostics.
1898void clang::EmitClangDiagsDefs(const RecordKeeper &Records, raw_ostream &OS,
1899 const std::string &Component) {
1900 // Write the #if guard
1901 if (!Component.empty()) {
1902 std::string ComponentName = StringRef(Component).upper();
1903 OS << "#ifdef " << ComponentName << "START\n";
1904 OS << "__" << ComponentName << "START = DIAG_START_" << ComponentName
1905 << ",\n";
1906 OS << "#undef " << ComponentName << "START\n";
1907 OS << "#endif\n\n";
1908 }
1909
1910 DiagnosticTextBuilder DiagTextBuilder(Records);
1911
1912 ArrayRef<const Record *> Diags =
1913 Records.getAllDerivedDefinitions(ClassName: "Diagnostic");
1914
1915 ArrayRef<const Record *> DiagGroups =
1916 Records.getAllDerivedDefinitions(ClassName: "DiagGroup");
1917
1918 DiagsInGroupTy DiagsInGroup;
1919 groupDiagnostics(Diags, DiagGroups, DiagsInGroup);
1920
1921 DiagCategoryIDMap CategoryIDs(Records);
1922 DiagGroupParentMap DGParentMap(Records);
1923 DiagStableIDsMap StableIDs(Records);
1924
1925 // Compute the set of diagnostics that are in -Wpedantic.
1926 RecordSet DiagsInPedantic;
1927 InferPedantic inferPedantic(DGParentMap, Diags, DiagGroups, DiagsInGroup);
1928 inferPedantic.compute(DiagsInPedantic: &DiagsInPedantic, GroupsInPedantic: (RecordVec*)nullptr);
1929
1930 for (const Record &R : make_pointee_range(Range&: Diags)) {
1931 // Check if this is an error that is accidentally in a warning
1932 // group.
1933 if (isError(Diag: R)) {
1934 if (const auto *Group = dyn_cast<DefInit>(Val: R.getValueInit(FieldName: "Group"))) {
1935 const Record *GroupRec = Group->getDef();
1936 StringRef GroupName = GroupRec->getValueAsString(FieldName: "GroupName");
1937 PrintFatalError(ErrorLoc: R.getLoc(), Msg: "Error " + R.getName() +
1938 " cannot be in a warning group [" + GroupName + "]");
1939 }
1940 }
1941
1942 // Check that all remarks have an associated diagnostic group.
1943 if (isRemark(Diag: R)) {
1944 if (!isa<DefInit>(Val: R.getValueInit(FieldName: "Group"))) {
1945 PrintFatalError(ErrorLoc: R.getLoc(), Msg: "Error " + R.getName() +
1946 " not in any diagnostic group");
1947 }
1948 }
1949
1950 // Filter by component.
1951 if (!Component.empty() && Component != R.getValueAsString(FieldName: "Component"))
1952 continue;
1953
1954 // Validate diagnostic wording for common issues.
1955 verifyDiagnosticWording(Diag: R);
1956
1957 OS << "DIAG(" << R.getName() << ", ";
1958 OS << R.getValueAsDef(FieldName: "Class")->getName();
1959 OS << ", (unsigned)diag::Severity::"
1960 << R.getValueAsDef(FieldName: "DefaultSeverity")->getValueAsString(FieldName: "Name");
1961
1962 // Description string.
1963 OS << ", \"";
1964 OS.write_escaped(Str: DiagTextBuilder.buildForDefinition(R: &R)) << '"';
1965
1966 // Warning group associated with the diagnostic. This is stored as an index
1967 // into the alphabetically sorted warning group table.
1968 if (const auto *DI = dyn_cast<DefInit>(Val: R.getValueInit(FieldName: "Group"))) {
1969 auto I = DiagsInGroup.find(x: DI->getDef()->getValueAsString(FieldName: "GroupName"));
1970 assert(I != DiagsInGroup.end());
1971 OS << ", " << I->second.IDNo;
1972 } else if (DiagsInPedantic.count(V: &R)) {
1973 auto I = DiagsInGroup.find(x: "pedantic");
1974 assert(I != DiagsInGroup.end() && "pedantic group not defined");
1975 OS << ", " << I->second.IDNo;
1976 } else {
1977 OS << ", 0";
1978 }
1979
1980 // SFINAE response.
1981 OS << ", " << R.getValueAsDef(FieldName: "SFINAE")->getName();
1982
1983 // Default warning has no Werror bit.
1984 if (R.getValueAsBit(FieldName: "WarningNoWerror"))
1985 OS << ", true";
1986 else
1987 OS << ", false";
1988
1989 if (R.getValueAsBit(FieldName: "ShowInSystemHeader"))
1990 OS << ", true";
1991 else
1992 OS << ", false";
1993
1994 if (R.getValueAsBit(FieldName: "ShowInSystemMacro"))
1995 OS << ", true";
1996 else
1997 OS << ", false";
1998
1999 if (R.getValueAsBit(FieldName: "Deferrable"))
2000 OS << ", true";
2001 else
2002 OS << ", false";
2003
2004 // Category number.
2005 OS << ", " << CategoryIDs.getID(CategoryString: getDiagnosticCategory(R: &R, DiagGroupParents&: DGParentMap));
2006
2007 // Stable ID.
2008 uint32_t StableIDOffset = StableIDs.getStableIDOffset(R);
2009 OS << ", " << StableIDOffset;
2010
2011 // Previous Stable IDs.
2012 uint32_t LegacyStableIDsStartOffset =
2013 StableIDs.getLegacyStableIDsStartOffset(Name: R.getName());
2014 OS << ", " << LegacyStableIDsStartOffset;
2015
2016 OS << ")\n";
2017 }
2018}
2019
2020//===----------------------------------------------------------------------===//
2021// Warning Group Tables generation
2022//===----------------------------------------------------------------------===//
2023
2024static std::string getDiagCategoryEnum(StringRef name) {
2025 if (name.empty())
2026 return "DiagCat_None";
2027 SmallString<256> enumName = StringRef("DiagCat_");
2028 for (char C : name)
2029 enumName += isalnum(C) ? C : '_';
2030 return std::string(enumName);
2031}
2032
2033/// Emit the array of diagnostic subgroups.
2034///
2035/// The array of diagnostic subgroups contains for each group a list of its
2036/// subgroups. The individual lists are separated by '-1'. Groups with no
2037/// subgroups are skipped.
2038///
2039/// \code
2040/// static const int16_t DiagSubGroups[] = {
2041/// /* Empty */ -1,
2042/// /* DiagSubGroup0 */ 142, -1,
2043/// /* DiagSubGroup13 */ 265, 322, 399, -1
2044/// }
2045/// \endcode
2046///
2047static void emitDiagSubGroups(DiagsInGroupTy &DiagsInGroup,
2048 RecordVec &GroupsInPedantic, raw_ostream &OS) {
2049 OS << "static const int16_t DiagSubGroups[] = {\n"
2050 << " /* Empty */ -1,\n";
2051 for (auto const &[Name, Group] : DiagsInGroup) {
2052 const bool IsPedantic = Name == "pedantic";
2053 const std::vector<StringRef> &SubGroups = Group.SubGroups;
2054 if (!SubGroups.empty() || (IsPedantic && !GroupsInPedantic.empty())) {
2055 OS << " /* DiagSubGroup" << Group.IDNo << " */ ";
2056 for (StringRef SubGroup : SubGroups) {
2057 auto RI = DiagsInGroup.find(x: SubGroup);
2058 assert(RI != DiagsInGroup.end() && "Referenced without existing?");
2059 OS << RI->second.IDNo << ", ";
2060 }
2061 // Emit the groups implicitly in "pedantic".
2062 if (IsPedantic) {
2063 for (auto const &Group : GroupsInPedantic) {
2064 StringRef GroupName = Group->getValueAsString(FieldName: "GroupName");
2065 auto RI = DiagsInGroup.find(x: GroupName);
2066 assert(RI != DiagsInGroup.end() && "Referenced without existing?");
2067 OS << RI->second.IDNo << ", ";
2068 }
2069 }
2070
2071 OS << "-1,\n";
2072 }
2073 }
2074 OS << "};\n\n";
2075}
2076
2077/// Emit the list of diagnostic arrays.
2078///
2079/// This data structure is a large array that contains itself arrays of varying
2080/// size. Each array represents a list of diagnostics. The different arrays are
2081/// separated by the value '-1'.
2082///
2083/// \code
2084/// static const int16_t DiagArrays[] = {
2085/// /* Empty */ -1,
2086/// /* DiagArray1 */ diag::warn_pragma_message,
2087/// -1,
2088/// /* DiagArray2 */ diag::warn_abs_too_small,
2089/// diag::warn_unsigned_abs,
2090/// diag::warn_wrong_absolute_value_type,
2091/// -1
2092/// };
2093/// \endcode
2094///
2095static void emitDiagArrays(DiagsInGroupTy &DiagsInGroup,
2096 RecordVec &DiagsInPedantic, raw_ostream &OS) {
2097 OS << "static const int16_t DiagArrays[] = {\n"
2098 << " /* Empty */ -1,\n";
2099 for (const auto &[Name, Group] : DiagsInGroup) {
2100 const bool IsPedantic = Name == "pedantic";
2101
2102 const std::vector<const Record *> &V = Group.DiagsInGroup;
2103 if (!V.empty() || (IsPedantic && !DiagsInPedantic.empty())) {
2104 OS << " /* DiagArray" << Group.IDNo << " */ ";
2105 for (auto *Record : V)
2106 OS << "diag::" << Record->getName() << ", ";
2107 // Emit the diagnostics implicitly in "pedantic".
2108 if (IsPedantic) {
2109 for (auto const &Diag : DiagsInPedantic)
2110 OS << "diag::" << Diag->getName() << ", ";
2111 }
2112 OS << "-1,\n";
2113 }
2114 }
2115 OS << "};\n\n";
2116}
2117
2118/// Emit a list of group names.
2119///
2120/// This creates an `llvm::StringTable` of all the diagnostic group names.
2121static void emitDiagGroupNames(const StringToOffsetTable &GroupNames,
2122 raw_ostream &OS) {
2123 GroupNames.EmitStringTableDef(OS, Name: "DiagGroupNames");
2124 OS << "\n";
2125}
2126
2127/// Emit diagnostic arrays and related data structures.
2128///
2129/// This creates the actual diagnostic array, an array of diagnostic subgroups
2130/// and an array of subgroup names.
2131///
2132/// \code
2133/// #ifdef GET_DIAG_ARRAYS
2134/// static const int16_t DiagArrays[];
2135/// static const int16_t DiagSubGroups[];
2136/// static constexpr llvm::StringTable DiagGroupNames;
2137/// #endif
2138/// \endcode
2139static void emitAllDiagArrays(DiagsInGroupTy &DiagsInGroup,
2140 RecordVec &DiagsInPedantic,
2141 RecordVec &GroupsInPedantic,
2142 const StringToOffsetTable &GroupNames,
2143 raw_ostream &OS) {
2144 OS << "\n#ifdef GET_DIAG_ARRAYS\n";
2145 emitDiagArrays(DiagsInGroup, DiagsInPedantic, OS);
2146 emitDiagSubGroups(DiagsInGroup, GroupsInPedantic, OS);
2147 emitDiagGroupNames(GroupNames, OS);
2148 OS << "#endif // GET_DIAG_ARRAYS\n\n";
2149}
2150
2151/// Emit diagnostic table.
2152///
2153/// The table is sorted by the name of the diagnostic group. Each element
2154/// consists of the name of the diagnostic group (given as offset in the
2155/// group name table), a reference to a list of diagnostics (optional) and a
2156/// reference to a set of subgroups (optional).
2157///
2158/// \code
2159/// #ifdef GET_DIAG_TABLE
2160/// {/* abi */ 159, /* DiagArray11 */ 19, /* Empty */ 0},
2161/// {/* aggregate-return */ 180, /* Empty */ 0, /* Empty */ 0},
2162/// {/* all */ 197, /* Empty */ 0, /* DiagSubGroup13 */ 3},
2163/// {/* deprecated */ 1981,/* DiagArray1 */ 348, /* DiagSubGroup3 */ 9},
2164/// #endif
2165/// \endcode
2166static void emitDiagTable(DiagsInGroupTy &DiagsInGroup,
2167 RecordVec &DiagsInPedantic,
2168 RecordVec &GroupsInPedantic,
2169 const StringToOffsetTable &GroupNames,
2170 raw_ostream &OS) {
2171 unsigned MaxLen = 0;
2172
2173 for (auto const &I: DiagsInGroup)
2174 MaxLen = std::max(a: MaxLen, b: (unsigned)I.first.size());
2175
2176 OS << "\n#ifdef DIAG_ENTRY\n";
2177 unsigned SubGroupIndex = 1, DiagArrayIndex = 1;
2178 for (auto const &[Name, GroupInfo] : DiagsInGroup) {
2179 // Group option string.
2180 OS << "DIAG_ENTRY(";
2181 OS << GroupInfo.GroupName << " /* ";
2182
2183 if (Name.find_first_not_of(Chars: "abcdefghijklmnopqrstuvwxyz"
2184 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
2185 "0123456789!@#$%^*-+=:?") != std::string::npos)
2186 PrintFatalError(Msg: "Invalid character in diagnostic group '" + Name + "'");
2187 OS << Name << " */, ";
2188 OS << *GroupNames.GetStringOffset(Str: Name) << ", ";
2189
2190 // Special handling for 'pedantic'.
2191 const bool IsPedantic = Name == "pedantic";
2192
2193 // Diagnostics in the group.
2194 const std::vector<const Record *> &V = GroupInfo.DiagsInGroup;
2195 const bool hasDiags =
2196 !V.empty() || (IsPedantic && !DiagsInPedantic.empty());
2197 if (hasDiags) {
2198 OS << "/* DiagArray" << GroupInfo.IDNo << " */ " << DiagArrayIndex
2199 << ", ";
2200 if (IsPedantic)
2201 DiagArrayIndex += DiagsInPedantic.size();
2202 DiagArrayIndex += V.size() + 1;
2203 } else {
2204 OS << "0, ";
2205 }
2206
2207 // Subgroups.
2208 const std::vector<StringRef> &SubGroups = GroupInfo.SubGroups;
2209 const bool hasSubGroups =
2210 !SubGroups.empty() || (IsPedantic && !GroupsInPedantic.empty());
2211 if (hasSubGroups) {
2212 OS << "/* DiagSubGroup" << GroupInfo.IDNo << " */ " << SubGroupIndex
2213 << ", ";
2214 if (IsPedantic)
2215 SubGroupIndex += GroupsInPedantic.size();
2216 SubGroupIndex += SubGroups.size() + 1;
2217 } else {
2218 OS << "0, ";
2219 }
2220
2221 std::string Documentation = GroupInfo.Defs.back()
2222 ->getValue(Name: "Documentation")
2223 ->getValue()
2224 ->getAsUnquotedString();
2225
2226 OS << "R\"(" << StringRef(Documentation).trim() << ")\"";
2227
2228 OS << ")\n";
2229 }
2230 OS << "#endif // DIAG_ENTRY\n\n";
2231}
2232
2233/// Emit the table of diagnostic categories.
2234///
2235/// The table has the form of macro calls that have two parameters. The
2236/// category's name as well as an enum that represents the category. The
2237/// table can be used by defining the macro 'CATEGORY' and including this
2238/// table right after.
2239///
2240/// \code
2241/// #ifdef GET_CATEGORY_TABLE
2242/// CATEGORY("Semantic Issue", DiagCat_Semantic_Issue)
2243/// CATEGORY("Lambda Issue", DiagCat_Lambda_Issue)
2244/// #endif
2245/// \endcode
2246static void emitCategoryTable(const RecordKeeper &Records, raw_ostream &OS) {
2247 DiagCategoryIDMap CategoriesByID(Records);
2248 OS << "\n#ifdef GET_CATEGORY_TABLE\n";
2249 for (auto const &C : CategoriesByID)
2250 OS << "CATEGORY(\"" << C << "\", " << getDiagCategoryEnum(name: C) << ")\n";
2251 OS << "#endif // GET_CATEGORY_TABLE\n\n";
2252}
2253
2254void clang::EmitClangDiagGroups(const RecordKeeper &Records, raw_ostream &OS) {
2255 // Compute a mapping from a DiagGroup to all of its parents.
2256 DiagGroupParentMap DGParentMap(Records);
2257
2258 ArrayRef<const Record *> Diags =
2259 Records.getAllDerivedDefinitions(ClassName: "Diagnostic");
2260
2261 ArrayRef<const Record *> DiagGroups =
2262 Records.getAllDerivedDefinitions(ClassName: "DiagGroup");
2263
2264 DiagsInGroupTy DiagsInGroup;
2265 groupDiagnostics(Diags, DiagGroups, DiagsInGroup);
2266
2267 // All extensions are implicitly in the "pedantic" group. Record the
2268 // implicit set of groups in the "pedantic" group, and use this information
2269 // later when emitting the group information for Pedantic.
2270 RecordVec DiagsInPedantic;
2271 RecordVec GroupsInPedantic;
2272 InferPedantic inferPedantic(DGParentMap, Diags, DiagGroups, DiagsInGroup);
2273 inferPedantic.compute(DiagsInPedantic: &DiagsInPedantic, GroupsInPedantic: &GroupsInPedantic);
2274
2275 StringToOffsetTable GroupNames;
2276 for (const auto &[Name, Group] : DiagsInGroup) {
2277 GroupNames.GetOrAddStringOffset(Str: Name);
2278 }
2279
2280 emitAllDiagArrays(DiagsInGroup, DiagsInPedantic, GroupsInPedantic, GroupNames,
2281 OS);
2282 emitDiagTable(DiagsInGroup, DiagsInPedantic, GroupsInPedantic, GroupNames,
2283 OS);
2284 emitCategoryTable(Records, OS);
2285}
2286
2287//===----------------------------------------------------------------------===//
2288// Diagnostic name index generation
2289//===----------------------------------------------------------------------===//
2290
2291void clang::EmitClangDiagsIndexName(const RecordKeeper &Records,
2292 raw_ostream &OS) {
2293 std::vector<const Record *> Diags =
2294 Records.getAllDerivedDefinitions(ClassName: "Diagnostic");
2295
2296 sort(C&: Diags, Comp: [](const Record *LHS, const Record *RHS) {
2297 return LHS->getName() < RHS->getName();
2298 });
2299
2300 for (const Record *Elem : Diags)
2301 OS << "DIAG_NAME_INDEX(" << Elem->getName() << ")\n";
2302}
2303
2304//===----------------------------------------------------------------------===//
2305// Diagnostic documentation generation
2306//===----------------------------------------------------------------------===//
2307
2308namespace docs {
2309namespace {
2310
2311bool isRemarkGroup(const Record *DiagGroup,
2312 const DiagsInGroupTy &DiagsInGroup) {
2313 bool AnyRemarks = false, AnyNonRemarks = false;
2314
2315 std::function<void(StringRef)> Visit = [&](StringRef GroupName) {
2316 auto &GroupInfo = DiagsInGroup.find(x: GroupName)->second;
2317 for (const Record *Diag : GroupInfo.DiagsInGroup)
2318 (isRemark(Diag: *Diag) ? AnyRemarks : AnyNonRemarks) = true;
2319 for (StringRef Name : GroupInfo.SubGroups)
2320 Visit(Name);
2321 };
2322 Visit(DiagGroup->getValueAsString(FieldName: "GroupName"));
2323
2324 if (AnyRemarks && AnyNonRemarks)
2325 PrintFatalError(
2326 ErrorLoc: DiagGroup->getLoc(),
2327 Msg: "Diagnostic group contains both remark and non-remark diagnostics");
2328 return AnyRemarks;
2329}
2330
2331std::string getDefaultSeverity(const Record *Diag) {
2332 return std::string(
2333 Diag->getValueAsDef(FieldName: "DefaultSeverity")->getValueAsString(FieldName: "Name"));
2334}
2335
2336std::set<std::string> getDefaultSeverities(const Record *DiagGroup,
2337 const DiagsInGroupTy &DiagsInGroup) {
2338 std::set<std::string> States;
2339
2340 std::function<void(StringRef)> Visit = [&](StringRef GroupName) {
2341 auto &GroupInfo = DiagsInGroup.find(x: GroupName)->second;
2342 for (const Record *Diag : GroupInfo.DiagsInGroup)
2343 States.insert(x: getDefaultSeverity(Diag));
2344 for (const auto &Name : GroupInfo.SubGroups)
2345 Visit(Name);
2346 };
2347 Visit(DiagGroup->getValueAsString(FieldName: "GroupName"));
2348 return States;
2349}
2350
2351/// Write the heading for a diagnostic flag, preceded by an explicit
2352/// cross-reference target. Naming the target keeps intra-page links working
2353/// without depending on how Sphinx slugifies heading text, which is a docutils
2354/// implementation detail that LLVM additionally overrides via
2355/// `myst_heading_slug_func`.
2356void writeHeader(StringRef Prefix, StringRef GroupName, raw_ostream &OS) {
2357 OS << "(" << Prefix << GroupName << ")=\n\n### " << Prefix << GroupName
2358 << "\n\n";
2359}
2360
2361void writeDiagnosticText(DiagnosticTextBuilder &Builder, const Record *R,
2362 StringRef Role, raw_ostream &OS) {
2363 StringRef Text = R->getValueAsString(FieldName: "Summary");
2364 if (Text == "%0")
2365 OS << "The text of this diagnostic is not controlled by Clang.\n\n";
2366 else
2367 OS << Builder.buildForDocumentation(Severity: Role, R) << '\n';
2368}
2369
2370void writeDocumentation(StringRef Documentation, raw_ostream &OS) {
2371 SmallVector<StringRef> Lines;
2372 Documentation.trim(Chars: "\n").split(A&: Lines, Separator: '\n');
2373
2374 size_t Indent = StringRef::npos;
2375 for (StringRef Line : Lines)
2376 if (size_t I = Line.find_first_not_of(Chars: " \t"); I != StringRef::npos)
2377 Indent = std::min(a: Indent, b: I);
2378
2379 for (StringRef Line : Lines)
2380 OS << Line.drop_front(N: std::min(a: Indent, b: Line.size())) << '\n';
2381}
2382
2383} // namespace
2384} // namespace docs
2385
2386void clang::EmitClangDiagDocs(const RecordKeeper &Records, raw_ostream &OS) {
2387 using namespace docs;
2388
2389 // Get the documentation introduction paragraph.
2390 const Record *Documentation = Records.getDef(Name: "GlobalDocumentation");
2391 if (!Documentation) {
2392 PrintFatalError(Msg: "The Documentation top-level definition is missing, "
2393 "no documentation will be generated.");
2394 return;
2395 }
2396
2397 OS << Documentation->getValueAsString(FieldName: "Intro") << "\n";
2398
2399 DiagnosticTextBuilder Builder(Records);
2400
2401 ArrayRef<const Record *> Diags =
2402 Records.getAllDerivedDefinitions(ClassName: "Diagnostic");
2403
2404 std::vector<const Record *> DiagGroups =
2405 Records.getAllDerivedDefinitions(ClassName: "DiagGroup");
2406 sort(C&: DiagGroups, Comp: diagGroupBeforeByName);
2407
2408 DiagGroupParentMap DGParentMap(Records);
2409
2410 DiagsInGroupTy DiagsInGroup;
2411 groupDiagnostics(Diags, DiagGroups, DiagsInGroup);
2412
2413 // Compute the set of diagnostics that are in -Wpedantic.
2414 {
2415 // Collect into vectors rather than sets: InferPedantic fills a vector by
2416 // marching the records in source order, so the result is deterministic
2417 // without a sort. A set would have to be sorted back into order, and
2418 // source location alone is not a total order because every record from a
2419 // multiclass reports the location of the `def` inside it.
2420 RecordVec DiagsInPedantic;
2421 RecordVec GroupsInPedantic;
2422 InferPedantic inferPedantic(DGParentMap, Diags, DiagGroups, DiagsInGroup);
2423 inferPedantic.compute(DiagsInPedantic: &DiagsInPedantic, GroupsInPedantic: &GroupsInPedantic);
2424 auto &PedDiags = DiagsInGroup["pedantic"];
2425 PedDiags.DiagsInGroup.insert(position: PedDiags.DiagsInGroup.end(),
2426 first: DiagsInPedantic.begin(),
2427 last: DiagsInPedantic.end());
2428 for (auto *Group : GroupsInPedantic)
2429 PedDiags.SubGroups.push_back(x: Group->getValueAsString(FieldName: "GroupName"));
2430 }
2431
2432 // FIXME: Write diagnostic categories and link to diagnostic groups in each.
2433
2434 // Write out the diagnostic groups.
2435 for (const Record *G : DiagGroups) {
2436 bool IsRemarkGroup = isRemarkGroup(DiagGroup: G, DiagsInGroup);
2437 StringRef GroupName = G->getValueAsString(FieldName: "GroupName");
2438 StringRef Prefix = IsRemarkGroup ? "-R" : "-W";
2439 auto &GroupInfo = DiagsInGroup[GroupName];
2440 bool IsSynonym =
2441 GroupInfo.DiagsInGroup.empty() && GroupInfo.SubGroups.size() == 1;
2442
2443 writeHeader(Prefix, GroupName, OS);
2444
2445 if (!IsSynonym) {
2446 // FIXME: Ideally, all the diagnostics in a group should have the same
2447 // default state, but that is not currently the case.
2448 auto DefaultSeverities = getDefaultSeverities(DiagGroup: G, DiagsInGroup);
2449 if (!DefaultSeverities.empty() && !DefaultSeverities.count(x: "Ignored")) {
2450 bool AnyNonErrors = DefaultSeverities.count(x: "Warning") ||
2451 DefaultSeverities.count(x: "Remark");
2452 if (!AnyNonErrors)
2453 OS << "This diagnostic is an error by default, but the flag `-Wno-"
2454 << GroupName << "` can be used to disable the error.\n\n";
2455 else
2456 OS << "This diagnostic is enabled by default.\n\n";
2457 } else if (DefaultSeverities.size() > 1) {
2458 OS << "Some of the diagnostics controlled by this flag are enabled "
2459 << "by default.\n\n";
2460 }
2461 }
2462
2463 if (!GroupInfo.SubGroups.empty()) {
2464 if (IsSynonym)
2465 OS << "Synonym for ";
2466 else if (GroupInfo.DiagsInGroup.empty())
2467 OS << "Controls ";
2468 else
2469 OS << "Also controls ";
2470
2471 sort(C&: GroupInfo.SubGroups);
2472 ListSeparator LS;
2473 // writeHeader emits an explicit target named after the flag, so a `{ref}`
2474 // with no explicit title links to it and renders as the flag name.
2475 for (StringRef Name : GroupInfo.SubGroups)
2476 OS << LS << "{ref}`" << Prefix << Name << "`";
2477 OS << ".\n\n";
2478 }
2479
2480 if (!GroupInfo.DiagsInGroup.empty()) {
2481 OS << "**Diagnostic text:**\n\n";
2482 for (const Record *D : GroupInfo.DiagsInGroup) {
2483 auto Severity = getDefaultSeverity(Diag: D);
2484 Severity[0] = tolower(c: Severity[0]);
2485 if (Severity == "ignored")
2486 Severity = IsRemarkGroup ? "remark" : "warning";
2487
2488 writeDiagnosticText(Builder, R: D, Role: Severity, OS);
2489 }
2490 }
2491
2492 auto Doc = G->getValueAsString(FieldName: "Documentation");
2493 if (!Doc.empty())
2494 writeDocumentation(Documentation: Doc, OS);
2495 else if (GroupInfo.SubGroups.empty() && GroupInfo.DiagsInGroup.empty())
2496 OS << "This diagnostic flag exists for GCC compatibility, and has no "
2497 "effect in Clang.\n";
2498 OS << "\n";
2499 }
2500}
2501