use std::ops::ControlFlow;
use proc_macro2::Span;
use syn::parse::Parse;
use syn::spanned::Spanned as _;
use syn::{Attribute, Error, Meta, Result, Token, WherePredicate};
use super::PunctTerminated;
use crate::process::Args;
use crate::util::error_sink::ErrorSink;
use crate::util::interval::Interval;
pub trait ParseHelperAttr: Sized {
const IDENT: &'static str;
fn try_from_meta(meta: Meta, validate_args: Option<&Args>) -> Result<Self>;
fn highest_mentioned_version(&self) -> Option<usize>;
fn try_from_attr(
attr: Attribute,
validate_args: Option<&Args>,
) -> Result<ControlFlow<(Span, Self), Attribute>> {
if attr.path().is_ident(Self::IDENT) {
let span = attr.span();
Self::try_from_meta(attr.meta, validate_args).map(|slf| ControlFlow::Break((span, slf)))
} else {
Ok(ControlFlow::Continue(attr))
}
}
}
pub trait HelperAttrSink {
type Attr: ParseHelperAttr;
fn try_add(&mut self, attr: Self::Attr, span: Span) -> Result<()>;
fn highest_mentioned_version(&self) -> Option<usize>;
}
impl<A: ParseHelperAttr> HelperAttrSink for Option<A> {
type Attr = A;
fn try_add(&mut self, attr: Self::Attr, span: Span) -> Result<()> {
if self.is_none() {
*self = Some(attr);
Ok(())
} else {
Err(Error::new(
span,
format!("`#[{}]` can only be given once.", Self::Attr::IDENT),
))
}
}
fn highest_mentioned_version(&self) -> Option<usize> {
self.as_ref().and_then(|a| a.highest_mentioned_version())
}
}
impl<A: ParseHelperAttr> HelperAttrSink for Vec<A> {
type Attr = A;
fn try_add(&mut self, attr: Self::Attr, _: Span) -> syn::Result<()> {
self.push(attr);
Ok(())
}
fn highest_mentioned_version(&self) -> Option<usize> {
self.iter()
.filter_map(|a| a.highest_mentioned_version())
.max()
}
}
pub trait HelperAttrProcessor {
fn try_process_attr(
&mut self,
attr: Attribute,
args: &Args,
errs: &mut ErrorSink,
) -> Option<ControlFlow<(), Attribute>>;
fn highest_mentioned_version(&self) -> Option<usize>;
fn process_attrs(
&mut self,
attrs: Vec<Attribute>,
args: &Args,
errs: &mut ErrorSink,
) -> Vec<Attribute> {
attrs
.into_iter()
.filter_map(|attr| {
self.try_process_attr(attr, args, errs)
.and_then(|cf| match cf {
ControlFlow::Break(()) => None,
ControlFlow::Continue(attr) => Some(attr),
})
})
.collect()
}
}
pub trait HelperAttrInPlace: ParseHelperAttr {
fn try_process_attr_for_version(
attr: Attribute,
version: usize,
validate_args: Option<&Args>,
) -> Result<Option<Attribute>>;
fn highest_mentioned_version(attr: &Attribute) -> Option<usize> {
Self::try_from_attr(attr.clone(), None).ok().and_then(|cf| {
match cf {
ControlFlow::Continue(_attr) => None,
ControlFlow::Break((_span, a)) => a.highest_mentioned_version(),
}
})
}
fn process_attrs_for_version(
attrs: Vec<Attribute>,
version: usize,
validate_args: Option<&Args>,
errs: &mut ErrorSink,
) -> Vec<Attribute> {
attrs
.into_iter()
.filter_map(|attr| {
errs.eat_err(Self::try_process_attr_for_version(
attr,
version,
validate_args,
))
.flatten()
})
.collect()
}
}
impl HelperAttrProcessor for () {
#[inline]
fn try_process_attr(
&mut self,
attr: Attribute,
_args: &Args,
_errs: &mut ErrorSink,
) -> Option<ControlFlow<(), Attribute>> {
Some(ControlFlow::Continue(attr))
}
#[inline]
fn highest_mentioned_version(&self) -> Option<usize> {
None
}
}
impl<S: HelperAttrSink, P: HelperAttrProcessor> HelperAttrProcessor for (S, P) {
fn try_process_attr(
&mut self,
attr: Attribute,
args: &Args,
errs: &mut ErrorSink,
) -> Option<ControlFlow<(), Attribute>> {
match errs.eat_err(S::Attr::try_from_attr(attr, Some(args)))? {
ControlFlow::Break((span, a)) => {
errs.eat_err(self.0.try_add(a, span).map(ControlFlow::Break))
}
ControlFlow::Continue(attr) => self.1.try_process_attr(attr, args, errs),
}
}
fn highest_mentioned_version(&self) -> Option<usize> {
self.0
.highest_mentioned_version()
.max(self.1.highest_mentioned_version())
}
}
#[derive(Debug, Copy, Clone)]
pub struct VersionFilter(pub Interval);
#[derive(Clone)]
#[cfg_attr(test, derive(Debug))]
pub struct VersionedAttr(pub Interval, pub Meta);
#[derive(Clone)]
#[cfg_attr(test, derive(Debug))]
pub struct VersionedWhere(pub Interval, pub PunctTerminated<WherePredicate, Token![,]>);
impl ParseHelperAttr for VersionFilter {
const IDENT: &'static str = "ver";
fn try_from_meta(meta: Meta, validate_args: Option<&Args>) -> Result<Self> {
meta.require_name_value()?;
let Meta::NameValue(nv) = meta else {
unreachable!()
};
Interval::try_from_expr(nv.value, validate_args.map(|a| (a.start, a.end))).map(Self)
}
#[inline]
fn highest_mentioned_version(&self) -> Option<usize> {
Some(self.0.highest_mentioned())
}
}
fn versions_comma_rest<T: Parse, R>(
meta: Meta,
f: impl FnOnce(Interval, T) -> R,
validate_args: Option<&Args>,
) -> Result<R> {
#[allow(dead_code)]
struct Helper<T>(syn::Expr, Token![,], T);
impl<T: Parse> Parse for Helper<T> {
fn parse(input: syn::parse::ParseStream) -> Result<Self> {
Ok(Self(input.parse()?, input.parse()?, input.parse()?))
}
}
meta.require_list()?;
let Meta::List(l) = meta else { unreachable!() };
let Helper(versions, _, rest) = syn::parse2(l.tokens)?;
let versions = Interval::try_from_expr(versions, validate_args.map(|a| (a.start, a.end)))?;
Ok(f(versions, rest))
}
impl ParseHelperAttr for VersionedAttr {
const IDENT: &'static str = "ver_attr";
#[inline]
fn try_from_meta(meta: Meta, validate_args: Option<&Args>) -> Result<Self> {
versions_comma_rest(meta, Self, validate_args)
}
#[inline]
fn highest_mentioned_version(&self) -> Option<usize> {
Some(self.0.highest_mentioned())
}
}
impl ParseHelperAttr for VersionedWhere {
const IDENT: &'static str = "ver_where";
#[inline]
fn try_from_meta(meta: Meta, validate_args: Option<&Args>) -> Result<Self> {
versions_comma_rest(meta, Self, validate_args)
}
#[inline]
fn highest_mentioned_version(&self) -> Option<usize> {
Some(self.0.highest_mentioned())
}
}
impl HelperAttrInPlace for VersionedAttr {
fn highest_mentioned_version(attr: &Attribute) -> Option<usize> {
Self::try_from_attr(attr.clone(), None).ok().and_then(|cf| {
match cf {
ControlFlow::Continue(_attr) => None,
ControlFlow::Break((_span, Self(iv, _))) => Some(iv.highest_mentioned()),
}
})
}
fn try_process_attr_for_version(
attr: Attribute,
version: usize,
validate_args: Option<&Args>,
) -> Result<Option<Attribute>> {
let style = attr.style;
let bracket_token = attr.bracket_token;
let pound_token = attr.pound_token;
Self::try_from_attr(attr, validate_args).map(|cf| {
match cf {
ControlFlow::Continue(attr) => Some(attr),
ControlFlow::Break((_span, Self(versions, meta))) => {
versions.contains(version).then_some(Attribute {
pound_token,
style,
bracket_token,
meta,
})
}
}
})
}
}