Support mixing shorthand and non-shorthand format args

This commit is contained in:
David Tolnay 2019-11-30 17:26:19 -08:00
parent 03e522411e
commit c05e9ed4ec
No known key found for this signature in database
GPG Key ID: F9BA143B95FF6D82
2 changed files with 51 additions and 10 deletions

View File

@ -1,24 +1,25 @@
use crate::ast::Field; use crate::ast::Field;
use crate::attr::Display; use crate::attr::Display;
use proc_macro2::TokenStream; use proc_macro2::TokenTree;
use quote::{format_ident, quote_spanned}; use quote::{format_ident, quote_spanned};
use std::collections::HashSet as Set; use std::collections::HashSet as Set;
use syn::{Ident, Index, LitStr, Member}; use syn::ext::IdentExt;
use syn::parse::{ParseStream, Parser};
use syn::{Ident, Index, LitStr, Member, Result, Token};
impl Display<'_> { impl Display<'_> {
// Transform `"error {var}"` to `"error {}", var`. // Transform `"error {var}"` to `"error {}", var`.
pub fn expand_shorthand(&mut self, fields: &[Field]) { pub fn expand_shorthand(&mut self, fields: &[Field]) {
if !self.args.is_empty() { let raw_args = self.args.clone();
return; let mut named_args = explicit_named_args.parse2(raw_args).unwrap();
} let fields: Set<Member> = fields.iter().map(|f| f.member.clone()).collect();
let span = self.fmt.span(); let span = self.fmt.span();
let fmt = self.fmt.value(); let fmt = self.fmt.value();
let mut read = fmt.as_str(); let mut read = fmt.as_str();
let mut out = String::new(); let mut out = String::new();
let mut args = TokenStream::new(); let mut args = self.args.clone();
let mut has_bonus_display = false; let mut has_bonus_display = false;
let fields: Set<Member> = fields.iter().map(|f| f.member.clone()).collect();
while let Some(brace) = read.find('{') { while let Some(brace) = read.find('{') {
out += &read[..brace + 1]; out += &read[..brace + 1];
@ -44,13 +45,24 @@ impl Display<'_> {
let ident = take_ident(&mut read); let ident = take_ident(&mut read);
Member::Named(Ident::new(&ident, span)) Member::Named(Ident::new(&ident, span))
} }
_ => return, _ => continue,
}; };
let ident = match &member { let local = match &member {
Member::Unnamed(index) => format_ident!("_{}", index), Member::Unnamed(index) => format_ident!("_{}", index),
Member::Named(ident) => ident.clone(), Member::Named(ident) => ident.clone(),
}; };
args.extend(quote_spanned!(span=> , #ident)); let mut formatvar = local.clone();
if formatvar.to_string().starts_with('_') {
// Work around leading underscore being rejected by 1.40 and
// older compilers. https://github.com/rust-lang/rust/pull/66847
formatvar = format_ident!("field_{}", formatvar);
}
out += &formatvar.to_string();
if !named_args.insert(formatvar.clone()) {
// Already specified in the format argument list.
continue;
}
args.extend(quote_spanned!(span=> , #formatvar = #local));
if read.starts_with('}') && fields.contains(&member) { if read.starts_with('}') && fields.contains(&member) {
has_bonus_display = true; has_bonus_display = true;
args.extend(quote_spanned!(span=> .as_display())); args.extend(quote_spanned!(span=> .as_display()));
@ -65,6 +77,23 @@ impl Display<'_> {
} }
} }
fn explicit_named_args(input: ParseStream) -> Result<Set<Ident>> {
let mut named_args = Set::new();
while !input.is_empty() {
if input.peek(Token![,]) && input.peek2(Ident::peek_any) && input.peek3(Token![=]) {
input.parse::<Token![,]>()?;
let ident: Ident = input.parse()?;
input.parse::<Token![=]>()?;
named_args.insert(ident);
} else {
input.parse::<TokenTree>()?;
}
}
Ok(named_args)
}
fn take_int(read: &mut &str) -> String { fn take_int(read: &mut &str) -> String {
let mut int = String::new(); let mut int = String::new();
for (i, ch) in read.char_indices() { for (i, ch) in read.char_indices() {

View File

@ -127,3 +127,15 @@ fn test_void() {
#[error("...")] #[error("...")]
pub enum Error {} pub enum Error {}
} }
#[test]
fn test_mixed() {
#[derive(Error, Debug)]
#[error("a={a} :: b={} :: c={c} :: d={d}", 1, c = 2, d = 3)]
struct Error {
a: usize,
d: usize,
}
assert("a=0 :: b=1 :: c=2 :: d=3", Error { a: 0, d: 0 });
}