use proc_macro2::Span;
use proc_macro_error::{emit_error, emit_warning};
use syn::{
parse::{Parse, ParseStream},
spanned::Spanned,
Attribute, Expr, LitStr, Token,
};
use self::forward::ForwardAttr;
use crate::{
call::Call,
helpers::{flatten_cfgs, visit_matching_attrs_parsed_mut, Attr, AttributeAction, HINT_REASON},
precondition::Precondition,
render_assure,
};
mod forward;
mod custom_keywords {
use syn::custom_keyword;
custom_keyword!(reason);
}
pub(crate) enum AssureAttr {
WithReason {
precondition: Precondition,
_comma: Token![,],
reason: Reason,
},
WithoutReason {
precondition: Precondition,
},
}
impl From<AssureAttr> for Precondition {
fn from(holds_statement: AssureAttr) -> Precondition {
match holds_statement {
AssureAttr::WithoutReason { precondition } => precondition,
AssureAttr::WithReason { precondition, .. } => precondition,
}
}
}
impl Spanned for AssureAttr {
fn span(&self) -> Span {
match self {
AssureAttr::WithReason {
precondition,
reason,
..
} => precondition
.span()
.join(reason.reason.span())
.unwrap_or_else(|| precondition.span()),
AssureAttr::WithoutReason { precondition } => precondition.span(),
}
}
}
impl Parse for AssureAttr {
fn parse(input: ParseStream) -> syn::Result<Self> {
let precondition = input.parse()?;
if input.is_empty() {
Ok(AssureAttr::WithoutReason { precondition })
} else {
let comma = input.parse()?;
let reason = input.parse()?;
Ok(AssureAttr::WithReason {
precondition,
_comma: comma,
reason,
})
}
}
}
pub(crate) struct Reason {
_reason_keyword: custom_keywords::reason,
_eq: Token![=],
reason: LitStr,
}
impl Parse for Reason {
fn parse(input: ParseStream) -> syn::Result<Self> {
let reason_keyword = input.parse()?;
let eq = input.parse()?;
let reason = input.parse()?;
Ok(Reason {
_reason_keyword: reason_keyword,
_eq: eq,
reason,
})
}
}
pub(crate) struct CallAttributes {
pub(crate) span: Span,
pub(crate) forward: Option<Attr<ForwardAttr>>,
pub(crate) assure_attributes: Vec<Attr<AssureAttr>>,
}
pub(crate) fn remove_call_attributes(attributes: &mut Vec<Attribute>) -> Option<CallAttributes> {
flatten_cfgs(attributes);
let mut forward = None;
let mut assure_attributes = Vec::new();
let preconditions_span = visit_matching_attrs_parsed_mut(attributes, "assure", |attr| {
assure_attributes.push(attr);
AttributeAction::Remove
});
let forward_span = visit_matching_attrs_parsed_mut(attributes, "forward", |attr| {
let span = attr.span();
if let Some(old_forward) = forward.replace(attr) {
emit_error!(
span,
"duplicate `forward` attribute";
help = old_forward.span() => "there can be just one location, try removing the wrong one"
);
}
AttributeAction::Remove
});
let span = match (preconditions_span, forward_span) {
(Some(preconditions_span), Some(forward_span)) => Some(
preconditions_span
.join(forward_span)
.unwrap_or(preconditions_span),
),
(Some(span), None) => Some(span),
(None, Some(span)) => Some(span),
(None, None) => None,
};
span.map(|span| CallAttributes {
span,
forward,
assure_attributes,
})
}
pub(crate) fn render_call(
CallAttributes {
span,
forward,
assure_attributes,
}: CallAttributes,
original_call: Call,
) -> Expr {
check_reasons(&assure_attributes);
let precondition = assure_attributes
.into_iter()
.map(|attr| attr.into())
.collect();
if let Some((forward, _, _)) = forward.map(|fwd| fwd.into_content()) {
forward.update_call(original_call, |call| {
render_assure(precondition, call, span)
})
} else {
let output = render_assure(precondition, original_call, span);
output.into()
}
}
fn check_reasons(assure_attributes: &[Attr<AssureAttr>]) {
for assure_attribute in assure_attributes.iter() {
match assure_attribute.content() {
AssureAttr::WithReason { reason, .. } => {
if let Some(reason) = unfinished_reason(&reason.reason) {
emit_warning!(
reason,
"you should specify a different here";
help = "specifying a meaningful reason will help you and others understand why this is ok in the future"
)
} else if reason.reason.value() == HINT_REASON {
let todo_help_msg = if cfg!(nightly) {
Some("using `TODO` here will emit a warning, reminding you to fix this later")
} else {
None
};
emit_error!(
reason.reason,
"you need to specify a different reason here";
help = "specifying a meaningful reason will help you and others understand why this is ok in the future";
help =? todo_help_msg
)
}
}
AssureAttr::WithoutReason { precondition } => emit_error!(
precondition.span(),
"you need to specify a reason why this precondition holds";
help = "add `, reason = {:?}`", HINT_REASON
),
}
}
}
fn unfinished_reason(reason: &LitStr) -> Option<&LitStr> {
let mut reason_val = reason.value();
reason_val.make_ascii_lowercase();
match &*reason_val {
"todo" | "?" | "" => Some(reason),
_ => None,
}
}