use proc_macro2::Span;
use syn::visit_mut::VisitMut;
use syn::{DeriveInput, GenericParam, Result};
use super::pseudo_macros::ExpandPseudoMacros;
use super::{Args, versioned_ident};
use crate::output::{
OutputEnum, OutputFields, OutputItemBase, OutputItems, OutputStructOrUnion, OutputVariant,
};
use crate::parse::args::MacroArg;
use crate::parse::helper_attrs::{
HelperAttrInPlace as _, VersionFilter, VersionedAttr, VersionedWhere,
};
use crate::parse::{
ParsedEnum, ParsedFields, ParsedGenericParams, ParsedInput, ParsedItemBase,
ParsedStructOrUnion, StructOrUnion,
};
use crate::util::error_sink::ErrorSink;
pub fn versioned(args: Vec<(Span, MacroArg)>, input: DeriveInput) -> Result<OutputItems> {
let mut errs = ErrorSink::new();
let args = Args::from_raw(args, &mut errs);
let input = ParsedInput::parse_from(input, &args)?;
match input {
ParsedInput::StructOrUnion(input) => match input.kind {
StructOrUnion::Struct => {
versioned_struct_or_union::<false>(args, input, errs).map(OutputItems::Structs)
}
StructOrUnion::Union => {
versioned_struct_or_union::<true>(args, input, errs).map(OutputItems::Unions)
}
},
ParsedInput::Enum(input) => versioned_enum(args, input, errs).map(OutputItems::Enums),
}
}
impl ParsedStructOrUnion {
pub fn highest_mentioned_version(&self) -> Option<usize> {
self.base
.highest_mentioned_version
.max(self.fields.highest_mentioned_version)
}
}
impl ParsedEnum {
pub fn highest_mentioned_version(&self) -> Option<usize> {
std::iter::once(self.base.highest_mentioned_version)
.chain(
self.variants
.iter()
.map(|var| var.highest_mentioned_version),
)
.max()
.flatten()
}
}
fn versioned_struct_or_union<const IS_UNION: bool>(
args: Args,
input: ParsedStructOrUnion,
mut errs: ErrorSink,
) -> Result<Vec<OutputStructOrUnion<IS_UNION>>> {
let mut errs2 = ErrorSink::new();
let res = versioned_item_base(
&args,
input.highest_mentioned_version(),
input.base,
&mut errs,
)
.map(|(version, base)| {
let errs = &mut errs2;
OutputStructOrUnion::<IS_UNION> {
base,
fields: versioned_fields(version, &input.fields, &args, errs),
}
})
.collect();
errs.append(errs2);
errs.finish_with(|| res)
}
fn versioned_enum(args: Args, input: ParsedEnum, mut errs: ErrorSink) -> Result<Vec<OutputEnum>> {
let mut errs2 = ErrorSink::new();
let res = versioned_item_base(
&args,
input.highest_mentioned_version(),
input.base,
&mut errs,
)
.map(|(version, base)| {
let errs = &mut errs2;
let variants = input
.variants
.iter()
.filter_map(|variant| {
let (versions, ()) = &variant.helpers;
let is_included =
versions.map_or(true, |VersionFilter(versions)| versions.contains(version));
is_included.then(|| OutputVariant {
attrs: VersionedAttr::process_attrs_for_version(
variant.attrs.clone(),
version,
Some(&args),
errs,
),
ident: variant.ident.clone(),
fields: versioned_fields(version, &variant.fields, &args, errs),
discriminant: variant.discriminant.as_ref().map(|(_, d)| d.clone()),
})
})
.collect();
OutputEnum { base, variants }
})
.collect();
errs.append(errs2);
errs.finish_with(|| res)
}
fn versioned_item_base<'a>(
args: &'a Args,
highest_mentioned_version: Option<usize>,
input: ParsedItemBase,
errs: &'a mut ErrorSink,
) -> impl Iterator<Item = (usize, OutputItemBase)> + 'a {
let end = args.end.unwrap_or_else(|| highest_mentioned_version.unwrap_or(args.start));
(args.start..=end).map(move |version| {
let mut generic_params = input.generic_params.as_ref().map(
|ParsedGenericParams {
highest_mentioned_version: _,
params,
}| {
params
.iter()
.filter_map(|(helpers, param)| {
let (versions, ()) = helpers;
let is_included = versions
.map_or(true, |VersionFilter(versions)| versions.contains(version));
is_included.then(|| {
let param = param.clone();
match param {
GenericParam::Lifetime(mut param) => {
param.attrs = VersionedAttr::process_attrs_for_version(
param.attrs,
version,
Some(args),
errs,
);
GenericParam::Lifetime(param)
}
GenericParam::Type(mut param) => {
param.attrs = VersionedAttr::process_attrs_for_version(
param.attrs,
version,
Some(args),
errs,
);
GenericParam::Type(param)
}
GenericParam::Const(mut param) => {
param.attrs = VersionedAttr::process_attrs_for_version(
param.attrs,
version,
Some(args),
errs,
);
GenericParam::Const(param)
}
}
})
})
.collect()
},
);
let (ver_where, ()) = &input.helpers;
let mut where_clause = input
.where_clause
.iter()
.flat_map(|wc| wc.predicates.iter().cloned())
.chain(
ver_where
.iter()
.flat_map(|VersionedWhere(versions, preds)| {
versions.contains(version).then(|| preds.0.iter().cloned())
})
.flatten(),
)
.collect();
let mut epm = ExpandPseudoMacros {
version,
validate_args: Some(args),
errs,
};
for param in generic_params.iter_mut().flatten() {
epm.visit_generic_param_mut(param);
}
for pred in &mut where_clause {
epm.visit_where_predicate_mut(pred);
}
let ident = args
.rename
.get(&crate::parse::args::RenameSource::Version(version))
.cloned()
.unwrap_or_else(|| versioned_ident(&input.ident, version));
let res = OutputItemBase {
attrs: VersionedAttr::process_attrs_for_version(
input.attrs.clone(),
version,
Some(args),
errs,
),
vis: input.vis.clone(),
ident,
generic_params,
where_clause,
};
(version, res)
})
}
fn versioned_fields(
version: usize,
input: &ParsedFields,
args: &Args,
errs: &mut ErrorSink,
) -> OutputFields {
let fields = input
.fields
.iter()
.filter_map(|(helpers, field)| {
let (versions, ()) = helpers;
let is_included =
versions.map_or(true, |VersionFilter(versions)| versions.contains(version));
is_included.then(|| {
let mut field = field.clone();
field.attrs = VersionedAttr::process_attrs_for_version(
field.attrs,
version,
Some(args),
errs,
);
ExpandPseudoMacros {
version,
validate_args: Some(args),
errs,
}
.visit_field_mut(&mut field);
field
})
})
.collect();
OutputFields {
fields,
flavor_bias: input.flavor_bias,
}
}