Merge pull request #241 from brendanzab/single-line-label-rendering

Improve single line label rendering
This commit is contained in:
Brendan Zabarauskas
2020-05-18 15:49:50 +10:00
committed by GitHub
19 changed files with 449 additions and 236 deletions
+11 -30
View File
@@ -32,16 +32,14 @@ impl Default for Config {
}
impl Config {
/// Measure the width of a string, taking into account the tab width.
pub fn width(&self, s: &str) -> usize {
/// Measure the unicode width of a character, taking into account the tab width.
pub fn width(&self, ch: char) -> usize {
use unicode_width::UnicodeWidthChar;
s.chars()
.map(|ch| match ch {
'\t' => self.tab_width,
_ => ch.width().unwrap_or(0),
})
.sum()
match ch {
'\t' => self.tab_width,
_ => ch.width().unwrap_or(0),
}
}
/// Construct a source writer using the current config.
@@ -279,29 +277,10 @@ pub struct Chars {
/// The character to use for the left of a multi-line label.
/// Defaults to: `'│'`.
pub multi_left: char,
}
impl Chars {
pub fn single_caret_char(&self, label_style: LabelStyle) -> char {
match label_style {
LabelStyle::Primary => self.single_primary_caret,
LabelStyle::Secondary => self.single_secondary_caret,
}
}
pub fn multi_caret_char_start(&self, label_style: LabelStyle) -> char {
match label_style {
LabelStyle::Primary => self.multi_primary_caret_start,
LabelStyle::Secondary => self.multi_secondary_caret_start,
}
}
pub fn multi_caret_char_end(&self, label_style: LabelStyle) -> char {
match label_style {
LabelStyle::Primary => self.multi_primary_caret_end,
LabelStyle::Secondary => self.multi_secondary_caret_end,
}
}
/// The character to use for the left of a pointer underneath a caret.
/// Defaults to: `'│'`.
pub pointer_left: char,
}
impl Default for Chars {
@@ -326,6 +305,8 @@ impl Default for Chars {
multi_bottom_left: '',
multi_bottom: '',
multi_left: '',
pointer_left: '',
}
}
}
+308 -146
View File
@@ -84,8 +84,10 @@ type Underline = (LabelStyle, VerticalBound);
/// snippet break ── │ · │
/// snippet line ── │ 38 │ │ Brownie lemon drops chocolate jelly-o candy canes. Danish marzipan
/// snippet line ── │ 39 │ │ jujubes soufflé carrot cake marshmallow tiramisu caramels candy canes.
/// │ │ │ ^^^^^^^^^^^^^^^^^^ blah blah
/// │ │ │ -------------------- blah blah
/// │ │ │ ^^^^^^^^^^^^^^^^^^^ -------------------- blah blah
/// │ │ │
/// │ │ │ blah blah
/// │ │ │ note: this is a note
/// snippet line ── │ 40 │ │ Fruitcake jelly-o danish toffee. Tootsie roll pastry cheesecake
/// snippet line ── │ 41 │ │ soufflé marzipan. Chocolate bar oat cake jujubes lollipop pastry
/// snippet line ── │ 42 │ │ cupcake. Candy canes cupcake toffee gingerbread candy canes muffin
@@ -229,6 +231,10 @@ impl<'writer, 'config> Renderer<'writer, 'config> {
num_multi_labels: usize,
multi_labels: &[(usize, MultiLabel<'_>)],
) -> io::Result<()> {
// Trim trailing newlines, linefeeds, and null chars from source, if they exist.
// FIXME: Use the number of trimmed placeholders when rendering single line carets
let source = source.trim_end_matches(['\n', '\r', '\0'].as_ref());
// Write source line
//
// ```text
@@ -260,20 +266,223 @@ impl<'writer, 'config> Renderer<'writer, 'config> {
}
// Write source
write!(self.config.source(self.writer), " {}", source.trim_end())?;
write!(self.config.source(self.writer), " {}", source)?;
write!(self, "\n")?;
}
// Write single labels underneath source
//
// ```text
// │ ^^^^ oh noes
// │ - ---- ^^^ second mutable borrow occurs here
// │ │ │
// │ │ first mutable borrow occurs here
// │ first borrow later used by call
// │ help: some help here
// ```
for (label_style, range, message) in single_labels.iter() {
if !single_labels.is_empty() {
// Our plan is as follows:
//
// 1. Do an initial scan to find:
// - The number of non-empty messages.
// - The right-most start and end positions of labels.
// - A candidate for a trailing label (where the label's message
// is printed to the left of the caret).
// 2. Check if the trailing label candidate overlaps another label -
// if so we print it underneath the carets with the other labels.
// 3. Print a line of carets, and (possibly) the trailing message
// to the left.
// 4. Print vertical lines pointing to the carets, and the messages
// for those carets.
//
// We try our best avoid introducing new dynamic allocations,
// instead preferring to iterate over the labels multiple times. It
// is unclear what the performance tradeoffs are however, so further
// investigation may be required.
// The number of non-empty messages to print.
let mut num_messages = 0;
// The right-most start position, eg:
//
// ```text
// -^^^^---- ^^^^^^^
// │
// right-most start position
// ```
let mut max_label_start = 0;
// The right-most end position, eg:
//
// ```text
// -^^^^---- ^^^^^^^
// │
// right-most end position
// ```
let mut max_label_end = 0;
// A trailing message, eg:
//
// ```text
// ^^^ second mutable borrow occurs here
// ```
let mut trailing_label = None;
for (label_index, label) in single_labels.iter().enumerate() {
let (_, range, message) = label;
if !message.is_empty() {
num_messages += 1;
}
max_label_start = std::cmp::max(max_label_start, range.start);
max_label_end = std::cmp::max(max_label_end, range.end);
// This is a candidate for the trailing label, so let's record it.
if range.end == max_label_end {
if message.is_empty() {
trailing_label = None;
} else {
trailing_label = Some((label_index, label));
}
}
}
if let Some((trailing_label_index, (_, trailing_range, _))) = trailing_label {
// Check to see if the trailing label candidate overlaps any of
// the other labels on the current line.
if single_labels
.iter()
.enumerate()
.filter(|(label_index, _)| *label_index != trailing_label_index)
.any(|(_, (_, range, _))| is_overlapping(trailing_range, range))
{
// If it does, we'll instead want to render it below the
// carets along with the other hanging labels.
trailing_label = None;
}
}
// Write a line of carets
//
// ```text
// │ ^^^^^^ -------^^^^^^^^^-------^^^^^----- ^^^^ trailing label message
// ```
self.outer_gutter(outer_padding)?;
self.border_left()?;
self.inner_gutter(severity, num_multi_labels, multi_labels)?;
self.label_single(severity, *label_style, source, range.clone(), message)?;
write!(self, " ")?;
let mut previous_label_style = None;
for (byte_index, ch) in source
.char_indices()
// Add a placeholder source column at the end to allow for
// printing carets at the end of lines, eg:
//
// ```text
// 1 │ Hello world!
// │ ^
// ```
//
// ' ' is used because its unicode width is equal to 1 according
// to `unicode_width::UnicodeWidthChar`.
.chain(std::iter::once((source.len(), ' ')))
{
// Find the current label style at this column
let column_range = byte_index..(byte_index + ch.len_utf8());
let current_label_style = single_labels
.iter()
.filter(|(_, range, _)| is_overlapping(range, &column_range))
.map(|(label_style, _, _)| *label_style)
.max_by_key(label_priority_key);
// Update writer style if necessary
if previous_label_style != current_label_style {
match current_label_style {
None => self.reset()?,
Some(label_style) => {
self.set_color(self.styles().label(severity, label_style))?;
}
}
}
let caret_ch = match current_label_style {
Some(LabelStyle::Primary) => Some(self.chars().single_primary_caret),
Some(LabelStyle::Secondary) => Some(self.chars().single_secondary_caret),
// Only print padding if we are before the end of the last single line caret
None if byte_index < max_label_end => Some(' '),
None => None,
};
if let Some(caret_ch) = caret_ch {
// FIXME: improve rendering for carets that occurring within character boundaries
for _ in 0..self.config.width(ch) {
write!(self, "{}", caret_ch)?;
}
}
previous_label_style = current_label_style;
}
// Reset style if it was previously set
if previous_label_style.is_some() {
self.reset()?;
}
// Write first trailing label message
if let Some((_, (label_style, _, message))) = trailing_label {
write!(self, " ")?;
self.set_color(self.styles().label(severity, *label_style))?;
write!(self, "{}", message)?;
self.reset()?;
}
write!(self, "\n")?;
// Write hanging labels pointing to carets
//
// ```text
// │ │ │
// │ │ first mutable borrow occurs here
// │ first borrow later used by call
// │ help: some help here
// ```
if num_messages > trailing_label.iter().count() {
// Write first set of vertical lines before hanging labels
//
// ```text
// │ │ │
// ```
self.outer_gutter(outer_padding)?;
self.border_left()?;
self.inner_gutter(severity, num_multi_labels, multi_labels)?;
write!(self, " ")?;
self.caret_pointers(
severity,
max_label_start,
single_labels,
trailing_label,
source.char_indices(),
)?;
write!(self, "\n")?;
// Write hanging labels pointing to carets
//
// ```text
// │ │ first mutable borrow occurs here
// │ first borrow later used by call
// │ help: some help here
// ```
for (label_style, range, message) in
hanging_labels(single_labels, trailing_label).rev()
{
self.outer_gutter(outer_padding)?;
self.border_left()?;
self.inner_gutter(severity, num_multi_labels, multi_labels)?;
write!(self, " ")?;
self.caret_pointers(
severity,
max_label_start,
single_labels,
trailing_label,
source
.char_indices()
.take_while(|(byte_index, _)| *byte_index < range.start),
)?;
self.set_color(self.styles().label(severity, *label_style))?;
write!(self, "{}", message)?;
self.reset()?;
write!(self, "\n")?;
}
}
}
// Write top or bottom label carets underneath source
@@ -454,34 +663,39 @@ impl<'writer, 'config> Renderer<'writer, 'config> {
Ok(())
}
/// Single-line label with a message.
///
/// ```text
/// ^^ expected `Int` but found `String`
/// ```
fn label_single(
/// Write vertical lines pointing to carets.
fn caret_pointers(
&mut self,
severity: Severity,
label_style: LabelStyle,
source: &str,
range: Range<usize>,
message: &str,
max_label_start: usize,
single_labels: &[SingleLabel<'_>],
trailing_label: Option<(usize, &SingleLabel<'_>)>,
char_indices: impl Iterator<Item = (usize, char)>,
) -> io::Result<()> {
let space_source = slice_at_char_boundaries(source, 0..range.start);
let space_len = self.config.width(space_source);
write!(self, " {space: >width$}", space = "", width = space_len)?;
self.set_color(self.styles().label(severity, label_style))?;
let source = slice_at_char_boundaries(source, range);
// We use `usize::max` here to ensure that we print at least one
// label character - even when we have a zero-length span.
for _ in 0..usize::max(self.config.width(source), 1) {
write!(self, "{}", self.chars().single_caret_char(label_style))?;
for (byte_index, ch) in char_indices {
let column_range = byte_index..(byte_index + ch.len_utf8());
let label_style = hanging_labels(single_labels, trailing_label)
.filter(|(_, range, _)| column_range.contains(&range.start))
.map(|(label_style, _, _)| *label_style)
.max_by_key(label_priority_key);
let spaces = match label_style {
None => 0..self.config.width(ch),
Some(label_style) => {
self.set_color(self.styles().label(severity, label_style))?;
write!(self, "{}", self.chars().pointer_left)?;
self.reset()?;
1..self.config.width(ch)
}
};
// Only print padding if we are before the end of the last single line caret
if byte_index <= max_label_start {
for _ in spaces {
write!(self, " ")?;
}
}
}
if !message.is_empty() {
write!(self, " {}", message)?;
}
self.reset()?;
write!(self, "\n")?;
Ok(())
}
@@ -511,36 +725,6 @@ impl<'writer, 'config> Renderer<'writer, 'config> {
Ok(())
}
/// The top of a multi-line label.
fn label_multi_top_line(
&mut self,
severity: Severity,
label_style: LabelStyle,
len: usize,
) -> io::Result<()> {
self.set_color(self.styles().label(severity, label_style))?;
for _ in 0..len {
write!(self, "{}", self.config.chars.multi_top)?;
}
self.reset()?;
Ok(())
}
/// The top of a multi-line label.
fn label_multi_bottom_line(
&mut self,
severity: Severity,
label_style: LabelStyle,
len: usize,
) -> io::Result<()> {
self.set_color(self.styles().label(severity, label_style))?;
for _ in 0..len {
write!(self, "{}", self.config.chars.multi_bottom)?;
}
self.reset()?;
Ok(())
}
/// The top-left of a multi-line label.
///
/// ```text
@@ -588,11 +772,22 @@ impl<'writer, 'config> Renderer<'writer, 'config> {
range: RangeTo<usize>,
) -> io::Result<()> {
self.set_color(self.styles().label(severity, label_style))?;
let source = slice_at_char_boundaries(source, 0..range.end);
for _ in 0..(self.config.width(source) + 1) {
write!(self, "{}", self.chars().multi_top)?;
for (_, ch) in source
.char_indices()
.take_while(|(byte_index, _)| *byte_index < range.end + 1)
{
// FIXME: improve rendering for carets that occurring within character boundaries
for _ in 0..self.config.width(ch) {
write!(self, "{}", self.chars().multi_top)?;
}
}
write!(self, "{}", self.chars().multi_caret_char_start(label_style))?;
let caret_start = match label_style {
LabelStyle::Primary => self.config.chars.multi_primary_caret_start,
LabelStyle::Secondary => self.config.chars.multi_secondary_caret_start,
};
write!(self, "{}", caret_start)?;
self.reset()?;
write!(self, "\n")?;
Ok(())
@@ -612,11 +807,22 @@ impl<'writer, 'config> Renderer<'writer, 'config> {
message: &str,
) -> io::Result<()> {
self.set_color(self.styles().label(severity, label_style))?;
let source = slice_at_char_boundaries(source, 0..range.end);
for _ in 0..self.config.width(source) {
write!(self, "{}", self.chars().multi_bottom)?;
for (_, ch) in source
.char_indices()
.take_while(|(byte_index, _)| *byte_index < range.end)
{
// FIXME: improve rendering for carets that occurring within character boundaries
for _ in 0..self.config.width(ch) {
write!(self, "{}", self.chars().multi_bottom)?;
}
}
write!(self, "{}", self.chars().multi_caret_char_end(label_style))?;
let caret_end = match label_style {
LabelStyle::Primary => self.config.chars.multi_primary_caret_start,
LabelStyle::Secondary => self.config.chars.multi_secondary_caret_start,
};
write!(self, "{}", caret_end)?;
if !message.is_empty() {
write!(self, " {}", message)?;
}
@@ -633,8 +839,16 @@ impl<'writer, 'config> Renderer<'writer, 'config> {
) -> io::Result<()> {
match underline {
None => self.inner_gutter_space(),
Some((ls, VerticalBound::Top)) => self.label_multi_top_line(severity, ls, 2),
Some((ls, VerticalBound::Bottom)) => self.label_multi_bottom_line(severity, ls, 2),
Some((label_style, vertical_bound)) => {
self.set_color(self.styles().label(severity, label_style))?;
let ch = match vertical_bound {
VerticalBound::Top => self.config.chars.multi_top,
VerticalBound::Bottom => self.config.chars.multi_bottom,
};
write!(self, "{0}{0}", ch)?;
self.reset()?;
Ok(())
}
}
}
@@ -701,83 +915,31 @@ impl<'writer, 'config> WriteColor for Renderer<'writer, 'config> {
}
}
/// Searches for character boundary from byte_index towards the end of the string.
fn closest_char_boundary(s: &str, byte_index: usize) -> usize {
let length = s.len();
for index in byte_index..=length {
if s.is_char_boundary(index) {
return index;
}
}
length
/// Check if two ranges overlap
fn is_overlapping(range0: &Range<usize>, range1: &Range<usize>) -> bool {
let start = std::cmp::max(range0.start, range1.start);
let end = std::cmp::min(range0.end, range1.end);
start < end
}
/// Searches for character boundary from byte_index towards the start of the string.
fn closest_char_boundary_rev(s: &str, byte_index: usize) -> usize {
for index in (0..=byte_index).rev() {
if s.is_char_boundary(index) {
return index;
}
}
0
}
/// Finds a valid unicode boundaries looking from `range.start` towards the beginning of the string.
/// From `range.end` towards the end of the string. Returning a `&str` of all characters
/// that overlapping the range.
fn slice_at_char_boundaries<'a>(s: &'a str, range: Range<usize>) -> &'a str {
let start = closest_char_boundary_rev(s, range.start);
let end = closest_char_boundary(s, range.end);
&s[start..end]
}
#[cfg(test)]
mod test {
use super::*;
use std::iter::repeat;
#[test]
fn test_boundary() {
let s = "🌞";
assert_eq!(closest_char_boundary(s, 0), 0);
assert_eq!(closest_char_boundary_rev(s, 0), 0);
for i in 1..s.len() {
assert_eq!(closest_char_boundary_rev(s, i), 0);
assert_eq!(closest_char_boundary(s, i), s.len());
}
}
#[test]
fn test_boundaries() {
let s = "🌑🌒🌓🌔";
let individually = ["🌑", "🌒", "🌓", "🌔"];
let mut expect = Vec::new();
// [(0, 0, "", ""),
// (0, 4, "🌑", "🌑"), repeated 4 times,
// (4, 4, "", ""), once
// (4, 8, "🌒", "🌒"), repeated 4 times, and so on (+4, +4)
// ...
// (16, 16, "", ""); 21]
expect.push((0, 0, "", ""));
for (idx, &char) in individually.iter().enumerate() {
let n = char.len();
assert_eq!(n, 4);
let expected_start = (idx % n) * n;
let expected_end = (idx % n) * n + n;
expect.extend(repeat((expected_start, expected_end, char, char)).take(n - 1));
expect.push((expected_end, expected_end, "", ""));
}
// drop mut.
let expect = expect;
let mut found = Vec::new();
for i in 0..=s.len() {
let sliced = slice_at_char_boundaries(s, i..i);
let prev = closest_char_boundary_rev(s, i);
let next = closest_char_boundary(s, i);
found.push((prev, next, &s[prev..next], sliced));
}
assert_eq!(found, expect);
/// For prioritizing primary labels over secondary labels when rendering carets.
fn label_priority_key(label_style: &LabelStyle) -> u8 {
match label_style {
LabelStyle::Secondary => 0,
LabelStyle::Primary => 1,
}
}
/// Return an iterator that yields the labels that require hanging messages
/// rendered underneath them.
fn hanging_labels<'labels, 'diagnostic>(
single_labels: &'labels [SingleLabel<'diagnostic>],
trailing_label: Option<(usize, &'labels SingleLabel<'diagnostic>)>,
) -> impl 'labels + DoubleEndedIterator<Item = &'labels SingleLabel<'diagnostic>> {
single_labels
.iter()
.enumerate()
.filter(|(_, (_, _, message))| !message.is_empty())
.filter(move |(i, _)| trailing_label.map_or(true, |(j, _)| *i != j))
.map(|(_, label)| label)
}
+12 -7
View File
@@ -143,14 +143,21 @@ where
// to piggyback off its lexicographic comparison implementation.
(range.start, range.end).cmp(&(label_start, label_end))
}) {
// If the ranges are the same, order the labels as they
// originally were specified in the diagnostic.
Ok(index) => index + 1,
Err(index) => index,
// If the ranges are the same, order the labels in reverse
// to how they were originally specified in the diagnostic.
// This helps with printing in the renderer.
Ok(index) | Err(index) => index,
};
// Ensure that we print at least one caret, even when we
// have a zero-length source range.
let mut label_range = label_start..label_end;
if label_range.len() == 0 {
label_range.end = label_range.start + 1;
}
line.single_labels
.insert(index, (label.style, label_start..label_end, &label.message));
.insert(index, (label.style, label_range, &label.message));
} else {
// Multiple lines
//
@@ -231,8 +238,6 @@ where
}
}
// TODO: Insert `None` spaces in `labeled_files`
// Header and message
//
// ```text
@@ -6,18 +6,24 @@ expression: TEST_DATA.emit_color(&config)
{fg:Blue}┌─{/} hello:1:7
{fg:Blue}│{/}
{fg:Blue}1{/} {fg:Blue}│{/} Hello world!
{fg:Blue}│{/} {fg:Green}^ middle{/}
{fg:Blue}│{/} {fg:Green}^{/} {fg:Green}middle{/}
{fg:Green bold bright}note{bold bright}: end of line{/}
{fg:Blue}┌─{/} hello:1:13
{fg:Blue}│{/}
{fg:Blue}1{/} {fg:Blue}│{/} Hello world!
{fg:Blue}│{/} {fg:Green}^ end of line{/}
{fg:Blue}│{/} {fg:Green}^{/} {fg:Green}end of line{/}
{fg:Green bold bright}note{bold bright}: end of file{/}
{fg:Green bold bright}note{bold bright}: end of line{/}
{fg:Blue}┌─{/} hello:2:11
{fg:Blue}│{/}
{fg:Blue}2{/} {fg:Blue}│{/} Bye world!
{fg:Blue}│{/} {fg:Green}^ end of file{/}
{fg:Blue}│{/} {fg:Green}^{/} {fg:Green}end of line{/}
{fg:Green bold bright}note{bold bright}: end of file{/}
{fg:Blue}┌─{/} hello:3:4
{fg:Blue}│{/}
{fg:Blue}3{/} {fg:Blue}│{/}
{fg:Blue}│{/} {fg:Green}^{/} {fg:Green}end of file{/}
@@ -14,10 +14,16 @@ note: end of line
1 │ Hello world!
│ ^ end of line
note: end of file
note: end of line
┌─ hello:2:11
2 │ Bye world!
│ ^ end of file
│ ^ end of line
note: end of file
┌─ hello:3:4
3 │
│ ^ end of file
@@ -4,5 +4,6 @@ expression: TEST_DATA.emit_color(&config)
---
hello:1:7: {fg:Green bold bright}note{bold bright}: middle{/}
hello:1:13: {fg:Green bold bright}note{bold bright}: end of line{/}
hello:2:11: {fg:Green bold bright}note{bold bright}: end of file{/}
hello:2:11: {fg:Green bold bright}note{bold bright}: end of line{/}
hello:3:4: {fg:Green bold bright}note{bold bright}: end of file{/}
@@ -4,5 +4,6 @@ expression: TEST_DATA.emit_no_color(&config)
---
hello:1:7: note: middle
hello:1:13: note: end of line
hello:2:11: note: end of file
hello:2:11: note: end of line
hello:3:4: note: end of file
@@ -6,14 +6,14 @@ expression: TEST_DATA.emit_color(&config)
{fg:Blue}┌─{/} FizzBuzz.fun:3:15
{fg:Blue}│{/}
{fg:Blue}3{/} {fg:Blue}│{/} fizz₁ : Nat → String
{fg:Blue}│{/} {fg:Blue}------ expected type `String` found here{/}
{fg:Blue}│{/} {fg:Blue}------{/} {fg:Blue}expected type `String` found here{/}
{fg:Blue}4{/} {fg:Blue}│{/} fizz₁ num = case (mod num 5) (mod num 3) of
{fg:Blue}│{/} {fg:Blue}╭{/}{fg:Blue}─────────────'{/}
{fg:Blue}5{/} {fg:Blue}│{/} {fg:Blue}│{/} 0 0 => "FizzBuzz"
{fg:Blue}6{/} {fg:Blue}│{/} {fg:Blue}│{/} 0 _ => "Fizz"
{fg:Blue}7{/} {fg:Blue}│{/} {fg:Blue}│{/} _ 0 => "Buzz"
{fg:Blue}8{/} {fg:Blue}│{/} {fg:Blue}│{/} _ _ => num
{fg:Blue}│{/} {fg:Blue}│{/} {fg:Red}^^^ expected `String`, found `Nat`{/}
{fg:Blue}│{/} {fg:Blue}│{/} {fg:Red}^^^{/} {fg:Red}expected `String`, found `Nat`{/}
{fg:Blue}│{/} {fg:Blue}╰{/}{fg:Blue}──────────────' `case` clauses have incompatible types{/}
{fg:Blue}│{/}
{fg:Blue}={/} expected type `String`
@@ -23,17 +23,17 @@ expression: TEST_DATA.emit_color(&config)
{fg:Blue}┌─{/} FizzBuzz.fun:10:15
{fg:Blue}│{/}
{fg:Blue}10{/} {fg:Blue}│{/} fizz₂ : Nat → String
{fg:Blue}│{/} {fg:Blue}------ expected type `String` found here{/}
{fg:Blue}│{/} {fg:Blue}------{/} {fg:Blue}expected type `String` found here{/}
{fg:Blue}11{/} {fg:Blue}│{/} fizz₂ num =
{fg:Blue}12{/} {fg:Blue}│{/} {fg:Blue}╭{/} case (mod num 5) (mod num 3) of
{fg:Blue}13{/} {fg:Blue}│{/} {fg:Blue}│{/} 0 0 => "FizzBuzz"
{fg:Blue}│{/} {fg:Blue}│{/} {fg:Blue}---------- this is found to be of type `String`{/}
{fg:Blue}│{/} {fg:Blue}│{/} {fg:Blue}----------{/} {fg:Blue}this is found to be of type `String`{/}
{fg:Blue}14{/} {fg:Blue}│{/} {fg:Blue}│{/} 0 _ => "Fizz"
{fg:Blue}│{/} {fg:Blue}│{/} {fg:Blue}------ this is found to be of type `String`{/}
{fg:Blue}│{/} {fg:Blue}│{/} {fg:Blue}------{/} {fg:Blue}this is found to be of type `String`{/}
{fg:Blue}15{/} {fg:Blue}│{/} {fg:Blue}│{/} _ 0 => "Buzz"
{fg:Blue}│{/} {fg:Blue}│{/} {fg:Blue}------ this is found to be of type `String`{/}
{fg:Blue}│{/} {fg:Blue}│{/} {fg:Blue}------{/} {fg:Blue}this is found to be of type `String`{/}
{fg:Blue}16{/} {fg:Blue}│{/} {fg:Blue}│{/} _ _ => num
{fg:Blue}│{/} {fg:Blue}│{/} {fg:Red}^^^ expected `String`, found `Nat`{/}
{fg:Blue}│{/} {fg:Blue}│{/} {fg:Red}^^^{/} {fg:Red}expected `String`, found `Nat`{/}
{fg:Blue}│{/} {fg:Blue}╰{/}{fg:Blue}──────────────────' `case` clauses have incompatible types{/}
{fg:Blue}│{/}
{fg:Blue}={/} expected type `String`
@@ -6,7 +6,7 @@ expression: TEST_DATA.emit_color(&config)
{fg:Blue}┌─{/} Data/Nat.fun:7:13
{fg:Blue}│{/}
{fg:Blue}7{/} {fg:Blue}│{/} {-# BUILTIN NATRAL Nat #-}
{fg:Blue}│{/} {fg:Red}^^^^^^ unknown builtin{/}
{fg:Blue}│{/} {fg:Red}^^^^^^{/} {fg:Red}unknown builtin{/}
{fg:Blue}│{/}
{fg:Blue}={/} there is a builtin with a similar name: `NATURAL`
@@ -14,7 +14,7 @@ expression: TEST_DATA.emit_color(&config)
{fg:Blue}┌─{/} Data/Nat.fun:17:16
{fg:Blue}│{/}
{fg:Blue}17{/} {fg:Blue}│{/} zero - succ n₂ = zero
{fg:Blue}│{/} {fg:Yellow}^^ unused parameter{/}
{fg:Blue}│{/} {fg:Yellow}^^{/} {fg:Yellow}unused parameter{/}
{fg:Blue}│{/}
{fg:Blue}={/} consider using a wildcard pattern: `_`
@@ -22,12 +22,12 @@ expression: TEST_DATA.emit_color(&config)
{fg:Blue}┌─{/} Test.fun:4:11
{fg:Blue}│{/}
{fg:Blue} 4{/} {fg:Blue}│{/} _ = 123 + "hello"
{fg:Blue}│{/} {fg:Red}^^^^^^^ expected `Nat`, found `String`{/}
{fg:Blue}│{/} {fg:Red}^^^^^^^{/} {fg:Red}expected `Nat`, found `String`{/}
{fg:Blue}│{/}
{fg:Blue}┌─{/} Data/Nat.fun:11:1
{fg:Blue}│{/}
{fg:Blue}11{/} {fg:Blue}│{/} _+_ : Nat → Nat → Nat
{fg:Blue}│{/} {fg:Blue}--------------------- based on the definition of `_+_`{/}
{fg:Blue}│{/} {fg:Blue}---------------------{/} {fg:Blue}based on the definition of `_+_`{/}
{fg:Blue}│{/}
{fg:Blue}={/} expected type `Nat`
found type `String`
@@ -7,9 +7,9 @@ expression: TEST_DATA.emit_color(&config)
{fg:Blue}│{/}
{fg:Blue}1{/} {fg:Blue}│{/} {fg:Blue}╭{/} match line_index.compare(self.last_line_index()) {
{fg:Blue}2{/} {fg:Blue}│{/} {fg:Blue}│{/} Ordering::Less => Ok(self.line_starts()[line_index.to_usize()]),
{fg:Blue}│{/} {fg:Blue}│{/} {fg:Blue}--------------------------------------------- this is found to be of type `Result<ByteIndex, LineIndexOutOfBoundsError>`{/}
{fg:Blue}│{/} {fg:Blue}│{/} {fg:Blue}---------------------------------------------{/} {fg:Blue}this is found to be of type `Result<ByteIndex, LineIndexOutOfBoundsError>`{/}
{fg:Blue}3{/} {fg:Blue}│{/} {fg:Blue}│{/} Ordering::Equal => Ok(self.source_span().end()),
{fg:Blue}│{/} {fg:Blue}│{/} {fg:Blue}---------------------------- this is found to be of type `Result<ByteIndex, LineIndexOutOfBoundsError>`{/}
{fg:Blue}│{/} {fg:Blue}│{/} {fg:Blue}----------------------------{/} {fg:Blue}this is found to be of type `Result<ByteIndex, LineIndexOutOfBoundsError>`{/}
{fg:Blue}4{/} {fg:Blue}│{/} {fg:Blue}│{/} Ordering::Greater => LineIndexOutOfBoundsError {
{fg:Blue}│{/} {fg:Red}╭{/}{fg:Red}─{/}{fg:Blue}│{/}{fg:Red}──────────────────────────────────^{/}
{fg:Blue}5{/} {fg:Blue}│{/} {fg:Red}│{/} {fg:Blue}│{/} given: line_index,
@@ -6,29 +6,35 @@ expression: TEST_DATA.emit_color(&config)
{fg:Blue}┌─{/} nested_impl_trait.rs:5:46
{fg:Blue}│{/}
{fg:Blue}5{/} {fg:Blue}│{/} fn bad_in_ret_position(x: impl Into<u32>) -> impl Into<impl Debug> { x }
{fg:Blue}│{/} {fg:Blue}--------------------- outer `impl Trait`{/}
{fg:Blue}│{/} {fg:Red}^^^^^^^^^^ nested `impl Trait` here{/}
{fg:Blue}│{/} {fg:Blue}----------{fg:Red}^^^^^^^^^^{fg:Blue}-{/}
{fg:Blue}│{/} {fg:Blue}│{/} {fg:Red}{/}
{fg:Blue}│{/} {fg:Blue}│{/} {fg:Red}nested `impl Trait` here{/}
{fg:Blue}│{/} {fg:Blue}outer `impl Trait`{/}
{fg:Red bold bright}error[E0121]{bold bright}: the type placeholder `_` is not allowed within types on item signatures{/}
{fg:Blue}┌─{/} typeck_type_placeholder_item.rs:1:18
{fg:Blue}│{/}
{fg:Blue}1{/} {fg:Blue}│{/} fn fn_test1() -> _ { 5 }
{fg:Blue}│{/} {fg:Red}^ not allowed in type signatures{/}
{fg:Blue}│{/} {fg:Blue}- help: replace with the correct return type: `i32`{/}
{fg:Blue}│{/} {fg:Red}^{/}
{fg:Blue}│{/} {fg:Red}│{/}
{fg:Blue}│{/} {fg:Red}not allowed in type signatures{/}
{fg:Blue}│{/} {fg:Blue}help: replace with the correct return type: `i32`{/}
{fg:Red bold bright}error[E0121]{bold bright}: the type placeholder `_` is not allowed within types on item signatures{/}
{fg:Blue}┌─{/} typeck_type_placeholder_item.rs:2:24
{fg:Blue}│{/}
{fg:Blue}2{/} {fg:Blue}│{/} fn fn_test2(x: i32) -> (_, _) { (x, x) }
{fg:Blue}│{/} {fg:Blue}------ help: replace with the correct return type: `(i32, i32)`{/}
{fg:Blue}│{/} {fg:Red}^ not allowed in type signatures{/}
{fg:Blue}│{/} {fg:Red}^ not allowed in type signatures{/}
{fg:Blue}│{/} {fg:Blue}-{fg:Red}^{fg:Blue}--{fg:Red}^{fg:Blue}-{/}
{fg:Blue}│{/} {fg:Blue}│{/}{fg:Red}│{/} {fg:Red}│{/}
{fg:Blue}│{/} {fg:Blue}│{/}{fg:Red}│{/} {fg:Red}not allowed in type signatures{/}
{fg:Blue}│{/} {fg:Blue}│{/}{fg:Red}not allowed in type signatures{/}
{fg:Blue}│{/} {fg:Blue}help: replace with the correct return type: `(i32, i32)`{/}
{fg:Red bold bright}error[E0277]{bold bright}: `std::rc::Rc<()>` cannot be sent between threads safely{/}
{fg:Blue}┌─{/} no_send_res_ports.rs:25:5
{fg:Blue}│{/}
{fg:Blue}25{/} {fg:Blue}│{/} thread::spawn(move|| {
{fg:Blue}│{/} {fg:Red}^^^^^^^^^^^^^ `std::rc::Rc<()>` cannot be sent between threads safely{/}
{fg:Blue}│{/} {fg:Red}^^^^^^^^^^^^^{/} {fg:Red}`std::rc::Rc<()>` cannot be sent between threads safely{/}
{fg:Blue}│{/} {fg:Blue}╭{/}{fg:Blue}───────────────────'{/}
{fg:Blue}26{/} {fg:Blue}│{/} {fg:Blue}│{/} let y = x;
{fg:Blue}27{/} {fg:Blue}│{/} {fg:Blue}│{/} println!("{:?}", y);
@@ -38,7 +44,7 @@ expression: TEST_DATA.emit_color(&config)
{fg:Blue}┌─{/} libstd/thread/mod.rs:5:8
{fg:Blue}│{/}
{fg:Blue} 5{/} {fg:Blue}│{/} F: Send + 'static,
{fg:Blue}│{/} {fg:Blue}---- required by this bound in `std::thread::spawn`{/}
{fg:Blue}│{/} {fg:Blue}----{/} {fg:Blue}required by this bound in `std::thread::spawn`{/}
{fg:Blue}│{/}
{fg:Blue}={/} help: within `[closure@no_send_res_ports.rs:29:19: 33:6 x:main::Foo]`, the trait `std::marker::Send` is not implemented for `std::rc::Rc<()>`
{fg:Blue}={/} note: required because it appears within the type `Port<()>`
@@ -6,23 +6,29 @@ error[E0666]: nested `impl Trait` is not allowed
┌─ nested_impl_trait.rs:5:46
5 │ fn bad_in_ret_position(x: impl Into<u32>) -> impl Into<impl Debug> { x }
│ --------------------- outer `impl Trait`
^^^^^^^^^^ nested `impl Trait` here
│ ----------^^^^^^^^^^-
│ │ nested `impl Trait` here
│ outer `impl Trait`
error[E0121]: the type placeholder `_` is not allowed within types on item signatures
┌─ typeck_type_placeholder_item.rs:1:18
1 │ fn fn_test1() -> _ { 5 }
│ ^ not allowed in type signatures
- help: replace with the correct return type: `i32`
│ ^
│ not allowed in type signatures
│ help: replace with the correct return type: `i32`
error[E0121]: the type placeholder `_` is not allowed within types on item signatures
┌─ typeck_type_placeholder_item.rs:2:24
2 │ fn fn_test2(x: i32) -> (_, _) { (x, x) }
│ ------ help: replace with the correct return type: `(i32, i32)`
^ not allowed in type signatures
^ not allowed in type signatures
│ -^--^-
││ │
││ not allowed in type signatures
│ │not allowed in type signatures
│ help: replace with the correct return type: `(i32, i32)`
error[E0277]: `std::rc::Rc<()>` cannot be sent between threads safely
┌─ no_send_res_ports.rs:25:5
@@ -6,9 +6,10 @@ expression: TEST_DATA.emit_color(&config)
{fg:Blue}┌─{/} one_line.rs:3:5
{fg:Blue}│{/}
{fg:Blue}3{/} {fg:Blue}│{/} v.push(v.pop().unwrap());
{fg:Blue}│{/} {fg:Blue}- first borrow later used by call{/}
{fg:Blue}│{/} {fg:Blue}---- first mutable borrow occurs here{/}
{fg:Blue}│{/} {fg:Red}^ second mutable borrow occurs here{/}
{fg:Blue}│{/} {fg:Blue}-{/} {fg:Blue}----{/} {fg:Red}^{/} {fg:Red}second mutable borrow occurs here{/}
{fg:Blue}│{/} {fg:Blue}│{/} {fg:Blue}│{/}
{fg:Blue}│{/} {fg:Blue}│{/} {fg:Blue}first mutable borrow occurs here{/}
{fg:Blue}│{/} {fg:Blue}first borrow later used by call{/}
{fg:Red bold bright}error{bold bright}: aborting due to previous error{/}
{fg:Blue}={/} For more information about this error, try `rustc --explain E0499`.
@@ -6,9 +6,10 @@ error[E0499]: cannot borrow `v` as mutable more than once at a time
┌─ one_line.rs:3:5
3 │ v.push(v.pop().unwrap());
│ - first borrow later used by call
---- first mutable borrow occurs here
^ second mutable borrow occurs here
│ - ---- ^ second mutable borrow occurs here
│ │
│ first mutable borrow occurs here
│ first borrow later used by call
error: aborting due to previous error
= For more information about this error, try `rustc --explain E0499`.
@@ -6,7 +6,9 @@ expression: TEST_DATA.emit_color(&config)
{fg:Blue}┌─{/} same_range:1:5
{fg:Blue}│{/}
{fg:Blue}1{/} {fg:Blue}│{/} ::S { }
{fg:Blue}│{/} {fg:Red}^ Unexpected '{'{/}
{fg:Blue}│{/} {fg:Blue}- Expected '('{/}
{fg:Blue}│{/} {fg:Red}^{/}
{fg:Blue}│{/} {fg:Red}│{/}
{fg:Blue}│{/} {fg:Red}Unexpected '{'{/}
{fg:Blue}│{/} {fg:Blue}Expected '('{/}
@@ -6,7 +6,9 @@ error: Unexpected token
┌─ same_range:1:5
1 │ ::S { }
│ ^ Unexpected '{'
- Expected '('
│ ^
│ Unexpected '{'
│ Expected '('
@@ -6,9 +6,24 @@ error[E01]: cow may not jump during new moon.
┌─ moon_jump.rs:1:1
1 │ 🐄🌑🐄🌒🐄🌓🐄🌔🐄🌕🐄🌖🐄🌗🐄🌘🐄
^^ Invalid jump
│ -- Cow range does not start at boundary.
│ ------ Cow does not start or end at boundary.
│ ^^ Invalid jump
note: invalid unicode range
┌─ moon_jump.rs:1:1
1 │ 🐄🌑🐄🌒🐄🌓🐄🌔🐄🌕🐄🌖🐄🌗🐄🌘🐄
│ -- Cow range does not start at boundary.
note: invalid unicode range
┌─ moon_jump.rs:1:3
1 │ 🐄🌑🐄🌒🐄🌓🐄🌔🐄🌕🐄🌖🐄🌗🐄🌘🐄
│ -- Cow range does not end at boundary.
note: invalid unicode range
┌─ moon_jump.rs:1:1
1 │ 🐄🌑🐄🌒🐄🌓🐄🌔🐄🌕🐄🌖🐄🌗🐄🌘🐄
│ ------ Cow does not start or end at boundary.
@@ -3,4 +3,7 @@ source: codespan-reporting/tests/term.rs
expression: TEST_DATA.emit_no_color(&config)
---
moon_jump.rs:1:1: error[E01]: cow may not jump during new moon.
note: invalid unicode range
note: invalid unicode range
note: invalid unicode range
+18 -3
View File
@@ -330,7 +330,7 @@ mod empty_ranges {
lazy_static::lazy_static! {
static ref TEST_DATA: TestData<'static, SimpleFile<&'static str, &'static str>> = {
let file = SimpleFile::new("hello", "Hello world!\nBye world!");
let file = SimpleFile::new("hello", "Hello world!\nBye world!\n ");
let eof = file.source().len();
let diagnostics = vec![
@@ -340,6 +340,9 @@ mod empty_ranges {
Diagnostic::note()
.with_message("end of line")
.with_labels(vec![Label::primary((), 12..12).with_message("end of line")]),
Diagnostic::note()
.with_message("end of line")
.with_labels(vec![Label::primary((), 23..23).with_message("end of line")]),
Diagnostic::note()
.with_message("end of file")
.with_labels(vec![Label::primary((), eof..eof).with_message("end of file")]),
@@ -741,14 +744,26 @@ mod unicode_spans {
.with_labels(vec![
Label::primary((), invalid_start..invalid_end)
.with_message("Invalid jump"),
]),
Diagnostic::note()
.with_message("invalid unicode range")
.with_labels(vec![
Label::secondary((), invalid_start.."🐄".len())
.with_message("Cow range does not start at boundary."),
]),
Diagnostic::note()
.with_message("invalid unicode range")
.with_labels(vec![
Label::secondary((), "🐄🌑".len().."🐄🌑🐄".len() - 1)
.with_message("Cow range does not end at boundary."),
]),
Diagnostic::note()
.with_message("invalid unicode range")
.with_labels(vec![
Label::secondary((), invalid_start.."🐄🌑🐄".len() - 1)
.with_message("Cow does not start or end at boundary."),
])];
]),
];
TestData{files: file, diagnostics }
};
}