forked from carbon-language/carbon-lang
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathemitter.h
More file actions
539 lines (469 loc) · 20 KB
/
emitter.h
File metadata and controls
539 lines (469 loc) · 20 KB
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
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
// 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_EMITTER_H_
#define CARBON_TOOLCHAIN_DIAGNOSTICS_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/consumer.h"
#include "toolchain/diagnostics/diagnostic.h"
#include "toolchain/diagnostics/kind.h"
namespace Carbon::Diagnostics {
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 AnnotationScope;
// The result of `DiagnosticConvert::ConvertLoc`. This is non-templated to allow
// sharing across converters.
struct ConvertedLoc {
// Becomes Message::loc.
Loc 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 = Diagnostics::TypeInfo<std::string>;
// };
template <typename StorageTypeT>
struct TypeInfo {
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 Emitter {
public:
// A builder-pattern type to provide a fluent interface for constructing
// a more complex diagnostic. See `Emitter::Build` for the
// expected usage.
// This is nodiscard to protect against accidentally building a diagnostic
// without emitting it.
class [[nodiscard]] Builder {
public:
// Builder is move-only and cannot be copied.
Builder(Builder&&) noexcept = default;
auto operator=(Builder&&) noexcept -> Builder& = default;
// Overrides the snippet for the most recently added diagnostic or note with
// the given text. The provided override should include the caret text as
// well as the source snippet. An empty snippet restores the default
// behavior of printing the original source line.
auto OverrideSnippet(llvm::StringRef snippet) -> Builder&;
// Adds a Note about the diagnostic, attached to the main diagnostic being
// built. The API mirrors the main emission API: `Emitter::Emit`. For the
// expected usage see the builder API: `Emitter::Build`.
template <typename... Args>
auto Note(LocT loc, const DiagnosticBase<Args...>& diagnostic_base,
Internal::NoTypeDeduction<Args>... args) -> Builder&;
// Emits the built diagnostic and its attached notes.
// For the expected usage see the builder API: `Emitter::Build`.
template <typename... Args>
auto Emit() & -> void;
// Prevent trivial uses of the builder; always `static_assert`s.
template <typename... Args>
auto Emit() && -> void;
// Returns true if this Builder 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 Emitter<LocT>;
friend class ContextBuilder;
template <typename... Args>
explicit Builder(Emitter<LocT>* emitter, LocT loc,
const DiagnosticBase<Args...>& diagnostic_base,
llvm::SmallVector<llvm::Any> args);
// 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
// Loc must be provided instead of a LocT in order to
// avoid potential recursion.
template <typename... Args>
auto AddMessageWithLoc(Loc 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 Message& message,
std::index_sequence<N...> /*indices*/) -> std::string;
// Whether a Context or SoftContext message has been added to the Builder.
auto has_context_message() const -> bool { return has_context_message_; }
Emitter<LocT>* emitter_;
Diagnostic diagnostic_;
bool has_context_message_ = false;
};
class ContextBuilder {
public:
// Adds a Context describing a higher level operation that failed due to the
// diagnostic being built. The API mirrors the main emission API:
// `Emitter::Emit`. For the expected usage see the builder API:
// `Emitter::Build`.
template <typename... Args>
auto Context(LocT loc, const DiagnosticBase<Args...>& diagnostic_base,
Internal::NoTypeDeduction<Args>... args) -> ContextBuilder&;
private:
friend class Emitter<LocT>;
explicit ContextBuilder(Emitter<LocT>* emitter, Builder* builder)
: emitter_(emitter), builder_(builder) {}
Emitter<LocT>* emitter_;
Builder* builder_;
};
// `consumer` is required to outlive the diagnostic emitter.
explicit Emitter(Consumer* consumer) : consumer_(consumer) {}
virtual ~Emitter() = 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) -> Builder;
// Adds a flush function to flush pending diagnostics that might be enqueued
// and not yet emitted. The flush function will be called whenever `Flush` is
// called.
//
// No mechanism is provided to unregister a flush function, so the function
// must ensure that it remains callable until the emitter is destroyed.
//
// This is used to register a handler to flush diagnostics from Clang.
auto AddFlushFn(std::function<auto()->void> flush_fn) -> void {
flush_fns_.push_back(std::move(flush_fn));
}
// Flush all pending diagnostics that are queued externally, such as Clang
// diagnostics. This should not be called when the external source might be in
// the middle of producing a diagnostic, such as between Clang producing an
// error and producing the attached notes.
//
// This is called automatically before any diagnostic annotator is added or
// removed, to flush any pending diagnostics with suitable notes attached, and
// when the emitter is destroyed.
auto Flush() -> void {
for (auto& flush_fn : flush_fns_) {
flush_fn();
}
}
// Verifies that a callback is registered to provide context if a diagnostic
// is emitted. Allows a code path to require context, which then means its
// diagnostics to be framed as Notes.
//
// This is best effort as the registered callback can in practice do nothing,
// but that would be highly unusual.
auto CheckHasContext() -> void { CARBON_CHECK(!context_fns_.empty()); }
protected:
// Callback type used to report context messages from ConvertLoc.
// Note that the first parameter type is Loc rather than
// LocT, because ConvertLoc must not recurse.
using ContextFnT =
llvm::function_ref<auto(Loc, const DiagnosticBase<>&)->void>;
// Converts a LocT to a Loc and its `last_byte_offset` (see
// `Message`). ConvertLoc may invoke context_fn to provide context
// messages.
virtual auto ConvertLoc(LocT loc, ContextFnT context_fn) const
-> ConvertedLoc = 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 ContextFn>
friend class ContextScope;
template <typename OtherLocT, typename ContextFn>
friend class AnnotationScope;
friend class NoLocEmitter;
Consumer* consumer_;
llvm::SmallVector<std::function<auto()->void>, 1> flush_fns_;
llvm::SmallVector<llvm::function_ref<auto(ContextBuilder& builder)->void>>
context_fns_;
llvm::SmallVector<llvm::function_ref<auto(Builder& builder)->void>>
annotate_fns_;
};
// This relies on `void*` location handling on `Emitter`.
//
// 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 NoLocEmitter : public Emitter<void*> {
public:
using Emitter::Emitter;
template <typename LocT>
explicit NoLocEmitter(const Emitter<LocT>& emitter)
: Emitter(emitter.consumer_) {}
// Emits an error. This specialization only applies to
// `NoLocEmitter`.
template <typename... Args>
auto Emit(const DiagnosticBase<Args...>& diagnostic_base,
Internal::NoTypeDeduction<Args>... args) -> void {
Emitter::Emit(nullptr, diagnostic_base, args...);
}
protected:
auto ConvertLoc(void* /*loc*/, ContextFnT /*context_fn*/) const
-> ConvertedLoc override {
return {.loc = {.filename = ""}, .last_byte_offset = -1};
}
};
// An RAII object that denotes a scope in which any diagnostic produced should
// become a note attached to the higher-level operation failure described by a
// Context message.
//
// This object is given a function `context` that will be called with a
// `ContextBuilder& builder` for any diagnostic that is emitted through the
// given emitter. That function can provide a context message that explains the
// higher level failure caused by the diagnostic by calling `builder.Context`.
template <typename LocT, typename ContextFn>
class ContextScope {
public:
ContextScope(Emitter<LocT>* emitter, ContextFn context)
requires requires(ContextFn context,
Emitter<LocT>::ContextBuilder& builder) {
{ context(builder) } -> std::same_as<void>;
}
: emitter_(emitter), context_(std::move(context)) {
emitter_->Flush();
emitter_->context_fns_.push_back(context_);
}
~ContextScope() {
emitter_->Flush();
emitter_->context_fns_.pop_back();
}
private:
Emitter<LocT>* emitter_;
// Make a copy of the context function to ensure that it lives long enough.
ContextFn context_;
};
template <typename LocT, typename ContextFn>
ContextScope(Emitter<LocT>* emitter, ContextFn context)
-> ContextScope<LocT, ContextFn>;
// 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
// `Builder& 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 AnnotationScope {
public:
AnnotationScope(Emitter<LocT>* emitter, AnnotateFn annotate)
requires requires(AnnotateFn annotate, Emitter<LocT>::Builder& builder) {
{ annotate(builder) } -> std::same_as<void>;
}
: emitter_(emitter), annotate_(std::move(annotate)) {
emitter_->Flush();
emitter_->annotate_fns_.push_back(annotate_);
}
~AnnotationScope() {
emitter_->Flush();
emitter_->annotate_fns_.pop_back();
}
private:
Emitter<LocT>* emitter_;
// Make a copy of the annotation function to ensure that it lives long enough.
AnnotateFn annotate_;
};
template <typename LocT, typename AnnotateFn>
AnnotationScope(Emitter<LocT>* emitter, AnnotateFn annotate)
-> AnnotationScope<LocT, AnnotateFn>;
// ============================================================================
// Only internal implementation details below this point.
// ============================================================================
namespace Internal {
// Determines whether there's a DiagnosticType member on Arg.
// Used by Emitter.
template <typename Arg>
concept HasDiagnosticType = requires { typename Arg::DiagnosticType; };
// The default implementation with no conversion.
template <typename Arg>
struct DiagnosticTypeForArg : public TypeInfo<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>
auto Emitter<LocT>::Builder::OverrideSnippet(llvm::StringRef snippet)
-> Builder& {
diagnostic_.messages.back().loc.snippet = snippet;
return *this;
}
template <typename LocT>
template <typename... Args>
auto Emitter<LocT>::Builder::Note(
LocT loc, const DiagnosticBase<Args...>& diagnostic_base,
Internal::NoTypeDeduction<Args>... args) -> Builder& {
CARBON_CHECK(diagnostic_base.Level == Level::Note ||
diagnostic_base.Level == Level::LocationInfo,
"{0}", static_cast<int>(diagnostic_base.Level));
AddMessage(LocT(loc), diagnostic_base, {emitter_->MakeAny<Args>(args)...});
return *this;
}
template <typename LocT>
template <typename... Args>
auto Emitter<LocT>::Builder::Emit() & -> void {
for (auto annotate_fn : llvm::reverse(emitter_->annotate_fns_)) {
annotate_fn(*this);
}
emitter_->consumer_->HandleDiagnostic(std::move(diagnostic_));
}
template <typename LocT>
template <typename... Args>
auto Emitter<LocT>::Builder::Emit() && -> void {
static_assert(false,
"Use `emitter.Emit(...)` or "
"`emitter.Build(...).Note(...).Emit(...)` "
"instead of `emitter.Build(...).Emit(...)`");
}
template <typename LocT>
template <typename... Args>
Emitter<LocT>::Builder::Builder(Emitter<LocT>* emitter, LocT loc,
const DiagnosticBase<Args...>& diagnostic_base,
llvm::SmallVector<llvm::Any> args)
: emitter_(emitter),
diagnostic_({.level = diagnostic_base.Level,
.is_on_scope = diagnostic_base.IsOnScope}) {
CARBON_CHECK(diagnostic_.level >= Level::Warning,
"building diagnostic with level {0}; expected Warning or Error",
diagnostic_.level);
ContextBuilder context_builder(emitter, this);
for (auto context_fn : emitter_->context_fns_) {
context_fn(context_builder);
}
AddMessage(LocT(loc), diagnostic_base, std::move(args));
CARBON_CHECK(diagnostic_base.Level != Level::Note);
}
template <typename LocT>
template <typename... Args>
auto Emitter<LocT>::Builder::AddMessage(
LocT loc, const DiagnosticBase<Args...>& diagnostic_base,
llvm::SmallVector<llvm::Any> args) -> void {
auto converted = emitter_->ConvertLoc(
loc,
[&](Loc context_loc, const DiagnosticBase<>& context_diagnostic_base) {
AddMessageWithLoc(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;
}
AddMessageWithLoc(converted.loc, diagnostic_base, args);
}
template <typename LocT>
template <typename... Args>
auto Emitter<LocT>::Builder::AddMessageWithLoc(
Loc loc, const DiagnosticBase<Args...>& diagnostic_base,
llvm::SmallVector<llvm::Any> args) -> void {
CARBON_CHECK(
diagnostic_base.Level <= diagnostic_.level,
"message with level {0} is higher than the diagnostic's level {1}",
diagnostic_base.Level, diagnostic_.level);
if (diagnostic_base.Level == Level::SoftContext ||
diagnostic_base.Level == Level::Context) {
has_context_message_ = true;
}
diagnostic_.messages.push_back(
Message{.kind = diagnostic_base.Kind,
.level = diagnostic_base.Level,
.loc = loc,
.format = diagnostic_base.Format,
.format_args = std::move(args),
.format_fn = [](const Message& message) -> std::string {
return FormatFn<Args...>(
message, std::make_index_sequence<sizeof...(Args)>());
}});
}
template <typename LocT>
template <typename... Args, size_t... N>
auto Emitter<LocT>::Builder::FormatFn(const Message& 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 Emitter<LocT>::Emit(LocT loc,
const DiagnosticBase<Args...>& diagnostic_base,
Internal::NoTypeDeduction<Args>... args) -> void {
Builder builder(this, loc, diagnostic_base, {MakeAny<Args>(args)...});
builder.Emit();
}
template <typename LocT>
template <typename... Args>
auto Emitter<LocT>::ContextBuilder::Context(
LocT loc, const DiagnosticBase<Args...>& diagnostic_base,
Internal::NoTypeDeduction<Args>... args) -> ContextBuilder& {
CARBON_CHECK(diagnostic_base.Level == Level::SoftContext ||
diagnostic_base.Level == Level::Context,
"{0}", static_cast<int>(diagnostic_base.Level));
if (builder_->has_context_message() &&
diagnostic_base.Level == Level::SoftContext) {
return *this;
}
builder_->AddMessage(LocT(loc), diagnostic_base,
{emitter_->template MakeAny<Args>(args)...});
return *this;
}
template <typename LocT>
template <typename... Args>
auto Emitter<LocT>::Build(LocT loc,
const DiagnosticBase<Args...>& diagnostic_base,
Internal::NoTypeDeduction<Args>... args) -> Builder {
return Builder(this, loc, diagnostic_base, {MakeAny<Args>(args)...});
}
template <typename LocT>
template <typename Arg>
auto Emitter<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::Diagnostics
#endif // CARBON_TOOLCHAIN_DIAGNOSTICS_EMITTER_H_