use proc_macro2::{Delimiter, Group, Literal, Punct, Spacing, Span, TokenStream, TokenTree};
use quote::{format_ident, quote, quote_spanned, ToTokens};
use std::collections::BTreeSet as Set;
use syn::parse::discouraged::Speculative;
use syn::parse::{End, ParseStream};
use syn::{
braced, bracketed, parenthesized, token, Attribute, Error, ExprPath, Ident, Index, LitFloat,
LitInt, LitStr, Meta, Result, Token,
};
pub struct Attrs<'a> {
pub display: Option<Display<'a>>,
pub source: Option<Source<'a>>,
pub backtrace: Option<&'a Attribute>,
pub from: Option<From<'a>>,
pub transparent: Option<Transparent<'a>>,
pub fmt: Option<Fmt<'a>>,
}
#[ derive(Clone)]
pub struct Display<'a> {
pub original: &'a Attribute,
pub fmt: LitStr,
pub args: TokenStream,
pub requires_fmt_machinery: bool,
pub has_bonus_display: bool,
pub infinite_recursive: bool,
pub implied_bounds: Set<(usize, Trait )>,
pub bindings: Vec<(Ident, TokenStream)>,
}
#[ derive(Copy, Clone)]
pub struct Source<'a> {
pub original: &'a Attribute,
pub span: Span,
}
#[ derive(Copy, Clone)]
pub struct From<'a> {
pub original: &'a Attribute,
pub span: Span,
}
#[ derive(Copy, Clone)]
pub struct Transparent<'a> {
pub original: &'a Attribute,
pub span: Span,
}
#[ derive(Clone)]
pub struct Fmt<'a> {
pub original: &'a Attribute,
pub path: ExprPath,
}
#[ derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
pub enum Trait {
Debug,
Display,
Octal,
LowerHex,
UpperHex,
Pointer,
Binary,
LowerExp,
UpperExp,
}
pub fn get(input: &[Attribute]) -> Result<Attrs> {
let mut attrs = Attrs {
display: None,
source: None,
backtrace: None,
from: None,
transparent: None,
fmt: None,
};
for attr in input {
if attr.path().is_ident("error" ) {
parse_error_attribute(&mut attrs, attr)?;
} else if attr.path().is_ident("source" ) {
attr.meta.require_path_only()?;
if attrs.source.is_some() {
return Err(Error::new_spanned(attr, "duplicate #[source] attribute" ));
}
let span = (attr.pound_token.span)
.join(attr.bracket_token.span.join())
.unwrap_or(attr.path().get_ident().unwrap().span());
attrs.source = Some(Source {
original: attr,
span,
});
} else if attr.path().is_ident("backtrace" ) {
attr.meta.require_path_only()?;
if attrs.backtrace.is_some() {
return Err(Error::new_spanned(attr, "duplicate #[backtrace] attribute" ));
}
attrs.backtrace = Some(attr);
} else if attr.path().is_ident("from" ) {
match attr.meta {
Meta::Path(_) => {}
Meta::List(_) | Meta::NameValue(_) => {
// Assume this is meant for derive_more crate or something.
continue ;
}
}
if attrs.from.is_some() {
return Err(Error::new_spanned(attr, "duplicate #[from] attribute" ));
}
let span = (attr.pound_token.span)
.join(attr.bracket_token.span.join())
.unwrap_or(attr.path().get_ident().unwrap().span());
attrs.from = Some(From {
original: attr,
span,
});
}
}
Ok(attrs)
}
fn parse_error_attribute<'a>(attrs: &mut Attrs<' a>, attr: &'a Attribute) -> Result<()> {
mod kw {
syn::custom_keyword!(transparent);
syn::custom_keyword!(fmt);
}
attr.parse_args_with(|input: ParseStream| {
let lookahead = input.lookahead1();
let fmt = if lookahead.peek(LitStr) {
input.parse::<LitStr>()?
} else if lookahead.peek(kw::transparent) {
let kw: kw::transparent = input.parse()?;
if attrs.transparent.is_some() {
return Err(Error::new_spanned(
attr,
"duplicate #[error(transparent)] attribute" ,
));
}
attrs.transparent = Some(Transparent {
original: attr,
span: kw.span,
});
return Ok(());
} else if lookahead.peek(kw::fmt) {
input.parse::<kw::fmt>()?;
input.parse::<Token![=]>()?;
let path: ExprPath = input.parse()?;
if attrs.fmt.is_some() {
return Err(Error::new_spanned(
attr,
"duplicate #[error(fmt = ...)] attribute" ,
));
}
attrs.fmt = Some(Fmt {
original: attr,
path,
});
return Ok(());
} else {
return Err(lookahead.error());
};
let args = if input.is_empty() || input.peek(Token![,]) && input.peek2(End) {
input.parse::<Option<Token![,]>>()?;
TokenStream::new()
} else {
parse_token_expr(input, false )?
};
let requires_fmt_machinery = !args.is_empty();
let display = Display {
original: attr,
fmt,
args,
requires_fmt_machinery,
has_bonus_display: false ,
infinite_recursive: false ,
implied_bounds: Set::new(),
bindings: Vec::new(),
};
if attrs.display.is_some() {
return Err(Error::new_spanned(
attr,
"only one #[error(...)] attribute is allowed" ,
));
}
attrs.display = Some(display);
Ok(())
})
}
fn parse_token_expr(input: ParseStream, mut begin_expr: bool) -> Result<TokenStream> {
let mut tokens = Vec::new();
while !input.is_empty() {
if input.peek(token::Group) {
let group: TokenTree = input.parse()?;
tokens.push(group);
begin_expr = false ;
continue ;
}
if begin_expr && input.peek(Token![.]) {
if input.peek2(Ident) {
input.parse::<Token![.]>()?;
begin_expr = false ;
continue ;
} else if input.peek2(LitInt) {
input.parse::<Token![.]>()?;
let int: Index = input.parse()?;
tokens.push({
let ident = format_ident!("_{}" , int.index, span = int.span);
TokenTree::Ident(ident)
});
begin_expr = false ;
continue ;
} else if input.peek2(LitFloat) {
let ahead = input.fork();
ahead.parse::<Token![.]>()?;
let float: LitFloat = ahead.parse()?;
let repr = float.to_string();
let mut indices = repr.split('.' ).map(syn::parse_str::<Index>);
if let (Some(Ok(first)), Some(Ok(second)), None) =
(indices.next(), indices.next(), indices.next())
{
input.advance_to(&ahead);
tokens.push({
let ident = format_ident!("_{}" , first, span = float.span());
TokenTree::Ident(ident)
});
tokens.push({
let mut punct = Punct::new('.' , Spacing::Alone);
punct.set_span(float.span());
TokenTree::Punct(punct)
});
tokens.push({
let mut literal = Literal::u32_unsuffixed(second.index);
literal.set_span(float.span());
TokenTree::Literal(literal)
});
begin_expr = false ;
continue ;
}
}
}
begin_expr = input.peek(Token![break ])
|| input.peek(Token![continue ])
|| input.peek(Token![if ])
|| input.peek(Token![in ])
|| input.peek(Token![match ])
|| input.peek(Token![mut ])
|| input.peek(Token![return ])
|| input.peek(Token![while ])
|| input.peek(Token![+])
|| input.peek(Token![&])
|| input.peek(Token![!])
|| input.peek(Token![^])
|| input.peek(Token![,])
|| input.peek(Token![/])
|| input.peek(Token![=])
|| input.peek(Token![>])
|| input.peek(Token![<])
|| input.peek(Token![|])
|| input.peek(Token![%])
|| input.peek(Token![;])
|| input.peek(Token![*])
|| input.peek(Token![-]);
let token: TokenTree = if input.peek(token::Paren) {
let content;
let delimiter = parenthesized!(content in input);
let nested = parse_token_expr(&content, true )?;
let mut group = Group::new(Delimiter::Parenthesis, nested);
group.set_span(delimiter.span.join());
TokenTree::Group(group)
} else if input.peek(token::Brace) {
let content;
let delimiter = braced!(content in input);
let nested = parse_token_expr(&content, true )?;
let mut group = Group::new(Delimiter::Brace, nested);
group.set_span(delimiter.span.join());
TokenTree::Group(group)
} else if input.peek(token::Bracket) {
let content;
let delimiter = bracketed!(content in input);
let nested = parse_token_expr(&content, true )?;
let mut group = Group::new(Delimiter::Bracket, nested);
group.set_span(delimiter.span.join());
TokenTree::Group(group)
} else {
input.parse()?
};
tokens.push(token);
}
Ok(TokenStream::from_iter(tokens))
}
impl ToTokens for Display<'_> {
fn to_tokens(&self , tokens: &mut TokenStream) {
if self .infinite_recursive {
let span = self .fmt.span();
tokens.extend(quote_spanned! {span=>
#[ warn(unconditional_recursion)]
fn _fmt() { _fmt() }
});
}
let fmt = &self .fmt;
let args = &self .args;
// Currently `write!(f, "text")` produces less efficient code than
// `f.write_str("text")`. We recognize the case when the format string
// has no braces and no interpolated values, and generate simpler code.
let write = if self .requires_fmt_machinery {
quote! {
::core::write!(__formatter, #fmt #args )
}
} else {
quote! {
__formatter.write_str(#fmt )
}
};
tokens.extend(if self .bindings.is_empty() {
write
} else {
let locals = self .bindings.iter().map(|(local, _value)| local);
let values = self .bindings.iter().map(|(_local, value)| value);
quote! {
match (#( #values ,)*) {
(#( #locals ,)*) => #write
}
}
});
}
}
impl ToTokens for Trait {
fn to_tokens(&self , tokens: &mut TokenStream) {
let trait_name = match self {
Trait ::Debug => "Debug" ,
Trait ::Display => "Display" ,
Trait ::Octal => "Octal" ,
Trait ::LowerHex => "LowerHex" ,
Trait ::UpperHex => "UpperHex" ,
Trait ::Pointer => "Pointer" ,
Trait ::Binary => "Binary" ,
Trait ::LowerExp => "LowerExp" ,
Trait ::UpperExp => "UpperExp" ,
};
let ident = Ident::new(trait_name, Span::call_site());
tokens.extend(quote!(::core::fmt::#ident ));
}
}
Messung V0.5 in Prozent C=77 H=93 G=85
¤ Dauer der Verarbeitung: 0.13 Sekunden
(vorverarbeitet am 2026-06-19)
¤
*© Formatika GbR, Deutschland