This commit is contained in:
Jana Dönszelmann 2026-04-02 08:13:29 +02:00
parent 9f401bda53
commit bb8fa818d2
No known key found for this signature in database
9 changed files with 1322 additions and 78 deletions

36
Cargo.lock generated
View file

@ -248,6 +248,17 @@ dependencies = [
"static_assertions", "static_assertions",
] ]
[[package]]
name = "console"
version = "0.16.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d64e8af5551369d19cf50138de61f1c42074ab970f74e99be916646777f8fc87"
dependencies = [
"encode_unicode",
"libc",
"windows-sys",
]
[[package]] [[package]]
name = "convert_case" name = "convert_case"
version = "0.10.0" version = "0.10.0"
@ -431,6 +442,12 @@ version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]]
name = "encode_unicode"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
[[package]] [[package]]
name = "equivalent" name = "equivalent"
version = "1.0.2" version = "1.0.2"
@ -634,6 +651,18 @@ dependencies = [
"rustversion", "rustversion",
] ]
[[package]]
name = "insta"
version = "1.47.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b4a6248eb93a4401ed2f37dfe8ea592d3cf05b7cf4f8efa867b6895af7e094e"
dependencies = [
"console",
"once_cell",
"similar",
"tempfile",
]
[[package]] [[package]]
name = "instability" name = "instability"
version = "0.3.11" version = "0.3.11"
@ -1387,6 +1416,7 @@ dependencies = [
"clap", "clap",
"crossterm", "crossterm",
"dumpster", "dumpster",
"insta",
"itertools", "itertools",
"jiff", "jiff",
"nix 0.31.1", "nix 0.31.1",
@ -1566,6 +1596,12 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "similar"
version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa"
[[package]] [[package]]
name = "siphasher" name = "siphasher"
version = "1.0.2" version = "1.0.2"

View file

@ -24,3 +24,4 @@ dumpster = "2.1"
winnow = {version="1", features=["parser"]} winnow = {version="1", features=["parser"]}
proptest = "1" proptest = "1"
proptest-derive = "0.8" proptest-derive = "0.8"
insta = "1"

View file

@ -5,3 +5,5 @@
# It is recommended to check this file in to source control so that # It is recommended to check this file in to source control so that
# everyone who runs the test benefits from these saved cases. # everyone who runs the test benefits from these saved cases.
cc 1dac24f74cdeb63f61d662876f276058bc71481f1df552aeea1293e22b682d59 # shrinks to original = "¡{}" cc 1dac24f74cdeb63f61d662876f276058bc71481f1df552aeea1293e22b682d59 # shrinks to original = "¡{}"
cc f7a17a233c11246ea8182505c41e30dc2a2f1c9020d5108f95403eb2de179fac # shrinks to original = ")"
cc 7bfbe4d3505dc0e94e5b87ae86e4b4554d9af477f4d0770161c2403ce39627f3 # shrinks to original = "!/\t"

View file

@ -5,6 +5,7 @@ use std::borrow::Cow;
pub enum Separator { pub enum Separator {
Eq, Eq,
Colon, Colon,
DoubleColon,
} }
#[derive(Copy, Clone, Debug, Arbitrary, PartialEq)] #[derive(Copy, Clone, Debug, Arbitrary, PartialEq)]
@ -36,6 +37,8 @@ pub struct Space<'a>(pub Cow<'a, str>);
#[derive(Copy, Clone, Debug, PartialEq, Arbitrary)] #[derive(Copy, Clone, Debug, PartialEq, Arbitrary)]
pub enum PathSep { pub enum PathSep {
/// Happens at the start of paths, for the no leading / case
None,
Slash, Slash,
Backslash, Backslash,
} }
@ -63,6 +66,7 @@ pub struct FileName<'a> {
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct Path<'a> { pub struct Path<'a> {
pub drive_excluding_colon: Option<char>, pub drive_excluding_colon: Option<char>,
pub segments: Vec<PathSegment<'a>>, pub segments: Vec<PathSegment<'a>>,
pub filename: FileName<'a>, pub filename: FileName<'a>,
} }
@ -101,7 +105,7 @@ pub enum Token<'a> {
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct Delimited<'a> { pub struct Delimited<'a> {
pub prefix: Atom<'a>, pub prefix: Option<Atom<'a>>,
pub delimiter: Delimiter, pub delimiter: Delimiter,
pub contents: Segments<'a>, pub contents: Segments<'a>,
} }

View file

@ -6,6 +6,7 @@ impl Display for Separator {
match self { match self {
Separator::Eq => write!(f, "="), Separator::Eq => write!(f, "="),
Separator::Colon => write!(f, ":"), Separator::Colon => write!(f, ":"),
Separator::DoubleColon => write!(f, "::"),
} }
} }
} }
@ -68,6 +69,7 @@ impl Display for PathSep {
match self { match self {
PathSep::Slash => write!(f, "/"), PathSep::Slash => write!(f, "/"),
PathSep::Backslash => write!(f, "\\"), PathSep::Backslash => write!(f, "\\"),
PathSep::None => Ok(()),
} }
} }
} }
@ -105,13 +107,20 @@ impl<'a> Display for FileName<'a> {
impl<'a> Display for Path<'a> { impl<'a> Display for Path<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(drive) = &self.drive_excluding_colon { let Self {
drive_excluding_colon,
segments,
filename,
} = self;
if let Some(drive) = &drive_excluding_colon {
write!(f, "{drive}:")?; write!(f, "{drive}:")?;
} }
for segment in &self.segments {
for segment in segments {
write!(f, "{segment}")?; write!(f, "{segment}")?;
} }
write!(f, "{}", self.filename) write!(f, "{filename}")
} }
} }
@ -152,7 +161,9 @@ impl<'a> Display for Token<'a> {
impl<'a> Display for Delimited<'a> { impl<'a> Display for Delimited<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.prefix)?; if let Some(prefix) = &self.prefix {
write!(f, "{prefix}")?;
}
self.delimiter.fmt_start(f)?; self.delimiter.fmt_start(f)?;
write!(f, "{}", self.contents)?; write!(f, "{}", self.contents)?;
self.delimiter.fmt_end(f) self.delimiter.fmt_end(f)

View file

@ -7,15 +7,24 @@ impl<'a> AnyString<'a> {
fn parse<E: ParserError<&'a str>>() -> impl Parser<&'a str, Self, E> { fn parse<E: ParserError<&'a str>>() -> impl Parser<&'a str, Self, E> {
use winnow::{combinator::*, prelude::*, token::*}; use winnow::{combinator::*, prelude::*, token::*};
// let (prefix, num_hashtags, quote) = let quote = alt((
let preamble = (
take_while(0.., |b: char| !b.is_whitespace()),
take_while(0.., |c| c == '#').map(|i: &'a str| i.len()),
alt((
'`'.value(QuoteType::Backtick), '`'.value(QuoteType::Backtick),
'\''.value(QuoteType::Single), '\''.value(QuoteType::Single),
'\"'.value(QuoteType::Double), '\"'.value(QuoteType::Double),
)), ));
macro_rules! surrounding {
() => {
take_while(0.., |b: char| {
!b.is_whitespace() && b.is_alphabetic() && !['\'', '"', '`', '#'].contains(&b)
})
};
}
let preamble = (
surrounding!(),
take_while(0.., |c| c == '#').map(|i: &'a str| i.len()),
quote,
); );
trace( trace(
@ -35,17 +44,16 @@ impl<'a> AnyString<'a> {
); );
let contents = repeat_till(0.., any, end).map(|(contents, _)| contents); let contents = repeat_till(0.., any, end).map(|(contents, _)| contents);
let suffix = take_while(0.., |b: char| !b.is_whitespace());
(contents, suffix).map(move |(contents, suffix): (Cow<'a, str>, &'a str)| { (contents, surrounding!()).map(
Self { move |(contents, suffix): (Cow<'a, str>, &'a str)| Self {
prefix: prefix.into(), prefix: prefix.into(),
ty: quote, ty: quote,
contents, contents,
num_hashtags, num_hashtags,
suffix: suffix.into(), suffix: suffix.into(),
} },
}) )
}, },
), ),
) )
@ -75,14 +83,15 @@ impl PathSep {
} }
impl Separator { impl Separator {
fn parser<'a, E: ParserError<&'a str>>() -> impl Parser<&'a str, Self, E> { fn parse<'a, E: ParserError<&'a str>>() -> impl Parser<&'a str, Self, E> {
use winnow::{combinator::*, prelude::*, token::*}; use winnow::{combinator::*, prelude::*, token::*};
trace( trace(
"separator", "separator",
alt(( alt((
literal('=').value(Self::Eq), "::".value(Self::DoubleColon),
literal(':').value(Self::Colon), (literal('=')).value(Self::Eq),
(literal(':')).value(Self::Colon),
)), )),
) )
} }
@ -147,95 +156,159 @@ impl<'a> Path<'a> {
fn parse<E: ParserError<&'a str>>() -> impl Parser<&'a str, Self, E> { fn parse<E: ParserError<&'a str>>() -> impl Parser<&'a str, Self, E> {
use winnow::{combinator::*, prelude::*, token::*}; use winnow::{combinator::*, prelude::*, token::*};
let till_next_sep = repeat_till( let terminator = || {
0.., alt((
any::<&'a str, E>.verify(|i: &char| !(*i).is_whitespace()), eof.value(()),
peek(alt((
PathSep::parse().value(()),
any::<&'a str, E> any::<&'a str, E>
.verify(|i: &char| (*i).is_whitespace()) .verify(|i: &char| {
(*i).is_whitespace()
|| !(i.is_alphanumeric()
|| ['_', '-', '\"', '\'', '.', '/', '\\'].contains(i))
})
.value(()), .value(()),
))), ))
};
let terminator_or_sep = || alt((PathSep::parse().value(()), terminator()));
let till_next_sep = || {
trace(
"till next sep",
repeat_till(0.., any::<&'a str, E>, peek(terminator_or_sep()))
.map(|(segment, _)| segment),
) )
.map(|(segment, _)| segment); };
let sep_and_next = let sep_and_next =
(PathSep::parse(), till_next_sep).map(|(leading_separator, segment)| PathSegment { (PathSep::parse(), till_next_sep()).map(|(leading_separator, segment)| PathSegment {
leading_separator, leading_separator,
segment, segment,
}); });
let opt_sep_and_next =
(opt(PathSep::parse()), till_next_sep()).map(|(leading_separator, segment)| {
PathSegment {
leading_separator: leading_separator.unwrap_or(PathSep::None),
segment,
}
});
let drive = opt(( let drive = opt((
any::<&'a str, E>.verify(|x: &char| matches!(*x, 'A'..='Z')), any::<&'a str, E>.verify(|x: &char| matches!(*x, 'A'..='Z' | 'a' ..= 'z')),
':', ':',
)) ))
.map(|i| i.map(|(letter, _): (char, char)| letter)); .map(|i| i.map(|(letter, _): (char, char)| letter));
let drive_and_segments = ( let drive_and_segments = (
drive, drive,
repeat_till( opt_sep_and_next,
1.., repeat_till(0.., sep_and_next, peek(terminator()))
sep_and_next, .map(|(segments, _): (Vec<PathSegment>, _)| segments),
peek(any.verify(|i: &char| (*i).is_whitespace())),
)
.map(|(segments, _): (Vec<PathSegment>, _)| {
let (rest, last) = {
let mut segments = segments;
let last = segments.pop().unwrap();
(segments, last)
};
(rest, FileName::parse(last))
}),
); );
trace( trace(
"path", "path",
drive_and_segments.map(|(drive, (segments, filename))| Self { drive_and_segments
.map(|(drive, segment, segments)| {
let (segments, last) = {
let mut segments = segments;
segments.insert(0, segment);
let last = segments.pop().unwrap();
(segments, last)
};
let filename = FileName::parse(last);
Self {
drive_excluding_colon: drive, drive_excluding_colon: drive,
segments, segments,
filename, filename,
}
})
.verify(|i| {
!i.segments.is_empty()
|| i.drive_excluding_colon.is_some()
|| i.filename.ext_excluding_dot.is_some()
|| !matches!(i.filename.leading_separator, PathSep::None)
}), }),
) )
} }
} }
impl<'a> Atom<'a> { impl<'a> Atom<'a> {
fn parse<E: ParserError<&'a str>>( fn parse<E: ParserError<&'a str>, T: 'a>(
except_chars: &'static [char], terminated_by: impl Parser<&'a str, T, E>,
) -> impl Parser<&'a str, Self, E> { ) -> impl Parser<&'a str, Self, E> {
use winnow::{combinator::*, prelude::*, token::*}; use winnow::{combinator::*, prelude::*, token::*};
trace( let text = repeat::<_, _, Cow<'a, str>, _, _>(
"atom",
alt((repeat(
1.., 1..,
any.verify(move |i: &char| !(*i).is_whitespace() && !except_chars.contains(i)), (
peek(not(terminated_by)),
any::<&str, _>.verify(move |i: &char| !(*i).is_whitespace()),
) )
.map(Self::Text),)), .map(|(_, i)| i),
) )
.map(Self::Text);
trace("atom", alt((text,)))
} }
} }
impl<'a> Token<'a> { impl<'a> Token<'a> {
fn parse<E: ParserError<&'a str> + 'a>() -> impl Parser<&'a str, Self, E> { fn parse_without_separator<E: ParserError<&'a str> + 'a>() -> impl Parser<&'a str, Self, E> {
use winnow::{combinator::*, prelude::*}; use winnow::{combinator::*, prelude::*};
let delimited: Box<dyn Parser<&'a str, Self, E>> = let delimited: Box<dyn Parser<&'a str, Self, E>> =
Box::new(Delimited::parse().map(Self::Delimited)); Box::new(Delimited::parse().map(Self::Delimited));
trace( trace(
"token", "token-without-sep",
alt(( alt((
"true".value(Self::True), "true".value(Self::True),
"false".value(Self::False), "false".value(Self::False),
"None".value(Self::None), "None".value(Self::None),
Path::parse().map(Self::Path), Path::parse().map(Self::Path),
AnyString::parse().map(Self::String),
Number::parse().map(Self::Number), Number::parse().map(Self::Number),
AnyString::parse().map(Self::String),
delimited, delimited,
Atom::parse(&[]).map(Self::Atom), Atom::parse(alt((Separator::parse().value(""), ")", "]", "}"))).map(Self::Atom),
)), )),
) )
} }
fn parse<E: ParserError<&'a str> + 'a>() -> Box<dyn Parser<&'a str, Self, E> + 'a> {
use winnow::{combinator::*, prelude::*};
let before = Self::parse_without_separator();
Box::new(trace(
"token",
alt((
(
before,
opt(
(Space::parse(), Separator::parse()).flat_map(|(space, sep)| {
let box_dyn_segment: Box<dyn Parser<_, _, _>> =
Box::new(Segment::parse());
box_dyn_segment.map(move |segment| (space.clone(), sep, segment))
}),
),
)
.map(|(before, trailer)| {
if let Some((space_before, separator, after)) = trailer {
Token::Separated {
before: Box::new(before),
space_before,
separator,
after: Box::new(after),
}
} else {
before
}
}),
Atom::parse(fail::<_, (), _>).map(Self::Atom),
)),
))
}
} }
impl<'a> Delimited<'a> { impl<'a> Delimited<'a> {
@ -245,7 +318,12 @@ impl<'a> Delimited<'a> {
trace( trace(
"delimited", "delimited",
( (
Atom::parse(&['(', '[', '{']), opt(Atom::parse(alt((
"(",
"[",
"{",
Separator::parse().value(""),
)))),
alt(( alt((
literal('(').map(|_| literal(')').value(Delimiter::Paren)), literal('(').map(|_| literal(')').value(Delimiter::Paren)),
literal('[').map(|_| literal(']').value(Delimiter::Bracket)), literal('[').map(|_| literal(']').value(Delimiter::Bracket)),
@ -264,11 +342,11 @@ impl<'a> Delimited<'a> {
impl<'a> Segments<'a> { impl<'a> Segments<'a> {
fn parse<E: ParserError<&'a str> + 'a, End: 'a>( fn parse<E: ParserError<&'a str> + 'a, End: 'a>(
end: impl Parser<&'a str, End, E>, end: impl Parser<&'a str, End, E> + 'a,
) -> impl Parser<&'a str, (Self, End), E> { ) -> Box<dyn Parser<&'a str, (Self, End), E> + 'a> {
use winnow::{combinator::*, prelude::*}; use winnow::{combinator::*, prelude::*};
trace( Box::new(trace(
"segments", "segments",
repeat_till(0.., Segment::parse(), (Space::parse(), end)).map( repeat_till(0.., Segment::parse(), (Space::parse(), end)).map(
|(segments, (trailing_space, end)): (Vec<_>, _)| { |(segments, (trailing_space, end)): (Vec<_>, _)| {
@ -281,18 +359,21 @@ impl<'a> Segments<'a> {
) )
}, },
), ),
) ))
} }
} }
impl<'a> Number<'a> { impl<'a> Number<'a> {
fn parse<E: ParserError<&'a str>>() -> impl Parser<&'a str, Self, E> { fn parse<E: ParserError<&'a str>>() -> impl Parser<&'a str, Self, E> {
use winnow::{ascii::*, combinator::*, prelude::*}; use winnow::{ascii::*, combinator::*, prelude::*, token::*};
trace( trace(
"number", "number",
alt((float::<_, f64, _>.take(), dec_int::<_, i64, _>.take())) (
.map(|i: &str| Self(i.into())), alt((float::<_, f64, _>.take(), dec_int::<_, i64, _>.take())),
peek(not(any::<&'a str, E>.verify(|x: &char| x.is_alphabetic()))),
)
.map(|(i, _): (&str, _)| Self(i.into())),
) )
} }
} }
@ -318,3 +399,371 @@ pub fn parse_input<'a>(i: &'a str) -> Result<Segments<'a>, String> {
.parse(i) .parse(i)
.map_err(|e| e.to_string()) .map_err(|e| e.to_string())
} }
#[cfg(test)]
mod tests {
use insta::assert_debug_snapshot;
use winnow::Parser;
use crate::format_debug_output::{
ast::{Path, Segments},
parse_input,
};
fn parse_path_only<'a>(i: &'a str) -> Path<'a> {
Path::parse::<winnow::error::EmptyError>().parse(i).unwrap()
}
fn parse<'a>(input: &'a str) -> Segments<'a> {
parse_input(input).unwrap()
}
#[test]
fn parse_path() {
assert_debug_snapshot!(parse_path_only(r#"tests/ui/impl-trait/unsized_coercion.rs"#), @r#"
Path {
drive_excluding_colon: None,
segments: [
PathSegment {
leading_separator: None,
segment: "tests",
},
PathSegment {
leading_separator: Slash,
segment: "ui",
},
PathSegment {
leading_separator: Slash,
segment: "impl-trait",
},
],
filename: FileName {
leading_separator: Slash,
segment: "unsized_coercion",
ext_excluding_dot: Some(
"rs",
),
location: None,
},
}
"#);
}
#[test]
fn parse_path_with_file_line() {
assert_debug_snapshot!(parse_path_only(r#"tests/ui/impl-trait/unsized_coercion.rs:3:4"#), @r#"
Path {
drive_excluding_colon: None,
segments: [
PathSegment {
leading_separator: None,
segment: "tests",
},
PathSegment {
leading_separator: Slash,
segment: "ui",
},
PathSegment {
leading_separator: Slash,
segment: "impl-trait",
},
],
filename: FileName {
leading_separator: Slash,
segment: "unsized_coercion",
ext_excluding_dot: Some(
"rs",
),
location: Some(
FileLocation {
line: "3",
offset: Some(
"4",
),
},
),
},
}
"#);
}
#[test]
fn parse_empty() {
assert_debug_snapshot!(parse(r#""#), @r#"
Segments {
segments: [],
trailing_space: Space(
"",
),
}
"#)
}
#[test]
fn parse_text() {
assert_debug_snapshot!(parse(r#"abc"#), @r#"
Segments {
segments: [
Segment {
leading_space: Space(
"",
),
token: Atom(
Text(
"abc",
),
),
},
],
trailing_space: Space(
"",
),
}
"#)
}
#[test]
fn parse_boolean() {
assert_debug_snapshot!(parse(r#"true"#), @r#"
Segments {
segments: [
Segment {
leading_space: Space(
"",
),
token: True,
},
],
trailing_space: Space(
"",
),
}
"#);
assert_debug_snapshot!(parse(r#"false"#), @r#"
Segments {
segments: [
Segment {
leading_space: Space(
"",
),
token: False,
},
],
trailing_space: Space(
"",
),
}
"#);
}
#[test]
fn parse_string() {
assert_debug_snapshot!(parse(r##""foo""##), @r#"
Segments {
segments: [
Segment {
leading_space: Space(
"",
),
token: String(
AnyString {
prefix: "",
ty: Double,
contents: "foo",
num_hashtags: 0,
suffix: "",
},
),
},
],
trailing_space: Space(
"",
),
}
"#);
assert_debug_snapshot!(parse(r##"#"foo"#"##), @r#"
Segments {
segments: [
Segment {
leading_space: Space(
"",
),
token: String(
AnyString {
prefix: "",
ty: Double,
contents: "foo",
num_hashtags: 1,
suffix: "",
},
),
},
],
trailing_space: Space(
"",
),
}
"#);
assert_debug_snapshot!(parse(r##"r#"foo"#"##), @r#"
Segments {
segments: [
Segment {
leading_space: Space(
"",
),
token: String(
AnyString {
prefix: "r",
ty: Double,
contents: "foo",
num_hashtags: 1,
suffix: "",
},
),
},
],
trailing_space: Space(
"",
),
}
"#);
assert_debug_snapshot!(parse(r##"c"foo""##), @r#"
Segments {
segments: [
Segment {
leading_space: Space(
"",
),
token: String(
AnyString {
prefix: "c",
ty: Double,
contents: "foo",
num_hashtags: 0,
suffix: "",
},
),
},
],
trailing_space: Space(
"",
),
}
"#);
assert_debug_snapshot!(parse(r##"b"foo""##), @r#"
Segments {
segments: [
Segment {
leading_space: Space(
"",
),
token: String(
AnyString {
prefix: "b",
ty: Double,
contents: "foo",
num_hashtags: 0,
suffix: "",
},
),
},
],
trailing_space: Space(
"",
),
}
"#);
assert_debug_snapshot!(parse(r##"'a'"##), @r#"
Segments {
segments: [
Segment {
leading_space: Space(
"",
),
token: String(
AnyString {
prefix: "",
ty: Single,
contents: "a",
num_hashtags: 0,
suffix: "",
},
),
},
],
trailing_space: Space(
"",
),
}
"#);
assert_debug_snapshot!(parse(r##"`b`"##), @r#"
Segments {
segments: [
Segment {
leading_space: Space(
"",
),
token: String(
AnyString {
prefix: "",
ty: Backtick,
contents: "b",
num_hashtags: 0,
suffix: "",
},
),
},
],
trailing_space: Space(
"",
),
}
"#);
assert_debug_snapshot!(parse(r##"b'foo'"##), @r#"
Segments {
segments: [
Segment {
leading_space: Space(
"",
),
token: String(
AnyString {
prefix: "b",
ty: Single,
contents: "foo",
num_hashtags: 0,
suffix: "",
},
),
},
],
trailing_space: Space(
"",
),
}
"#);
assert_debug_snapshot!(parse(r##"b`foo`"##), @r#"
Segments {
segments: [
Segment {
leading_space: Space(
"",
),
token: String(
AnyString {
prefix: "b",
ty: Backtick,
contents: "foo",
num_hashtags: 0,
suffix: "",
},
),
},
],
trailing_space: Space(
"",
),
}
"#);
}
}

View file

@ -1,6 +1,6 @@
use super::ast::*; use super::ast::*;
use crate::format_debug_output::parse_input;
use crate::format_debug_output::{Config, into_spans}; use crate::format_debug_output::{Config, into_spans};
use crate::format_debug_output::{ast::*, parse_input};
use proptest::prelude::*; use proptest::prelude::*;
use proptest::proptest; use proptest::proptest;
@ -155,7 +155,8 @@ impl Token<'static> {
impl Delimited<'static> { impl Delimited<'static> {
#[cfg(test)] #[cfg(test)]
fn arb(token: impl Strategy<Value = Token<'static>>) -> impl Strategy<Value = Self> { fn arb(token: impl Strategy<Value = Token<'static>>) -> impl Strategy<Value = Self> {
(Atom::arb(), any::<Delimiter>(), Segments::arb(token)).prop_map( use proptest::option::*;
(of(Atom::arb()), any::<Delimiter>(), Segments::arb(token)).prop_map(
|(prefix, delimiter, contents)| Self { |(prefix, delimiter, contents)| Self {
prefix, prefix,
delimiter, delimiter,

View file

@ -82,6 +82,7 @@ mod private {
match self { match self {
Separator::Eq => cx.push("=", Kind::Separator), Separator::Eq => cx.push("=", Kind::Separator),
Separator::Colon => cx.push(":", Kind::Separator), Separator::Colon => cx.push(":", Kind::Separator),
Separator::DoubleColon => cx.push("::", Kind::Separator),
} }
} }
} }
@ -166,7 +167,12 @@ mod private {
space_before, space_before,
separator, separator,
after, after,
} => todo!(), } => {
before.into_spans(cx);
space_before.into_spans(cx);
separator.into_spans(cx);
after.into_spans(cx);
}
Token::Delimited(delimited) => { Token::Delimited(delimited) => {
delimited.into_spans(cx); delimited.into_spans(cx);
} }
@ -210,7 +216,8 @@ mod private {
} = self; } = self;
match prefix { match prefix {
Atom::Text(text) => cx.push(text, Kind::Constructor), Some(Atom::Text(text)) => cx.push(text, Kind::Constructor),
None => {}
} }
match delimiter { match delimiter {
@ -233,3 +240,736 @@ mod private {
} }
} }
} }
#[cfg(test)]
mod tests {
use insta::assert_debug_snapshot;
use super::Kind;
use crate::format_debug_output::{Config, into_spans, parse_input};
fn spans(input: &str) -> Vec<(String, Kind)> {
let res = parse_input(input).unwrap();
into_spans(
res,
Config {
collapse_space: true,
},
)
.into_iter()
.map(|i| (i.text.into_owned(), i.kind))
.collect()
}
#[test]
fn spans_ex1() {
assert_debug_snapshot!(spans(
r#"def_id=DefId(0:3 ~ unsized_coercion[10fa]::Trait)"#
), @r#"
[
(
"def_id",
Text,
),
(
"=",
Separator,
),
(
"DefId",
Constructor,
),
(
"(",
Delimiter(
0,
),
),
(
"0",
Number,
),
(
":",
Separator,
),
(
"3",
Number,
),
(
" ",
Space(
1,
),
),
(
"~",
Text,
),
(
" ",
Space(
1,
),
),
(
"unsized_coercion",
Constructor,
),
(
"[",
Delimiter(
1,
),
),
(
"10fa",
Text,
),
(
"]",
Delimiter(
1,
),
),
(
"::",
Separator,
),
(
"Trait",
Text,
),
(
")",
Delimiter(
0,
),
),
]
"#)
}
#[test]
fn spans_ex2() {
assert_debug_snapshot!(spans(
r#"data=TypeNs("MetaSized") visible_parent=DefId(2:3984 ~ core[bcc4]::marker) actual_parent=Some(DefId(2:3984 ~ core[bcc4]::marker))"#
), @r#"
[
(
"data",
Text,
),
(
"=",
Separator,
),
(
"TypeNs",
Constructor,
),
(
"(",
Delimiter(
0,
),
),
(
"",
StringSurroundings,
),
(
"\"",
Separator,
),
(
"MetaSized",
String,
),
(
"\"",
Separator,
),
(
"",
StringSurroundings,
),
(
")",
Delimiter(
0,
),
),
(
" ",
Space(
1,
),
),
(
"visible_parent",
Text,
),
(
"=",
Separator,
),
(
"DefId",
Constructor,
),
(
"(",
Delimiter(
0,
),
),
(
"2",
Number,
),
(
":",
Separator,
),
(
"3984",
Number,
),
(
" ",
Space(
1,
),
),
(
"~",
Text,
),
(
" ",
Space(
1,
),
),
(
"core",
Constructor,
),
(
"[",
Delimiter(
1,
),
),
(
"bcc4",
Text,
),
(
"]",
Delimiter(
1,
),
),
(
"::",
Separator,
),
(
"marker",
Text,
),
(
")",
Delimiter(
0,
),
),
(
" ",
Space(
1,
),
),
(
"actual_parent",
Text,
),
(
"=",
Separator,
),
(
"Some",
Constructor,
),
(
"(",
Delimiter(
0,
),
),
(
"DefId",
Constructor,
),
(
"(",
Delimiter(
1,
),
),
(
"2",
Number,
),
(
":",
Separator,
),
(
"3984",
Number,
),
(
" ",
Space(
1,
),
),
(
"~",
Text,
),
(
" ",
Space(
1,
),
),
(
"core",
Constructor,
),
(
"[",
Delimiter(
2,
),
),
(
"bcc4",
Text,
),
(
"]",
Delimiter(
2,
),
),
(
"::",
Separator,
),
(
"marker",
Text,
),
(
")",
Delimiter(
1,
),
),
(
")",
Delimiter(
0,
),
),
]
"#)
}
#[test]
fn spans_ex3() {
assert_debug_snapshot!(spans(
r#"insert(DefId(0:4 ~ unsized_coercion[10fa]::{impl#0})): inserting TraitRef <u32 as Trait> into specialization graph"#
), @r#"
[
(
"insert",
Constructor,
),
(
"(",
Delimiter(
0,
),
),
(
"DefId",
Constructor,
),
(
"(",
Delimiter(
1,
),
),
(
"0",
Number,
),
(
":",
Separator,
),
(
"4",
Number,
),
(
" ",
Space(
1,
),
),
(
"~",
Text,
),
(
" ",
Space(
1,
),
),
(
"unsized_coercion",
Constructor,
),
(
"[",
Delimiter(
2,
),
),
(
"10fa",
Text,
),
(
"]",
Delimiter(
2,
),
),
(
"::",
Separator,
),
(
"{",
Delimiter(
2,
),
),
(
"impl#0",
Text,
),
(
"}",
Delimiter(
2,
),
),
(
")",
Delimiter(
1,
),
),
(
")",
Delimiter(
0,
),
),
(
":",
Separator,
),
(
" ",
Space(
1,
),
),
(
"inserting",
Text,
),
(
" ",
Space(
1,
),
),
(
"TraitRef",
Text,
),
(
" ",
Space(
1,
),
),
(
"<u32",
Text,
),
(
" ",
Space(
1,
),
),
(
"as",
Text,
),
(
" ",
Space(
1,
),
),
(
"Trait>",
Text,
),
(
" ",
Space(
1,
),
),
(
"into",
Text,
),
(
" ",
Space(
1,
),
),
(
"specialization",
Text,
),
(
" ",
Space(
1,
),
),
(
"graph",
Text,
),
]
"#)
}
#[test]
fn spans_ex4() {
assert_debug_snapshot!(spans(
r#"inspecting def_id=DefId(3:662 ~ alloc[ef11]::boxed::Box) span=tests/ui/impl-trait/unsized_coercion.rs:12:15: 12:30 (#0)"#
), @r##"
[
(
"inspecting",
Text,
),
(
" ",
Space(
1,
),
),
(
"def_id",
Text,
),
(
"=",
Separator,
),
(
"DefId",
Constructor,
),
(
"(",
Delimiter(
0,
),
),
(
"3",
Number,
),
(
":",
Separator,
),
(
"662",
Number,
),
(
" ",
Space(
1,
),
),
(
"~",
Text,
),
(
" ",
Space(
1,
),
),
(
"alloc",
Constructor,
),
(
"[",
Delimiter(
1,
),
),
(
"ef11",
Text,
),
(
"]",
Delimiter(
1,
),
),
(
"::",
Separator,
),
(
"boxed",
Text,
),
(
"::",
Separator,
),
(
"Box",
Text,
),
(
")",
Delimiter(
0,
),
),
(
" ",
Space(
1,
),
),
(
"span",
Text,
),
(
"=",
Separator,
),
(
"tests/ui/impl-trait/unsized_coercion.rs",
Path,
),
(
":",
Separator,
),
(
"12",
Number,
),
(
":",
Separator,
),
(
"15",
Number,
),
(
":",
Separator,
),
(
" ",
Space(
1,
),
),
(
"12",
Number,
),
(
":",
Separator,
),
(
"30",
Number,
),
(
" ",
Space(
1,
),
),
(
"(",
Delimiter(
0,
),
),
(
"#0",
Text,
),
(
")",
Delimiter(
0,
),
),
]
"##)
}
}

View file

@ -215,10 +215,10 @@ impl Into<Line<'static>> for Styled<'_, LineText> {
let style = match kind { let style = match kind {
SpanKind::Delimiter(_) => style.fg(self.styles.delimiter).bold(), SpanKind::Delimiter(_) => style.fg(self.styles.delimiter).bold(),
SpanKind::Separator => style.fg(self.styles.faded), SpanKind::Separator => style.fg(self.styles.faded),
SpanKind::Number => style.fg(self.styles.literal).underlined(), SpanKind::Number => style.fg(self.styles.literal),
SpanKind::Literal => style.fg(self.styles.literal).underlined(), SpanKind::Literal => style.fg(self.styles.literal).dim(),
SpanKind::String => style.fg(self.styles.literal).italic(), SpanKind::String => style.fg(self.styles.literal),
SpanKind::Path => style.fg(self.styles.literal).italic(), SpanKind::Path => style.fg(self.styles.literal).underlined(),
SpanKind::Space(_) => style, SpanKind::Space(_) => style,
SpanKind::Constructor => style.fg(self.styles.literal), SpanKind::Constructor => style.fg(self.styles.literal),
SpanKind::StringSurroundings => style.fg(self.styles.faded), SpanKind::StringSurroundings => style.fg(self.styles.faded),