xref: /llvm-project/mlir/include/mlir/Dialect/Transform/Utils/DiagnosedSilenceableFailure.h (revision 0a81ace0047a2de93e71c82cdf0977fc989660df)
1 //===- DiagnosedSilenceableFailure.h - Tri-state result ----------- 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 // This file declares the DiagnosedSilenceableFailure class allowing to store
10 // a tri-state result (definite failure, recoverable failure, success) with an
11 // optional associated list of diagnostics.
12 //
13 //===----------------------------------------------------------------------===//
14 
15 #include "mlir/IR/Diagnostics.h"
16 #include "mlir/IR/Operation.h"
17 #include <optional>
18 
19 #ifndef MLIR_DIALECT_TRANSFORM_UTILS_DIAGNOSEDSILENCEABLEFAILURE_H
20 #define MLIR_DIALECT_TRANSFORM_UTILS_DIAGNOSEDSILENCEABLEFAILURE_H
21 
22 namespace mlir {
23 /// The result of a transform IR operation application. This can have one of the
24 /// three states:
25 ///   - success;
26 ///   - silenceable (recoverable) failure with yet-unreported diagnostic;
27 ///   - definite failure.
28 /// Silenceable failure is intended to communicate information about
29 /// transformations that did not apply but in a way that supports recovery,
30 /// for example, they did not modify the payload IR or modified it in some
31 /// predictable way. They are associated with a Diagnostic that provides more
32 /// details on the failure. Silenceable failure can be discarded, turning the
33 /// result into success, or "reported", emitting the diagnostic and turning the
34 /// result into definite failure.
35 /// Transform IR operations containing other operations are allowed to do either
36 /// with the results of the nested transformations, but must propagate definite
37 /// failures as their diagnostics have been already reported to the user.
38 class [[nodiscard]] DiagnosedSilenceableFailure {
39 public:
40   DiagnosedSilenceableFailure(const DiagnosedSilenceableFailure &) = delete;
41   DiagnosedSilenceableFailure &
42   operator=(const DiagnosedSilenceableFailure &) = delete;
43   DiagnosedSilenceableFailure(DiagnosedSilenceableFailure &&) = default;
44   DiagnosedSilenceableFailure &
45   operator=(DiagnosedSilenceableFailure &&) = default;
46 
47   /// Constructs a DiagnosedSilenceableFailure in the success state.
success()48   static DiagnosedSilenceableFailure success() {
49     return DiagnosedSilenceableFailure(::mlir::success());
50   }
51 
52   /// Constructs a DiagnosedSilenceableFailure in the failure state. Typically,
53   /// a diagnostic has been emitted before this.
definiteFailure()54   static DiagnosedSilenceableFailure definiteFailure() {
55     return DiagnosedSilenceableFailure(::mlir::failure());
56   }
57 
58   /// Constructs a DiagnosedSilenceableFailure in the silenceable failure state,
59   /// ready to emit the given diagnostic. This is considered a failure
60   /// regardless of the diagnostic severity.
silenceableFailure(Diagnostic && diag)61   static DiagnosedSilenceableFailure silenceableFailure(Diagnostic &&diag) {
62     return DiagnosedSilenceableFailure(std::forward<Diagnostic>(diag));
63   }
64   static DiagnosedSilenceableFailure
silenceableFailure(SmallVector<Diagnostic> && diag)65   silenceableFailure(SmallVector<Diagnostic> &&diag) {
66     return DiagnosedSilenceableFailure(
67         std::forward<SmallVector<Diagnostic>>(diag));
68   }
69 
70   /// Converts all kinds of failure into a LogicalResult failure, emitting the
71   /// diagnostic if necessary. Must not be called more than once.
72   LogicalResult checkAndReport();
73 
74   /// Returns `true` if this is a success.
succeeded()75   bool succeeded() const {
76     return ::mlir::succeeded(result) && diagnostics.empty();
77   }
78 
79   /// Returns `true` if this is a definite failure.
isDefiniteFailure()80   bool isDefiniteFailure() const {
81     return ::mlir::failed(result) && diagnostics.empty();
82   }
83 
84   /// Returns `true` if this is a silenceable failure.
isSilenceableFailure()85   bool isSilenceableFailure() const { return !diagnostics.empty(); }
86 
87   /// Returns the diagnostic message without emitting it. Expects this object
88   /// to be a silenceable failure.
getMessage()89   std::string getMessage() const {
90     std::string res;
91     for (auto &diagnostic : diagnostics) {
92       res.append(diagnostic.str());
93       res.append("\n");
94     }
95     return res;
96   }
97 
98   /// Returns a string representation of the failure mode (for error reporting).
getStatusString()99   std::string getStatusString() const {
100     if (succeeded())
101       return "success";
102     if (isSilenceableFailure())
103       return "silenceable failure";
104     return "definite failure";
105   }
106 
107   /// Converts silenceable failure into LogicalResult success without reporting
108   /// the diagnostic, preserves the other states.
silence()109   LogicalResult silence() {
110     if (!diagnostics.empty()) {
111       diagnostics.clear();
112       result = ::mlir::success();
113     }
114     return result;
115   }
116 
117   /// Take the diagnostics and silence.
takeDiagnostics(SmallVectorImpl<Diagnostic> & diags)118   void takeDiagnostics(SmallVectorImpl<Diagnostic> &diags) {
119     assert(!diagnostics.empty() && "expected a diagnostic to be present");
120     diags.append(std::make_move_iterator(diagnostics.begin()),
121                  std::make_move_iterator(diagnostics.end()));
122   }
123 
124   /// Streams the given values into the last diagnostic.
125   /// Expects this object to be a silenceable failure.
126   template <typename T>
127   DiagnosedSilenceableFailure &operator<<(T &&value) & {
128     assert(isSilenceableFailure() &&
129            "can only append output in silenceable failure state");
130     diagnostics.back() << std::forward<T>(value);
131     return *this;
132   }
133   template <typename T>
134   DiagnosedSilenceableFailure &&operator<<(T &&value) && {
135     return std::move(this->operator<<(std::forward<T>(value)));
136   }
137 
138   /// Attaches a note to the last diagnostic.
139   /// Expects this object to be a silenceable failure.
140   Diagnostic &attachNote(std::optional<Location> loc = std::nullopt) {
141     assert(isSilenceableFailure() &&
142            "can only attach notes to silenceable failures");
143     return diagnostics.back().attachNote(loc);
144   }
145 
146 private:
DiagnosedSilenceableFailure(LogicalResult result)147   explicit DiagnosedSilenceableFailure(LogicalResult result) : result(result) {}
DiagnosedSilenceableFailure(Diagnostic && diagnostic)148   explicit DiagnosedSilenceableFailure(Diagnostic &&diagnostic)
149       : result(failure()) {
150     diagnostics.emplace_back(std::move(diagnostic));
151   }
DiagnosedSilenceableFailure(SmallVector<Diagnostic> && diagnostics)152   explicit DiagnosedSilenceableFailure(SmallVector<Diagnostic> &&diagnostics)
153       : diagnostics(std::move(diagnostics)), result(failure()) {}
154 
155   /// The diagnostics associated with this object. If non-empty, the object is
156   /// considered to be in the silenceable failure state regardless of the
157   /// `result` field.
158   SmallVector<Diagnostic, 1> diagnostics;
159 
160   /// The "definite" logical state, either success or failure.
161   /// Ignored if the diagnostics message is present.
162   LogicalResult result;
163 
164 #if LLVM_ENABLE_ABI_BREAKING_CHECKS
165   /// Whether the associated diagnostics have been reported.
166   /// Diagnostics reporting consumes the diagnostics, so we need a mechanism to
167   /// differentiate reported diagnostics from a state where it was never
168   /// created.
169   bool reported = false;
170 #endif // LLVM_ENABLE_ABI_BREAKING_CHECKS
171 };
172 
173 class DiagnosedDefiniteFailure;
174 
175 DiagnosedDefiniteFailure emitDefiniteFailure(Location loc,
176                                              const Twine &message = {});
177 
178 /// A compatibility class connecting `InFlightDiagnostic` to
179 /// `DiagnosedSilenceableFailure` while providing an interface similar to the
180 /// former. Implicitly convertible to `DiagnosticSilenceableFailure` in definite
181 /// failure state and to `LogicalResult` failure. Reports the error on
182 /// conversion or on destruction. Instances of this class can be created by
183 /// `emitDefiniteFailure()`.
184 class DiagnosedDefiniteFailure {
185   friend DiagnosedDefiniteFailure emitDefiniteFailure(Location loc,
186                                                       const Twine &message);
187 
188 public:
189   /// Only move-constructible because it carries an in-flight diagnostic.
190   DiagnosedDefiniteFailure(DiagnosedDefiniteFailure &&) = default;
191 
192   /// Forward the message to the diagnostic.
193   template <typename T>
194   DiagnosedDefiniteFailure &operator<<(T &&value) & {
195     diag << std::forward<T>(value);
196     return *this;
197   }
198   template <typename T>
199   DiagnosedDefiniteFailure &&operator<<(T &&value) && {
200     return std::move(this->operator<<(std::forward<T>(value)));
201   }
202 
203   /// Attaches a note to the error.
204   Diagnostic &attachNote(std::optional<Location> loc = std::nullopt) {
205     return diag.attachNote(loc);
206   }
207 
208   /// Implicit conversion to DiagnosedSilenceableFailure in the definite failure
209   /// state. Reports the error.
DiagnosedSilenceableFailure()210   operator DiagnosedSilenceableFailure() {
211     diag.report();
212     return DiagnosedSilenceableFailure::definiteFailure();
213   }
214 
215   /// Implicit conversion to LogicalResult in the failure state. Reports the
216   /// error.
LogicalResult()217   operator LogicalResult() {
218     diag.report();
219     return failure();
220   }
221 
222 private:
223   /// Constructs a definite failure at the given location with the given
224   /// message.
DiagnosedDefiniteFailure(Location loc,const Twine & message)225   explicit DiagnosedDefiniteFailure(Location loc, const Twine &message)
226       : diag(emitError(loc, message)) {}
227 
228   /// Copy-construction and any assignment is disallowed to prevent repeated
229   /// error reporting.
230   DiagnosedDefiniteFailure(const DiagnosedDefiniteFailure &) = delete;
231   DiagnosedDefiniteFailure &
232   operator=(const DiagnosedDefiniteFailure &) = delete;
233   DiagnosedDefiniteFailure &operator=(DiagnosedDefiniteFailure &&) = delete;
234 
235   /// The error message.
236   InFlightDiagnostic diag;
237 };
238 
239 /// Emits a definite failure with the given message. The returned object allows
240 /// for last-minute modification to the error message, such as attaching notes
241 /// and completing the message. It will be reported when the object is
242 /// destructed or converted.
emitDefiniteFailure(Location loc,const Twine & message)243 inline DiagnosedDefiniteFailure emitDefiniteFailure(Location loc,
244                                                     const Twine &message) {
245   return DiagnosedDefiniteFailure(loc, message);
246 }
247 inline DiagnosedDefiniteFailure emitDefiniteFailure(Operation *op,
248                                                     const Twine &message = {}) {
249   return emitDefiniteFailure(op->getLoc(), message);
250 }
251 
252 /// Emits a silenceable failure with the given message. A silenceable failure
253 /// must be either suppressed or converted into a definite failure and reported
254 /// to the user.
255 inline DiagnosedSilenceableFailure
256 emitSilenceableFailure(Location loc, const Twine &message = {}) {
257   Diagnostic diag(loc, DiagnosticSeverity::Error);
258   diag << message;
259   return DiagnosedSilenceableFailure::silenceableFailure(std::move(diag));
260 }
261 inline DiagnosedSilenceableFailure
262 emitSilenceableFailure(Operation *op, const Twine &message = {}) {
263   return emitSilenceableFailure(op->getLoc(), message);
264 }
265 } // namespace mlir
266 
267 #endif // MLIR_DIALECT_TRANSFORM_UTILS_DIAGNOSEDSILENCEABLEFAILURE_H
268