1 //===- PartialDiagnostic.h - Diagnostic "closures" --------------*- C++ -*-===//
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 /// \file
10 /// Implements a partial diagnostic that can be emitted anwyhere
11 /// in a DiagnosticBuilder stream.
12 //
13 //===----------------------------------------------------------------------===//
14 
15 #ifndef LLVM_CLANG_BASIC_PARTIALDIAGNOSTIC_H
16 #define LLVM_CLANG_BASIC_PARTIALDIAGNOSTIC_H
17 
18 #include "clang/Basic/Diagnostic.h"
19 #include "clang/Basic/LLVM.h"
20 #include "clang/Basic/SourceLocation.h"
21 #include "llvm/ADT/SmallVector.h"
22 #include "llvm/ADT/StringRef.h"
23 #include <cassert>
24 #include <cstdint>
25 #include <string>
26 #include <type_traits>
27 #include <utility>
28 
29 namespace clang {
30 
31 class PartialDiagnostic : public StreamingDiagnostic {
32 private:
33   // NOTE: Sema assumes that PartialDiagnostic is location-invariant
34   // in the sense that its bits can be safely memcpy'ed and destructed
35   // in the new location.
36 
37   /// The diagnostic ID.
38   mutable unsigned DiagID = 0;
39 public:
40   struct NullDiagnostic {};
41 
42   /// Create a null partial diagnostic, which cannot carry a payload,
43   /// and only exists to be swapped with a real partial diagnostic.
44   PartialDiagnostic(NullDiagnostic) {}
45 
46   PartialDiagnostic(unsigned DiagID, DiagStorageAllocator &Allocator_)
47       : StreamingDiagnostic(Allocator_), DiagID(DiagID) {}
48 
49   PartialDiagnostic(const PartialDiagnostic &Other)
50       : StreamingDiagnostic(), DiagID(Other.DiagID) {
51     Allocator = Other.Allocator;
52     if (Other.DiagStorage) {
53       DiagStorage = getStorage();
54       *DiagStorage = *Other.DiagStorage;
55     }
56   }
57 
58   template <typename T> const PartialDiagnostic &operator<<(const T &V) const {
59     const StreamingDiagnostic &DB = *this;
60     DB << V;
61     return *this;
62   }
63 
64   // It is necessary to limit this to rvalue reference to avoid calling this
65   // function with a bitfield lvalue argument since non-const reference to
66   // bitfield is not allowed.
67   template <typename T, typename = typename std::enable_if<
68                             !std::is_lvalue_reference<T>::value>::type>
69   const PartialDiagnostic &operator<<(T &&V) const {
70     const StreamingDiagnostic &DB = *this;
71     DB << std::move(V);
72     return *this;
73   }
74 
75   PartialDiagnostic(PartialDiagnostic &&Other) : DiagID(Other.DiagID) {
76     Allocator = Other.Allocator;
77     DiagStorage = Other.DiagStorage;
78     Other.DiagStorage = nullptr;
79   }
80 
81   PartialDiagnostic(const PartialDiagnostic &Other,
82                     DiagnosticStorage *DiagStorage_)
83       : DiagID(Other.DiagID) {
84     Allocator = reinterpret_cast<DiagStorageAllocator *>(~uintptr_t(0));
85     DiagStorage = DiagStorage_;
86     if (Other.DiagStorage)
87       *this->DiagStorage = *Other.DiagStorage;
88   }
89 
90   PartialDiagnostic(const Diagnostic &Other, DiagStorageAllocator &Allocator_)
91       : DiagID(Other.getID()) {
92     Allocator = &Allocator_;
93     // Copy arguments.
94     for (unsigned I = 0, N = Other.getNumArgs(); I != N; ++I) {
95       if (Other.getArgKind(I) == DiagnosticsEngine::ak_std_string)
96         AddString(Other.getArgStdStr(I));
97       else
98         AddTaggedVal(Other.getRawArg(I), Other.getArgKind(I));
99     }
100 
101     // Copy source ranges.
102     for (unsigned I = 0, N = Other.getNumRanges(); I != N; ++I)
103       AddSourceRange(Other.getRange(I));
104 
105     // Copy fix-its.
106     for (unsigned I = 0, N = Other.getNumFixItHints(); I != N; ++I)
107       AddFixItHint(Other.getFixItHint(I));
108   }
109 
110   PartialDiagnostic &operator=(const PartialDiagnostic &Other) {
111     DiagID = Other.DiagID;
112     if (Other.DiagStorage) {
113       if (!DiagStorage)
114         DiagStorage = getStorage();
115 
116       *DiagStorage = *Other.DiagStorage;
117     } else {
118       freeStorage();
119     }
120 
121     return *this;
122   }
123 
124   PartialDiagnostic &operator=(PartialDiagnostic &&Other) {
125     freeStorage();
126 
127     DiagID = Other.DiagID;
128     DiagStorage = Other.DiagStorage;
129     Allocator = Other.Allocator;
130 
131     Other.DiagStorage = nullptr;
132     return *this;
133   }
134 
135   void swap(PartialDiagnostic &PD) {
136     std::swap(DiagID, PD.DiagID);
137     std::swap(DiagStorage, PD.DiagStorage);
138     std::swap(Allocator, PD.Allocator);
139   }
140 
141   unsigned getDiagID() const { return DiagID; }
142   void setDiagID(unsigned ID) { DiagID = ID; }
143 
144   void Emit(const DiagnosticBuilder &DB) const {
145     if (!DiagStorage)
146       return;
147 
148     // Add all arguments.
149     for (unsigned i = 0, e = DiagStorage->NumDiagArgs; i != e; ++i) {
150       if ((DiagnosticsEngine::ArgumentKind)DiagStorage->DiagArgumentsKind[i]
151             == DiagnosticsEngine::ak_std_string)
152         DB.AddString(DiagStorage->DiagArgumentsStr[i]);
153       else
154         DB.AddTaggedVal(DiagStorage->DiagArgumentsVal[i],
155             (DiagnosticsEngine::ArgumentKind)DiagStorage->DiagArgumentsKind[i]);
156     }
157 
158     // Add all ranges.
159     for (const CharSourceRange &Range : DiagStorage->DiagRanges)
160       DB.AddSourceRange(Range);
161 
162     // Add all fix-its.
163     for (const FixItHint &Fix : DiagStorage->FixItHints)
164       DB.AddFixItHint(Fix);
165   }
166 
167   void EmitToString(DiagnosticsEngine &Diags,
168                     SmallVectorImpl<char> &Buf) const {
169     // FIXME: It should be possible to render a diagnostic to a string without
170     //        messing with the state of the diagnostics engine.
171     DiagnosticBuilder DB(Diags.Report(getDiagID()));
172     Emit(DB);
173     Diagnostic(&Diags).FormatDiagnostic(Buf);
174     DB.Clear();
175     Diags.Clear();
176   }
177 
178   /// Clear out this partial diagnostic, giving it a new diagnostic ID
179   /// and removing all of its arguments, ranges, and fix-it hints.
180   void Reset(unsigned DiagID = 0) {
181     this->DiagID = DiagID;
182     freeStorage();
183   }
184 
185   bool hasStorage() const { return DiagStorage != nullptr; }
186 
187   /// Retrieve the string argument at the given index.
188   StringRef getStringArg(unsigned I) {
189     assert(DiagStorage && "No diagnostic storage?");
190     assert(I < DiagStorage->NumDiagArgs && "Not enough diagnostic args");
191     assert(DiagStorage->DiagArgumentsKind[I]
192              == DiagnosticsEngine::ak_std_string && "Not a string arg");
193     return DiagStorage->DiagArgumentsStr[I];
194   }
195 };
196 
197 inline const DiagnosticBuilder &operator<<(const DiagnosticBuilder &DB,
198                                            const PartialDiagnostic &PD) {
199   PD.Emit(DB);
200   return DB;
201 }
202 
203 /// A partial diagnostic along with the source location where this
204 /// diagnostic occurs.
205 using PartialDiagnosticAt = std::pair<SourceLocation, PartialDiagnostic>;
206 
207 } // namespace clang
208 
209 #endif // LLVM_CLANG_BASIC_PARTIALDIAGNOSTIC_H
210