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::attr::Display;
use proc_macro2::TokenStream;
use proc_macro2::TokenTree;
use quote::{format_ident, quote_spanned};
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<'_> {
// Transform `"error {var}"` to `"error {}", var`.
pub fn expand_shorthand(&mut self, fields: &[Field]) {
if !self.args.is_empty() {
return;
}
let raw_args = self.args.clone();
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 fmt = self.fmt.value();
let mut read = fmt.as_str();
let mut out = String::new();
let mut args = TokenStream::new();
let mut args = self.args.clone();
let mut has_bonus_display = false;
let fields: Set<Member> = fields.iter().map(|f| f.member.clone()).collect();
while let Some(brace) = read.find('{') {
out += &read[..brace + 1];
@ -44,13 +45,24 @@ impl Display<'_> {
let ident = take_ident(&mut read);
Member::Named(Ident::new(&ident, span))
}
_ => return,
_ => continue,
};
let ident = match &member {
let local = match &member {
Member::Unnamed(index) => format_ident!("_{}", index),
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) {
has_bonus_display = true;
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 {
let mut int = String::new();
for (i, ch) in read.char_indices() {

View File

@ -127,3 +127,15 @@ fn test_void() {
#[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 });
}