forked from carbon-language/carbon-lang
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdiagnostic_emitter.h
393 lines (338 loc) · 14.7 KB
/
diagnostic_emitter.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#ifndef CARBON_TOOLCHAIN_DIAGNOSTICS_DIAGNOSTIC_EMITTER_H_
#define CARBON_TOOLCHAIN_DIAGNOSTICS_DIAGNOSTIC_EMITTER_H_
#include <cstdint>
#include <string>
#include <type_traits>
#include <utility>
#include "common/check.h"
#include "llvm/ADT/Any.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/Support/FormatVariadic.h"
#include "toolchain/diagnostics/diagnostic.h"
#include "toolchain/diagnostics/diagnostic_consumer.h"
#include "toolchain/diagnostics/diagnostic_kind.h"
namespace Carbon {
namespace Internal {
// Disable type deduction based on `args`; the type of `diagnostic_base`
// determines the diagnostic's parameter types.
template <typename Arg>
using NoTypeDeduction = std::type_identity_t<Arg>;
} // namespace Internal
template <typename LocT, typename AnnotateFn>
class DiagnosticAnnotationScope;
// The result of `DiagnosticConvert::ConvertLoc`. This is non-templated to allow
// sharing across converters.
struct ConvertedDiagnosticLoc {
// Becomes DiagnosticMessage::loc.
DiagnosticLoc loc;
// Becomes Diagnostic::last_byte_offset.
int32_t last_byte_offset;
};
// Used by types to indicate a diagnostic type conversion that results in the
// provided StorageType. For example, to convert NameId to a std::string, we
// write:
//
// struct NameId {
// using DiagnosticType = DiagnosticTypeInfo<std::string>;
// };
template <typename StorageTypeT>
struct DiagnosticTypeInfo {
using StorageType = StorageTypeT;
};
// Manages the creation of reports, the testing if diagnostics are enabled, and
// the collection of reports.
//
// This class is parameterized by a location type, allowing different
// diagnostic clients to provide location information in whatever form is most
// convenient for them, such as a position within a buffer when lexing, a token
// when parsing, or a parse tree node when type-checking, and to allow unit
// tests to be decoupled from any concrete location representation.
template <typename LocT>
class DiagnosticEmitter {
public:
// A builder-pattern type to provide a fluent interface for constructing
// a more complex diagnostic. See `DiagnosticEmitter::Build` for the
// expected usage.
// This is nodiscard to protect against accidentally building a diagnostic
// without emitting it.
class [[nodiscard]] DiagnosticBuilder {
public:
// DiagnosticBuilder is move-only and cannot be copied.
DiagnosticBuilder(DiagnosticBuilder&&) noexcept = default;
auto operator=(DiagnosticBuilder&&) noexcept
-> DiagnosticBuilder& = default;
// Adds a note diagnostic attached to the main diagnostic being built.
// The API mirrors the main emission API: `DiagnosticEmitter::Emit`.
// For the expected usage see the builder API: `DiagnosticEmitter::Build`.
template <typename... Args>
auto Note(LocT loc, const DiagnosticBase<Args...>& diagnostic_base,
Internal::NoTypeDeduction<Args>... args) -> DiagnosticBuilder&;
// Emits the built diagnostic and its attached notes.
// For the expected usage see the builder API: `DiagnosticEmitter::Build`.
template <typename... Args>
auto Emit() -> void;
// Returns true if this DiagnosticBuilder may emit a diagnostic. Can be used
// to avoid excess work computing notes, etc, if no diagnostic is going to
// be emitted anyway.
explicit operator bool() { return emitter_; }
private:
friend class DiagnosticEmitter<LocT>;
template <typename... Args>
explicit DiagnosticBuilder(DiagnosticEmitter<LocT>* emitter, LocT loc,
const DiagnosticBase<Args...>& diagnostic_base,
llvm::SmallVector<llvm::Any> args);
// Create a null `DiagnosticBuilder` that will not emit anything. Notes will
// be silently ignored.
DiagnosticBuilder() : emitter_(nullptr) {}
// Adds a message to the diagnostic, handling conversion of the location and
// arguments.
template <typename... Args>
auto AddMessage(LocT loc, const DiagnosticBase<Args...>& diagnostic_base,
llvm::SmallVector<llvm::Any> args) -> void;
// Adds a message to the diagnostic, handling conversion of the arguments. A
// DiagnosticLoc must be provided instead of a LocT in order to
// avoid potential recursion.
template <typename... Args>
auto AddMessageWithDiagnosticLoc(
DiagnosticLoc loc, const DiagnosticBase<Args...>& diagnostic_base,
llvm::SmallVector<llvm::Any> args) -> void;
// Handles the cast of llvm::Any to Args types for formatv.
// TODO: Custom formatting can be provided with an format_provider, but that
// affects all formatv calls. Consider replacing formatv with a custom call
// that allows diagnostic-specific formatting.
template <typename... Args, size_t... N>
static auto FormatFn(const DiagnosticMessage& message,
std::index_sequence<N...> /*indices*/) -> std::string;
DiagnosticEmitter<LocT>* emitter_;
Diagnostic diagnostic_;
};
// `consumer` is required to outlive the diagnostic emitter.
explicit DiagnosticEmitter(DiagnosticConsumer* consumer)
: consumer_(consumer) {}
virtual ~DiagnosticEmitter() = default;
// Emits an error.
//
// When passing arguments, they may be buffered. As a consequence, lifetimes
// may outlive the `Emit` call.
template <typename... Args>
auto Emit(LocT loc, const DiagnosticBase<Args...>& diagnostic_base,
Internal::NoTypeDeduction<Args>... args) -> void;
// A fluent interface for building a diagnostic and attaching notes for added
// context or information. For example:
//
// emitter_.Build(loc1, MyDiagnostic)
// .Note(loc2, MyDiagnosticNote)
// .Emit();
template <typename... Args>
auto Build(LocT loc, const DiagnosticBase<Args...>& diagnostic_base,
Internal::NoTypeDeduction<Args>... args) -> DiagnosticBuilder;
// Create a null `DiagnosticBuilder` that will not emit anything. Notes will
// be silently ignored.
auto BuildSuppressed() -> DiagnosticBuilder { return DiagnosticBuilder(); }
protected:
// Callback type used to report context messages from ConvertLoc.
// Note that the first parameter type is DiagnosticLoc rather than
// LocT, because ConvertLoc must not recurse.
using ContextFnT =
llvm::function_ref<auto(DiagnosticLoc, const DiagnosticBase<>&)->void>;
// Converts a LocT to a DiagnosticLoc and its `last_byte_offset` (see
// `DiagnosticMessage`). ConvertLoc may invoke context_fn to provide context
// messages.
virtual auto ConvertLoc(LocT loc, ContextFnT context_fn) const
-> ConvertedDiagnosticLoc = 0;
// Converts arg types as needed. Most children don't customize conversion, so
// the default returns the argument unchanged.
virtual auto ConvertArg(llvm::Any arg) const -> llvm::Any { return arg; }
private:
// Converts an argument to llvm::Any for storage, handling input to storage
// type conversion when needed.
template <typename Arg>
auto MakeAny(Arg arg) -> llvm::Any;
template <typename OtherLocT, typename AnnotateFn>
friend class DiagnosticAnnotationScope;
DiagnosticConsumer* consumer_;
llvm::SmallVector<llvm::function_ref<auto(DiagnosticBuilder& builder)->void>>
annotate_fns_;
};
// This relies on `void*` location handling on `DiagnosticEmitter`.
//
// TODO: Based on how this ends up used or if we get more distinct emitters, it
// might be worth considering having diagnostics specify that they don't apply
// to source-location carrying emitters. For example, this might look like a
// `CARBON_NO_LOC_DIAGNOSTIC` macro, or some other factoring. But it might end
// up being more noise than it is worth.
class NoLocDiagnosticEmitter : public DiagnosticEmitter<void*> {
public:
using DiagnosticEmitter::DiagnosticEmitter;
// Emits an error. This specialization only applies to
// `NoLocDiagnosticEmitter`.
template <typename... Args>
auto Emit(const DiagnosticBase<Args...>& diagnostic_base,
Internal::NoTypeDeduction<Args>... args) -> void {
DiagnosticEmitter::Emit(nullptr, diagnostic_base, args...);
}
protected:
auto ConvertLoc(void* /*loc*/, ContextFnT /*context_fn*/) const
-> ConvertedDiagnosticLoc override {
return {.loc = {.filename = ""}, .last_byte_offset = -1};
}
};
// An RAII object that denotes a scope in which any diagnostic produced should
// be annotated in some way.
//
// This object is given a function `annotate` that will be called with a
// `DiagnosticBuilder& builder` for any diagnostic that is emitted through the
// given emitter. That function can annotate the diagnostic by calling
// `builder.Note` to add notes.
template <typename LocT, typename AnnotateFn>
class DiagnosticAnnotationScope {
public:
DiagnosticAnnotationScope(DiagnosticEmitter<LocT>* emitter,
AnnotateFn annotate)
: emitter_(emitter), annotate_(std::move(annotate)) {
emitter_->annotate_fns_.push_back(annotate_);
}
~DiagnosticAnnotationScope() { emitter_->annotate_fns_.pop_back(); }
private:
DiagnosticEmitter<LocT>* emitter_;
// Make a copy of the annotation function to ensure that it lives long enough.
AnnotateFn annotate_;
};
template <typename LocT, typename AnnotateFn>
DiagnosticAnnotationScope(DiagnosticEmitter<LocT>* emitter, AnnotateFn annotate)
-> DiagnosticAnnotationScope<LocT, AnnotateFn>;
// ============================================================================
// Only internal implementation details below this point.
// ============================================================================
namespace Internal {
// Determines whether there's a DiagnosticType member on Arg.
// Used by DiagnosticEmitter.
template <typename Arg>
concept HasDiagnosticType = requires { typename Arg::DiagnosticType; };
// The default implementation with no conversion.
template <typename Arg>
struct DiagnosticTypeForArg : public DiagnosticTypeInfo<Arg> {};
// Exposes a custom conversion for an argument type.
template <typename Arg>
requires HasDiagnosticType<Arg>
struct DiagnosticTypeForArg<Arg> : public Arg::DiagnosticType {};
} // namespace Internal
template <typename LocT>
template <typename... Args>
auto DiagnosticEmitter<LocT>::DiagnosticBuilder::Note(
LocT loc, const DiagnosticBase<Args...>& diagnostic_base,
Internal::NoTypeDeduction<Args>... args) -> DiagnosticBuilder& {
if (!emitter_) {
return *this;
}
CARBON_CHECK(diagnostic_base.Level == DiagnosticLevel::Note ||
diagnostic_base.Level == DiagnosticLevel::LocationInfo,
"{0}", static_cast<int>(diagnostic_base.Level));
AddMessage(loc, diagnostic_base, {emitter_->MakeAny<Args>(args)...});
return *this;
}
template <typename LocT>
template <typename... Args>
auto DiagnosticEmitter<LocT>::DiagnosticBuilder::Emit() -> void {
if (!emitter_) {
return;
}
for (auto annotate_fn : llvm::reverse(emitter_->annotate_fns_)) {
annotate_fn(*this);
}
emitter_->consumer_->HandleDiagnostic(std::move(diagnostic_));
}
template <typename LocT>
template <typename... Args>
DiagnosticEmitter<LocT>::DiagnosticBuilder::DiagnosticBuilder(
DiagnosticEmitter<LocT>* emitter, LocT loc,
const DiagnosticBase<Args...>& diagnostic_base,
llvm::SmallVector<llvm::Any> args)
: emitter_(emitter), diagnostic_({.level = diagnostic_base.Level}) {
AddMessage(loc, diagnostic_base, std::move(args));
CARBON_CHECK(diagnostic_base.Level != DiagnosticLevel::Note);
}
template <typename LocT>
template <typename... Args>
auto DiagnosticEmitter<LocT>::DiagnosticBuilder::AddMessage(
LocT loc, const DiagnosticBase<Args...>& diagnostic_base,
llvm::SmallVector<llvm::Any> args) -> void {
if (!emitter_) {
return;
}
auto converted = emitter_->ConvertLoc(
loc, [&](DiagnosticLoc context_loc,
const DiagnosticBase<>& context_diagnostic_base) {
AddMessageWithDiagnosticLoc(context_loc, context_diagnostic_base, {});
});
// Use the last byte offset from the first message.
if (diagnostic_.messages.empty()) {
diagnostic_.last_byte_offset = converted.last_byte_offset;
}
AddMessageWithDiagnosticLoc(converted.loc, diagnostic_base, args);
}
template <typename LocT>
template <typename... Args>
auto DiagnosticEmitter<LocT>::DiagnosticBuilder::AddMessageWithDiagnosticLoc(
DiagnosticLoc loc, const DiagnosticBase<Args...>& diagnostic_base,
llvm::SmallVector<llvm::Any> args) -> void {
if (!emitter_) {
return;
}
diagnostic_.messages.emplace_back(DiagnosticMessage{
.kind = diagnostic_base.Kind,
.level = diagnostic_base.Level,
.loc = loc,
.format = diagnostic_base.Format,
.format_args = std::move(args),
.format_fn = [](const DiagnosticMessage& message) -> std::string {
return FormatFn<Args...>(message,
std::make_index_sequence<sizeof...(Args)>());
}});
}
template <typename LocT>
template <typename... Args, size_t... N>
auto DiagnosticEmitter<LocT>::DiagnosticBuilder::FormatFn(
const DiagnosticMessage& message, std::index_sequence<N...> /*indices*/)
-> std::string {
static_assert(sizeof...(Args) == sizeof...(N), "Invalid template args");
CARBON_CHECK(message.format_args.size() == sizeof...(Args),
"Argument count mismatch on {0}: {1} != {2}", message.kind,
message.format_args.size(), sizeof...(Args));
return llvm::formatv(
message.format.data(),
llvm::any_cast<
typename Internal::DiagnosticTypeForArg<Args>::StorageType>(
message.format_args[N])...);
}
template <typename LocT>
template <typename... Args>
auto DiagnosticEmitter<LocT>::Emit(
LocT loc, const DiagnosticBase<Args...>& diagnostic_base,
Internal::NoTypeDeduction<Args>... args) -> void {
DiagnosticBuilder(this, loc, diagnostic_base, {MakeAny<Args>(args)...})
.Emit();
}
template <typename LocT>
template <typename... Args>
auto DiagnosticEmitter<LocT>::Build(
LocT loc, const DiagnosticBase<Args...>& diagnostic_base,
Internal::NoTypeDeduction<Args>... args) -> DiagnosticBuilder {
return DiagnosticBuilder(this, loc, diagnostic_base,
{MakeAny<Args>(args)...});
}
template <typename LocT>
template <typename Arg>
auto DiagnosticEmitter<LocT>::MakeAny(Arg arg) -> llvm::Any {
llvm::Any converted = ConvertArg(arg);
using Storage = Internal::DiagnosticTypeForArg<Arg>::StorageType;
CARBON_CHECK(llvm::any_cast<Storage>(&converted),
"Failed to convert argument of type {0} to its storage type {1}",
typeid(Arg).name(), typeid(Storage).name());
return converted;
}
} // namespace Carbon
#endif // CARBON_TOOLCHAIN_DIAGNOSTICS_DIAGNOSTIC_EMITTER_H_