use proc_macro2::TokenStream;
use quote::{format_ident, quote, ToTokens};
use std::{collections::BTreeSet, fmt, mem};
use syn::{
parenthesized,
parse::{Parse, ParseStream, Result},
punctuated::Punctuated,
token, Expr, Ident, Lit, LitBool, LitStr, Path, Type,
};
use crate::{ModuleName, SuffixKind, Transformation, UserInput};
macro_rules! join_syn_error {
($r1:expr, $r2:expr) => {
match ($r1, $r2) {
(Err(mut e1), Err(e2)) => {
e1.combine(e2);
Err(e1)
}
(Err(e), _) | (_, Err(e)) => Err(e),
(Ok(v1), Ok(v2)) => Ok((v1, v2)),
}
};
}
mod attr;
mod enum_impl;
mod field_container_impl;
mod field_impl;
mod named_struct_impl;
mod tuple_struct_field_impl;
mod tuple_struct_impl;
mod variant_impl;
pub(crate) use enum_impl::parse_enum;
pub(crate) use named_struct_impl::parse_named_struct;
pub(crate) use tuple_struct_impl::parse_tuple_struct;
use attr::{ErrorForLocation as _, ErrorLocation};
mod kw {
use syn::custom_keyword;
custom_keyword!(backtrace);
custom_keyword!(context);
custom_keyword!(crate_root);
custom_keyword!(display);
custom_keyword!(implicit);
custom_keyword!(module);
custom_keyword!(provide);
custom_keyword!(source);
custom_keyword!(transparent);
custom_keyword!(visibility);
custom_keyword!(whatever);
custom_keyword!(from);
custom_keyword!(exact);
custom_keyword!(generic);
custom_keyword!(name);
custom_keyword!(suffix);
custom_keyword!(opt);
}
#[derive(Default)]
struct SynErrors(Option<syn::Error>);
impl SynErrors {
fn push(&mut self, e: syn::Error) {
if let Some(prev_e) = &mut self.0 {
prev_e.combine(e);
} else {
self.0 = Some(e);
}
}
fn push_new(&mut self, span: impl quote::ToTokens, txt: impl fmt::Display) {
let e = syn::Error::new_spanned(span, txt);
self.push(e);
}
fn push_invalid<A>(&mut self, attr: A, location: ErrorLocation)
where
A: AttributeMeta,
{
self.push_new(attr, <A::Meta as attr::Attribute>::INVALID.on(location));
}
fn push_invalid_flag<A>(&mut self, attr: A, location: ErrorLocation)
where
A: FlagAttribute,
A::Meta: attr::FlagAttribute,
{
let i = if attr.has_arg() {
<A::Meta as attr::Attribute>::INVALID
} else {
<A::Meta as attr::FlagAttribute>::BASE_INVALID
};
self.push_new(attr, i.on(location));
}
fn finish<T>(self, value: T) -> syn::Result<T> {
match self.0 {
Some(e) => Err(e),
None => Ok(value),
}
}
fn assume_failed<T>(self) -> syn::Result<T> {
match self.0 {
Some(e) => Err(e),
None => unreachable!("No error recorded"),
}
}
}
#[derive(Default)]
struct DocCommentBuilder {
reached_end_of_doc_comment: bool,
content: String,
}
impl DocCommentBuilder {
fn push(&mut self, line: &str) {
if self.reached_end_of_doc_comment {
return;
}
let trimmed = line.trim();
if trimmed.is_empty() {
self.reached_end_of_doc_comment = true;
return;
}
if !self.content.is_empty() {
self.content.push(' ');
}
self.content.push_str(trimmed);
}
fn finish(self) -> Option<crate::DocComment> {
let Self { content, .. } = self;
if content.is_empty() {
None
} else {
let shorthand_names = extract_field_names(&content)
.map(|n| quote::format_ident!("{}", n))
.collect();
Some(crate::DocComment {
content,
shorthand_names,
})
}
}
}
enum Attribute {
Backtrace(Backtrace),
ContextFlag(ContextFlag),
ContextName(ContextName),
ContextSuffix(ContextSuffix),
CrateRoot(CrateRoot),
Display(Display),
DocComment(DocComment),
Implicit(Implicit),
Module(Module),
ProvideFlag(ProvideFlag),
ProvideExpression(ProvideExpression),
SourceFlag(SourceFlag),
SourceFrom(SourceFrom),
Transparent(Transparent),
Visibility(Visibility),
Whatever(Whatever),
}
fn syn_attrs(
attrs: &[syn::Attribute],
errors: &mut SynErrors,
mut f: impl FnMut(&mut SynErrors, Attribute),
) {
for attr in attrs {
if attr.path().is_ident("snafu") {
let attr_list = <Punctuated<NestedAttribute, token::Comma>>::parse_terminated;
let a = match attr.parse_args_with(attr_list) {
Ok(a) => a,
Err(e) => {
errors.push(e);
continue;
}
};
let mut f = |a| f(errors, a);
for pair in a.into_pairs() {
match pair.into_value() {
NestedAttribute::Backtrace(a) => f(Attribute::Backtrace(a)),
NestedAttribute::Context(a) => match a {
Context::Flag(a) => f(Attribute::ContextFlag(a)),
Context::Name(a) => f(Attribute::ContextName(a)),
Context::Suffix(a) => f(Attribute::ContextSuffix(a)),
},
NestedAttribute::CrateRoot(a) => f(Attribute::CrateRoot(a)),
NestedAttribute::Display(a) => f(Attribute::Display(a)),
NestedAttribute::Implicit(a) => f(Attribute::Implicit(a)),
NestedAttribute::Module(a) => f(Attribute::Module(a)),
NestedAttribute::Provide(a) => match a {
Provide::Flag(a) => f(Attribute::ProvideFlag(a)),
Provide::Expression(a) => f(Attribute::ProvideExpression(a)),
},
NestedAttribute::Source(a) => a.flatten(|a| match a {
Source::Flag(a) => f(Attribute::SourceFlag(a)),
Source::From(a) => f(Attribute::SourceFrom(a)),
}),
NestedAttribute::Transparent(a) => f(Attribute::Transparent(a)),
NestedAttribute::Visibility(a) => f(Attribute::Visibility(a)),
NestedAttribute::Whatever(a) => f(Attribute::Whatever(a)),
}
}
} else if attr.path().is_ident("doc") {
if let Ok(comment) = syn::parse2::<DocComment>(attr.meta.to_token_stream()) {
f(errors, Attribute::DocComment(comment));
}
}
}
}
struct AtMostOne<T, D> {
inner: AtMostOneInner<T>,
message: D,
}
impl<T, D> AtMostOne<T, D>
where
D: fmt::Display,
{
fn new(message: D) -> Self {
Self {
inner: Default::default(),
message,
}
}
fn push(&mut self, value: T)
where
T: quote::ToTokens,
{
let inner = mem::take(&mut self.inner);
self.inner = match inner {
AtMostOneInner::Empty => AtMostOneInner::One(value),
AtMostOneInner::One(_old_value) => {
let new_error = syn::Error::new_spanned(value, &self.message);
AtMostOneInner::Err(new_error)
}
AtMostOneInner::Err(mut error) => {
let new_error = syn::Error::new_spanned(value, &self.message);
error.combine(new_error);
AtMostOneInner::Err(error)
}
};
}
fn finish(self) -> syn::Result<Option<T>> {
match self.inner {
AtMostOneInner::Empty => Ok(None),
AtMostOneInner::One(value) => Ok(Some(value)),
AtMostOneInner::Err(error) => Err(error),
}
}
fn finish_default(self, errors: &mut SynErrors) -> Option<T> {
match self.finish() {
Ok(v) => v,
Err(e) => {
errors.push(e);
None
}
}
}
fn finish_exactly_one(self, span: impl quote::ToTokens) -> syn::Result<T> {
match self.inner {
AtMostOneInner::Empty => Err(syn::Error::new_spanned(span, &self.message)),
AtMostOneInner::One(value) => Ok(value),
AtMostOneInner::Err(error) => Err(error),
}
}
}
impl<T> AtMostOne<T, attr::ErrorWithLocation<attr::DuplicateAttribute>> {
fn attribute<A>(_attr: A, location: ErrorLocation) -> Self
where
A: attr::Attribute,
{
Self::new(A::DUPLICATE.on(location))
}
}
impl<T, D> Extend<T> for AtMostOne<T, D>
where
T: quote::ToTokens,
D: fmt::Display,
{
fn extend<I>(&mut self, iter: I)
where
I: IntoIterator<Item = T>,
{
for value in iter {
self.push(value);
}
}
}
enum AtMostOneInner<T> {
Empty,
One(T),
Err(syn::Error),
}
impl<T> Default for AtMostOneInner<T> {
fn default() -> Self {
AtMostOneInner::Empty
}
}
struct Sidecar<S, T>(S, T);
impl<S, T> quote::ToTokens for Sidecar<S, T>
where
S: quote::ToTokens,
{
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
self.0.to_tokens(tokens);
}
}
enum NestedAttribute {
Backtrace(Backtrace),
Context(Context),
CrateRoot(CrateRoot),
Display(Display),
Implicit(Implicit),
Module(Module),
Provide(Provide),
Source(NestedSource),
Transparent(Transparent),
Visibility(Visibility),
Whatever(Whatever),
}
impl Parse for NestedAttribute {
fn parse(input: ParseStream) -> Result<Self> {
let lookahead = input.lookahead1();
if lookahead.peek(kw::backtrace) {
input.parse().map(NestedAttribute::Backtrace)
} else if lookahead.peek(kw::context) {
input.parse().map(NestedAttribute::Context)
} else if lookahead.peek(kw::crate_root) {
input.parse().map(NestedAttribute::CrateRoot)
} else if lookahead.peek(kw::display) {
input.parse().map(NestedAttribute::Display)
} else if lookahead.peek(kw::implicit) {
input.parse().map(NestedAttribute::Implicit)
} else if lookahead.peek(kw::module) {
input.parse().map(NestedAttribute::Module)
} else if lookahead.peek(kw::provide) {
input.parse().map(NestedAttribute::Provide)
} else if lookahead.peek(kw::source) {
input.parse().map(NestedAttribute::Source)
} else if lookahead.peek(kw::transparent) {
input.parse().map(NestedAttribute::Transparent)
} else if lookahead.peek(kw::visibility) {
input.parse().map(NestedAttribute::Visibility)
} else if lookahead.peek(kw::whatever) {
input.parse().map(NestedAttribute::Whatever)
} else {
Err(lookahead.error())
}
}
}
struct Backtrace {
backtrace_token: kw::backtrace,
arg: MaybeArg<LitBool>,
}
impl Parse for Backtrace {
fn parse(input: ParseStream) -> Result<Self> {
Ok(Self {
backtrace_token: input.parse()?,
arg: input.parse()?,
})
}
}
impl ToTokens for Backtrace {
fn to_tokens(&self, tokens: &mut TokenStream) {
self.backtrace_token.to_tokens(tokens);
self.arg.to_tokens(tokens);
}
}
enum Context {
Flag(ContextFlag),
Name(ContextName),
Suffix(ContextSuffix),
}
impl Parse for Context {
fn parse(input: ParseStream) -> Result<Self> {
let context_token = input.parse()?;
let arg = input.parse::<MaybeArg<ContextArg>>()?;
Ok(match arg {
MaybeArg::None => Context::Flag(ContextFlag {
context_token,
arg: MaybeArg::None,
}),
MaybeArg::Some {
paren_token,
content,
} => match content {
ContextArg::Flag { value } => Context::Flag(ContextFlag {
context_token,
arg: MaybeArg::Some {
paren_token,
content: value,
},
}),
ContextArg::Name {
name_token,
paren_token: inner_paren_token,
name,
} => Context::Name(ContextName {
context_token,
paren_token,
name_token,
inner_paren_token,
name,
}),
ContextArg::Suffix {
suffix_token,
paren_token: inner_paren_token,
suffix,
} => Context::Suffix(ContextSuffix {
context_token,
paren_token,
suffix_token,
inner_paren_token,
suffix,
}),
},
})
}
}
struct ContextFlag {
context_token: kw::context,
arg: MaybeArg<LitBool>,
}
impl ToTokens for ContextFlag {
fn to_tokens(&self, tokens: &mut TokenStream) {
self.context_token.to_tokens(tokens);
self.arg.to_tokens(tokens);
}
}
struct ContextName {
context_token: kw::context,
paren_token: token::Paren,
name_token: kw::name,
inner_paren_token: token::Paren,
name: Ident,
}
impl ToTokens for ContextName {
fn to_tokens(&self, tokens: &mut TokenStream) {
self.context_token.to_tokens(tokens);
self.paren_token.surround(tokens, |tokens| {
self.name_token.to_tokens(tokens);
self.inner_paren_token.surround(tokens, |tokens| {
self.name.to_tokens(tokens);
});
});
}
}
struct ContextSuffix {
context_token: kw::context,
paren_token: token::Paren,
suffix_token: kw::suffix,
inner_paren_token: token::Paren,
suffix: SuffixArg,
}
impl ToTokens for ContextSuffix {
fn to_tokens(&self, tokens: &mut TokenStream) {
self.context_token.to_tokens(tokens);
self.paren_token.surround(tokens, |tokens| {
self.suffix_token.to_tokens(tokens);
self.inner_paren_token.surround(tokens, |tokens| {
self.suffix.to_tokens(tokens);
});
});
}
}
enum ContextArg {
Flag {
value: LitBool,
},
Name {
name_token: kw::name,
paren_token: token::Paren,
name: Ident,
},
Suffix {
suffix_token: kw::suffix,
paren_token: token::Paren,
suffix: SuffixArg,
},
}
impl Parse for ContextArg {
fn parse(input: ParseStream) -> Result<Self> {
let lookahead = input.lookahead1();
if lookahead.peek(LitBool) {
Ok(ContextArg::Flag {
value: input.parse()?,
})
} else if lookahead.peek(kw::suffix) {
let content;
Ok(ContextArg::Suffix {
suffix_token: input.parse()?,
paren_token: parenthesized!(content in input),
suffix: content.parse()?,
})
} else if lookahead.peek(kw::name) {
let content;
Ok(ContextArg::Name {
name_token: input.parse()?,
paren_token: parenthesized!(content in input),
name: content.parse()?,
})
} else {
Err(lookahead.error())
}
}
}
enum SuffixArg {
Flag { value: LitBool },
Suffix { suffix: Ident },
}
impl SuffixArg {
fn into_suffix_kind(self) -> SuffixKind {
match self {
SuffixArg::Flag { value } => {
if value.value {
SuffixKind::Default
} else {
SuffixKind::None
}
}
SuffixArg::Suffix { suffix } => SuffixKind::Some(suffix),
}
}
}
impl Parse for SuffixArg {
fn parse(input: ParseStream) -> Result<Self> {
let lookahead = input.lookahead1();
if lookahead.peek(LitBool) {
Ok(SuffixArg::Flag {
value: input.parse()?,
})
} else if lookahead.peek(Ident) {
Ok(SuffixArg::Suffix {
suffix: input.parse()?,
})
} else {
Err(lookahead.error())
}
}
}
impl ToTokens for SuffixArg {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
SuffixArg::Flag { value } => {
value.to_tokens(tokens);
}
SuffixArg::Suffix { suffix } => {
suffix.to_tokens(tokens);
}
}
}
}
struct CrateRoot {
crate_root_token: kw::crate_root,
paren_token: token::Paren,
arg: Path,
}
fn into_crate_root(crate_root: Option<CrateRoot>) -> UserInput {
match crate_root {
Some(cr) => Box::new(cr.arg),
None => Box::new(quote! { ::snafu }),
}
}
impl Parse for CrateRoot {
fn parse(input: ParseStream) -> Result<Self> {
let content;
Ok(Self {
crate_root_token: input.parse()?,
paren_token: parenthesized!(content in input),
arg: content.parse()?,
})
}
}
impl ToTokens for CrateRoot {
fn to_tokens(&self, tokens: &mut TokenStream) {
self.crate_root_token.to_tokens(tokens);
self.paren_token.surround(tokens, |tokens| {
self.arg.to_tokens(tokens);
});
}
}
struct Display {
display_token: kw::display,
paren_token: token::Paren,
args: Punctuated<Expr, token::Comma>,
}
impl Display {
fn into_display(self) -> crate::Display {
let exprs: Vec<_> = self.args.into_iter().collect();
let mut shorthand_names = BTreeSet::new();
let mut assigned_names = BTreeSet::new();
if let Some((Expr::Lit(l), args)) = exprs.split_first() {
if let Lit::Str(s) = &l.lit {
let format_str = s.value();
let names = extract_field_names(&format_str).map(|n| format_ident!("{}", n));
shorthand_names.extend(names);
}
for arg in args {
if let Expr::Assign(a) = arg {
if let Expr::Path(p) = &*a.left {
assigned_names.extend(p.path.get_ident().cloned());
}
}
}
}
crate::Display {
exprs,
shorthand_names,
assigned_names,
}
}
}
pub(crate) fn extract_field_names(mut s: &str) -> impl Iterator<Item = &str> {
std::iter::from_fn(move || loop {
let open_curly = s.find('{')?;
s = &s[open_curly + '{'.len_utf8()..];
if s.starts_with('{') {
s = &s['{'.len_utf8()..];
continue;
}
let end_curly = s.find('}')?;
let format_contents = &s[..end_curly];
let name = match format_contents.find(':') {
Some(idx) => &format_contents[..idx],
None => format_contents,
};
if name.is_empty() {
continue;
}
return Some(name);
})
}
impl Parse for Display {
fn parse(input: ParseStream) -> Result<Self> {
let content;
Ok(Self {
display_token: input.parse()?,
paren_token: parenthesized!(content in input),
args: Punctuated::parse_terminated(&content)?,
})
}
}
impl ToTokens for Display {
fn to_tokens(&self, tokens: &mut TokenStream) {
self.display_token.to_tokens(tokens);
self.paren_token.surround(tokens, |tokens| {
self.args.to_tokens(tokens);
});
}
}
struct DocComment {
_doc_ident: Ident,
_eq_token: token::Eq,
str: LitStr,
}
impl Parse for DocComment {
fn parse(input: ParseStream) -> Result<Self> {
Ok(Self {
_doc_ident: input.parse()?,
_eq_token: input.parse()?,
str: input.parse()?,
})
}
}
struct Implicit {
implicit_token: kw::implicit,
arg: MaybeArg<LitBool>,
}
impl Parse for Implicit {
fn parse(input: ParseStream) -> Result<Self> {
Ok(Self {
implicit_token: input.parse()?,
arg: input.parse()?,
})
}
}
impl ToTokens for Implicit {
fn to_tokens(&self, tokens: &mut TokenStream) {
self.implicit_token.to_tokens(tokens);
self.arg.to_tokens(tokens);
}
}
struct Module {
module_token: kw::module,
arg: MaybeArg<Ident>,
}
impl Module {
fn into_value(self) -> ModuleName {
match self.arg.into_option() {
None => ModuleName::Default,
Some(name) => ModuleName::Custom(name),
}
}
}
impl Parse for Module {
fn parse(input: ParseStream) -> Result<Self> {
Ok(Self {
module_token: input.parse()?,
arg: input.parse()?,
})
}
}
impl ToTokens for Module {
fn to_tokens(&self, tokens: &mut TokenStream) {
self.module_token.to_tokens(tokens);
self.arg.to_tokens(tokens);
}
}
enum Provide {
Flag(ProvideFlag),
Expression(ProvideExpression),
}
impl Parse for Provide {
fn parse(input: ParseStream) -> Result<Self> {
let provide_token = input.parse()?;
let arg = input.parse::<MaybeArg<ProvideArg>>()?;
Ok(match arg {
MaybeArg::None => Provide::Flag(ProvideFlag {
provide_token,
value: MaybeArg::None,
}),
MaybeArg::Some {
paren_token,
content,
} => match content {
ProvideArg::Flag { value } => Provide::Flag(ProvideFlag {
provide_token,
value: MaybeArg::Some {
paren_token,
content: value,
},
}),
ProvideArg::Expression {
flags,
ty,
arrow,
expr,
} => {
let p = ProvideExpression {
provide_token,
paren_token,
flags,
ty,
arrow,
expr,
};
Provide::Expression(p)
}
},
})
}
}
struct ProvideFlag {
provide_token: kw::provide,
value: MaybeArg<LitBool>,
}
impl ToTokens for ProvideFlag {
fn to_tokens(&self, tokens: &mut TokenStream) {
self.provide_token.to_tokens(tokens);
self.value.to_tokens(tokens);
}
}
struct ProvideExpression {
provide_token: kw::provide,
paren_token: token::Paren,
flags: ProvideFlags,
ty: Type,
arrow: token::FatArrow,
expr: Expr,
}
impl ProvideExpression {
fn into_provide(self) -> crate::Provide {
crate::Provide {
is_opt: self.flags.is_opt(),
is_ref: self.flags.is_ref(),
ty: self.ty,
expr: self.expr,
}
}
}
impl ToTokens for ProvideExpression {
fn to_tokens(&self, tokens: &mut TokenStream) {
self.provide_token.to_tokens(tokens);
self.paren_token.surround(tokens, |tokens| {
self.flags.to_tokens(tokens);
self.ty.to_tokens(tokens);
self.arrow.to_tokens(tokens);
self.expr.to_tokens(tokens);
});
}
}
enum ProvideArg {
Flag {
value: LitBool,
},
Expression {
flags: ProvideFlags,
ty: Type,
arrow: token::FatArrow,
expr: Expr,
},
}
impl Parse for ProvideArg {
fn parse(input: ParseStream) -> Result<Self> {
if input.peek(LitBool) {
Ok(ProvideArg::Flag {
value: input.parse()?,
})
} else {
Ok(ProvideArg::Expression {
flags: input.parse()?,
ty: input.parse()?,
arrow: input.parse()?,
expr: input.parse()?,
})
}
}
}
struct ProvideFlags(Punctuated<ProvideFlagInner, token::Comma>);
impl ProvideFlags {
fn is_opt(&self) -> bool {
self.0.iter().any(ProvideFlagInner::is_opt)
}
fn is_ref(&self) -> bool {
self.0.iter().any(ProvideFlagInner::is_ref)
}
}
impl Parse for ProvideFlags {
fn parse(input: ParseStream) -> Result<Self> {
let mut flags = Punctuated::new();
while ProvideFlagInner::peek(input) {
flags.push_value(input.parse()?);
flags.push_punct(input.parse()?);
}
Ok(Self(flags))
}
}
impl ToTokens for ProvideFlags {
fn to_tokens(&self, tokens: &mut TokenStream) {
self.0.to_tokens(tokens)
}
}
enum ProvideFlagInner {
Opt(kw::opt),
Ref(token::Ref),
}
impl ProvideFlagInner {
fn peek(input: ParseStream) -> bool {
input.peek(kw::opt) || input.peek(token::Ref)
}
fn is_opt(&self) -> bool {
matches!(self, ProvideFlagInner::Opt(_))
}
fn is_ref(&self) -> bool {
matches!(self, ProvideFlagInner::Ref(_))
}
}
impl Parse for ProvideFlagInner {
fn parse(input: ParseStream) -> Result<Self> {
let lookahead = input.lookahead1();
if lookahead.peek(kw::opt) {
input.parse().map(ProvideFlagInner::Opt)
} else if lookahead.peek(token::Ref) {
input.parse().map(ProvideFlagInner::Ref)
} else {
Err(lookahead.error())
}
}
}
impl ToTokens for ProvideFlagInner {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
ProvideFlagInner::Opt(v) => v.to_tokens(tokens),
ProvideFlagInner::Ref(v) => v.to_tokens(tokens),
}
}
}
enum Source {
Flag(SourceFlag),
From(SourceFrom),
}
struct SourceFlag {
source_token: kw::source,
value: MaybeArg<LitBool>,
}
impl ToTokens for SourceFlag {
fn to_tokens(&self, tokens: &mut TokenStream) {
self.source_token.to_tokens(tokens);
self.value.to_tokens(tokens);
}
}
#[derive(Clone)]
struct SourceFrom {
source_token: kw::source,
paren_token: token::Paren,
value: SourceFromArg,
}
fn into_transformation(
source_from: Option<SourceFrom>,
target_ty: Type,
default_from_is_generic: bool,
) -> Transformation {
match source_from {
Some(SourceFrom { value, .. }) => match value.value {
SourceFromValue::Exact(_) => Transformation::None {
target_ty,
from_is_generic: false,
},
SourceFromValue::Generic(_) => Transformation::None {
target_ty,
from_is_generic: true,
},
SourceFromValue::Transform(SourceFromTransform { r#type, expr, .. }) => {
Transformation::Transform {
source_ty: r#type,
target_ty,
expr,
}
}
},
None => Transformation::None {
target_ty,
from_is_generic: default_from_is_generic,
},
}
}
impl ToTokens for SourceFrom {
fn to_tokens(&self, tokens: &mut TokenStream) {
self.source_token.to_tokens(tokens);
self.paren_token.surround(tokens, |tokens| {
self.value.to_tokens(tokens);
});
}
}
struct NestedSource {
source_token: kw::source,
args: MaybeArg<Punctuated<SourceArg, token::Comma>>,
}
impl NestedSource {
fn flatten(self, mut f: impl FnMut(Source)) {
let source_token = self.source_token;
match self.args {
MaybeArg::None => f(Source::Flag(SourceFlag {
source_token,
value: MaybeArg::None,
})),
MaybeArg::Some {
paren_token,
content,
} => {
for sa in content {
let s = match sa {
SourceArg::Flag { value } => Source::Flag(SourceFlag {
source_token,
value: MaybeArg::Some {
paren_token,
content: value,
},
}),
SourceArg::From(value) => Source::From(SourceFrom {
source_token,
paren_token,
value,
}),
};
f(s);
}
}
}
}
}
impl Parse for NestedSource {
fn parse(input: ParseStream) -> Result<Self> {
Ok(Self {
source_token: input.parse()?,
args: MaybeArg::parse_with(input, Punctuated::parse_terminated)?,
})
}
}
enum SourceArg {
Flag { value: LitBool },
From(SourceFromArg),
}
impl Parse for SourceArg {
fn parse(input: ParseStream) -> Result<Self> {
let lookahead = input.lookahead1();
if lookahead.peek(LitBool) {
Ok(SourceArg::Flag {
value: input.parse()?,
})
} else if lookahead.peek(kw::from) {
input.parse().map(SourceArg::From)
} else {
Err(lookahead.error())
}
}
}
#[derive(Clone)]
struct SourceFromArg {
from_token: kw::from,
paren_token: token::Paren,
value: SourceFromValue,
}
impl Parse for SourceFromArg {
fn parse(input: ParseStream) -> Result<Self> {
let content;
Ok(SourceFromArg {
from_token: input.parse()?,
paren_token: parenthesized!(content in input),
value: content.parse()?,
})
}
}
impl ToTokens for SourceFromArg {
fn to_tokens(&self, tokens: &mut TokenStream) {
self.from_token.to_tokens(tokens);
self.paren_token.surround(tokens, |tokens| {
self.value.to_tokens(tokens);
});
}
}
#[derive(Clone)]
enum SourceFromValue {
Exact(kw::exact),
Generic(kw::generic),
Transform(SourceFromTransform),
}
impl Parse for SourceFromValue {
fn parse(input: ParseStream) -> Result<Self> {
if input.peek(kw::exact) {
input.parse().map(Self::Exact)
} else if input.peek(kw::generic) {
input.parse().map(Self::Generic)
} else {
let span = input.span();
let txt = "expected one of: `exact`, `generic` or a type followed by a comma and an expression";
input.parse().map(Self::Transform).map_err(|e| {
let mut e1 = syn::Error::new(span, txt);
e1.combine(e);
e1
})
}
}
}
impl ToTokens for SourceFromValue {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
SourceFromValue::Exact(exact) => exact.to_tokens(tokens),
SourceFromValue::Generic(generic) => generic.to_tokens(tokens),
SourceFromValue::Transform(transform) => transform.to_tokens(tokens),
}
}
}
#[derive(Clone)]
struct SourceFromTransform {
r#type: Type,
comma_token: token::Comma,
expr: Expr,
}
impl Parse for SourceFromTransform {
fn parse(input: ParseStream) -> Result<Self> {
Ok(Self {
r#type: input.parse()?,
comma_token: input.parse()?,
expr: input.parse()?,
})
}
}
impl ToTokens for SourceFromTransform {
fn to_tokens(&self, tokens: &mut TokenStream) {
self.r#type.to_tokens(tokens);
self.comma_token.to_tokens(tokens);
self.expr.to_tokens(tokens);
}
}
struct Transparent {
transparent_token: kw::transparent,
arg: MaybeArg<LitBool>,
}
impl Parse for Transparent {
fn parse(input: ParseStream) -> Result<Self> {
Ok(Self {
transparent_token: input.parse()?,
arg: input.parse()?,
})
}
}
impl ToTokens for Transparent {
fn to_tokens(&self, tokens: &mut TokenStream) {
self.transparent_token.to_tokens(tokens);
self.arg.to_tokens(tokens);
}
}
struct Visibility {
visibility_token: kw::visibility,
visibility: MaybeArg<syn::Visibility>,
}
impl Visibility {
fn into_arbitrary(self) -> Box<dyn ToTokens> {
self.visibility
.into_option()
.map_or_else(super::private_visibility, |v| Box::new(v))
}
}
impl Parse for Visibility {
fn parse(input: ParseStream) -> Result<Self> {
Ok(Self {
visibility_token: input.parse()?,
visibility: input.parse()?,
})
}
}
impl ToTokens for Visibility {
fn to_tokens(&self, tokens: &mut TokenStream) {
self.visibility_token.to_tokens(tokens);
self.visibility.to_tokens(tokens);
}
}
struct Whatever {
whatever_token: kw::whatever,
}
impl Parse for Whatever {
fn parse(input: ParseStream) -> Result<Self> {
Ok(Self {
whatever_token: input.parse()?,
})
}
}
impl ToTokens for Whatever {
fn to_tokens(&self, tokens: &mut TokenStream) {
self.whatever_token.to_tokens(tokens);
}
}
enum MaybeArg<T> {
None,
Some {
paren_token: token::Paren,
content: T,
},
}
impl<T> MaybeArg<T> {
fn to_option(&self) -> Option<&T> {
match self {
MaybeArg::None => None,
MaybeArg::Some { content, .. } => Some(content),
}
}
fn into_option(self) -> Option<T> {
match self {
MaybeArg::None => None,
MaybeArg::Some { content, .. } => Some(content),
}
}
fn parse_with<F>(input: ParseStream<'_>, parser: F) -> Result<Self>
where
F: FnOnce(ParseStream<'_>) -> Result<T>,
{
let lookahead = input.lookahead1();
if lookahead.peek(token::Paren) {
let content;
Ok(MaybeArg::Some {
paren_token: parenthesized!(content in input),
content: parser(&content)?,
})
} else {
Ok(MaybeArg::None)
}
}
}
impl<T: Parse> Parse for MaybeArg<T> {
fn parse(input: ParseStream) -> Result<Self> {
Self::parse_with(input, Parse::parse)
}
}
impl<T: ToTokens> ToTokens for MaybeArg<T> {
fn to_tokens(&self, tokens: &mut TokenStream) {
if let MaybeArg::Some {
paren_token,
content,
} = self
{
paren_token.surround(tokens, |tokens| {
content.to_tokens(tokens);
});
}
}
}
trait AttributeMeta: quote::ToTokens {
type Meta: attr::Attribute;
}
trait FlagAttribute: AttributeMeta
where
Self::Meta: attr::FlagAttribute,
{
fn arg(&self) -> &MaybeArg<LitBool>;
fn has_arg(&self) -> bool {
matches!(self.arg(), MaybeArg::Some { .. })
}
fn is_enabled(&self) -> bool {
self.arg().to_option().map_or(true, |v| v.value)
}
}
macro_rules! def_attributes {
($($name:ident),* $(,)?) => {
$(
impl AttributeMeta for $name {
type Meta = attr::$name;
}
)*
};
}
def_attributes![
Backtrace,
ContextFlag,
ContextName,
ContextSuffix,
CrateRoot,
Display,
Implicit,
Module,
ProvideExpression,
ProvideFlag,
SourceFlag,
SourceFrom,
Transparent,
Visibility,
Whatever,
];
macro_rules! def_flag_attributes {
($(($name:ident, $arg:ident)),* $(,)?) => {
$(
impl FlagAttribute for $name {
fn arg(&self) -> &MaybeArg<LitBool> {
&self.$arg
}
}
)*
};
}
def_flag_attributes![
(Backtrace, arg),
(ContextFlag, arg),
(Implicit, arg),
(ProvideFlag, value),
(SourceFlag, value),
(Transparent, arg),
];
#[cfg(test)]
mod test {
use super::*;
fn names(s: &str) -> Vec<&str> {
extract_field_names(s).collect::<Vec<_>>()
}
#[test]
fn ignores_positional_arguments() {
assert_eq!(names("{}"), [] as [&str; 0]);
}
#[test]
fn finds_named_argument() {
assert_eq!(names("{a}"), ["a"]);
}
#[test]
fn finds_multiple_named_arguments() {
assert_eq!(names("{a} {b}"), ["a", "b"]);
}
#[test]
fn ignores_escaped_braces() {
assert_eq!(names("{{a}}"), [] as [&str; 0]);
}
#[test]
fn finds_named_arguments_around_escaped() {
assert_eq!(names("{a} {{b}} {c}"), ["a", "c"]);
}
#[test]
fn ignores_format_spec() {
assert_eq!(names("{a:?}"), ["a"]);
}
}