Merge pull request #39 from dtolnay/path
Support interpolating paths as if they had a Display impl
This commit is contained in:
commit
2e1939158a
|
@ -15,6 +15,7 @@ thiserror-impl = { version = "=1.0.4", path = "impl" }
|
|||
|
||||
[dev-dependencies]
|
||||
anyhow = "1.0"
|
||||
ref-cast = "1.0"
|
||||
rustversion = "1.0"
|
||||
trybuild = "1.0"
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ pub struct Display<'a> {
|
|||
pub fmt: LitStr,
|
||||
pub args: TokenStream,
|
||||
pub was_shorthand: bool,
|
||||
pub has_bonus_display: bool,
|
||||
}
|
||||
|
||||
pub fn get(input: &[Attribute]) -> Result<Attrs> {
|
||||
|
@ -74,6 +75,7 @@ fn parse_display(attr: &Attribute) -> Result<Display> {
|
|||
fmt: input.parse()?,
|
||||
args: parse_token_expr(input, false)?,
|
||||
was_shorthand: false,
|
||||
has_bonus_display: false,
|
||||
};
|
||||
display.expand_shorthand();
|
||||
Ok(display)
|
||||
|
|
|
@ -77,10 +77,19 @@ fn impl_struct(input: Struct) -> TokenStream {
|
|||
});
|
||||
|
||||
let display_impl = input.attrs.display.as_ref().map(|display| {
|
||||
let use_as_display = if display.has_bonus_display {
|
||||
Some(quote! {
|
||||
#[allow(unused_imports)]
|
||||
use thiserror::private::{DisplayAsDisplay, PathAsDisplay};
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let pat = fields_pat(&input.fields);
|
||||
quote! {
|
||||
impl #impl_generics std::fmt::Display for #ty #ty_generics #where_clause {
|
||||
fn fmt(&self, __formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
#use_as_display
|
||||
#[allow(unused_variables)]
|
||||
let Self #pat = self;
|
||||
#display
|
||||
|
@ -215,6 +224,20 @@ fn impl_enum(input: Enum) -> TokenStream {
|
|||
};
|
||||
|
||||
let display_impl = if input.has_display() {
|
||||
let use_as_display = if input.variants.iter().any(|v| {
|
||||
v.attrs
|
||||
.display
|
||||
.as_ref()
|
||||
.expect(valid::CHECKED)
|
||||
.has_bonus_display
|
||||
}) {
|
||||
Some(quote! {
|
||||
#[allow(unused_imports)]
|
||||
use thiserror::private::{DisplayAsDisplay, PathAsDisplay};
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let void_deref = if input.variants.is_empty() {
|
||||
Some(quote!(*))
|
||||
} else {
|
||||
|
@ -231,6 +254,7 @@ fn impl_enum(input: Enum) -> TokenStream {
|
|||
Some(quote! {
|
||||
impl #impl_generics std::fmt::Display for #ty #ty_generics #where_clause {
|
||||
fn fmt(&self, __formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
#use_as_display
|
||||
#[allow(unused_variables)]
|
||||
match #void_deref self {
|
||||
#(#arms,)*
|
||||
|
|
|
@ -15,6 +15,7 @@ impl Display<'_> {
|
|||
let mut read = fmt.as_str();
|
||||
let mut out = String::new();
|
||||
let mut args = TokenStream::new();
|
||||
let mut has_bonus_display = false;
|
||||
|
||||
while let Some(brace) = read.find('{') {
|
||||
out += &read[..=brace];
|
||||
|
@ -35,12 +36,17 @@ impl Display<'_> {
|
|||
};
|
||||
let ident = Ident::new(&var, span);
|
||||
args.extend(quote_spanned!(span=> , #ident));
|
||||
if read.starts_with('}') {
|
||||
has_bonus_display = true;
|
||||
args.extend(quote_spanned!(span=> .as_display()));
|
||||
}
|
||||
}
|
||||
|
||||
out += read;
|
||||
self.fmt = LitStr::new(&out, self.fmt.span());
|
||||
self.args = args;
|
||||
self.was_shorthand = true;
|
||||
self.has_bonus_display = has_bonus_display;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -85,6 +91,7 @@ mod tests {
|
|||
fmt: LitStr::new(input, Span::call_site()),
|
||||
args: TokenStream::new(),
|
||||
was_shorthand: false,
|
||||
has_bonus_display: false,
|
||||
};
|
||||
display.expand_shorthand();
|
||||
assert_eq!(fmt, display.fmt.value());
|
||||
|
@ -93,12 +100,12 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_expand() {
|
||||
assert("error {var}", "error {}", ", var");
|
||||
assert("error {var}", "error {}", ", var . as_display ( )");
|
||||
assert("fn main() {{ }}", "fn main() {{ }}", "");
|
||||
assert(
|
||||
"{v} {v:?} {0} {0:?}",
|
||||
"{} {:?} {} {:?}",
|
||||
", v , v , _0 , _0",
|
||||
", v . as_display ( ) , v , _0 . as_display ( ) , _0",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
use std::fmt::Display;
|
||||
use std::path::{self, Path, PathBuf};
|
||||
|
||||
pub trait DisplayAsDisplay {
|
||||
fn as_display(&self) -> Self;
|
||||
}
|
||||
|
||||
impl<T: Display> DisplayAsDisplay for &T {
|
||||
fn as_display(&self) -> Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub trait PathAsDisplay {
|
||||
fn as_display(&self) -> path::Display<'_>;
|
||||
}
|
||||
|
||||
impl PathAsDisplay for Path {
|
||||
fn as_display(&self) -> path::Display<'_> {
|
||||
self.display()
|
||||
}
|
||||
}
|
||||
|
||||
impl PathAsDisplay for PathBuf {
|
||||
fn as_display(&self) -> path::Display<'_> {
|
||||
self.display()
|
||||
}
|
||||
}
|
|
@ -136,6 +136,7 @@
|
|||
//! [`anyhow`]: https://github.com/dtolnay/anyhow
|
||||
|
||||
mod aserror;
|
||||
mod display;
|
||||
|
||||
pub use thiserror_impl::*;
|
||||
|
||||
|
@ -143,4 +144,5 @@ pub use thiserror_impl::*;
|
|||
#[doc(hidden)]
|
||||
pub mod private {
|
||||
pub use crate::aserror::AsDynError;
|
||||
pub use crate::display::{DisplayAsDisplay, PathAsDisplay};
|
||||
}
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
use ref_cast::RefCast;
|
||||
use std::fmt::Display;
|
||||
use std::path::{Path, PathBuf};
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("failed to read '{file}'")]
|
||||
struct StructPathBuf {
|
||||
file: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Error, Debug, RefCast)]
|
||||
#[repr(C)]
|
||||
#[error("failed to read '{file}'")]
|
||||
struct StructPath {
|
||||
file: Path,
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
enum EnumPathBuf {
|
||||
#[error("failed to read '{0}'")]
|
||||
Read(PathBuf),
|
||||
}
|
||||
|
||||
fn assert<T: Display>(expected: &str, value: T) {
|
||||
assert_eq!(expected, value.to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_display() {
|
||||
let path = Path::new("/thiserror");
|
||||
let file = path.to_owned();
|
||||
assert("failed to read '/thiserror'", StructPathBuf { file });
|
||||
let file = path.to_owned();
|
||||
assert("failed to read '/thiserror'", EnumPathBuf::Read(file));
|
||||
assert("failed to read '/thiserror'", StructPath::ref_cast(path));
|
||||
}
|
Loading…
Reference in New Issue