FE 0.13.1
Header-only C++ frontend library
Loading...
Searching...
No Matches
error.h
Go to the documentation of this file.
1#pragma once
2
3#include <cassert>
4
5#include <array>
6#include <exception>
7#include <format>
8#include <functional>
9#include <iostream>
10#include <sstream>
11#include <string>
12#include <string_view>
13#include <utility>
14#include <vector>
15
16#include "fe/diag.h"
17#include "fe/format.h"
18#include "fe/loc.h"
19#include "fe/term.h"
20
21namespace fe {
22
23struct Driver;
24
25/// Collects diagnostics and hands each to the Diag that lays it out.
26/// Error::ack once you are done: it throws an Error::Bail if anything went wrong.
27class Error {
28public:
29 using Tag = Diag::Tag;
30
31 /// What Error::ack and Error::bail throw.
32 /// It carries the finished text, so nothing in here points into a SrcMap any more and it may
33 /// propagate past the Driver - and the Src%s - that produced it.
34 class Bail : public std::exception {
35 public:
36 Bail(std::string what, size_t num_errors, size_t num_warnings)
37 : what_(std::move(what))
38 , num_errors_(num_errors)
39 , num_warnings_(num_warnings) {}
40
41 const char* what() const noexcept override { return what_.c_str(); }
42 size_t num_errors() const { return num_errors_; }
43 size_t num_warnings() const { return num_warnings_; }
44
45 friend std::ostream& operator<<(std::ostream& os, const Bail& bail) { return os << bail.what_; }
46
47 private:
48 std::string what_;
49 size_t num_errors_;
50 size_t num_warnings_;
51 };
52
53 /// A secondary message elaborating a Msg.
54 /// A Note with a Loc of its own reads as a diagnostic of its own - header line plus snippet;
55 /// one without has nowhere else to point and renders as a `= note:` continuation line.
56 struct Note {
58 std::string str;
59 };
60
61 /// One Tag::Error or Tag::Warn together with the Note%s that belong to it.
62 struct Msg {
65 std::string str;
66 std::vector<Note> notes;
67 };
68
69 /// A sink of your own; Driver::error is the one every frontend building block already reports to.
70 /// @warning A Msg::loc points into @p driver's SrcMap and Error::msg renders through Diag::render,
71 /// so @p driver must outlive this Error.
72 explicit Error(const Driver& driver)
73 : driver_(&driver) {}
74
75 /// @name Getters
76 ///@{
77 const auto& msgs() const { return msgs_; }
78 bool empty() const { return msgs_.empty(); }
79 bool ok() const { return num_errors() == 0; } ///< Nothing recorded that must stop the compilation?
80 size_t num_errors() const { return num_[size_t(Tag::Error)]; }
81 size_t num_warnings() const { return num_[size_t(Tag::Warn)]; }
82 size_t num_notes() const { return num_[size_t(Tag::Note)]; }
83 bool truncated() const { return truncated_; } ///< Did Diag::max_errors drop anything?
84 ///@}
85
86 /// @name Add a Message
87 /// Each of these yields the Error again, so a diagnostic, its Note%s, and a closing Error::bail chain.
88 ///@{
89 /// Records the message @p fmt renders; see Diag::render.
90 /// @p tag must be Tag::Error or Tag::Warn - a Tag::Note belongs to Error::note.
91 Error& msg(Loc loc, Tag tag, const std::function<std::string()>& fmt) {
92 msg_(loc, tag, fmt);
93 return *this;
94 }
95
96 // clang-format off
97 /// @note Formats via `std::vformat` because Diag::render may render @p s more than once.
98 template<class... Args> Error& msg(Loc loc, Tag tag, std::format_string<Args...> s, Args&&... args) {
99 msg_(loc, tag, [&] { return std::vformat(s.get(), std::make_format_args(args...)); });
100 return *this;
101 }
102
103 template<class... Args> Error& error(Loc loc, std::format_string<Args...> s, Args&&... args) { return msg(loc, Tag::Error, s, std::forward<Args>(args)...); }
104 template<class... Args> Error& warn (Loc loc, std::format_string<Args...> s, Args&&... args) { return msg(loc, Tag::Warn, s, std::forward<Args>(args)...); }
105
106 /// A `= note:` continuation of the diagnostic being built; it has no Loc of its own to point at.
107 template<class... Args> Error& note(std::format_string<Args...> s, Args&&... args) {
108 note_(Loc(), [&] { return std::vformat(s.get(), std::make_format_args(args...)); });
109 return *this;
110 }
111
112 /// A Note that points *elsewhere*; dropped when @p loc adds nothing.
113 /// A @p loc overlapping the primary one is already covered by its snippet and so points nowhere new.
114 /// An invalid @p loc points *nowhere* and degrades to the `= note:` continuation above.
115 /// The renderer gives @p loc a header line of its own, so phrase the message to stand alone.
116 template<class... Args> Error& note(Loc loc, std::format_string<Args...> s, Args&&... args) {
117 if (loc && (loc & primary_loc_())) return *this;
118 note_(loc, [&] { return std::vformat(s.get(), std::make_format_args(args...)); });
119 return *this;
120 }
121 // clang-format on
122 ///@}
123
124 /// @name Handle Errors/Warnings
125 ///@{
126 void clear() {
127 msgs_.clear();
128 num_ = {};
129 truncated_ = false;
130 dropped_ = false;
131 }
132
133 /// Renders everything collected so far the way it would appear on @p os; @p os only decides the coloring.
134 std::string str(std::ostream& os = std::cerr) const {
136 auto oss = std::ostringstream();
137 oss << *this;
138 return oss.str();
139 }
140
141 /// Streams everything collected so far to @p os and claims it.
142 /// @returns the number of Tag::Error%s that were reported.
143 size_t report(std::ostream& os = std::cerr) {
144 auto num = num_errors();
145 if (!empty()) os << *this;
146 clear();
147 return num;
148 }
149
150 /// Claims everything collected so far and throws it as a Bail rendered for @p os.
151 [[noreturn]] void bail(std::ostream& os = std::cerr) {
152 auto bail = Bail(str(os), num_errors(), num_warnings());
153 clear();
154 throw bail;
155 }
156
157 /// If errors occurred, Error::bail; otherwise Error::report any warnings to @p os.
158 void ack(std::ostream& os = std::cerr) {
159 if (num_errors() != 0) bail(os);
160 report(os);
161 }
162 ///@}
163
164 /// Hands every Msg, its Note%s, and the closing summary to Diag.
165 friend std::ostream& operator<<(std::ostream& os, const Error& e) {
166 const auto& diag = e.diag();
167
168 for (const auto& msg : e.msgs_) {
169 diag.header(os, msg.loc, msg.tag, msg.str);
170 diag.snippet(os, msg.loc, msg.tag);
171 for (const auto& note : msg.notes)
172 diag.note(os, note.loc, note.str);
173 }
174
175 diag.summary(os, e.num_errors(), e.num_warnings(), e.truncated_);
176 return os;
177 }
178
179private:
180 const Diag& diag() const; ///< Driver is incomplete here - it owns an Error of its own.
181
182 /// Loc of the Msg that subsequent Note%s belong to.
183 Loc primary_loc_() const { return msgs_.empty() ? Loc() : msgs_.back().loc; }
184
185 void msg_(Loc loc, Tag tag, const std::function<std::string()>& fmt) {
186 assert(tag != Tag::Note && "a note belongs to Error::note");
187 const auto& d = diag();
188 if (tag == Tag::Warn && d.werror) tag = Tag::Error;
189
190 if (tag == Tag::Error && d.max_errors != 0 && num_errors() >= d.max_errors) {
191 truncated_ = dropped_ = true;
192 return;
193 }
194
195 dropped_ = false;
196 ++num_[size_t(tag)];
197 msgs_.emplace_back(loc, tag, d.render(fmt));
198 }
199
200 void note_(Loc loc, const std::function<std::string()>& fmt) {
201 if (dropped_) return;
202 assert(!msgs_.empty() && "a note needs an error or warning to attach to");
203 ++num_[size_t(Tag::Note)];
204 msgs_.back().notes.emplace_back(loc, diag().render(fmt));
205 }
206
207 const Driver* driver_;
208 std::vector<Msg> msgs_;
209 std::array<size_t, 3> num_ = {};
210 bool truncated_ = false;
211 bool dropped_ = false; ///< Was the Msg that Note%s would attach to dropped?
212};
213
214} // namespace fe
215
216#ifndef DOXYGEN // clang-format off
217template<> struct std::formatter<fe::Error > : fe::ostream_formatter {};
218template<> struct std::formatter<fe::Error::Bail> : fe::ostream_formatter {};
219template<> struct std::formatter<fe::Diag::Tag > : fe::ostream_formatter {};
220#endif // clang-format on
How a diagnostic lays out - and how much of it an Error keeps.
Definition diag.h:19
virtual void summary(std::ostream &, size_t num_errors, size_t num_warnings, bool truncated) const
Bail(std::string what, size_t num_errors, size_t num_warnings)
Definition error.h:36
size_t num_warnings() const
Definition error.h:43
size_t num_errors() const
Definition error.h:42
friend std::ostream & operator<<(std::ostream &os, const Bail &bail)
Definition error.h:45
const char * what() const noexcept override
Definition error.h:41
Error(const Driver &driver)
A sink of your own; Driver::error is the one every frontend building block already reports to.
Definition error.h:72
size_t num_notes() const
Definition error.h:82
std::string str(std::ostream &os=std::cerr) const
Renders everything collected so far the way it would appear on os; os only decides the coloring.
Definition error.h:134
Error & msg(Loc loc, Tag tag, const std::function< std::string()> &fmt)
Definition error.h:91
void ack(std::ostream &os=std::cerr)
If errors occurred, Error::bail; otherwise Error::report any warnings to os.
Definition error.h:158
const auto & msgs() const
Definition error.h:77
void clear()
Definition error.h:126
void bail(std::ostream &os=std::cerr)
Claims everything collected so far and throws it as a Bail rendered for os.
Definition error.h:151
bool truncated() const
Did Diag::max_errors drop anything?
Definition error.h:83
Error & msg(Loc loc, Tag tag, std::format_string< Args... > s, Args &&... args)
Definition error.h:98
Error & warn(Loc loc, std::format_string< Args... > s, Args &&... args)
Definition error.h:104
friend std::ostream & operator<<(std::ostream &os, const Error &e)
Hands every Msg, its Notes, and the closing summary to Diag.
Definition error.h:165
size_t report(std::ostream &os=std::cerr)
Streams everything collected so far to os and claims it.
Definition error.h:143
size_t num_warnings() const
Definition error.h:81
Error & note(std::format_string< Args... > s, Args &&... args)
A = note: continuation of the diagnostic being built; it has no Loc of its own to point at.
Definition error.h:107
Diag::Tag Tag
Definition error.h:29
std::string str
Definition error.h:65
size_t num_errors() const
Definition error.h:80
bool empty() const
Definition error.h:78
Error & error(Loc loc, std::format_string< Args... > s, Args &&... args)
Definition error.h:103
std::string str
Definition error.h:58
std::vector< Note > notes
Definition error.h:66
bool ok() const
Nothing recorded that must stop the compilation?
Definition error.h:79
Error & note(Loc loc, std::format_string< Args... > s, Args &&... args)
A Note that points elsewhere; dropped when loc adds nothing.
Definition error.h:116
One Tag::Error or Tag::Warn together with the Notes that belong to it.
Definition error.h:62
A secondary message elaborating a Msg.
Definition error.h:56
Restore< Mode, &mode, &set_mode > ScopedMode
Overrides the color mode for the duration of the scope.
Definition term.h:226
bool use_color(std::ostream &os) noexcept
Whether color escape sequences are emitted for os right now.
Definition term.h:191
Definition algo.h:17
basic_ostream_formatter< char > ostream_formatter
Definition format.h:64
Definition span.h:129
Use/derive from this class for "global" variables that you need all over the place.
Definition driver.h:22
Location within a Src: the half-open byte range [Loc::begin, Loc::end).
Definition loc.h:46
constexpr Loc()=default
Creates an invalid Location.