Skip to content

Commit f709873

Browse files
committed
feat: add Display support for Path and PathBuf
Prior to this change, we could not display Paths or PathBufs via display and their `Path::display` function because we only support identifiers in the formatting strings and don't do anything fancy with those args other than paste them verbatim into the args for the generated `write!` calls. This change adds special support for Path and PathBuf displaying via autoref specialization. We introduce two new traits, one which handles anything that implements display and just returns self, and another which specifically works for Path and PathBuf and returns `std::path::Display`. On top of this we manually route all display impl format args thru this trait by putting `(&arg).get_display()` rather than `arg` in the generated format args list.
1 parent c3c1f41 commit f709873

File tree

4 files changed

+79
-4
lines changed

4 files changed

+79
-4
lines changed

Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ A derive macro for implementing the display Trait via a doc comment and string i
1616
[lib]
1717
proc-macro = true
1818

19-
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
19+
[features]
20+
default = ["std"]
21+
std = []
2022

2123
[dependencies]
2224
syn = "1.0"
@@ -28,3 +30,4 @@ trybuild = "1.0"
2830
static_assertions = "0.3.4"
2931
libc = { version = "0.2", default-features = false }
3032
rustversion = "1.0.0"
33+
pretty_assertions = "0.6.1"

src/expand.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,42 @@ pub fn derive(input: &DeriveInput) -> Result<TokenStream> {
1111
}
1212
}
1313

14+
#[cfg(feature = "std")]
15+
fn specialization() -> TokenStream {
16+
quote! {
17+
trait DisplayToDisplayDoc {
18+
fn get_display(&self) -> Self;
19+
}
20+
21+
impl<T: core::fmt::Display> DisplayToDisplayDoc for &T {
22+
fn get_display(&self) -> Self {
23+
self
24+
}
25+
}
26+
27+
trait PathToDisplayDoc {
28+
fn get_display(&self) -> std::path::Display<'_>;
29+
}
30+
31+
impl PathToDisplayDoc for std::path::Path {
32+
fn get_display(&self) -> std::path::Display<'_> {
33+
self.display()
34+
}
35+
}
36+
37+
impl PathToDisplayDoc for std::path::PathBuf {
38+
fn get_display(&self) -> std::path::Display<'_> {
39+
self.display()
40+
}
41+
}
42+
}
43+
}
44+
45+
#[cfg(not(feature = "std"))]
46+
fn specialization() -> TokenStream {
47+
quote! {}
48+
}
49+
1450
fn impl_struct(input: &DeriveInput, data: &DataStruct) -> Result<TokenStream> {
1551
let ty = &input.ident;
1652
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
@@ -38,7 +74,11 @@ fn impl_struct(input: &DeriveInput, data: &DataStruct) -> Result<TokenStream> {
3874
}
3975
});
4076

77+
let needed_traits = specialization();
78+
4179
Ok(quote! {
80+
#needed_traits
81+
4282
#display
4383
})
4484
}
@@ -89,7 +129,11 @@ fn impl_enum(input: &DeriveInput, data: &DataEnum) -> Result<TokenStream> {
89129
return Err(Error::new_spanned(input, "Missing doc comments"));
90130
};
91131

132+
let needed_traits = specialization();
133+
92134
Ok(quote! {
135+
#needed_traits
136+
93137
#display
94138
})
95139
}

src/fmt.rs

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,22 +15,39 @@ impl Display {
1515
while let Some(brace) = read.find('{') {
1616
out += &read[..=brace];
1717
read = &read[brace + 1..];
18+
19+
// skip cases where we find a {{
1820
if read.starts_with('{') {
1921
out.push('{');
2022
read = &read[1..];
2123
continue;
2224
}
25+
2326
let next = match read.chars().next() {
2427
Some(next) => next,
2528
None => return,
2629
};
30+
2731
let var = match next {
2832
'0'..='9' => take_int(&mut read),
2933
'a'..='z' | 'A'..='Z' | '_' => take_ident(&mut read),
3034
_ => return,
3135
};
36+
3237
let ident = Ident::new(&var, span);
33-
args.extend(quote_spanned!(span=> , #ident));
38+
39+
let next = match read.chars().next() {
40+
Some(next) => next,
41+
None => return,
42+
};
43+
44+
let arg = if next == '}' {
45+
quote_spanned!(span=> , (&#ident).get_display())
46+
} else {
47+
quote_spanned!(span=> , #ident)
48+
};
49+
50+
args.extend(arg);
3451
}
3552

3653
out += read;
@@ -71,6 +88,7 @@ fn take_ident(read: &mut &str) -> String {
7188
#[cfg(test)]
7289
mod tests {
7390
use super::*;
91+
use pretty_assertions::assert_eq;
7492
use proc_macro2::Span;
7593

7694
fn assert(input: &str, fmt: &str, args: &str) {
@@ -85,12 +103,15 @@ mod tests {
85103

86104
#[test]
87105
fn test_expand() {
88-
assert("error {var}", "error {}", ", var");
106+
assert("error {var}", "error {}", ", ( & var ) . get_display ( )");
89107
assert("fn main() {{ }}", "fn main() {{ }}", "");
90108
assert(
91109
"{v} {v:?} {0} {0:?}",
92110
"{} {:?} {} {:?}",
93-
", v , v , _0 , _0",
111+
", ( & v ) . get_display ( ) , v , ( & _0 ) . get_display ( ) , _0",
94112
);
113+
114+
// assert("The path {0.display()}", "The path {}", "0.display()");
115+
// assert("The path {0.display():?}", "The path {:?}", "0.display()");
95116
}
96117
}

tests/happy.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use displaydoc::Display;
2+
// use std::path::PathBuf;
23

34
#[derive(Display)]
45
enum Happy {
@@ -17,6 +18,8 @@ enum Happy {
1718
/// Variant5 just has {0} many problems
1819
/// but multi line comments aren't one of them
1920
Variant5(u32),
21+
// /// The path {0.display()}
22+
// Variant6(PathBuf),
2023
}
2124

2225
fn assert_display<T: std::fmt::Display>(input: T, expected: &'static str) {
@@ -34,4 +37,8 @@ fn does_it_print() {
3437
"Variant4 wants to have a lot of lines\n\n Lets see how this works out for it",
3538
);
3639
assert_display(Happy::Variant5(2), "Variant5 just has 2 many problems");
40+
// assert_display(
41+
// Happy::Variant6(PathBuf::from("/var/log/happy")),
42+
// "The path /var/log/happy",
43+
// );
3744
}

0 commit comments

Comments
 (0)