use crate::{
parse::{
self,
attr::{self, ErrorForLocation as _, ErrorLocation},
into_transformation, AtMostOne, Attribute, Backtrace, FlagAttribute as _, ProvideFlag,
Sidecar, SourceFlag, SourceFrom, SynErrors,
},
Field, SourceField,
};
const IMPLICIT_SOURCE_FIELD_NAME: &str = "source";
const IMPLICIT_BACKTRACE_FIELD_NAME: &str = "backtrace";
fn is_implicit_source(name: &proc_macro2::Ident) -> bool {
name == IMPLICIT_SOURCE_FIELD_NAME
}
fn is_implicit_backtrace(name: &proc_macro2::Ident) -> bool {
name == IMPLICIT_BACKTRACE_FIELD_NAME
}
fn is_implicit_provide(name: &proc_macro2::Ident) -> bool {
is_implicit_backtrace(name)
}
struct Attributes {
backtrace: Option<Backtrace>,
implicit: bool,
provide_flag: Option<ProvideFlag>,
source_attr_enabled: Option<(bool, SourceOrigin)>,
source_from: Option<SourceFrom>,
}
impl Attributes {
fn from_syn(attrs: &[syn::Attribute]) -> syn::Result<Self> {
let location = ErrorLocation::OnField;
let mut errors = SynErrors::default();
let mut backtraces = AtMostOne::attribute(attr::Backtrace, location);
let mut implicits = AtMostOne::attribute(attr::Implicit, location);
let mut provide_flags = AtMostOne::attribute(attr::ProvideFlag, location);
let mut source_flags = AtMostOne::attribute(attr::SourceFlag, location);
let mut source_froms = AtMostOne::attribute(attr::SourceFrom, location);
parse::syn_attrs(attrs, &mut errors, |errors, attr| {
use Attribute::*;
match attr {
Backtrace(a) => backtraces.push(a),
ContextFlag(a) => errors.push_invalid_flag(a, location),
ContextName(a) => errors.push_invalid(a, location),
ContextSuffix(a) => errors.push_invalid(a, location),
CrateRoot(a) => errors.push_invalid(a, location),
Display(a) => errors.push_invalid(a, location),
DocComment(_a) => { }
Implicit(a) => implicits.push(a),
Module(a) => errors.push_invalid(a, location),
ProvideFlag(a) => provide_flags.push(a),
ProvideExpression(a) => errors.push_invalid(a, location),
SourceFlag(a) => source_flags.push(a),
SourceFrom(a) => source_froms.push(a),
Transparent(a) => errors.push_invalid_flag(a, location),
Visibility(a) => errors.push_invalid(a, location),
Whatever(a) => errors.push_invalid(a, location),
}
});
let backtrace = backtraces.finish_default(&mut errors);
let implicit = implicits.finish_default(&mut errors);
let provide_flag = provide_flags.finish_default(&mut errors);
let source_flag = source_flags.finish_default(&mut errors);
let source_from = source_froms.finish_default(&mut errors);
let implicit = implicit.map(|i| (i.is_enabled(), i));
let implicit = match implicit {
None => false,
Some((true, _)) => true,
Some((false, i)) => {
errors.push_new(i, attr::Implicit::FALSE_DOES_NOTHING);
false
}
};
let source_attr_enabled = match (source_flag, &source_from) {
(None, None) => None,
(None, Some(source_from)) => Some((true, SourceOrigin::From(source_from.clone()))),
(Some(source_flag), None) => {
Some((source_flag.is_enabled(), SourceOrigin::Flag(source_flag)))
}
(Some(source_flag), Some(source_from)) => {
if !source_flag.is_enabled() {
let txt = attr::Source::FALSE_AND_FROM_INCOMPATIBLE.on(location);
errors.push_new(source_flag, txt);
errors.push_new(source_from, txt);
None
} else {
Some((true, SourceOrigin::Flag(source_flag)))
}
}
};
errors.finish(Attributes {
backtrace,
implicit,
provide_flag,
source_attr_enabled,
source_from,
})
}
}
pub(super) fn parse_field(syn_field: &syn::Field) -> syn::Result<FieldKind> {
let name = syn_field
.ident
.as_ref()
.ok_or_else(|| syn::Error::new_spanned(syn_field, "Must have a named field"));
let attrs = Attributes::from_syn(&syn_field.attrs);
let (name, attrs) = join_syn_error!(name, attrs)?;
let Attributes {
backtrace,
implicit,
provide_flag,
source_attr_enabled,
source_from,
} = attrs;
let mut errors = SynErrors::default();
let backtrace = backtrace.map(|b| (b.is_enabled(), BacktraceOrigin::Attribute(b)));
let backtrace = match (backtrace, is_implicit_backtrace(name)) {
(None, v) => v.then(|| BacktraceOrigin::Field(syn_field.clone())),
(Some((true, span)), true) => Some(span),
(Some((false, _)), true) => None,
(Some((true, span)), false) => Some(span),
(Some((false, span)), false) => {
errors.push_new(span, attr::Backtrace::FALSE_ON_WRONG_FIELD);
None
}
};
let source = match (source_attr_enabled, is_implicit_source(name)) {
(None, v) => v.then(|| SourceOrigin::Field(syn_field.clone())),
(Some((true, span)), true) => Some(span),
(Some((false, _)), true) => None,
(Some((true, span)), false) => Some(span),
(Some((false, span)), false) => {
errors.push_new(span, attr::Source::FALSE_ON_WRONG_FIELD);
None
}
};
let provide = provide_flag.map(|p| (p.is_enabled(), p));
let provide = match (provide, is_implicit_provide(name)) {
(None, v) => v,
(Some((true, _)), true) => true,
(Some((false, _)), true) => false,
(Some((true, _)), false) => true,
(Some((false, p)), false) => {
errors.push_new(p, attr::Provide::FALSE_ON_WRONG_FIELD);
false
}
};
let field = Field {
name: name.clone(),
ty: syn_field.ty.clone(),
provide,
original: syn_field.clone(),
};
let field = if let Some(span) = source {
let Field {
name, ty, provide, ..
} = field;
let transformation = into_transformation(source_from, ty, false);
let backtrace_delegate = backtrace.is_some();
let field = SourceField {
name,
transformation,
backtrace_delegate,
provide,
};
FieldKind::Source(Sidecar(span, field))
} else if let Some(span) = backtrace {
FieldKind::Backtrace(Sidecar(span, field))
} else if implicit {
FieldKind::Implicit(field)
} else {
FieldKind::User(field)
};
errors.finish(field)
}
pub(super) enum FieldKind {
Backtrace(Sidecar<BacktraceOrigin, Field>),
Implicit(Field),
Source(Sidecar<SourceOrigin, SourceField>),
User(Field),
}
pub(super) enum BacktraceOrigin {
Attribute(Backtrace),
Field(syn::Field),
}
impl quote::ToTokens for BacktraceOrigin {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
match self {
BacktraceOrigin::Attribute(backtrace) => backtrace.to_tokens(tokens),
BacktraceOrigin::Field(field) => field.to_tokens(tokens),
}
}
}
pub(super) enum SourceOrigin {
Field(syn::Field),
Flag(SourceFlag),
From(SourceFrom),
}
impl quote::ToTokens for SourceOrigin {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
match self {
SourceOrigin::Field(field) => field.to_tokens(tokens),
SourceOrigin::Flag(flat_source_flag) => flat_source_flag.to_tokens(tokens),
SourceOrigin::From(flat_source_from) => flat_source_from.to_tokens(tokens),
}
}
}