tamer: diagnose: Introduction of diagnostic system
This is a working concept that will continue to evolve. I wanted to start
with some basic output before getting too carried away, since there's a lot
of potential here.
This is heavily influenced by Rust's helpful diagnostic messages, but will
take some time to realize a lot of the things that Rust does. The next step
will be to resolve line and column numbers, and then possibly include
snippets and underline spans, placing the labels alongside them. I need to
balance this work with everything else I have going on.
This is a large commit, but it converts the existing Error Display impls
into Diagnostic. This separation is a bit verbose, so I'll see how this
ends up evolving.
Diagnostics are tied to Error at the moment, but I imagine in the future
that any object would be able to describe itself, error or not, which would
be useful in the future both for the Summary Page and for query
functionality, to help developers understand the systems they are writing
using TAME.
Output is integrated into tameld only in this commit; I'll add tamec
next. Examples of what this outputs are available in the test cases in this
commit.
DEV-10935
2022-04-13 14:41:54 -04:00
|
|
|
// Diagnostic system rendering
|
|
|
|
//
|
2023-01-17 23:09:25 -05:00
|
|
|
// Copyright (C) 2014-2023 Ryan Specialty, LLC.
|
tamer: diagnose: Introduction of diagnostic system
This is a working concept that will continue to evolve. I wanted to start
with some basic output before getting too carried away, since there's a lot
of potential here.
This is heavily influenced by Rust's helpful diagnostic messages, but will
take some time to realize a lot of the things that Rust does. The next step
will be to resolve line and column numbers, and then possibly include
snippets and underline spans, placing the labels alongside them. I need to
balance this work with everything else I have going on.
This is a large commit, but it converts the existing Error Display impls
into Diagnostic. This separation is a bit verbose, so I'll see how this
ends up evolving.
Diagnostics are tied to Error at the moment, but I imagine in the future
that any object would be able to describe itself, error or not, which would
be useful in the future both for the Summary Page and for query
functionality, to help developers understand the systems they are writing
using TAME.
Output is integrated into tameld only in this commit; I'll add tamec
next. Examples of what this outputs are available in the test cases in this
commit.
DEV-10935
2022-04-13 14:41:54 -04:00
|
|
|
//
|
|
|
|
// This file is part of TAME.
|
|
|
|
//
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
|
|
// it under the terms of the GNU General Public License as published by
|
|
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
|
|
// (at your option) any later version.
|
|
|
|
//
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
// GNU General Public License for more details.
|
|
|
|
//
|
|
|
|
// You should have received a copy of the GNU General Public License
|
|
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
2022-05-02 09:44:53 -04:00
|
|
|
//! Rendering of diagnostic report.
|
|
|
|
//!
|
2022-05-02 09:49:22 -04:00
|
|
|
//! This module is responsible for [resolving](super::resolve) and
|
2022-05-02 09:44:53 -04:00
|
|
|
//! rendering spans into a formatted [`Report`],
|
|
|
|
//! which in turn can be rendered into a string with [`Display::fmt`].
|
|
|
|
//!
|
|
|
|
//! See the [parent module](super) for more summary information.
|
tamer: diagnose: Introduction of diagnostic system
This is a working concept that will continue to evolve. I wanted to start
with some basic output before getting too carried away, since there's a lot
of potential here.
This is heavily influenced by Rust's helpful diagnostic messages, but will
take some time to realize a lot of the things that Rust does. The next step
will be to resolve line and column numbers, and then possibly include
snippets and underline spans, placing the labels alongside them. I need to
balance this work with everything else I have going on.
This is a large commit, but it converts the existing Error Display impls
into Diagnostic. This separation is a bit verbose, so I'll see how this
ends up evolving.
Diagnostics are tied to Error at the moment, but I imagine in the future
that any object would be able to describe itself, error or not, which would
be useful in the future both for the Summary Page and for query
functionality, to help developers understand the systems they are writing
using TAME.
Output is integrated into tameld only in this commit; I'll add tamec
next. Examples of what this outputs are available in the test cases in this
commit.
DEV-10935
2022-04-13 14:41:54 -04:00
|
|
|
|
2022-04-27 10:45:31 -04:00
|
|
|
// NB: `write!` together with `\n` is preferred to `writeln!` so that there
|
|
|
|
// is only a single sequence of characters to search for while tracking
|
|
|
|
// down newlines,
|
|
|
|
// rather than using both.
|
|
|
|
|
tamer: diagnostic: Column resolution
Determining the column number is not as simple as performing byte
arithmetic, because certain characters have different widths. Even if we
only accepted ASCII, control characters aren't visible to the user.
This uses the unicode-width crate as an alternative to POSIX wcwidth, to
determine (hopefully) the number of fixed-width cells that a unicode
character will take up on a terminal. For example, control characters are
zero-width, while an emoji is likely double-width. See test cases for more
information on that.
There is also the unicode-segmentation crate, which can handle extended
grapheme clusters and such, but (a) we'll be outputting the line to the
terminal and (b) there's no guarantee that the user's editor displays
grapheme clusters as a single column. LSP measures in UTF-16,
apparently. I use both Emacs and Vim from a terminal, so unicode-width
applies to me. There's too much variation to try to solve that right now.
The columns can be considered a visual span---this gives us enough
information to draw line annotations, which will happen soon.
Here are some useful links:
- https://hsivonen.fi/string-length/
- https://unicode.org/reports/tr29/
- https://github.com/rust-analyzer/rowan/issues/17
- https://www.reddit.com/r/rust/comments/gpw2ra/how_is_the_rust_compiler_able_to_tell_the_visible/
DEV-10935
2022-04-21 14:16:21 -04:00
|
|
|
use super::{
|
2022-05-02 09:49:22 -04:00
|
|
|
resolve::{
|
2022-04-29 11:57:50 -04:00
|
|
|
Column, ResolvedSpan, ResolvedSpanData, SourceLine, SpanResolver,
|
|
|
|
SpanResolverError,
|
2022-04-28 13:24:36 -04:00
|
|
|
},
|
2022-04-26 10:46:47 -04:00
|
|
|
AnnotatedSpan, Diagnostic, Label, Level,
|
tamer: diagnostic: Column resolution
Determining the column number is not as simple as performing byte
arithmetic, because certain characters have different widths. Even if we
only accepted ASCII, control characters aren't visible to the user.
This uses the unicode-width crate as an alternative to POSIX wcwidth, to
determine (hopefully) the number of fixed-width cells that a unicode
character will take up on a terminal. For example, control characters are
zero-width, while an emoji is likely double-width. See test cases for more
information on that.
There is also the unicode-segmentation crate, which can handle extended
grapheme clusters and such, but (a) we'll be outputting the line to the
terminal and (b) there's no guarantee that the user's editor displays
grapheme clusters as a single column. LSP measures in UTF-16,
apparently. I use both Emacs and Vim from a terminal, so unicode-width
applies to me. There's too much variation to try to solve that right now.
The columns can be considered a visual span---this gives us enough
information to draw line annotations, which will happen soon.
Here are some useful links:
- https://hsivonen.fi/string-length/
- https://unicode.org/reports/tr29/
- https://github.com/rust-analyzer/rowan/issues/17
- https://www.reddit.com/r/rust/comments/gpw2ra/how_is_the_rust_compiler_able_to_tell_the_visible/
DEV-10935
2022-04-21 14:16:21 -04:00
|
|
|
};
|
2022-04-26 10:14:51 -04:00
|
|
|
use crate::span::{Context, Span, UNKNOWN_SPAN};
|
2022-04-27 11:27:55 -04:00
|
|
|
use std::{
|
2022-04-28 14:33:08 -04:00
|
|
|
fmt::{self, Display, Write},
|
2022-04-27 11:27:55 -04:00
|
|
|
num::NonZeroU32,
|
2022-04-28 23:37:07 -04:00
|
|
|
ops::Add,
|
2022-04-27 11:27:55 -04:00
|
|
|
};
|
tamer: diagnose: Introduction of diagnostic system
This is a working concept that will continue to evolve. I wanted to start
with some basic output before getting too carried away, since there's a lot
of potential here.
This is heavily influenced by Rust's helpful diagnostic messages, but will
take some time to realize a lot of the things that Rust does. The next step
will be to resolve line and column numbers, and then possibly include
snippets and underline spans, placing the labels alongside them. I need to
balance this work with everything else I have going on.
This is a large commit, but it converts the existing Error Display impls
into Diagnostic. This separation is a bit verbose, so I'll see how this
ends up evolving.
Diagnostics are tied to Error at the moment, but I imagine in the future
that any object would be able to describe itself, error or not, which would
be useful in the future both for the Summary Page and for query
functionality, to help developers understand the systems they are writing
using TAME.
Output is integrated into tameld only in this commit; I'll add tamec
next. Examples of what this outputs are available in the test cases in this
commit.
DEV-10935
2022-04-13 14:41:54 -04:00
|
|
|
|
2022-05-02 09:44:53 -04:00
|
|
|
/// Render a [`Report`] with detailed diagnostic information.
|
|
|
|
///
|
|
|
|
/// Rendering of reports is layered---this
|
|
|
|
/// report can be further rendered into a string using [`Display::fmt`].
|
tamer: diagnose: Introduction of diagnostic system
This is a working concept that will continue to evolve. I wanted to start
with some basic output before getting too carried away, since there's a lot
of potential here.
This is heavily influenced by Rust's helpful diagnostic messages, but will
take some time to realize a lot of the things that Rust does. The next step
will be to resolve line and column numbers, and then possibly include
snippets and underline spans, placing the labels alongside them. I need to
balance this work with everything else I have going on.
This is a large commit, but it converts the existing Error Display impls
into Diagnostic. This separation is a bit verbose, so I'll see how this
ends up evolving.
Diagnostics are tied to Error at the moment, but I imagine in the future
that any object would be able to describe itself, error or not, which would
be useful in the future both for the Summary Page and for query
functionality, to help developers understand the systems they are writing
using TAME.
Output is integrated into tameld only in this commit; I'll add tamec
next. Examples of what this outputs are available in the test cases in this
commit.
DEV-10935
2022-04-13 14:41:54 -04:00
|
|
|
pub trait Reporter {
|
|
|
|
/// Render diagnostic report.
|
|
|
|
///
|
2022-04-27 14:58:50 -04:00
|
|
|
/// The provided [`Report`] implements [`Display`].
|
|
|
|
///
|
|
|
|
/// Please be mindful of where this report is being rendered to
|
|
|
|
/// (via [`Display`]).
|
tamer: diagnose: Introduction of diagnostic system
This is a working concept that will continue to evolve. I wanted to start
with some basic output before getting too carried away, since there's a lot
of potential here.
This is heavily influenced by Rust's helpful diagnostic messages, but will
take some time to realize a lot of the things that Rust does. The next step
will be to resolve line and column numbers, and then possibly include
snippets and underline spans, placing the labels alongside them. I need to
balance this work with everything else I have going on.
This is a large commit, but it converts the existing Error Display impls
into Diagnostic. This separation is a bit verbose, so I'll see how this
ends up evolving.
Diagnostics are tied to Error at the moment, but I imagine in the future
that any object would be able to describe itself, error or not, which would
be useful in the future both for the Summary Page and for query
functionality, to help developers understand the systems they are writing
using TAME.
Output is integrated into tameld only in this commit; I'll add tamec
next. Examples of what this outputs are available in the test cases in this
commit.
DEV-10935
2022-04-13 14:41:54 -04:00
|
|
|
/// For example,
|
|
|
|
/// if rendering to standard out,
|
|
|
|
/// it is a good idea to buffer the entire report before flushing to
|
|
|
|
/// stdout,
|
|
|
|
/// otherwise the report may become interleaved with other
|
|
|
|
/// concurrent processes
|
|
|
|
/// (e.g. if TAMER is being invoked using `make -jN`).
|
|
|
|
///
|
2022-04-27 14:58:50 -04:00
|
|
|
/// It is also important to note that this method
|
|
|
|
/// _does not return [`Result`]_ and should never fail,
|
|
|
|
/// unless due to a panic in the standard library
|
|
|
|
/// (e.g. due to allocation failure).
|
|
|
|
/// The report absorbs errors during processing and renders those errors
|
|
|
|
/// to the report itself,
|
|
|
|
/// ensuring both that the user is made aware of the problem
|
|
|
|
/// and that we're not inadvertently suppressing the actual
|
|
|
|
/// diagnostic messages that were requested.
|
2022-10-25 23:44:53 -04:00
|
|
|
fn render<'d, D: Diagnostic>(&mut self, diagnostic: &'d D)
|
|
|
|
-> Report<'d, D>;
|
2022-10-21 15:35:27 -04:00
|
|
|
|
|
|
|
/// Whether any reports have been rendered with an error level or higher.
|
|
|
|
fn has_errors(&self) -> bool;
|
|
|
|
|
|
|
|
/// Number of reports with an error level or higher that have been
|
|
|
|
/// rendered.
|
|
|
|
fn error_count(&self) -> usize;
|
tamer: diagnose: Introduction of diagnostic system
This is a working concept that will continue to evolve. I wanted to start
with some basic output before getting too carried away, since there's a lot
of potential here.
This is heavily influenced by Rust's helpful diagnostic messages, but will
take some time to realize a lot of the things that Rust does. The next step
will be to resolve line and column numbers, and then possibly include
snippets and underline spans, placing the labels alongside them. I need to
balance this work with everything else I have going on.
This is a large commit, but it converts the existing Error Display impls
into Diagnostic. This separation is a bit verbose, so I'll see how this
ends up evolving.
Diagnostics are tied to Error at the moment, but I imagine in the future
that any object would be able to describe itself, error or not, which would
be useful in the future both for the Summary Page and for query
functionality, to help developers understand the systems they are writing
using TAME.
Output is integrated into tameld only in this commit; I'll add tamec
next. Examples of what this outputs are available in the test cases in this
commit.
DEV-10935
2022-04-13 14:41:54 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Render diagnostic report in a highly visual way.
|
|
|
|
///
|
|
|
|
/// This report is modeled after Rust's default error reporting,
|
|
|
|
/// most notable for including sections of source code associated with
|
|
|
|
/// spans,
|
|
|
|
/// underlining spans,
|
|
|
|
/// and including helpful information that walks the user through
|
|
|
|
/// understanding why the error occurred and how to approach resolving
|
|
|
|
/// it.
|
2022-04-20 12:08:46 -04:00
|
|
|
pub struct VisualReporter<R: SpanResolver> {
|
2022-10-20 14:31:25 -04:00
|
|
|
/// Span resolver.
|
|
|
|
///
|
|
|
|
/// This is responsible for resolving a span to a filename with line and
|
|
|
|
/// column numbers.
|
2022-10-25 23:44:53 -04:00
|
|
|
resolver: R,
|
2022-10-21 15:35:27 -04:00
|
|
|
|
|
|
|
/// Number of reports with a severity level of error or higher.
|
2022-10-25 23:44:53 -04:00
|
|
|
err_n: usize,
|
2022-04-20 12:08:46 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
impl<R: SpanResolver> VisualReporter<R> {
|
|
|
|
pub fn new(resolver: R) -> Self {
|
2022-10-25 23:44:53 -04:00
|
|
|
Self { resolver, err_n: 0 }
|
2022-04-20 12:08:46 -04:00
|
|
|
}
|
2022-04-26 10:14:51 -04:00
|
|
|
}
|
2022-04-20 12:08:46 -04:00
|
|
|
|
2022-04-26 10:14:51 -04:00
|
|
|
impl<R: SpanResolver> Reporter for VisualReporter<R> {
|
2022-10-25 23:44:53 -04:00
|
|
|
fn render<'d, D: Diagnostic>(
|
|
|
|
&mut self,
|
|
|
|
diagnostic: &'d D,
|
|
|
|
) -> Report<'d, D> {
|
|
|
|
let mspans =
|
|
|
|
describe_resolved(|span| self.resolver.resolve(span), diagnostic);
|
2022-04-27 10:45:31 -04:00
|
|
|
|
2022-04-27 15:06:57 -04:00
|
|
|
let mut report = Report::empty(Message(diagnostic));
|
2022-04-29 09:53:22 -04:00
|
|
|
report.extend(mspans.map(Into::into));
|
2022-04-28 23:37:07 -04:00
|
|
|
report.normalize_gutters();
|
2022-12-16 16:24:50 -05:00
|
|
|
report.finalize_sections();
|
|
|
|
|
2022-10-21 15:35:27 -04:00
|
|
|
if report.level.is_error() {
|
|
|
|
// Not worried about overflow panic
|
|
|
|
// (you have bigger problems if there are that many errors).
|
2022-10-25 23:44:53 -04:00
|
|
|
self.err_n += 1;
|
2022-10-21 15:35:27 -04:00
|
|
|
}
|
|
|
|
|
2022-04-27 14:58:50 -04:00
|
|
|
report
|
2022-04-27 10:45:31 -04:00
|
|
|
}
|
2022-10-21 15:35:27 -04:00
|
|
|
|
|
|
|
fn has_errors(&self) -> bool {
|
|
|
|
self.error_count() > 0
|
|
|
|
}
|
|
|
|
|
|
|
|
fn error_count(&self) -> usize {
|
2022-10-25 23:44:53 -04:00
|
|
|
self.err_n
|
2022-10-21 15:35:27 -04:00
|
|
|
}
|
2022-04-27 10:45:31 -04:00
|
|
|
}
|
tamer: diagnostic: Column resolution
Determining the column number is not as simple as performing byte
arithmetic, because certain characters have different widths. Even if we
only accepted ASCII, control characters aren't visible to the user.
This uses the unicode-width crate as an alternative to POSIX wcwidth, to
determine (hopefully) the number of fixed-width cells that a unicode
character will take up on a terminal. For example, control characters are
zero-width, while an emoji is likely double-width. See test cases for more
information on that.
There is also the unicode-segmentation crate, which can handle extended
grapheme clusters and such, but (a) we'll be outputting the line to the
terminal and (b) there's no guarantee that the user's editor displays
grapheme clusters as a single column. LSP measures in UTF-16,
apparently. I use both Emacs and Vim from a terminal, so unicode-width
applies to me. There's too much variation to try to solve that right now.
The columns can be considered a visual span---this gives us enough
information to draw line annotations, which will happen soon.
Here are some useful links:
- https://hsivonen.fi/string-length/
- https://unicode.org/reports/tr29/
- https://github.com/rust-analyzer/rowan/issues/17
- https://www.reddit.com/r/rust/comments/gpw2ra/how_is_the_rust_compiler_able_to_tell_the_visible/
DEV-10935
2022-04-21 14:16:21 -04:00
|
|
|
|
2022-04-29 11:57:50 -04:00
|
|
|
/// Request a diagnostic description and immediately resolve the provided
|
|
|
|
/// [`AnnotatedSpan`]s into [`MaybeResolvedSpan`]s.
|
|
|
|
///
|
|
|
|
/// Adjacent identical [`Span`]s are elided such that the first one is
|
|
|
|
/// resolved but the second one produces [`MaybeResolvedSpan::Elided`];
|
|
|
|
/// this avoids the expensive column resolution and source line
|
|
|
|
/// allocation for a span that was just processed and whose section will
|
|
|
|
/// be squashed anyway
|
|
|
|
/// (see [`Section::maybe_squash_into`]).
|
tamer: Integrate clippy
This invokes clippy as part of `make check` now, which I had previously
avoided doing (I'll elaborate on that below).
This commit represents the changes needed to resolve all the warnings
presented by clippy. Many changes have been made where I find the lints to
be useful and agreeable, but there are a number of lints, rationalized in
`src/lib.rs`, where I found the lints to be disagreeable. I have provided
rationale, primarily for those wondering why I desire to deviate from the
default lints, though it does feel backward to rationalize why certain lints
ought to be applied (the reverse should be true).
With that said, this did catch some legitimage issues, and it was also
helpful in getting some older code up-to-date with new language additions
that perhaps I used in new code but hadn't gone back and updated old code
for. My goal was to get clippy working without errors so that, in the
future, when others get into TAMER and are still getting used to Rust,
clippy is able to help guide them in the right direction.
One of the reasons I went without clippy for so long (though I admittedly
forgot I wasn't using it for a period of time) was because there were a
number of suggestions that I found disagreeable, and I didn't take the time
to go through them and determine what I wanted to follow. Furthermore, it
was hard to make that judgment when I was new to the language and lacked
the necessary experience to do so.
One thing I would like to comment further on is the use of `format!` with
`expect`, which is also what the diagnostic system convenience methods
do (which clippy does not cover). Because of all the work I've done trying
to understand Rust and looking at disassemblies and seeing what it
optimizes, I falsely assumed that Rust would convert such things into
conditionals in my otherwise-pure code...but apparently that's not the case,
when `format!` is involved.
I noticed that, after making the suggested fix with `get_ident`, Rust
proceeded to then inline it into each call site and then apply further
optimizations. It was also previously invoking the thread lock (for the
interner) unconditionally and invoking the `Display` implementation. That
is not at all what I intended for, despite knowing the eager semantics of
function calls in Rust.
Anyway, possibly more to come on that, I'm just tired of typing and need to
move on. I'll be returning to investigate further diagnostic messages soon.
2023-01-12 10:46:48 -05:00
|
|
|
fn describe_resolved<D, F>(
|
2022-04-29 11:57:50 -04:00
|
|
|
mut resolve: F,
|
tamer: Integrate clippy
This invokes clippy as part of `make check` now, which I had previously
avoided doing (I'll elaborate on that below).
This commit represents the changes needed to resolve all the warnings
presented by clippy. Many changes have been made where I find the lints to
be useful and agreeable, but there are a number of lints, rationalized in
`src/lib.rs`, where I found the lints to be disagreeable. I have provided
rationale, primarily for those wondering why I desire to deviate from the
default lints, though it does feel backward to rationalize why certain lints
ought to be applied (the reverse should be true).
With that said, this did catch some legitimage issues, and it was also
helpful in getting some older code up-to-date with new language additions
that perhaps I used in new code but hadn't gone back and updated old code
for. My goal was to get clippy working without errors so that, in the
future, when others get into TAMER and are still getting used to Rust,
clippy is able to help guide them in the right direction.
One of the reasons I went without clippy for so long (though I admittedly
forgot I wasn't using it for a period of time) was because there were a
number of suggestions that I found disagreeable, and I didn't take the time
to go through them and determine what I wanted to follow. Furthermore, it
was hard to make that judgment when I was new to the language and lacked
the necessary experience to do so.
One thing I would like to comment further on is the use of `format!` with
`expect`, which is also what the diagnostic system convenience methods
do (which clippy does not cover). Because of all the work I've done trying
to understand Rust and looking at disassemblies and seeing what it
optimizes, I falsely assumed that Rust would convert such things into
conditionals in my otherwise-pure code...but apparently that's not the case,
when `format!` is involved.
I noticed that, after making the suggested fix with `get_ident`, Rust
proceeded to then inline it into each call site and then apply further
optimizations. It was also previously invoking the thread lock (for the
interner) unconditionally and invoking the `Display` implementation. That
is not at all what I intended for, despite knowing the eager semantics of
function calls in Rust.
Anyway, possibly more to come on that, I'm just tired of typing and need to
move on. I'll be returning to investigate further diagnostic messages soon.
2023-01-12 10:46:48 -05:00
|
|
|
diagnostic: &D,
|
|
|
|
) -> impl Iterator<Item = MaybeResolvedSpan<ResolvedSpan>>
|
2022-04-29 11:57:50 -04:00
|
|
|
where
|
|
|
|
D: Diagnostic,
|
|
|
|
F: FnMut(Span) -> Result<ResolvedSpan, SpanResolverError>,
|
|
|
|
{
|
|
|
|
diagnostic.describe().into_iter().scan(
|
|
|
|
UNKNOWN_SPAN,
|
|
|
|
move |prev_span, AnnotatedSpan(span, level, olabel)| {
|
|
|
|
// Avoid re-resolving
|
|
|
|
// (and allocating memory for the source lines of)
|
|
|
|
// a span that was just resolved,
|
|
|
|
// which will just be squashed with the previous anyway.
|
|
|
|
if *prev_span == span {
|
2022-04-29 12:10:32 -04:00
|
|
|
return Some(MaybeResolvedSpan::Elided(span, level, olabel));
|
2022-04-29 11:57:50 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
*prev_span = span;
|
|
|
|
|
|
|
|
Some(match resolve(span) {
|
2022-04-29 12:10:32 -04:00
|
|
|
Ok(rspan) => MaybeResolvedSpan::Resolved(rspan, level, olabel),
|
|
|
|
Err(e) => MaybeResolvedSpan::Unresolved(span, level, olabel, e),
|
2022-04-29 11:57:50 -04:00
|
|
|
})
|
|
|
|
},
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2022-04-28 11:00:36 -04:00
|
|
|
/// A [`Span`] that may have been resolved.
|
|
|
|
///
|
|
|
|
/// The span will remain unresolved if an error occurred,
|
|
|
|
/// in which case the error will be provided.
|
|
|
|
/// The idea is to provide as much fallback information as is useful to the
|
|
|
|
/// user so that they can still debug the problem without the benefit of
|
|
|
|
/// the resolved context.
|
|
|
|
///
|
|
|
|
/// Furthermore,
|
|
|
|
/// it is important that the underlying diagnostic message
|
|
|
|
/// (e.g. error)
|
|
|
|
/// never be masked by an error of our own.
|
|
|
|
#[derive(Debug, PartialEq, Eq)]
|
|
|
|
enum MaybeResolvedSpan<'d, S: ResolvedSpanData> {
|
2022-04-29 12:10:32 -04:00
|
|
|
Resolved(S, Level, Option<Label<'d>>),
|
|
|
|
Elided(Span, Level, Option<Label<'d>>),
|
|
|
|
Unresolved(Span, Level, Option<Label<'d>>, SpanResolverError),
|
2022-04-28 11:00:36 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
impl<'d, S: ResolvedSpanData> MaybeResolvedSpan<'d, S> {
|
|
|
|
/// We should never mask an error with our own;
|
|
|
|
/// the diagnostic system is supposed to _help_ the user in diagnosing
|
|
|
|
/// problems,
|
|
|
|
/// not hinder them by masking it.
|
2022-04-28 13:24:36 -04:00
|
|
|
fn system_lines(&self) -> Vec<SectionLine<'static>> {
|
2022-04-28 11:00:36 -04:00
|
|
|
match self {
|
2022-04-29 12:10:32 -04:00
|
|
|
Self::Resolved(rspan, ..) if rspan.col_num().is_none() => vec![
|
|
|
|
SectionLine::Footnote(
|
2022-04-28 11:00:36 -04:00
|
|
|
Level::Help,
|
|
|
|
"unable to calculate columns because the line is \
|
|
|
|
not a valid UTF-8 string"
|
|
|
|
.into(),
|
2022-04-29 12:10:32 -04:00
|
|
|
),
|
|
|
|
SectionLine::Footnote(
|
2022-04-28 11:00:36 -04:00
|
|
|
Level::Help,
|
|
|
|
"you have been provided with 0-indexed \
|
|
|
|
line-relative inclusive byte offsets"
|
|
|
|
.into(),
|
2022-04-29 12:10:32 -04:00
|
|
|
),
|
2022-04-28 11:00:36 -04:00
|
|
|
],
|
|
|
|
|
2022-04-29 12:10:32 -04:00
|
|
|
Self::Unresolved(.., e) => {
|
|
|
|
vec![SectionLine::Footnote(
|
2022-04-28 11:00:36 -04:00
|
|
|
Level::Help,
|
|
|
|
format!(
|
|
|
|
"an error occurred while trying to look up \
|
|
|
|
information about this span: {e}"
|
|
|
|
)
|
|
|
|
.into(),
|
2022-04-29 12:10:32 -04:00
|
|
|
)]
|
2022-04-28 11:00:36 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
_ => vec![],
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-02 09:44:53 -04:00
|
|
|
/// Diagnostic report.
|
|
|
|
///
|
|
|
|
/// This contains the raw data that,
|
|
|
|
/// when rendered with [`Display::fmt`],
|
|
|
|
/// will produce a robust report to help guide the user through diagnosing
|
|
|
|
/// and resolving problems.
|
2022-04-27 10:45:31 -04:00
|
|
|
#[derive(Debug)]
|
2022-04-27 15:11:29 -04:00
|
|
|
pub struct Report<'d, D: Diagnostic> {
|
2022-05-02 09:44:53 -04:00
|
|
|
/// Summary message of the contents of the report.
|
|
|
|
///
|
|
|
|
/// This message should be suitable on its own,
|
|
|
|
/// e.g. a typical one-line error message.
|
2022-04-27 15:11:29 -04:00
|
|
|
msg: Message<'d, D>,
|
2022-05-02 09:44:53 -04:00
|
|
|
|
|
|
|
/// The largest severity level found within all of the [`Section`]s of
|
|
|
|
/// the report.
|
|
|
|
///
|
|
|
|
/// This level should be used alongside the summary message to describe
|
|
|
|
/// how severe of a problem this report represents.
|
2022-04-27 10:45:31 -04:00
|
|
|
level: Level,
|
2022-05-02 09:44:53 -04:00
|
|
|
|
|
|
|
/// Sections of the report.
|
|
|
|
///
|
|
|
|
/// A section contains a header describing a location in the source
|
|
|
|
/// code (line and column numbers),
|
|
|
|
/// followed by annotated source code and descriptive labels.
|
|
|
|
secs: Vec<Section<'d>>,
|
|
|
|
|
|
|
|
/// The maximum line number encountered in each of the [`Section`]s of
|
|
|
|
/// the report.
|
|
|
|
///
|
|
|
|
/// This number is used to determine the gutter width,
|
|
|
|
/// which contains the line numbers of the annotated source lines.
|
|
|
|
/// It can be propagated to all [`Section`]s using
|
|
|
|
/// [`normalize_gutters`](Report::normalize_gutters).
|
2022-04-28 23:37:07 -04:00
|
|
|
line_max: NonZeroU32,
|
2022-04-27 10:45:31 -04:00
|
|
|
}
|
|
|
|
|
2022-04-27 15:11:29 -04:00
|
|
|
impl<'d, D: Diagnostic> Report<'d, D> {
|
2022-05-02 09:44:53 -04:00
|
|
|
/// Create an empty report.
|
|
|
|
///
|
|
|
|
/// To add to the body of the report,
|
|
|
|
/// use [`Extend::extend`].
|
2022-04-27 15:11:29 -04:00
|
|
|
fn empty(msg: Message<'d, D>) -> Self {
|
2022-04-27 10:45:31 -04:00
|
|
|
Self {
|
|
|
|
msg,
|
|
|
|
secs: Vec::new(),
|
|
|
|
level: Level::default(),
|
2022-04-28 23:37:07 -04:00
|
|
|
line_max: NonZeroU32::MIN,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Make all sections' gutters the same width.
|
|
|
|
///
|
|
|
|
/// This is only necessary because [`Section`] is expected to be wholly
|
|
|
|
/// self-contained when rendering.
|
|
|
|
fn normalize_gutters(&mut self) {
|
|
|
|
for sec in self.secs.iter_mut() {
|
|
|
|
sec.line_max = self.line_max;
|
2022-04-27 10:45:31 -04:00
|
|
|
}
|
|
|
|
}
|
2022-12-16 16:24:50 -05:00
|
|
|
|
|
|
|
/// Finalize section formatting before display to the user.
|
|
|
|
fn finalize_sections(&mut self) {
|
|
|
|
self.secs.iter_mut().for_each(Section::finalize)
|
|
|
|
}
|
2022-04-27 10:45:31 -04:00
|
|
|
}
|
|
|
|
|
2022-04-27 15:11:29 -04:00
|
|
|
impl<'d, D: Diagnostic> Extend<Section<'d>> for Report<'d, D> {
|
2022-05-02 09:44:53 -04:00
|
|
|
/// Extend the body of the report.
|
|
|
|
///
|
|
|
|
/// This tracks the most severe [`Level`] and highest line number seen.
|
|
|
|
/// Further,
|
|
|
|
/// adjacent sections may be squashed if they meet certain criteria
|
|
|
|
/// (see [`Section::maybe_squash_into`]).
|
2022-04-27 15:11:29 -04:00
|
|
|
fn extend<T: IntoIterator<Item = Section<'d>>>(&mut self, secs: T) {
|
2022-04-27 10:45:31 -04:00
|
|
|
for sec in secs {
|
|
|
|
self.level = self.level.min(sec.level());
|
2022-04-28 23:37:07 -04:00
|
|
|
self.line_max = self.line_max.max(sec.line_max);
|
2022-04-28 10:30:04 -04:00
|
|
|
|
|
|
|
// Add the section if it cannot be squashed into the previous.
|
|
|
|
let remain = sec.maybe_squash_into(self.secs.last_mut());
|
|
|
|
self.secs.extend(remain);
|
2022-04-27 10:45:31 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-27 15:11:29 -04:00
|
|
|
impl<'d, D: Diagnostic> Display for Report<'d, D> {
|
2022-04-27 10:45:31 -04:00
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
write!(f, "{level}: {msg}\n", level = self.level, msg = self.msg)?;
|
2022-04-28 14:33:08 -04:00
|
|
|
|
|
|
|
self.secs.iter().try_fold(true, |first, sec| {
|
|
|
|
if !first {
|
|
|
|
f.write_char('\n')?;
|
|
|
|
}
|
|
|
|
|
|
|
|
sec.fmt(f)?;
|
|
|
|
Ok(false)
|
|
|
|
})?;
|
|
|
|
|
|
|
|
Ok(())
|
2022-04-27 10:45:31 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-02 09:44:53 -04:00
|
|
|
/// Summary diagnostic message.
|
2022-04-27 10:45:31 -04:00
|
|
|
#[derive(Debug)]
|
2022-04-27 15:11:29 -04:00
|
|
|
struct Message<'d, D: Diagnostic>(&'d D);
|
2022-04-27 10:45:31 -04:00
|
|
|
|
2022-04-27 15:11:29 -04:00
|
|
|
impl<'d, D: Diagnostic> Display for Message<'d, D> {
|
2022-04-27 15:06:57 -04:00
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
Display::fmt(self.0, f)
|
2022-04-27 10:45:31 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-28 10:30:04 -04:00
|
|
|
/// A section of a [`Report`] describing a [`Span`].
|
|
|
|
///
|
|
|
|
/// Adjacent sections describing the same [`Span`] ought to be squashed
|
|
|
|
/// (see [`Section::maybe_squash_into`]),
|
|
|
|
/// but not non-adjacent ones,
|
|
|
|
/// since reports ought to be able to produce narratives that may
|
|
|
|
/// revisit previous spans in an attempt to describe what occurred and
|
|
|
|
/// how to correct it.
|
|
|
|
///
|
|
|
|
/// Each section is delimited by a heading that provides a summary context
|
|
|
|
/// and enough of a visual distinction to be able to skim diagnostic
|
|
|
|
/// messages quickly.
|
|
|
|
#[derive(Debug, PartialEq, Eq)]
|
|
|
|
struct Section<'d> {
|
2022-05-02 09:44:53 -04:00
|
|
|
/// Heading that delimits the beginning of each section.
|
|
|
|
///
|
|
|
|
/// The heading describes the location of its principal [`Span`].
|
2022-04-28 10:30:04 -04:00
|
|
|
heading: SpanHeading,
|
2022-05-02 09:44:53 -04:00
|
|
|
|
|
|
|
/// The most severe [`Level`] encountered in this section body.
|
2022-04-28 10:30:04 -04:00
|
|
|
level: Level,
|
2022-05-02 09:44:53 -04:00
|
|
|
|
|
|
|
/// The principal [`Span`] that this section describes.
|
|
|
|
///
|
|
|
|
/// If a section contains information about multiple spans,
|
|
|
|
/// this represents the one that the user should focus on.
|
2022-04-28 10:30:04 -04:00
|
|
|
span: Span,
|
2022-05-02 09:44:53 -04:00
|
|
|
|
|
|
|
/// Annotated source lines and labels.
|
2022-04-28 13:24:36 -04:00
|
|
|
body: Vec<SectionLine<'d>>,
|
2022-05-02 09:44:53 -04:00
|
|
|
|
|
|
|
/// The largest line number encountered in this section.
|
|
|
|
///
|
|
|
|
/// This is used to determine how wide to render the gutter,
|
|
|
|
/// which contain the line numbers for source lines.
|
2022-04-28 23:37:07 -04:00
|
|
|
line_max: NonZeroU32,
|
2022-04-27 10:45:31 -04:00
|
|
|
}
|
|
|
|
|
tamer: Integrate clippy
This invokes clippy as part of `make check` now, which I had previously
avoided doing (I'll elaborate on that below).
This commit represents the changes needed to resolve all the warnings
presented by clippy. Many changes have been made where I find the lints to
be useful and agreeable, but there are a number of lints, rationalized in
`src/lib.rs`, where I found the lints to be disagreeable. I have provided
rationale, primarily for those wondering why I desire to deviate from the
default lints, though it does feel backward to rationalize why certain lints
ought to be applied (the reverse should be true).
With that said, this did catch some legitimage issues, and it was also
helpful in getting some older code up-to-date with new language additions
that perhaps I used in new code but hadn't gone back and updated old code
for. My goal was to get clippy working without errors so that, in the
future, when others get into TAMER and are still getting used to Rust,
clippy is able to help guide them in the right direction.
One of the reasons I went without clippy for so long (though I admittedly
forgot I wasn't using it for a period of time) was because there were a
number of suggestions that I found disagreeable, and I didn't take the time
to go through them and determine what I wanted to follow. Furthermore, it
was hard to make that judgment when I was new to the language and lacked
the necessary experience to do so.
One thing I would like to comment further on is the use of `format!` with
`expect`, which is also what the diagnostic system convenience methods
do (which clippy does not cover). Because of all the work I've done trying
to understand Rust and looking at disassemblies and seeing what it
optimizes, I falsely assumed that Rust would convert such things into
conditionals in my otherwise-pure code...but apparently that's not the case,
when `format!` is involved.
I noticed that, after making the suggested fix with `get_ident`, Rust
proceeded to then inline it into each call site and then apply further
optimizations. It was also previously invoking the thread lock (for the
interner) unconditionally and invoking the `Display` implementation. That
is not at all what I intended for, despite knowing the eager semantics of
function calls in Rust.
Anyway, possibly more to come on that, I'm just tired of typing and need to
move on. I'll be returning to investigate further diagnostic messages soon.
2023-01-12 10:46:48 -05:00
|
|
|
impl<'d> Section<'d> {
|
2022-04-29 13:10:04 -04:00
|
|
|
/// Create a new section from a resolved span.
|
|
|
|
fn new_resolved<S: ResolvedSpanData>(
|
|
|
|
rspan: S,
|
|
|
|
level: Level,
|
|
|
|
mut olabel: Option<Label<'d>>,
|
|
|
|
syslines: Vec<SectionLine<'static>>,
|
|
|
|
) -> Self {
|
|
|
|
let heading = SpanHeading::from(&rspan);
|
|
|
|
|
|
|
|
let span = rspan.unresolved_span();
|
|
|
|
let src = rspan.into_lines();
|
|
|
|
let nlines = src.len();
|
|
|
|
|
2022-04-29 15:53:31 -04:00
|
|
|
let mut body = Vec::with_capacity(4);
|
2022-04-29 13:10:04 -04:00
|
|
|
let mut line_max = NonZeroU32::MIN;
|
|
|
|
|
|
|
|
src.into_iter().enumerate().for_each(|(i, srcline)| {
|
|
|
|
let line_num = srcline.num();
|
|
|
|
|
|
|
|
// Note that lines are intentionally _not_ ordered,
|
|
|
|
// since reports may jump around a file to produce a
|
|
|
|
// narrative.
|
|
|
|
line_max = line_max.max(line_num);
|
|
|
|
|
|
|
|
let label = if i == nlines - 1 { olabel.take() } else { None };
|
|
|
|
|
2022-04-29 15:53:31 -04:00
|
|
|
Self::render_src(&mut body, srcline, level, label);
|
2022-04-29 13:10:04 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
// System messages should appear _after_ all requested diagnostic
|
|
|
|
// messages.
|
|
|
|
body.extend(syslines);
|
|
|
|
|
|
|
|
Section {
|
|
|
|
heading,
|
|
|
|
span,
|
|
|
|
level,
|
|
|
|
body,
|
|
|
|
line_max,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Create a new section from a span that failed to resolve.
|
|
|
|
fn new_unresolved(
|
|
|
|
span: Span,
|
|
|
|
level: Level,
|
|
|
|
olabel: Option<Label<'d>>,
|
|
|
|
syslines: Vec<SectionLine<'static>>,
|
|
|
|
) -> Self {
|
|
|
|
let mut body = Vec::new();
|
|
|
|
|
|
|
|
body.extend(olabel.map(|label| SectionLine::Footnote(level, label)));
|
|
|
|
body.extend(syslines);
|
|
|
|
|
|
|
|
Section {
|
|
|
|
heading: SpanHeading(
|
|
|
|
span.context(),
|
|
|
|
HeadingLineNum::Unresolved(span),
|
|
|
|
),
|
|
|
|
level,
|
|
|
|
span,
|
|
|
|
body,
|
|
|
|
line_max: NonZeroU32::MIN,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-29 15:53:31 -04:00
|
|
|
/// Render a `SourceLine` to the body with the appropriate annotations.
|
|
|
|
///
|
|
|
|
/// If the columns are known,
|
|
|
|
/// then the span will be marked along with an optional label.
|
|
|
|
/// Otherwise,
|
|
|
|
/// the line will be rendered without annotations,
|
|
|
|
/// with any optional label appearing as a footnote.
|
|
|
|
fn render_src(
|
|
|
|
dest: &mut Vec<SectionLine<'d>>,
|
|
|
|
src: SourceLine,
|
|
|
|
level: Level,
|
|
|
|
label: Option<Label<'d>>,
|
|
|
|
) {
|
|
|
|
if let Some(col) = src.column() {
|
|
|
|
dest.extend(vec![
|
|
|
|
SectionLine::SourceLinePadding,
|
|
|
|
SectionLine::SourceLine(src.into()),
|
|
|
|
SectionLine::SourceLineMark(LineMark { col, level, label }),
|
2022-12-16 16:24:50 -05:00
|
|
|
SectionLine::SourceLinePadding,
|
2022-04-29 15:53:31 -04:00
|
|
|
]);
|
|
|
|
} else {
|
|
|
|
dest.extend(vec![
|
|
|
|
SectionLine::SourceLinePadding,
|
|
|
|
SectionLine::SourceLine(src.into()),
|
|
|
|
SectionLine::SourceLinePadding,
|
|
|
|
]);
|
|
|
|
|
|
|
|
dest.extend(label.map(|l| SectionLine::Footnote(level, l)));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-29 13:10:04 -04:00
|
|
|
/// Most severe [`Level`] associated with this section.
|
|
|
|
///
|
|
|
|
/// This value is updated as lines are added to the body.
|
2022-04-27 10:45:31 -04:00
|
|
|
fn level(&self) -> Level {
|
2022-04-28 10:30:04 -04:00
|
|
|
self.level
|
2022-04-27 10:45:31 -04:00
|
|
|
}
|
tamer: diagnostic: Column resolution
Determining the column number is not as simple as performing byte
arithmetic, because certain characters have different widths. Even if we
only accepted ASCII, control characters aren't visible to the user.
This uses the unicode-width crate as an alternative to POSIX wcwidth, to
determine (hopefully) the number of fixed-width cells that a unicode
character will take up on a terminal. For example, control characters are
zero-width, while an emoji is likely double-width. See test cases for more
information on that.
There is also the unicode-segmentation crate, which can handle extended
grapheme clusters and such, but (a) we'll be outputting the line to the
terminal and (b) there's no guarantee that the user's editor displays
grapheme clusters as a single column. LSP measures in UTF-16,
apparently. I use both Emacs and Vim from a terminal, so unicode-width
applies to me. There's too much variation to try to solve that right now.
The columns can be considered a visual span---this gives us enough
information to draw line annotations, which will happen soon.
Here are some useful links:
- https://hsivonen.fi/string-length/
- https://unicode.org/reports/tr29/
- https://github.com/rust-analyzer/rowan/issues/17
- https://www.reddit.com/r/rust/comments/gpw2ra/how_is_the_rust_compiler_able_to_tell_the_visible/
DEV-10935
2022-04-21 14:16:21 -04:00
|
|
|
|
2022-04-28 10:30:04 -04:00
|
|
|
/// Squash self into the provided [`Section`] if they represent the same
|
|
|
|
/// [`Span`],
|
|
|
|
/// otherwise do nothing.
|
|
|
|
///
|
|
|
|
/// If squashed,
|
|
|
|
/// [`None`] is returned.
|
|
|
|
/// Otherwise [`Some`] is returned with `self`.
|
|
|
|
/// This return value can be used with [`Extend`] to extend a vector of
|
|
|
|
/// sections with the value after this operation.
|
|
|
|
///
|
|
|
|
/// The term "squash" is borrowed from `git rebase`.
|
|
|
|
fn maybe_squash_into(
|
|
|
|
self,
|
|
|
|
extend: Option<&mut Section<'d>>,
|
|
|
|
) -> Option<Self> {
|
|
|
|
match extend {
|
|
|
|
Some(extend_sec) if self.span == extend_sec.span => {
|
2022-04-29 13:07:51 -04:00
|
|
|
// Note that system labels shouldn't exist for elided spans
|
|
|
|
// and so they should not be duplicated when squashing.
|
2022-04-28 14:33:08 -04:00
|
|
|
extend_sec.body.extend(
|
|
|
|
self.body
|
|
|
|
.into_iter()
|
|
|
|
.filter_map(SectionLine::into_footnote),
|
|
|
|
);
|
|
|
|
|
2022-04-28 10:30:04 -04:00
|
|
|
None
|
2022-04-26 10:14:51 -04:00
|
|
|
}
|
2022-04-28 10:30:04 -04:00
|
|
|
|
|
|
|
_ => Some(self),
|
2022-04-27 10:45:31 -04:00
|
|
|
}
|
|
|
|
}
|
2022-04-28 23:37:07 -04:00
|
|
|
|
|
|
|
/// Maximum width of the text in the gutter.
|
|
|
|
///
|
|
|
|
/// Note that the gutter contains a single character of padding before
|
|
|
|
/// its delimiter,
|
|
|
|
/// which this width _does not_ account for.
|
|
|
|
///
|
|
|
|
/// The minimum width is 2.
|
|
|
|
///
|
|
|
|
/// ```text
|
|
|
|
/// --> heading
|
|
|
|
/// |
|
|
|
|
/// 1 | source line
|
|
|
|
/// ^^
|
|
|
|
/// gutter width is 2
|
|
|
|
/// ```
|
|
|
|
fn gutter_text_width(&self) -> usize {
|
2022-08-12 16:16:06 -04:00
|
|
|
self.line_max.ilog10().add(1).max(2) as usize
|
2022-04-28 23:37:07 -04:00
|
|
|
}
|
2022-12-16 16:24:50 -05:00
|
|
|
|
|
|
|
/// Finalize formatting of this section before display to the user.
|
|
|
|
///
|
|
|
|
/// This is the last chance to clean things up.
|
|
|
|
fn finalize(&mut self) {
|
|
|
|
use SectionLine::SourceLinePadding;
|
|
|
|
|
|
|
|
// Padding is added conservatively during generation,
|
|
|
|
// which may lead to adjacent padding for multi-line spans.
|
|
|
|
// That padding can be merged into a single line.
|
tamer: Integrate clippy
This invokes clippy as part of `make check` now, which I had previously
avoided doing (I'll elaborate on that below).
This commit represents the changes needed to resolve all the warnings
presented by clippy. Many changes have been made where I find the lints to
be useful and agreeable, but there are a number of lints, rationalized in
`src/lib.rs`, where I found the lints to be disagreeable. I have provided
rationale, primarily for those wondering why I desire to deviate from the
default lints, though it does feel backward to rationalize why certain lints
ought to be applied (the reverse should be true).
With that said, this did catch some legitimage issues, and it was also
helpful in getting some older code up-to-date with new language additions
that perhaps I used in new code but hadn't gone back and updated old code
for. My goal was to get clippy working without errors so that, in the
future, when others get into TAMER and are still getting used to Rust,
clippy is able to help guide them in the right direction.
One of the reasons I went without clippy for so long (though I admittedly
forgot I wasn't using it for a period of time) was because there were a
number of suggestions that I found disagreeable, and I didn't take the time
to go through them and determine what I wanted to follow. Furthermore, it
was hard to make that judgment when I was new to the language and lacked
the necessary experience to do so.
One thing I would like to comment further on is the use of `format!` with
`expect`, which is also what the diagnostic system convenience methods
do (which clippy does not cover). Because of all the work I've done trying
to understand Rust and looking at disassemblies and seeing what it
optimizes, I falsely assumed that Rust would convert such things into
conditionals in my otherwise-pure code...but apparently that's not the case,
when `format!` is involved.
I noticed that, after making the suggested fix with `get_ident`, Rust
proceeded to then inline it into each call site and then apply further
optimizations. It was also previously invoking the thread lock (for the
interner) unconditionally and invoking the `Display` implementation. That
is not at all what I intended for, despite knowing the eager semantics of
function calls in Rust.
Anyway, possibly more to come on that, I'm just tired of typing and need to
move on. I'll be returning to investigate further diagnostic messages soon.
2023-01-12 10:46:48 -05:00
|
|
|
self.body.dedup_by(|a, b| {
|
|
|
|
matches!((a, b), (SourceLinePadding, SourceLinePadding),)
|
2022-12-16 16:24:50 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
// Padding at the very end of the section is not desirable since the
|
|
|
|
// report already adds padding around sections.
|
|
|
|
if self.body.last() == Some(&SourceLinePadding) {
|
|
|
|
self.body.pop();
|
|
|
|
}
|
|
|
|
}
|
2022-04-27 10:45:31 -04:00
|
|
|
}
|
2022-04-26 10:14:51 -04:00
|
|
|
|
tamer: Integrate clippy
This invokes clippy as part of `make check` now, which I had previously
avoided doing (I'll elaborate on that below).
This commit represents the changes needed to resolve all the warnings
presented by clippy. Many changes have been made where I find the lints to
be useful and agreeable, but there are a number of lints, rationalized in
`src/lib.rs`, where I found the lints to be disagreeable. I have provided
rationale, primarily for those wondering why I desire to deviate from the
default lints, though it does feel backward to rationalize why certain lints
ought to be applied (the reverse should be true).
With that said, this did catch some legitimage issues, and it was also
helpful in getting some older code up-to-date with new language additions
that perhaps I used in new code but hadn't gone back and updated old code
for. My goal was to get clippy working without errors so that, in the
future, when others get into TAMER and are still getting used to Rust,
clippy is able to help guide them in the right direction.
One of the reasons I went without clippy for so long (though I admittedly
forgot I wasn't using it for a period of time) was because there were a
number of suggestions that I found disagreeable, and I didn't take the time
to go through them and determine what I wanted to follow. Furthermore, it
was hard to make that judgment when I was new to the language and lacked
the necessary experience to do so.
One thing I would like to comment further on is the use of `format!` with
`expect`, which is also what the diagnostic system convenience methods
do (which clippy does not cover). Because of all the work I've done trying
to understand Rust and looking at disassemblies and seeing what it
optimizes, I falsely assumed that Rust would convert such things into
conditionals in my otherwise-pure code...but apparently that's not the case,
when `format!` is involved.
I noticed that, after making the suggested fix with `get_ident`, Rust
proceeded to then inline it into each call site and then apply further
optimizations. It was also previously invoking the thread lock (for the
interner) unconditionally and invoking the `Display` implementation. That
is not at all what I intended for, despite knowing the eager semantics of
function calls in Rust.
Anyway, possibly more to come on that, I'm just tired of typing and need to
move on. I'll be returning to investigate further diagnostic messages soon.
2023-01-12 10:46:48 -05:00
|
|
|
impl<'d, S> From<MaybeResolvedSpan<'d, S>> for Section<'d>
|
2022-04-27 10:45:31 -04:00
|
|
|
where
|
|
|
|
S: ResolvedSpanData,
|
|
|
|
{
|
2022-04-27 15:11:29 -04:00
|
|
|
fn from(mspan: MaybeResolvedSpan<'d, S>) -> Self {
|
2022-04-28 14:33:08 -04:00
|
|
|
let syslines = mspan.system_lines();
|
|
|
|
|
2022-04-29 13:10:04 -04:00
|
|
|
match mspan {
|
|
|
|
MaybeResolvedSpan::Resolved(rspan, level, olabel) => {
|
|
|
|
Self::new_resolved(rspan, level, olabel, syslines)
|
2022-04-28 13:24:36 -04:00
|
|
|
}
|
2022-04-29 13:10:04 -04:00
|
|
|
// Note that elided will be squashed,
|
|
|
|
// so it's okay that it's marked as unresolved here.
|
2022-04-29 12:10:32 -04:00
|
|
|
MaybeResolvedSpan::Elided(span, level, olabel)
|
|
|
|
| MaybeResolvedSpan::Unresolved(span, level, olabel, _) => {
|
2022-04-29 13:10:04 -04:00
|
|
|
Self::new_unresolved(span, level, olabel, syslines)
|
2022-04-27 14:58:50 -04:00
|
|
|
}
|
|
|
|
}
|
2022-04-27 10:45:31 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-27 15:11:29 -04:00
|
|
|
impl<'d> Display for Section<'d> {
|
2022-04-27 10:45:31 -04:00
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
2022-04-28 23:37:07 -04:00
|
|
|
let gutterw = self.gutter_text_width();
|
|
|
|
|
|
|
|
// The heading has a hanging indentation,
|
|
|
|
// which is accomplished by simply omitting spaces above the
|
|
|
|
// gutter's `" |"`,
|
|
|
|
// which amounts to two characters wide.
|
|
|
|
write!(f, "{:gutterw$}{heading}\n", "", heading = self.heading)?;
|
2022-04-27 10:45:31 -04:00
|
|
|
|
2022-04-28 22:59:13 -04:00
|
|
|
self.body.iter().try_for_each(|line| {
|
2022-04-28 23:37:07 -04:00
|
|
|
line.fmt_gutter(gutterw, f)?;
|
|
|
|
write!(f, "{line}\n")
|
2022-04-28 22:59:13 -04:00
|
|
|
})
|
2022-04-26 10:14:51 -04:00
|
|
|
}
|
|
|
|
}
|
tamer: diagnostic: Column resolution
Determining the column number is not as simple as performing byte
arithmetic, because certain characters have different widths. Even if we
only accepted ASCII, control characters aren't visible to the user.
This uses the unicode-width crate as an alternative to POSIX wcwidth, to
determine (hopefully) the number of fixed-width cells that a unicode
character will take up on a terminal. For example, control characters are
zero-width, while an emoji is likely double-width. See test cases for more
information on that.
There is also the unicode-segmentation crate, which can handle extended
grapheme clusters and such, but (a) we'll be outputting the line to the
terminal and (b) there's no guarantee that the user's editor displays
grapheme clusters as a single column. LSP measures in UTF-16,
apparently. I use both Emacs and Vim from a terminal, so unicode-width
applies to me. There's too much variation to try to solve that right now.
The columns can be considered a visual span---this gives us enough
information to draw line annotations, which will happen soon.
Here are some useful links:
- https://hsivonen.fi/string-length/
- https://unicode.org/reports/tr29/
- https://github.com/rust-analyzer/rowan/issues/17
- https://www.reddit.com/r/rust/comments/gpw2ra/how_is_the_rust_compiler_able_to_tell_the_visible/
DEV-10935
2022-04-21 14:16:21 -04:00
|
|
|
|
2022-04-27 10:45:31 -04:00
|
|
|
/// Heading describing the context of a (hopefully resolved) span.
|
2022-04-26 10:14:51 -04:00
|
|
|
///
|
|
|
|
/// The ideal header contains the context along with the line, and column
|
|
|
|
/// numbers,
|
|
|
|
/// visually distinguishable from surrounding lines to allow the user to
|
|
|
|
/// quickly skip between reports.
|
2022-04-28 10:30:04 -04:00
|
|
|
#[derive(Debug, PartialEq, Eq)]
|
2022-04-27 14:23:58 -04:00
|
|
|
struct SpanHeading(Context, HeadingLineNum);
|
tamer: diagnose: Introduction of diagnostic system
This is a working concept that will continue to evolve. I wanted to start
with some basic output before getting too carried away, since there's a lot
of potential here.
This is heavily influenced by Rust's helpful diagnostic messages, but will
take some time to realize a lot of the things that Rust does. The next step
will be to resolve line and column numbers, and then possibly include
snippets and underline spans, placing the labels alongside them. I need to
balance this work with everything else I have going on.
This is a large commit, but it converts the existing Error Display impls
into Diagnostic. This separation is a bit verbose, so I'll see how this
ends up evolving.
Diagnostics are tied to Error at the moment, but I imagine in the future
that any object would be able to describe itself, error or not, which would
be useful in the future both for the Summary Page and for query
functionality, to help developers understand the systems they are writing
using TAME.
Output is integrated into tameld only in this commit; I'll add tamec
next. Examples of what this outputs are available in the test cases in this
commit.
DEV-10935
2022-04-13 14:41:54 -04:00
|
|
|
|
2022-04-27 14:23:58 -04:00
|
|
|
impl Display for SpanHeading {
|
2022-04-26 10:14:51 -04:00
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
let Self(ctx, line) = self;
|
2022-04-27 10:45:31 -04:00
|
|
|
write!(f, "--> {ctx}{line}")
|
2022-04-26 10:14:51 -04:00
|
|
|
}
|
|
|
|
}
|
tamer: diagnose: Introduction of diagnostic system
This is a working concept that will continue to evolve. I wanted to start
with some basic output before getting too carried away, since there's a lot
of potential here.
This is heavily influenced by Rust's helpful diagnostic messages, but will
take some time to realize a lot of the things that Rust does. The next step
will be to resolve line and column numbers, and then possibly include
snippets and underline spans, placing the labels alongside them. I need to
balance this work with everything else I have going on.
This is a large commit, but it converts the existing Error Display impls
into Diagnostic. This separation is a bit verbose, so I'll see how this
ends up evolving.
Diagnostics are tied to Error at the moment, but I imagine in the future
that any object would be able to describe itself, error or not, which would
be useful in the future both for the Summary Page and for query
functionality, to help developers understand the systems they are writing
using TAME.
Output is integrated into tameld only in this commit; I'll add tamec
next. Examples of what this outputs are available in the test cases in this
commit.
DEV-10935
2022-04-13 14:41:54 -04:00
|
|
|
|
2022-04-29 13:10:04 -04:00
|
|
|
impl<S: ResolvedSpanData> From<&S> for SpanHeading {
|
2022-04-26 13:18:34 -04:00
|
|
|
/// Span header containing the (hopefully resolved) context.
|
2022-04-29 13:10:04 -04:00
|
|
|
fn from(rspan: &S) -> Self {
|
|
|
|
SpanHeading(
|
|
|
|
rspan.context(),
|
|
|
|
HeadingLineNum::Resolved(
|
|
|
|
rspan.line_num(),
|
|
|
|
rspan.col_num().map(HeadingColNum::Resolved).unwrap_or_else(
|
|
|
|
|| HeadingColNum::Unresolved {
|
|
|
|
unresolved_span: rspan.unresolved_span(),
|
|
|
|
first_line_span: rspan.first_line_span(),
|
|
|
|
},
|
2022-04-27 14:23:58 -04:00
|
|
|
),
|
|
|
|
),
|
2022-04-29 13:10:04 -04:00
|
|
|
)
|
2022-04-26 13:18:34 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-26 10:14:51 -04:00
|
|
|
/// Span line number or fallback representation.
|
|
|
|
///
|
|
|
|
/// This is also responsible for attempting to produce a column number,
|
|
|
|
/// provided that a line number is available.
|
|
|
|
///
|
|
|
|
/// If a span could not be resolved,
|
|
|
|
/// offsets should be rendered in place of lines and columns.
|
2022-04-28 10:30:04 -04:00
|
|
|
#[derive(Debug, PartialEq, Eq)]
|
2022-04-27 11:27:55 -04:00
|
|
|
enum HeadingLineNum {
|
2022-04-27 14:23:58 -04:00
|
|
|
Resolved(NonZeroU32, HeadingColNum),
|
2022-04-26 10:14:51 -04:00
|
|
|
Unresolved(Span),
|
|
|
|
}
|
|
|
|
|
2022-04-27 11:27:55 -04:00
|
|
|
impl Display for HeadingLineNum {
|
2022-04-26 10:14:51 -04:00
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
match self {
|
2022-04-27 14:23:58 -04:00
|
|
|
Self::Resolved(line_num, col) => {
|
2022-04-27 11:40:46 -04:00
|
|
|
write!(f, ":{line_num}{col}")
|
2022-04-27 11:27:55 -04:00
|
|
|
}
|
|
|
|
|
2022-04-26 10:14:51 -04:00
|
|
|
// This is not ideal,
|
|
|
|
// but provides reasonable fallback information in a
|
|
|
|
// situation where the diagnostic system fails.
|
|
|
|
// The user still has enough information to diagnose the
|
|
|
|
// problem,
|
|
|
|
// albeit presented in a significantly less friendly way.
|
|
|
|
Self::Unresolved(span) => {
|
|
|
|
write!(
|
|
|
|
f,
|
|
|
|
" offset {}--{}",
|
|
|
|
span.offset(),
|
|
|
|
span.endpoints_saturated().1.offset(),
|
|
|
|
)
|
tamer: diagnose: Introduction of diagnostic system
This is a working concept that will continue to evolve. I wanted to start
with some basic output before getting too carried away, since there's a lot
of potential here.
This is heavily influenced by Rust's helpful diagnostic messages, but will
take some time to realize a lot of the things that Rust does. The next step
will be to resolve line and column numbers, and then possibly include
snippets and underline spans, placing the labels alongside them. I need to
balance this work with everything else I have going on.
This is a large commit, but it converts the existing Error Display impls
into Diagnostic. This separation is a bit verbose, so I'll see how this
ends up evolving.
Diagnostics are tied to Error at the moment, but I imagine in the future
that any object would be able to describe itself, error or not, which would
be useful in the future both for the Summary Page and for query
functionality, to help developers understand the systems they are writing
using TAME.
Output is integrated into tameld only in this commit; I'll add tamec
next. Examples of what this outputs are available in the test cases in this
commit.
DEV-10935
2022-04-13 14:41:54 -04:00
|
|
|
}
|
2022-04-26 10:14:51 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
tamer: diagnose: Introduction of diagnostic system
This is a working concept that will continue to evolve. I wanted to start
with some basic output before getting too carried away, since there's a lot
of potential here.
This is heavily influenced by Rust's helpful diagnostic messages, but will
take some time to realize a lot of the things that Rust does. The next step
will be to resolve line and column numbers, and then possibly include
snippets and underline spans, placing the labels alongside them. I need to
balance this work with everything else I have going on.
This is a large commit, but it converts the existing Error Display impls
into Diagnostic. This separation is a bit verbose, so I'll see how this
ends up evolving.
Diagnostics are tied to Error at the moment, but I imagine in the future
that any object would be able to describe itself, error or not, which would
be useful in the future both for the Summary Page and for query
functionality, to help developers understand the systems they are writing
using TAME.
Output is integrated into tameld only in this commit; I'll add tamec
next. Examples of what this outputs are available in the test cases in this
commit.
DEV-10935
2022-04-13 14:41:54 -04:00
|
|
|
|
2022-04-26 10:14:51 -04:00
|
|
|
/// Column number or fallback representation.
|
|
|
|
///
|
|
|
|
/// If a column could not be resolved,
|
|
|
|
/// it should fall back to displaying byte offsets relative to the start
|
|
|
|
/// of the line.
|
2022-04-28 10:30:04 -04:00
|
|
|
#[derive(Debug, PartialEq, Eq)]
|
2022-04-27 10:58:53 -04:00
|
|
|
enum HeadingColNum {
|
|
|
|
Resolved(Column),
|
|
|
|
Unresolved {
|
|
|
|
unresolved_span: Span,
|
|
|
|
first_line_span: Span,
|
|
|
|
},
|
|
|
|
}
|
2022-04-26 10:14:51 -04:00
|
|
|
|
2022-04-27 10:58:53 -04:00
|
|
|
impl Display for HeadingColNum {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
match self {
|
tamer: Integrate clippy
This invokes clippy as part of `make check` now, which I had previously
avoided doing (I'll elaborate on that below).
This commit represents the changes needed to resolve all the warnings
presented by clippy. Many changes have been made where I find the lints to
be useful and agreeable, but there are a number of lints, rationalized in
`src/lib.rs`, where I found the lints to be disagreeable. I have provided
rationale, primarily for those wondering why I desire to deviate from the
default lints, though it does feel backward to rationalize why certain lints
ought to be applied (the reverse should be true).
With that said, this did catch some legitimage issues, and it was also
helpful in getting some older code up-to-date with new language additions
that perhaps I used in new code but hadn't gone back and updated old code
for. My goal was to get clippy working without errors so that, in the
future, when others get into TAMER and are still getting used to Rust,
clippy is able to help guide them in the right direction.
One of the reasons I went without clippy for so long (though I admittedly
forgot I wasn't using it for a period of time) was because there were a
number of suggestions that I found disagreeable, and I didn't take the time
to go through them and determine what I wanted to follow. Furthermore, it
was hard to make that judgment when I was new to the language and lacked
the necessary experience to do so.
One thing I would like to comment further on is the use of `format!` with
`expect`, which is also what the diagnostic system convenience methods
do (which clippy does not cover). Because of all the work I've done trying
to understand Rust and looking at disassemblies and seeing what it
optimizes, I falsely assumed that Rust would convert such things into
conditionals in my otherwise-pure code...but apparently that's not the case,
when `format!` is involved.
I noticed that, after making the suggested fix with `get_ident`, Rust
proceeded to then inline it into each call site and then apply further
optimizations. It was also previously invoking the thread lock (for the
interner) unconditionally and invoking the `Display` implementation. That
is not at all what I intended for, despite knowing the eager semantics of
function calls in Rust.
Anyway, possibly more to come on that, I'm just tired of typing and need to
move on. I'll be returning to investigate further diagnostic messages soon.
2023-01-12 10:46:48 -05:00
|
|
|
Self::Resolved(col) => write!(f, ":{col}"),
|
2022-04-26 10:14:51 -04:00
|
|
|
|
|
|
|
// The column is unavailable,
|
|
|
|
// which means that the line must have contained invalid UTF-8.
|
|
|
|
// Output what we can in an attempt to help the user debug.
|
2022-04-27 10:58:53 -04:00
|
|
|
Self::Unresolved {
|
|
|
|
unresolved_span,
|
|
|
|
first_line_span,
|
|
|
|
} => {
|
|
|
|
let rel = unresolved_span
|
|
|
|
.relative_to(*first_line_span)
|
2022-04-26 10:14:51 -04:00
|
|
|
.unwrap_or(UNKNOWN_SPAN);
|
|
|
|
|
|
|
|
write!(
|
|
|
|
f,
|
|
|
|
" bytes {}--{}",
|
|
|
|
rel.offset(),
|
|
|
|
rel.endpoints_saturated().1.offset()
|
|
|
|
)
|
|
|
|
}
|
tamer: diagnose: Introduction of diagnostic system
This is a working concept that will continue to evolve. I wanted to start
with some basic output before getting too carried away, since there's a lot
of potential here.
This is heavily influenced by Rust's helpful diagnostic messages, but will
take some time to realize a lot of the things that Rust does. The next step
will be to resolve line and column numbers, and then possibly include
snippets and underline spans, placing the labels alongside them. I need to
balance this work with everything else I have going on.
This is a large commit, but it converts the existing Error Display impls
into Diagnostic. This separation is a bit verbose, so I'll see how this
ends up evolving.
Diagnostics are tied to Error at the moment, but I imagine in the future
that any object would be able to describe itself, error or not, which would
be useful in the future both for the Summary Page and for query
functionality, to help developers understand the systems they are writing
using TAME.
Output is integrated into tameld only in this commit; I'll add tamec
next. Examples of what this outputs are available in the test cases in this
commit.
DEV-10935
2022-04-13 14:41:54 -04:00
|
|
|
}
|
2022-04-26 10:14:51 -04:00
|
|
|
}
|
|
|
|
}
|
tamer: diagnose: Introduction of diagnostic system
This is a working concept that will continue to evolve. I wanted to start
with some basic output before getting too carried away, since there's a lot
of potential here.
This is heavily influenced by Rust's helpful diagnostic messages, but will
take some time to realize a lot of the things that Rust does. The next step
will be to resolve line and column numbers, and then possibly include
snippets and underline spans, placing the labels alongside them. I need to
balance this work with everything else I have going on.
This is a large commit, but it converts the existing Error Display impls
into Diagnostic. This separation is a bit verbose, so I'll see how this
ends up evolving.
Diagnostics are tied to Error at the moment, but I imagine in the future
that any object would be able to describe itself, error or not, which would
be useful in the future both for the Summary Page and for query
functionality, to help developers understand the systems they are writing
using TAME.
Output is integrated into tameld only in this commit; I'll add tamec
next. Examples of what this outputs are available in the test cases in this
commit.
DEV-10935
2022-04-13 14:41:54 -04:00
|
|
|
|
2022-04-28 22:43:31 -04:00
|
|
|
/// Line of output in a [`Section`] body.
|
2022-04-28 13:24:36 -04:00
|
|
|
#[derive(Debug, PartialEq, Eq)]
|
|
|
|
enum SectionLine<'d> {
|
2022-05-02 09:44:53 -04:00
|
|
|
/// Padding for a possibly annotated source line.
|
|
|
|
///
|
|
|
|
/// A padding line is intended to add extra space above or below a
|
|
|
|
/// source line to make it easier to read.
|
|
|
|
/// Padding lines contain a gutter,
|
|
|
|
/// but no line number.
|
2022-04-28 22:43:31 -04:00
|
|
|
SourceLinePadding,
|
2022-05-02 09:44:53 -04:00
|
|
|
|
|
|
|
/// A line of source code.
|
2022-04-28 22:43:31 -04:00
|
|
|
SourceLine(SectionSourceLine),
|
2022-05-02 09:44:53 -04:00
|
|
|
|
|
|
|
/// Source line annotations
|
|
|
|
/// (marks and labels).
|
2022-04-28 22:43:31 -04:00
|
|
|
SourceLineMark(LineMark<'d>),
|
2022-05-02 09:44:53 -04:00
|
|
|
|
|
|
|
/// A label that is not rendered as a line annotation.
|
|
|
|
///
|
|
|
|
/// Footnotes are intended too appear at the end of a [`Section`] and
|
|
|
|
/// contain supplemental information.
|
2022-04-29 12:10:32 -04:00
|
|
|
Footnote(Level, Label<'d>),
|
2022-04-28 13:24:36 -04:00
|
|
|
}
|
|
|
|
|
2022-04-28 14:33:08 -04:00
|
|
|
impl<'d> SectionLine<'d> {
|
2022-04-28 23:37:07 -04:00
|
|
|
/// Format the gutter to the left of the section body for this line.
|
|
|
|
///
|
|
|
|
/// Note that the provided `text_width` _does not include_ a single
|
|
|
|
/// character of padding and a single-character delimiter that are
|
|
|
|
/// expected to follow.
|
|
|
|
/// The width is guaranteed to be at least as wide as the number of
|
|
|
|
/// characters needed to represent the line number in base-10.
|
2022-04-28 22:59:13 -04:00
|
|
|
///
|
|
|
|
/// For example:
|
|
|
|
///
|
|
|
|
/// ```text
|
|
|
|
/// |
|
2022-04-28 23:37:07 -04:00
|
|
|
/// 1 | source line
|
|
|
|
/// | ------
|
2022-04-28 22:59:13 -04:00
|
|
|
/// = note: notice the delim change for this footnote
|
2022-04-28 23:37:07 -04:00
|
|
|
/// ^^
|
|
|
|
/// gutter `text_width` of 2
|
2022-04-28 22:59:13 -04:00
|
|
|
/// ```
|
2022-04-28 23:37:07 -04:00
|
|
|
fn fmt_gutter(
|
|
|
|
&self,
|
|
|
|
text_width: usize,
|
|
|
|
f: &mut fmt::Formatter,
|
|
|
|
) -> fmt::Result {
|
2022-04-28 22:59:13 -04:00
|
|
|
match self {
|
2022-04-28 23:37:07 -04:00
|
|
|
Self::SourceLinePadding | Self::SourceLineMark(..) => {
|
|
|
|
write!(f, "{:text_width$} |", "")
|
|
|
|
}
|
|
|
|
Self::SourceLine(src) => write!(f, "{:>text_width$} |", src.num()),
|
|
|
|
Self::Footnote(..) => write!(f, "{:text_width$} =", ""),
|
2022-04-28 22:59:13 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-02 09:44:53 -04:00
|
|
|
/// Attempt to convert a line into a footnote.
|
|
|
|
///
|
|
|
|
/// If there is no [`Level`] and [`Label`] available,
|
|
|
|
/// [`None`] is returned.
|
2022-04-28 14:33:08 -04:00
|
|
|
fn into_footnote(self) -> Option<Self> {
|
|
|
|
match self {
|
2022-04-28 22:43:31 -04:00
|
|
|
Self::SourceLinePadding => None,
|
|
|
|
Self::SourceLine(..) => None,
|
|
|
|
Self::SourceLineMark(LineMark { level, label, .. }) => {
|
2022-04-29 12:10:32 -04:00
|
|
|
label.map(|l| Self::Footnote(level, l))
|
2022-04-28 22:43:31 -04:00
|
|
|
}
|
2022-04-28 14:33:08 -04:00
|
|
|
|
|
|
|
Self::Footnote(..) => Some(self),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-28 13:24:36 -04:00
|
|
|
impl<'d> Display for SectionLine<'d> {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
match self {
|
2022-04-28 22:59:13 -04:00
|
|
|
Self::SourceLinePadding => Ok(()),
|
|
|
|
Self::SourceLine(line) => line.fmt(f),
|
|
|
|
Self::SourceLineMark(mark) => mark.fmt(f),
|
2022-04-29 12:10:32 -04:00
|
|
|
Self::Footnote(level, label) => write!(f, " {level}: {label}"),
|
2022-04-28 13:24:36 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-02 09:44:53 -04:00
|
|
|
/// A [`SourceLine`] displayed within a [`Section`].
|
2022-04-28 13:24:36 -04:00
|
|
|
#[derive(Debug, PartialEq, Eq)]
|
2022-04-28 22:43:31 -04:00
|
|
|
struct SectionSourceLine(SourceLine);
|
|
|
|
|
2022-04-28 23:37:07 -04:00
|
|
|
impl SectionSourceLine {
|
|
|
|
fn num(&self) -> NonZeroU32 {
|
|
|
|
self.0.num()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-28 22:43:31 -04:00
|
|
|
impl From<SourceLine> for SectionSourceLine {
|
|
|
|
fn from(line: SourceLine) -> Self {
|
|
|
|
Self(line)
|
|
|
|
}
|
2022-04-28 13:24:36 -04:00
|
|
|
}
|
|
|
|
|
2022-04-28 22:43:31 -04:00
|
|
|
impl Display for SectionSourceLine {
|
2022-04-28 13:24:36 -04:00
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
2022-04-28 22:43:31 -04:00
|
|
|
write!(f, " {line}", line = self.0)
|
2022-04-28 13:24:36 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// A type of line annotation that marks columns and provides labels,
|
|
|
|
/// if available.
|
2022-05-02 09:44:53 -04:00
|
|
|
///
|
|
|
|
/// Marks are displayed below a [`SectionSourceLine`] and are intended to
|
|
|
|
/// visually display a [`Span`].
|
|
|
|
/// Column resolution
|
2022-05-02 09:49:22 -04:00
|
|
|
/// (see [`super::resolve`])
|
2022-05-02 09:44:53 -04:00
|
|
|
/// exists primarily for mark rendering.
|
2022-04-28 13:24:36 -04:00
|
|
|
#[derive(Debug, PartialEq, Eq)]
|
|
|
|
struct LineMark<'d> {
|
|
|
|
level: Level,
|
2022-04-28 22:13:51 -04:00
|
|
|
col: Column,
|
2022-04-28 13:24:36 -04:00
|
|
|
label: Option<Label<'d>>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'d> Display for LineMark<'d> {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
2022-04-28 22:13:51 -04:00
|
|
|
let Self { col, level, .. } = self;
|
2022-04-28 15:40:41 -04:00
|
|
|
|
2022-04-28 22:13:51 -04:00
|
|
|
let underline = level
|
|
|
|
.mark_char()
|
|
|
|
.to_string()
|
|
|
|
.repeat((col.end().get() - col.start().get()) as usize + 1);
|
2022-04-28 15:21:04 -04:00
|
|
|
|
2022-04-28 22:13:51 -04:00
|
|
|
let lpad = col.start().get() as usize - 1;
|
2022-04-28 16:18:16 -04:00
|
|
|
|
2022-04-28 22:13:51 -04:00
|
|
|
write!(f, " {:lpad$}{underline}", "")?;
|
2022-04-28 15:21:04 -04:00
|
|
|
|
2022-04-28 13:24:36 -04:00
|
|
|
if let Some(label) = self.label.as_ref() {
|
2022-04-28 16:18:16 -04:00
|
|
|
// TODO: If the span is at the end of a long line,
|
|
|
|
// this is more likely to wrap on the user's terminal and be
|
|
|
|
// unpleasant to read.
|
|
|
|
write!(f, " {level}: {label}", level = self.level)?;
|
2022-04-28 13:24:36 -04:00
|
|
|
}
|
|
|
|
|
2022-04-28 22:43:31 -04:00
|
|
|
Ok(())
|
2022-04-28 13:24:36 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-02 09:44:53 -04:00
|
|
|
/// Mark styling.
|
2022-04-28 15:40:41 -04:00
|
|
|
trait MarkChar {
|
2022-05-02 09:44:53 -04:00
|
|
|
/// Character used to underline the columns applicable to a given span
|
|
|
|
/// underneath a source line.
|
2022-04-28 15:40:41 -04:00
|
|
|
fn mark_char(&self) -> char;
|
|
|
|
}
|
|
|
|
|
|
|
|
impl MarkChar for Level {
|
|
|
|
fn mark_char(&self) -> char {
|
|
|
|
match self {
|
|
|
|
Level::InternalError => '!',
|
|
|
|
Level::Error => '^',
|
|
|
|
Level::Note | Level::Help => '-',
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
tamer: diagnose: Introduction of diagnostic system
This is a working concept that will continue to evolve. I wanted to start
with some basic output before getting too carried away, since there's a lot
of potential here.
This is heavily influenced by Rust's helpful diagnostic messages, but will
take some time to realize a lot of the things that Rust does. The next step
will be to resolve line and column numbers, and then possibly include
snippets and underline spans, placing the labels alongside them. I need to
balance this work with everything else I have going on.
This is a large commit, but it converts the existing Error Display impls
into Diagnostic. This separation is a bit verbose, so I'll see how this
ends up evolving.
Diagnostics are tied to Error at the moment, but I imagine in the future
that any object would be able to describe itself, error or not, which would
be useful in the future both for the Summary Page and for query
functionality, to help developers understand the systems they are writing
using TAME.
Output is integrated into tameld only in this commit; I'll add tamec
next. Examples of what this outputs are available in the test cases in this
commit.
DEV-10935
2022-04-13 14:41:54 -04:00
|
|
|
#[cfg(test)]
|
2022-04-29 09:19:53 -04:00
|
|
|
mod test;
|