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