use std::collections::BTreeSet;
use crate::{ModuleName, ProvideKind, SnafuAttribute};
use proc_macro2::TokenStream;
use quote::{format_ident, ToTokens};
use syn::{
parenthesized,
parse::{Parse, ParseStream, Result},
punctuated::Punctuated,
token, Expr, Ident, Lit, LitBool, LitStr, Path, Type,
};
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!(visibility);
custom_keyword!(whatever);
custom_keyword!(from);
custom_keyword!(suffix);
custom_keyword!(chain);
custom_keyword!(opt);
custom_keyword!(priority);
}
pub(crate) fn attributes_from_syn(
attrs: Vec<syn::Attribute>,
) -> super::MultiSynResult<Vec<SnafuAttribute>> {
let mut ours = Vec::new();
let mut errs = Vec::new();
for attr in attrs {
if attr.path.is_ident("snafu") {
let attr_list = Punctuated::<Attribute, token::Comma>::parse_terminated;
match attr.parse_args_with(attr_list) {
Ok(attrs) => {
ours.extend(attrs.into_iter().map(Into::into));
}
Err(e) => errs.push(e),
}
} else if attr.path.is_ident("doc") {
if let Ok(comment) = syn::parse2::<DocComment>(attr.tokens) {
ours.push(comment.into());
}
}
}
for attr in &ours {
if let SnafuAttribute::Provide(tt, ProvideKind::Expression(p)) = attr {
if p.is_chain && !p.is_ref {
errs.push(syn::Error::new_spanned(
tt,
"May only chain to references; please add `ref` flag",
));
}
}
}
if errs.is_empty() {
Ok(ours)
} else {
Err(errs)
}
}
enum Attribute {
Backtrace(Backtrace),
Context(Context),
CrateRoot(CrateRoot),
Display(Display),
Implicit(Implicit),
Module(Module),
Provide(Provide),
Source(Source),
Visibility(Visibility),
Whatever(Whatever),
}
impl From<Attribute> for SnafuAttribute {
fn from(other: Attribute) -> Self {
use self::Attribute::*;
match other {
Backtrace(b) => SnafuAttribute::Backtrace(b.to_token_stream(), b.into_bool()),
Context(c) => SnafuAttribute::Context(c.to_token_stream(), c.into_component()),
CrateRoot(cr) => SnafuAttribute::CrateRoot(cr.to_token_stream(), cr.into_arbitrary()),
Display(d) => SnafuAttribute::Display(d.to_token_stream(), d.into_display()),
Implicit(d) => SnafuAttribute::Implicit(d.to_token_stream(), d.into_bool()),
Module(v) => SnafuAttribute::Module(v.to_token_stream(), v.into_value()),
Provide(v) => SnafuAttribute::Provide(v.to_token_stream(), v.into_value()),
Source(s) => SnafuAttribute::Source(s.to_token_stream(), s.into_components()),
Visibility(v) => SnafuAttribute::Visibility(v.to_token_stream(), v.into_arbitrary()),
Whatever(o) => SnafuAttribute::Whatever(o.to_token_stream()),
}
}
}
impl Parse for Attribute {
fn parse(input: ParseStream) -> Result<Self> {
let lookahead = input.lookahead1();
if lookahead.peek(kw::backtrace) {
input.parse().map(Attribute::Backtrace)
} else if lookahead.peek(kw::context) {
input.parse().map(Attribute::Context)
} else if lookahead.peek(kw::crate_root) {
input.parse().map(Attribute::CrateRoot)
} else if lookahead.peek(kw::display) {
input.parse().map(Attribute::Display)
} else if lookahead.peek(kw::implicit) {
input.parse().map(Attribute::Implicit)
} else if lookahead.peek(kw::module) {
input.parse().map(Attribute::Module)
} else if lookahead.peek(kw::provide) {
input.parse().map(Attribute::Provide)
} else if lookahead.peek(kw::source) {
input.parse().map(Attribute::Source)
} else if lookahead.peek(kw::visibility) {
input.parse().map(Attribute::Visibility)
} else if lookahead.peek(kw::whatever) {
input.parse().map(Attribute::Whatever)
} else {
Err(lookahead.error())
}
}
}
struct Backtrace {
backtrace_token: kw::backtrace,
arg: MaybeArg<LitBool>,
}
impl Backtrace {
fn into_bool(self) -> bool {
self.arg.into_option().map_or(true, |a| a.value)
}
}
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);
}
}
struct Context {
context_token: kw::context,
arg: MaybeArg<ContextArg>,
}
impl Context {
fn into_component(self) -> super::Context {
use super::{Context::*, SuffixKind};
match self.arg.into_option() {
None => Flag(true),
Some(arg) => match arg {
ContextArg::Flag { value } => Flag(value.value),
ContextArg::Suffix {
suffix:
SuffixArg::Flag {
value: LitBool { value: true, .. },
},
..
} => Suffix(SuffixKind::Default),
ContextArg::Suffix {
suffix:
SuffixArg::Flag {
value: LitBool { value: false, .. },
},
..
} => Suffix(SuffixKind::None),
ContextArg::Suffix {
suffix: SuffixArg::Suffix { suffix, .. },
..
} => Suffix(SuffixKind::Some(suffix)),
},
}
}
}
impl Parse for Context {
fn parse(input: ParseStream) -> Result<Self> {
Ok(Self {
context_token: input.parse()?,
arg: input.parse()?,
})
}
}
impl ToTokens for Context {
fn to_tokens(&self, tokens: &mut TokenStream) {
self.context_token.to_tokens(tokens);
self.arg.to_tokens(tokens);
}
}
enum ContextArg {
Flag {
value: LitBool,
},
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 {
Err(lookahead.error())
}
}
}
impl ToTokens for ContextArg {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
ContextArg::Flag { value } => {
value.to_tokens(tokens);
}
ContextArg::Suffix {
suffix_token,
paren_token,
suffix,
} => {
suffix_token.to_tokens(tokens);
paren_token.surround(tokens, |tokens| {
suffix.to_tokens(tokens);
})
}
}
}
}
enum SuffixArg {
Flag { value: LitBool },
Suffix { suffix: Ident },
}
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(syn::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,
}
impl CrateRoot {
fn into_arbitrary(self) -> Box<dyn ToTokens> {
Box::new(self.arg)
}
}
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 {
eq_token: token::Eq,
str: LitStr,
}
impl DocComment {
fn into_value(self) -> String {
self.str.value()
}
}
impl From<DocComment> for SnafuAttribute {
fn from(other: DocComment) -> Self {
SnafuAttribute::DocComment(other.to_token_stream(), other.into_value())
}
}
impl Parse for DocComment {
fn parse(input: ParseStream) -> Result<Self> {
Ok(Self {
eq_token: input.parse()?,
str: input.parse()?,
})
}
}
impl ToTokens for DocComment {
fn to_tokens(&self, tokens: &mut TokenStream) {
self.eq_token.to_tokens(tokens);
self.str.to_tokens(tokens);
}
}
struct Implicit {
implicit_token: kw::implicit,
arg: MaybeArg<LitBool>,
}
impl Implicit {
fn into_bool(self) -> bool {
self.arg.into_option().map_or(true, |a| a.value)
}
}
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);
}
}
struct Provide {
provide_token: kw::provide,
arg: MaybeArg<ProvideArg>,
}
impl Provide {
fn into_value(self) -> ProvideKind {
match self.arg.into_option() {
None => ProvideKind::Flag(true),
Some(ProvideArg::Flag { value }) => ProvideKind::Flag(value.value),
Some(ProvideArg::Expression {
flags,
ty,
arrow: _,
expr,
}) => ProvideKind::Expression(crate::Provide {
is_chain: flags.is_chain(),
is_opt: flags.is_opt(),
is_priority: flags.is_priority(),
is_ref: flags.is_ref(),
ty,
expr,
}),
}
}
}
impl Parse for Provide {
fn parse(input: ParseStream) -> Result<Self> {
Ok(Self {
provide_token: input.parse()?,
arg: input.parse()?,
})
}
}
impl ToTokens for Provide {
fn to_tokens(&self, tokens: &mut TokenStream) {
self.provide_token.to_tokens(tokens);
self.arg.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()?,
})
}
}
}
impl ToTokens for ProvideArg {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
ProvideArg::Flag { value } => {
value.to_tokens(tokens);
}
ProvideArg::Expression {
flags,
ty,
arrow,
expr,
} => {
flags.to_tokens(tokens);
ty.to_tokens(tokens);
arrow.to_tokens(tokens);
expr.to_tokens(tokens);
}
}
}
}
struct ProvideFlags(Punctuated<ProvideFlag, token::Comma>);
impl ProvideFlags {
fn is_chain(&self) -> bool {
self.0.iter().any(ProvideFlag::is_chain)
}
fn is_opt(&self) -> bool {
self.0.iter().any(ProvideFlag::is_opt)
}
fn is_priority(&self) -> bool {
self.0.iter().any(ProvideFlag::is_priority)
}
fn is_ref(&self) -> bool {
self.0.iter().any(ProvideFlag::is_ref)
}
}
impl Parse for ProvideFlags {
fn parse(input: ParseStream) -> Result<Self> {
let mut flags = Punctuated::new();
while ProvideFlag::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 ProvideFlag {
Chain(kw::chain),
Opt(kw::opt),
Priority(kw::priority),
Ref(token::Ref),
}
impl ProvideFlag {
fn peek(input: ParseStream) -> bool {
input.peek(kw::chain)
|| input.peek(kw::opt)
|| input.peek(kw::priority)
|| input.peek(token::Ref)
}
fn is_chain(&self) -> bool {
match self {
ProvideFlag::Chain(_) => true,
_ => false,
}
}
fn is_opt(&self) -> bool {
match self {
ProvideFlag::Opt(_) => true,
_ => false,
}
}
fn is_priority(&self) -> bool {
match self {
ProvideFlag::Priority(_) => true,
_ => false,
}
}
fn is_ref(&self) -> bool {
match self {
ProvideFlag::Ref(_) => true,
_ => false,
}
}
}
impl Parse for ProvideFlag {
fn parse(input: ParseStream) -> Result<Self> {
let lookahead = input.lookahead1();
if lookahead.peek(kw::chain) {
input.parse().map(ProvideFlag::Chain)
} else if lookahead.peek(kw::opt) {
input.parse().map(ProvideFlag::Opt)
} else if lookahead.peek(kw::priority) {
input.parse().map(ProvideFlag::Priority)
} else if lookahead.peek(token::Ref) {
input.parse().map(ProvideFlag::Ref)
} else {
Err(lookahead.error())
}
}
}
impl ToTokens for ProvideFlag {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
ProvideFlag::Chain(v) => v.to_tokens(tokens),
ProvideFlag::Opt(v) => v.to_tokens(tokens),
ProvideFlag::Priority(v) => v.to_tokens(tokens),
ProvideFlag::Ref(v) => v.to_tokens(tokens),
}
}
}
struct Source {
source_token: kw::source,
args: MaybeArg<Punctuated<SourceArg, token::Comma>>,
}
impl Source {
fn into_components(self) -> Vec<super::Source> {
match self.args.into_option() {
None => vec![super::Source::Flag(true)],
Some(args) => args
.into_iter()
.map(|sa| match sa {
SourceArg::Flag { value } => super::Source::Flag(value.value),
SourceArg::From { r#type, expr, .. } => super::Source::From(r#type, expr),
})
.collect(),
}
}
}
impl Parse for Source {
fn parse(input: ParseStream) -> Result<Self> {
Ok(Self {
source_token: input.parse()?,
args: MaybeArg::parse_with(&input, Punctuated::parse_terminated)?,
})
}
}
impl ToTokens for Source {
fn to_tokens(&self, tokens: &mut TokenStream) {
self.source_token.to_tokens(tokens);
self.args.to_tokens(tokens);
}
}
enum SourceArg {
Flag {
value: LitBool,
},
From {
from_token: kw::from,
paren_token: token::Paren,
r#type: Type,
comma_token: token::Comma,
expr: Expr,
},
}
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) {
let content;
Ok(SourceArg::From {
from_token: input.parse()?,
paren_token: parenthesized!(content in input),
r#type: content.parse()?,
comma_token: content.parse()?,
expr: content.parse()?,
})
} else {
Err(lookahead.error())
}
}
}
impl ToTokens for SourceArg {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
SourceArg::Flag { value } => {
value.to_tokens(tokens);
}
SourceArg::From {
from_token,
paren_token,
r#type,
comma_token,
expr,
} => {
from_token.to_tokens(tokens);
paren_token.surround(tokens, |tokens| {
r#type.to_tokens(tokens);
comma_token.to_tokens(tokens);
expr.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 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);
});
}
}
}
#[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"]);
}
}