CppInterOp
C++ Language Interoperability Layer
Loading...
Searching...
No Matches
Error.h
Go to the documentation of this file.
1//===--- Error.h - Result<T>, ErrorRef, DiagnosticRef -----------*- C++ -*-===//
2//
3// Part of the compiler-research project, under the Apache License v2.0 with
4// LLVM Exceptions.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// Cpp::Result<T> carries either a T or an ErrorRef. ErrorRef encodes
10// either an inline Status code (no allocation) or a pointer to an
11// ErrorSlice owning drained Clang diagnostics, an optional structured
12// payload, and producer attribution.
13//
14//===----------------------------------------------------------------------===//
15
16#ifndef CPPINTEROP_ERROR_H
17#define CPPINTEROP_ERROR_H
18
20
21#ifdef __cplusplus
22
23#include <cstddef>
24#include <cstdint>
25#include <new>
26#include <string>
27#include <type_traits>
28#include <utility>
29#include <vector>
30
31namespace Cpp {
32
33/// Outcome of a fallible API call.
34enum class Status : uint8_t {
35 Ok = 0,
36 NotFound, // name lookup found nothing
37 InvalidArgument, // precondition violated by caller
38 Ambiguous, // name lookup returned multiple matches
39 ParseError, // Clang parser rejected the input
40 CompileError, // Sema/codegen rejected the input
41};
42
43/// Severity of a captured diagnostic.
44enum class DiagnosticSeverity : uint8_t { Note, Warning, Error, Fatal };
45
46/// Non-owning view over a contiguous range. Returned from accessors so
47/// callers can iterate without committing to a container type.
48template <typename T> struct ArrayView {
49 const T* Data = nullptr;
50 size_t Size = 0;
51
52 [[nodiscard]] const T* begin() const { return Data; }
53 [[nodiscard]] const T* end() const { return Data + Size; }
54 [[nodiscard]] size_t size() const { return Size; }
55 [[nodiscard]] bool empty() const { return Size == 0; }
56 const T& operator[](size_t I) const { return Data[I]; }
57};
58
59/// 8-byte opaque handle to one captured diagnostic. Valid while the
60/// owning error (or interpreter) keeps its storage alive.
61struct DiagnosticRef {
62 const void* data = nullptr;
63
64 [[nodiscard]] bool isNull() const { return data == nullptr; }
65
66 // Member-function surface (forwarders defined in ErrorInternal.cpp).
67 CPPINTEROP_API DiagnosticSeverity severity() const;
68 CPPINTEROP_API const char* message() const;
69 CPPINTEROP_API const char* file() const;
70 CPPINTEROP_API unsigned line() const;
71 CPPINTEROP_API unsigned column() const;
72};
73
74/// Field accessors. The TableGen binding surface; member functions
75/// above forward here. Safe to call on a null DiagnosticRef: empty
76/// strings, zero positions, Severity::Note.
77CPPINTEROP_API DiagnosticSeverity GetDiagnosticSeverity(DiagnosticRef D);
78CPPINTEROP_API const char* GetDiagnosticMessage(DiagnosticRef D);
79CPPINTEROP_API const char* GetDiagnosticFile(DiagnosticRef D);
80CPPINTEROP_API unsigned GetDiagnosticLine(DiagnosticRef D);
81CPPINTEROP_API unsigned GetDiagnosticColumn(DiagnosticRef D);
82
83//
84// Encoding (8 bytes, single uintptr_t):
85// bits == 0 : Ok.
86// bits & 1 == 1 : inline Status in bits[1..7]. No allocation.
87// bits & 1 == 0,
88// bits != 0 : pointer to ErrorSlice (diagnostics, payload,
89// refcount). Result<T> manages the refcount.
90
91struct ErrorSlice; // defined in lib/CppInterOp/ErrorInternal.h
92
93struct ErrorRef {
94 uintptr_t bits = 0;
95
96 [[nodiscard]] bool isOk() const { return bits == 0; }
97 [[nodiscard]] bool isInline() const { return (bits & 1U) == 1U; }
98 [[nodiscard]] bool isSlice() const { return bits != 0 && (bits & 1U) == 0u; }
99
100 /// Pack a Status code into the ErrorRef bits. Status::Ok produces
101 /// the canonical null encoding so isOk() stays a single comparison.
102 static ErrorRef makeInline(Status S) {
103 if (S == Status::Ok)
104 return ErrorRef{};
105 return ErrorRef{(static_cast<uintptr_t>(S) << 1) | 1U};
106 }
107
108 /// The inline-encoded Status. Only meaningful when isInline() is
109 /// true; callers should prefer status() which handles both shapes.
110 [[nodiscard]] Status inlineStatus() const {
111 return static_cast<Status>(bits >> 1);
112 }
113
114 /// Wrap a slice pointer. The slice's alignment must keep bit 0 clear
115 /// (ErrorSlice is alignas(16) for this reason).
116 static ErrorRef makeSlice(const ErrorSlice* S) {
117 return ErrorRef{reinterpret_cast<uintptr_t>(S)};
118 }
119 [[nodiscard]] const ErrorSlice* slice() const {
120 return isSlice() ? reinterpret_cast<const ErrorSlice*>(bits) : nullptr;
121 }
122
123 // Member-function surface (defined in Error.cpp).
124 CPPINTEROP_API Status status() const;
125 CPPINTEROP_API ArrayView<DiagnosticRef> diagnostics() const;
126 CPPINTEROP_API const char* producer() const;
127 CPPINTEROP_API const char* producerSignature() const;
128 CPPINTEROP_API class ErrorRecord record() const;
129};
130
131/// Free-function aliases over the ErrorRef accessors. The TableGen
132/// binding surface; the member functions above forward here.
133CPPINTEROP_API Status GetStatus(ErrorRef E);
134CPPINTEROP_API ArrayView<DiagnosticRef> GetDiagnostics(ErrorRef E);
135
136/// Refcount hooks. Result<T> calls these around copy/move/dtor; they
137/// are no-ops on Ok and inline encodings.
138CPPINTEROP_API void RetainErrorRef(ErrorRef E);
139CPPINTEROP_API void ReleaseErrorRef(ErrorRef E);
140
141//
142// Deep-copy snapshot for callers that want a plain value-type and do
143// not need the originating slice kept alive. Strings are copied;
144// nothing points back into slice storage.
145
146struct DiagnosticInfo {
147 DiagnosticSeverity Severity = DiagnosticSeverity::Error;
148 std::string Message;
149 std::string File;
150 unsigned Line = 0;
151 unsigned Column = 0;
152};
153
154class ErrorRecord {
155public:
156 Status Code = Status::Ok;
157 std::vector<DiagnosticInfo> Diagnostics;
158 // Pointers into static __func__ / __PRETTY_FUNCTION__ storage. No
159 // copying needed; the storage outlives every record.
160 const char* Producer = nullptr;
161 const char* ProducerSignature = nullptr;
162};
163
164//
165// Extends a slice's lifetime past the originating Result<T> via one
166// refcount bump on construction and one decrement on destruction.
167// Inline errors are pure value -- no refcount work.
168
169class [[nodiscard]] CapturedError {
170 ErrorRef Ref;
171
172public:
173 CapturedError() = default;
174 explicit CapturedError(ErrorRef E) : Ref(E) { RetainErrorRef(Ref); }
175
176 CapturedError(const CapturedError& O) : Ref(O.Ref) { RetainErrorRef(Ref); }
177 CapturedError(CapturedError&& O) noexcept : Ref(O.Ref) { O.Ref = ErrorRef{}; }
178
179 CapturedError& operator=(const CapturedError& O) {
180 if (this != &O) {
181 ReleaseErrorRef(Ref);
182 Ref = O.Ref;
183 RetainErrorRef(Ref);
184 }
185 return *this;
186 }
187 CapturedError& operator=(CapturedError&& O) noexcept {
188 if (this != &O) {
189 ReleaseErrorRef(Ref);
190 Ref = O.Ref;
191 O.Ref = ErrorRef{};
192 }
193 return *this;
194 }
195
196 ~CapturedError() { ReleaseErrorRef(Ref); }
197
198 [[nodiscard]] bool ok() const { return Ref.isOk(); }
199 explicit operator bool() const { return ok(); }
200 [[nodiscard]] Status status() const { return Ref.status(); }
201 [[nodiscard]] ErrorRef ref() const { return Ref; }
202
203 [[nodiscard]] ArrayView<DiagnosticRef> diagnostics() const {
204 return Ref.diagnostics();
205 }
206 [[nodiscard]] const char* producer() const { return Ref.producer(); }
207 [[nodiscard]] const char* producerSignature() const {
208 return Ref.producerSignature();
209 }
210 [[nodiscard]] ErrorRecord record() const { return Ref.record(); }
211};
212
213[[noreturn]] CPPINTEROP_API void ResultAbort_ValueOnError(const ErrorRef& Err);
214
215#ifndef NDEBUG
216[[noreturn]] CPPINTEROP_API void
217ResultAbort_UncheckedOnDtor(const ErrorRef& Err);
218#endif
219
220template <typename T> class [[nodiscard]] Result {
221 static_assert(!std::is_same<T, void>::value,
222 "Result<void> is provided via specialization below");
223
224 static constexpr size_t kStorageSize = sizeof(T) > sizeof(ErrorRef)
225 ? sizeof(T)
226 : sizeof(ErrorRef);
227 static constexpr size_t kStorageAlign = alignof(T) > alignof(ErrorRef)
228 ? alignof(T)
229 : alignof(ErrorRef);
230
231 union {
232 alignas(kStorageAlign) unsigned char m_ValueBytes[kStorageSize];
233 ErrorRef m_ErrState;
234 };
235 bool m_HasError = false;
236
237#ifndef NDEBUG
238 mutable bool m_Unchecked = true;
239 void markChecked() const { m_Unchecked = false; }
240#else
241 void markChecked() const {}
242#endif
243
244 T* valuePtr() { return reinterpret_cast<T*>(m_ValueBytes); }
245 const T* valuePtr() const { return reinterpret_cast<const T*>(m_ValueBytes); }
246
247public:
248 Result() { new (m_ValueBytes) T(); }
249
250 Result(T V) { new (m_ValueBytes) T(std::move(V)); }
251
252 Result(ErrorRef E) : m_ErrState(E), m_HasError(!E.isOk()) {
253 if (m_HasError)
254 RetainErrorRef(m_ErrState);
255 }
256
257 Result(const Result& Other) : m_HasError(Other.m_HasError) {
258 if (m_HasError) {
259 m_ErrState = Other.m_ErrState;
260 RetainErrorRef(m_ErrState);
261 } else {
262 new (m_ValueBytes) T(*Other.valuePtr());
263 }
264#ifndef NDEBUG
265 m_Unchecked = Other.m_Unchecked;
266 Other.m_Unchecked = false;
267#endif
268 }
269
270 Result(Result&& Other) noexcept : m_HasError(Other.m_HasError) {
271 if (m_HasError) {
272 m_ErrState = Other.m_ErrState;
273 Other.m_ErrState = ErrorRef{};
274 Other.m_HasError = false;
275 } else {
276 new (m_ValueBytes) T(std::move(*Other.valuePtr()));
277 }
278#ifndef NDEBUG
279 m_Unchecked = Other.m_Unchecked;
280 Other.m_Unchecked = false;
281#endif
282 }
283
284 ~Result() {
285#ifndef NDEBUG
286 if (m_Unchecked && m_HasError)
287 ResultAbort_UncheckedOnDtor(m_ErrState);
288#endif
289 if (m_HasError)
290 ReleaseErrorRef(m_ErrState);
291 else
292 valuePtr()->~T();
293 }
294
295 Result& operator=(const Result&) = delete;
296 Result& operator=(Result&&) = delete;
297
298 void ignore() const { markChecked(); }
299
300 [[nodiscard]] bool ok() const {
301 markChecked();
302 return !m_HasError;
303 }
304 explicit operator bool() const { return ok(); }
305
306 [[nodiscard]] Status status() const {
307 markChecked();
308 return m_HasError ? m_ErrState.status() : Status::Ok;
309 }
310
311 [[nodiscard]] ErrorRef error() const {
312 markChecked();
313 return m_HasError ? m_ErrState : ErrorRef{};
314 }
315
316 /// Share ownership of the carried ErrorRef with a CapturedError so
317 /// the error outlives this Result. One refcount bump on slice
318 /// errors; no-op for inline ones.
319 [[nodiscard]] CapturedError share() const {
320 markChecked();
321 return m_HasError ? CapturedError(m_ErrState) : CapturedError();
322 }
323
324 T value() const {
325 markChecked();
326 if (m_HasError)
327 ResultAbort_ValueOnError(m_ErrState);
328 return *valuePtr();
329 }
330
331 T value_or(T fallback) const {
332 markChecked();
333 return m_HasError ? std::move(fallback) : *valuePtr();
334 }
335};
336
337// Void specialization.
338
339template <> class [[nodiscard]] Result<void> {
340 ErrorRef m_ErrState;
341#ifndef NDEBUG
342 mutable bool m_Unchecked = true;
343 void markChecked() const { m_Unchecked = false; }
344#else
345 void markChecked() const {}
346#endif
347
348public:
349 Result() = default;
350 Result(ErrorRef E) : m_ErrState(E) {
351 if (!E.isOk())
352 RetainErrorRef(m_ErrState);
353 }
354
355 Result(const Result& O) : m_ErrState(O.m_ErrState) {
356 if (!m_ErrState.isOk())
357 RetainErrorRef(m_ErrState);
358#ifndef NDEBUG
359 m_Unchecked = O.m_Unchecked;
360 O.m_Unchecked = false;
361#endif
362 }
363
364 Result(Result&& O) noexcept : m_ErrState(O.m_ErrState) {
365#ifndef NDEBUG
366 m_Unchecked = O.m_Unchecked;
367 O.m_Unchecked = false;
368#endif
369 O.m_ErrState = ErrorRef{};
370 }
371
372 ~Result() {
373#ifndef NDEBUG
374 if (m_Unchecked && !m_ErrState.isOk())
375 ResultAbort_UncheckedOnDtor(m_ErrState);
376#endif
377 if (!m_ErrState.isOk())
378 ReleaseErrorRef(m_ErrState);
379 }
380
381 Result& operator=(const Result&) = delete;
382 Result& operator=(Result&&) = delete;
383
384 void ignore() const { markChecked(); }
385
386 [[nodiscard]] bool ok() const {
387 markChecked();
388 return m_ErrState.isOk();
389 }
390 explicit operator bool() const { return ok(); }
391
392 [[nodiscard]] Status status() const {
393 markChecked();
394 return m_ErrState.status();
395 }
396
397 [[nodiscard]] ErrorRef error() const {
398 markChecked();
399 return m_ErrState;
400 }
401
402 [[nodiscard]] CapturedError share() const {
403 markChecked();
404 return CapturedError(m_ErrState);
405 }
406};
407
408} // namespace Cpp
409
410#endif // __cplusplus
411
412#endif // CPPINTEROP_ERROR_H
#define CPPINTEROP_API
Definition Box.h:70
void RetainErrorRef(ErrorRef E)
Status GetStatus(ErrorRef E)
const char * GetDiagnosticFile(DiagnosticRef D)
void ResultAbort_UncheckedOnDtor(const ErrorRef &)
unsigned GetDiagnosticLine(DiagnosticRef D)
DiagnosticSeverity GetDiagnosticSeverity(DiagnosticRef D)
ArrayView< DiagnosticRef > GetDiagnostics(ErrorRef E)
const char * GetDiagnosticMessage(DiagnosticRef D)
void ReleaseErrorRef(ErrorRef E)
void ResultAbort_ValueOnError(const ErrorRef &)
unsigned GetDiagnosticColumn(DiagnosticRef D)