Skip to content
← Back

src/logging/logger.ghul

1
namespace Logging is
2
use System.Exception;
3
use System.NotImplementedException;
4
5
use Collections.STACK;
6
7
use IO.Std;
8
use IO.TextWriter;
9
use IO.StringWriter;
10
11
use Source;
12
13
struct DEBUG_THEN_EXIT: Disposable is
14
_initialized: bool;
15
_depth: int;
16
17
init(depth: int) is
18
_initialized = true;
19
_depth = depth;
20
si
21
22
init() is
23
// not debugging, so dispose doesn't need to do anything
24
si
25
26
dispose() is
27
if _initialized then
28
while _debug_depth > _depth do
29
debug_exit();
30
od
31
fi
32
33
_initialized = false;
34
si
35
si
36
37
_debug_depth: int;
38
39
debug_enter() -> DEBUG_THEN_EXIT is
40
debug_always(">>>");
41
let result = DEBUG_THEN_EXIT(_debug_depth);
42
43
_debug_depth = _debug_depth + 1;
44
45
return result;
46
si
47
48
in_debug() -> bool => _debug_depth > 0;
49
50
debug_enter(want_debug: bool) -> DEBUG_THEN_EXIT =>
51
if want_debug then
52
debug_enter();
53
else
54
DEBUG_THEN_EXIT();
55
fi;
56
57
debug_exit() is
58
_debug_depth = _debug_depth - 1;
59
debug_always("<<<");
60
si
61
62
debug_exit(want_debug: bool) is
63
if want_debug then
64
debug_exit();
65
fi
66
si
67
68
debug_reset() is
69
_debug_depth = 0;
70
si
71
72
debug_indent() is
73
if _debug_depth > 0 then
74
_debug_depth = _debug_depth + 1;
75
fi
76
si
77
78
debug_unindent() is
79
if _debug_depth > 0 then
80
_debug_depth = _debug_depth - 1;
81
fi
82
si
83
84
debug(message: string) is
85
if _debug_depth > 0 then
86
debug_always(message);
87
fi
88
si
89
90
debug_always(message: string) is
91
for m in message.replace_line_endings("\n").split(['\n']) do
92
for i in 0.._debug_depth do
93
IO.Std.error.write(" ");
94
od
95
96
IO.Std.error.write_line(m);
97
od
98
99
IO.Std.error.flush();
100
si
101
102
trait Logger is
103
is_poisoned: bool;
104
has_consumed_error: bool;
105
has_consumed_any: bool;
106
107
error_count: int;
108
any_errors: bool;
109
110
// The source paths that currently hold at least one error-severity
111
// diagnostic. The analysis-mode incremental interface path uses
112
// this as a conservative dependent set: a body that failed to bind
113
// a name recorded no reference, so reference-based dependency
114
// scans cannot see it, yet an interface edit may be exactly what
115
// cures (or should re-report) its error.
116
paths_with_errors: Collections.Iterable[string];
117
118
// True iff this speculation level recorded no errors and didn't
119
// consume any wild / inferred / placeholder types. Used by the
120
// iterative inference loop in COMPILE_EXPRESSIONS to detect
121
// convergence: a body walk that was clean is one we don't need
122
// to retry.
123
is_clean: bool;
124
125
depth: int;
126
127
speculate();
128
roll_back() -> DIAGNOSTICS_STATE;
129
commit();
130
mark() -> int;
131
release(mark: int);
132
133
speculate_then_commit() -> LOGGER_SPECULATE_THEN_COMMIT;
134
speculate_then_backtrack() -> LOGGER_SPECULATE_THEN_BACKTRACK;
135
mark_then_release() -> MARK_THEN_RELEASE;
136
137
// True while an analysis-mode (language-service) request is
138
// being served, false during batch compilation.
139
is_analysis: bool;
140
141
// The set of files the client currently has open. Replaced whole by
142
// each client update. Editor-only hints are suppressed at the source
143
// for any file not in this set, since they are invisible for a file
144
// the user is not viewing. Returns true iff the resulting set differs
145
// from the previous one, so the caller can invalidate any cached
146
// compile whose hint output was gated on the previous set.
147
set_open_files(paths: Collections.Iterable[string]) -> bool;
148
is_file_open(path: string) -> bool;
149
150
start_analysis();
151
end_analysis();
152
153
set_is_compiling_expressions(value: bool);
154
155
merge(state: DIAGNOSTICS_STATE);
156
157
exception(location: LOCATION, exception: Exception, message: string);
158
fatal(location: LOCATION, message: string);
159
error(location: LOCATION, message: string);
160
// A lexer-produced error, written to the base diagnostics state so it
161
// survives parser speculation rollback (see DIAGNOSTICS_STORE).
162
lexer_error(location: LOCATION, message: string);
163
warn(location: LOCATION, message: string);
164
warn(location: LOCATION, code: string, message: string);
165
poison(location: LOCATION, message: string);
166
info(location: LOCATION, message: string);
167
info(location: LOCATION, code: string, message: string);
168
hint(location: LOCATION, message: string);
169
hint(location: LOCATION, code: string, message: string);
170
171
// Record an editor-only inlay hint at `location`: a terse `label`
172
// (the ghost text) plus an optional `detail` (hover tooltip). Never
173
// published as a diagnostic; surfaced only by the INLAY_HINTS query.
174
// Callers gate on `want_hint_for` first, exactly as for a hint.
175
inlay(location: LOCATION, code: string, label: string, detail: string?);
176
177
// Every inlay carrier recorded for `path` in the current state, in
178
// emission order. Consulted by the INLAY_HINTS handler.
179
inlays_for(path: string) -> Collections.Iterable[DIAGNOSTIC_MESSAGE];
180
181
// Suppress every subsequent warn/info/hint carrying this code.
182
// Accumulating — calling twice with different codes silences both.
183
// Populated by `--suppress` / `--no-warn-*` from the driver.
184
suppress(code: string);
185
is_suppressed(code: string?) -> bool;
186
187
// Promote warnings to errors. `set_all_warnings_are_errors(true)`
188
// routes every subsequent `warn(...)` to error level; `promote_to_error(code)`
189
// does the same only for warns carrying that slug. Populated by
190
// `--warn-as-error <slug,slug,...>` from the driver, with the literal
191
// slug `all` mapping to `set_all_warnings_are_errors(true)`.
192
// Suppression wins: a warning silenced by `suppress` or by an
193
// `@suppress(...)` region is never resurrected as an error.
194
set_all_warnings_are_errors(value: bool);
195
promote_to_error(code: string);
196
197
// Demote warnings carrying this code to editor-only hints.
198
// Populated by `--warn-as-hint <slug,slug,...>` from the driver.
199
// A demoted warn is emitted as a hint under the same gate as a
200
// native hint: dropped entirely in batch compilation, surfaced in
201
// analysis mode only for a file the client has open. Suppression
202
// still wins: a code that is both suppressed and demoted is
203
// dropped. Demotion wins over promotion to error.
204
demote_to_hint(code: string);
205
206
// Demote warnings carrying this code to info-level diagnostics.
207
// Populated by `--warn-as-info <slug,slug,...>` from the driver.
208
// Unlike a hint, info stays a normal batch-visible diagnostic:
209
// it is not subject to the editor-only gate. Suppression still
210
// wins; a code demoted to a hint takes precedence over info.
211
demote_to_info(code: string);
212
213
// Whether a hint at `location` should be generated: true while
214
// serving an analysis request for a file the client has open. The
215
// single gate every hint site consults before doing any
216
// hint-specific work.
217
want_hint_for(location: LOCATION?) -> bool;
218
219
// Lexical-scope suppression: anything emitted at a location
220
// covered by `region` carrying `code` is dropped, on top of the
221
// global `suppress` set. Populated by the `@suppress(...)`
222
// pragma collector. The location-aware `is_suppressed` consults
223
// both the global set and the registered regions. Regions are
224
// stored per source file so analysis-mode re-walks can clear
225
// exactly the file being re-walked.
226
register_suppression_region(region: LOCATION, code: string);
227
clear_suppression_regions(path: string);
228
clear_suppression_regions();
229
is_suppressed(code: string?, location: LOCATION) -> bool;
230
231
poison(location: LOCATION);
232
mark_consumed_error();
233
mark_consumed_any();
234
235
// Convenience: mark consumed-any iff `consumed` is true.
236
// Removes the `if add_*(...) then logger.mark_consumed_any() fi`
237
// boilerplate at sites that record progress against a
238
// Variable accumulator.
239
mark_consumed_any_if(consumed: bool);
240
241
clear_consumed_error();
242
clear_consumed_any();
243
244
write_poison_messages();
245
246
clear(path: string, analysis_only: bool);
247
248
clear_global_declaration_diagnostics(path: string);
249
clear_expression_diagnostics(path: string);
250
251
write_all_diagnostics(writer: TextWriter, formatter: DiagnosticFormatter);
252
si
253
254
struct LOGGER_SPECULATE_THEN_COMMIT: Disposable is
255
_logger: Logger?;
256
257
has_consumed_any: bool => _logger!.has_consumed_any;
258
has_consumed_error: bool => _logger!.has_consumed_error;
259
260
any_errors: bool => _logger!.any_errors;
261
262
is_speculating: bool => _logger != null;
263
264
init(logger: Logger) is
265
_logger = logger;
266
_logger.speculate();
267
si
268
269
backtrack() -> DIAGNOSTICS_STATE is
270
let result = _logger!.roll_back();
271
_logger = null;
272
return result;
273
si
274
275
backtrack_if_speculating() -> DIAGNOSTICS_STATE? is
276
if _logger != null then
277
return backtrack();
278
else
279
return null;
280
fi
281
si
282
283
backtrack_and_restart() -> DIAGNOSTICS_STATE is
284
let logger = _logger!;
285
let result = logger.roll_back();
286
logger.speculate();
287
return result;
288
si
289
290
commit() is
291
_logger!.commit();
292
_logger = null;
293
si
294
295
cancel() is
296
_logger = null;
297
si
298
299
dispose() is
300
if _logger != null then
301
_logger.commit();
302
_logger = null;
303
fi
304
si
305
si
306
307
struct LOGGER_SPECULATE_THEN_BACKTRACK: Disposable is
308
_logger: Logger?;
309
310
has_consumed_any: bool => _logger!.has_consumed_any;
311
has_consumed_error: bool => _logger!.has_consumed_error;
312
313
any_errors: bool => _logger!.any_errors;
314
315
is_speculating: bool => _logger != null;
316
317
init(logger: Logger) is
318
_logger = logger;
319
_logger.speculate();
320
si
321
322
backtrack() -> DIAGNOSTICS_STATE is
323
let result = _logger!.roll_back();
324
_logger = null;
325
return result;
326
si
327
328
backtrack_and_restart() -> DIAGNOSTICS_STATE is
329
let logger = _logger!;
330
let result = logger.roll_back();
331
logger.speculate();
332
return result;
333
si
334
335
commit() is
336
_logger!.commit();
337
_logger = null;
338
si
339
340
cancel() is
341
_logger = null;
342
si
343
344
dispose() is
345
if _logger != null then
346
_logger.roll_back();
347
_logger = null;
348
fi
349
si
350
si
351
352
struct MARK_THEN_RELEASE: Disposable is
353
_logger: Logger;
354
_mark: int;
355
356
init(logger: Logger) is
357
_logger = logger;
358
_mark = _logger.mark();
359
si
360
361
dispose() is
362
_logger.release(_mark);
363
si
364
si
365
si