use crate::{
datastructure::StructOrEnum,
field_access::{Access, ComputeTrait},
ident_or_index::IdentOrIndexRef,
parse_utils::ParsePunctuated,
tokenizers::tstr_tokens,
utils::SpanExt,
write_docs::DocsFor,
};
use as_derive_utils::{
datastructure::FieldIdent,
gen_params_in::{GenParamsIn, InWhat},
return_spanned_err,
};
#[allow(unused_imports)]
use core_extensions::{matches, SelfOps};
use proc_macro2::{Span, TokenStream as TokenStream2};
use quote::{quote, quote_spanned, ToTokens, TokenStreamExt};
use syn::{
parse::{Parse, ParseStream},
punctuated::Punctuated,
token, Attribute, Ident, TraitItem,
};
use std::fmt::{self, Display};
#[cfg(test)]
mod tests;
mod attribute_parsing;
mod parsing;
use self::attribute_parsing::StructuralAliasOptions;
pub(crate) struct StructuralAliasesHack {
pub(crate) tokens: TokenStream2,
}
pub(crate) struct StructuralAliases<'a> {
pub(crate) list: Vec<StructuralAlias<'a>>,
}
pub(crate) struct StructuralAlias<'a> {
pub(crate) span: Span,
pub(crate) ident: &'a syn::Ident,
pub(crate) attrs: Vec<Attribute>,
pub(crate) vis: syn::Visibility,
pub(crate) generics: syn::Generics,
pub(crate) supertraits: Punctuated<syn::TypeParamBound, token::Add>,
pub(crate) extra_items: Vec<TraitItem>,
pub(crate) datatype: StructuralDataType<'a>,
pub(crate) options: StructuralAliasOptions<'a>,
}
#[derive(Debug)]
pub struct StructuralDataType<'a> {
pub(crate) type_name: Option<&'a syn::Ident>,
pub(crate) variants: Vec<StructuralVariant<'a>>,
pub(crate) fields: Vec<StructuralField<'a>>,
}
#[derive(Debug, Clone)]
pub struct StructuralVariant<'a> {
pub(crate) name: IdentType<'a>,
pub(crate) pub_vari_rename: Option<IdentOrIndexRef<'a>>,
pub(crate) fields: Vec<StructuralField<'a>>,
pub(crate) is_newtype: bool,
pub(crate) replace_bounds: Option<&'a ReplaceBounds>,
}
#[derive(Debug, Copy, Clone)]
pub(crate) struct TinyStructuralField<'a> {
pub(crate) access: Access,
pub(crate) ident: IdentType<'a>,
pub(crate) ty: FieldType<'a>,
}
#[derive(Debug, Copy, Clone)]
pub(crate) struct StructuralField<'a> {
pub(crate) access: Access,
pub(crate) ident: IdentType<'a>,
pub(crate) pub_field_rename: Option<IdentOrIndexRef<'a>>,
pub(crate) ty: FieldType<'a>,
}
#[derive(Debug, Copy, Clone)]
pub(crate) enum FieldType<'a> {
Ty(&'a syn::Type),
Impl(&'a TypeParamBounds),
}
#[derive(Debug, Copy, Clone)]
#[allow(dead_code)]
pub(crate) enum Exhaustiveness<'a> {
Nonexhaustive,
Exhaustive,
AndExhaustive { name: &'a Ident },
}
pub(crate) type TypeParamBounds = Punctuated<syn::TypeParamBound, syn::token::Add>;
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub(crate) enum IdentType<'a> {
Ident(IdentOrIndexRef<'a>),
Generic(&'a Ident),
SomeType(&'a syn::Type),
}
impl<'a> Parse for StructuralAliasesHack {
fn parse(input: ParseStream<'_>) -> Result<Self, syn::Error> {
let arenas = crate::arenas::Arenas::default();
let sa = StructuralAliases::parse(&arenas, input)?;
Ok(StructuralAliasesHack {
tokens: self::macro_impl(sa)?,
})
}
}
macro_rules! declare_structural_field_methods {
() => {
pub(crate) fn compute_trait(&self, soe: StructOrEnum) -> ComputeTrait {
self.access.compute_trait(soe)
}
};
}
impl<'a> TinyStructuralField<'a> {
declare_structural_field_methods! {}
}
impl<'a> StructuralField<'a> {
declare_structural_field_methods! {}
}
impl<'a> From<&'a Ident> for IdentType<'a> {
#[inline]
fn from(from: &'a Ident) -> Self {
IdentType::Ident(IdentOrIndexRef::Ident(from))
}
}
impl<'a> From<&'_ FieldIdent<'a>> for IdentType<'a> {
fn from(x: &'_ FieldIdent<'a>) -> Self {
IdentType::Ident(IdentOrIndexRef::from(x))
}
}
impl<'a> Display for IdentType<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
IdentType::Ident(ident) => Display::fmt(&ident.tstr_tokens(), f),
IdentType::Generic(ident) => Display::fmt(ident, f),
IdentType::SomeType(ty) => Display::fmt(&ty.to_token_stream(), f),
}
}
}
impl<'a> ToTokens for IdentType<'a> {
fn to_tokens(&self, ts: &mut TokenStream2) {
match self {
IdentType::Ident(ident) => ts.append_all(ident.tstr_tokens()),
IdentType::Generic(ident) => ident.to_tokens(ts),
IdentType::SomeType(ty) => ty.to_tokens(ts),
}
}
}
impl<'a> IdentType<'a> {
pub(crate) fn tstr_tokens(self) -> TokenStream2 {
match self {
IdentType::Ident(ident) => ident.tstr_tokens(),
IdentType::Generic(ident) => ident.to_token_stream(),
IdentType::SomeType(ty) => ty.to_token_stream(),
}
}
pub(crate) fn span(self) -> Span {
match self {
IdentType::Ident(ident) => ident.span(),
IdentType::Generic(ident) => ident.span(),
IdentType::SomeType(ty) => syn::spanned::Spanned::span(ty),
}
}
pub(crate) fn tokens(self) -> TokenStream2 {
match self {
IdentType::Ident(ident) => ident.tstr_tokens(),
IdentType::Generic(ident) => ident.to_token_stream(),
IdentType::SomeType(ty) => ty.to_token_stream(),
}
}
}
impl From<Option<IdentType<'_>>> for StructOrEnum {
fn from(opt: Option<IdentType<'_>>) -> Self {
match opt {
Some(_) => StructOrEnum::Enum,
None => StructOrEnum::Struct,
}
}
}
#[derive(Debug, Clone)]
pub(crate) struct ReplaceBounds {
pub(crate) bounds: String,
pub(crate) span: Span,
}
impl ReplaceBounds {
pub(crate) const NEEDLE: &'static str = "@variant";
pub(crate) fn to_tokens(
&self,
field_bounds: &mut TokenStream2,
variant_name: IdentType<'_>,
) -> Result<(), syn::Error> {
let bounds_str = syn::LitStr::new(
&self.bounds.replace(Self::NEEDLE, &variant_name.to_string()),
Span::call_site(),
);
let bounds: TypeParamBounds = bounds_str.parse::<ParsePunctuated<_, _>>()?.list;
let plus = <syn::Token!(+)>::default();
for bound in bounds {
bound.to_tokens(field_bounds);
plus.to_tokens(field_bounds);
}
Ok(())
}
pub(crate) fn get_docs(&self, variant_name: IdentType<'_>) -> String {
let replacement = match variant_name {
IdentType::Ident(ident) => format!("TS!({})", ident.display()),
IdentType::Generic(ident) => ident.to_string(),
IdentType::SomeType(ty) => ty.to_token_stream().to_string(),
};
self.bounds.replace(Self::NEEDLE, &replacement)
}
}
impl<'a> FieldType<'a> {
pub(crate) fn span(&self) -> Span {
match self {
FieldType::Ty(x) => syn::spanned::Spanned::span(x),
FieldType::Impl(x) => syn::spanned::Spanned::span(x),
}
}
}
impl<'a> ToTokens for FieldType<'a> {
fn to_tokens(&self, tokens: &mut TokenStream2) {
match self {
FieldType::Ty(x) => x.to_tokens(tokens),
FieldType::Impl(x) => {
<syn::Token!(impl)>::default().to_tokens(tokens);
x.to_tokens(tokens);
}
}
}
}
impl<'a> TinyStructuralField<'a> {
pub(crate) fn tokens(&self, soe: StructOrEnum) -> TokenStream2 {
let TinyStructuralField { ident, ty, .. } = *self;
let the_trait = self.compute_trait(soe).trait_tokens();
let ident = ident.tstr_tokens();
quote!(
structural::pmr::#the_trait<
#ident,
Ty=#ty,
>
)
}
}
#[cfg(test)]
pub(crate) fn derive_from_str(saf: &str) -> Result<TokenStream2, syn::Error> {
syn::parse_str::<StructuralAliasesHack>(saf).map(|x| x.tokens)
}
pub(crate) fn macro_impl(aliases: StructuralAliases<'_>) -> Result<TokenStream2, syn::Error> {
let list = aliases.list;
if list.is_empty() {
return Ok(quote!());
}
let mut out = TokenStream2::new();
let extra_where_preds = Punctuated::default();
for saf in list {
let trait_docs =
if saf.options.generate_docs && matches!(syn::Visibility::Public{..} = saf.vis) {
Some(
("A trait which aliases `structural` accessor traits,\
generated by the `structural_alias` macro.\n\n")
.to_string(),
)
} else {
None
};
let tokens = StructuralAliasParams {
span: saf.span,
attrs: &saf.attrs,
docs: trait_docs.clone(),
vis: &saf.vis,
ident: &saf.ident,
generics: &saf.generics,
extra_where_preds: &extra_where_preds,
supertraits: &saf.supertraits,
trait_items: &saf.extra_items,
variant_trait: None,
enum_exhaustiveness: saf.options.enum_exhaustiveness,
datatype: &saf.datatype,
}
.tokens()?;
if saf.options.debug_print {
println!("\n\n\n{}\n\n\n", tokens);
}
out.append_all(tokens);
}
Ok(out)
}
fn process_field(
field: &StructuralField<'_>,
variant_ident: Option<IdentType<'_>>,
field_bounds: &mut TokenStream2,
) {
use self::FieldType as FT;
let soe = StructOrEnum::from(variant_ident);
let span = field.ident.span().combine_span(field.ty.span());
let field_name = field.ident.tstr_tokens();
let aaoo = field.compute_trait(soe);
let assoc_ty = match &field.ty {
FT::Ty(ty) => quote!(Ty=#ty),
FT::Impl(bounds) => quote!(Ty:#bounds),
};
match variant_ident {
Some(variant_ident) => {
let variant_name_param = variant_ident.tokens();
let vf_trait = ComputeTrait {
struct_or_enum: StructOrEnum::Enum,
..aaoo
};
field_bounds.append_all(quote_spanned!(span=>
structural::pmr::#vf_trait<
#variant_name_param,
#field_name,
#assoc_ty
>+
));
}
None => {
let trait_ = aaoo.trait_tokens();
field_bounds.append_all(quote_spanned!(span=>
structural::#trait_<
#field_name,
#assoc_ty
>+
));
}
}
}
pub(crate) struct StructuralAliasParams<'a, A, I> {
pub(crate) span: Span,
pub(crate) attrs: A,
pub(crate) docs: Option<String>,
pub(crate) vis: &'a syn::Visibility,
pub(crate) ident: &'a Ident,
pub(crate) generics: &'a syn::Generics,
pub(crate) extra_where_preds: &'a Punctuated<syn::WherePredicate, syn::Token!(,)>,
pub(crate) supertraits: &'a Punctuated<syn::TypeParamBound, token::Add>,
pub(crate) trait_items: I,
pub(crate) variant_trait: Option<&'a Ident>,
pub(crate) enum_exhaustiveness: Exhaustiveness<'a>,
pub(crate) datatype: &'a StructuralDataType<'a>,
}
impl<'a, A, I> StructuralAliasParams<'a, A, I> {
pub(crate) fn tokens(self) -> Result<TokenStream2, syn::Error>
where
A: IntoIterator + Copy,
A::Item: ToTokens,
I: IntoIterator<Item = &'a TraitItem> + Copy,
{
for_delegation(self)
}
}
#[allow(clippy::cognitive_complexity)]
pub(crate) fn for_delegation<'a, A, I>(
StructuralAliasParams {
span,
attrs,
mut docs,
vis,
mut ident,
generics,
extra_where_preds,
supertraits,
trait_items,
variant_trait,
enum_exhaustiveness,
datatype,
}: StructuralAliasParams<'a, A, I>,
) -> Result<TokenStream2, syn::Error>
where
A: IntoIterator + Copy,
A::Item: ToTokens,
I: IntoIterator<Item = &'a TraitItem> + Copy,
{
use std::fmt::Write;
let mut tokens = TokenStream2::new();
let variant_name_generic;
let owned_datatype;
let mut borrowed_datatype = datatype;
if let Some(x) = variant_trait {
variant_name_generic = Ident::new("__VariantName", x.span());
owned_datatype = StructuralDataType {
type_name: datatype.type_name,
fields: Vec::new(),
variants: vec![StructuralVariant {
name: IdentType::Generic(&variant_name_generic),
pub_vari_rename: None,
fields: datatype.fields.clone(),
is_newtype: false,
replace_bounds: None,
}],
};
borrowed_datatype = &owned_datatype;
let sap = StructuralAliasParams {
span,
attrs,
docs: docs.clone(),
vis,
ident,
generics,
extra_where_preds,
supertraits,
trait_items,
variant_trait: None,
enum_exhaustiveness: Exhaustiveness::Nonexhaustive,
datatype,
};
tokens.append_all(sap.tokens()?);
ident = x;
}
let datatype = borrowed_datatype;
if let Some(docs) = &mut docs {
let docs_for = if variant_trait.is_some() {
DocsFor::VsiTrait
} else {
DocsFor::Trait
};
crate::write_docs::write_datatype_docs(docs, docs_for, datatype)?;
if !supertraits.is_empty() {
docs.push_str("### supertraits\n\n");
for supertrait in supertraits {
let _ = writeln!(docs, "- `{}`", supertrait.to_token_stream(),);
}
}
}
let attrs = attrs;
let mut field_bounds = TokenStream2::new();
let mut out_trait_items = TokenStream2::new();
for item in trait_items {
let (is_defaulted, item_name) = match item {
TraitItem::Const(x) => {
x.to_tokens(&mut out_trait_items);
(x.default.is_some(), "associated constant")
}
TraitItem::Method(x) => {
x.to_tokens(&mut out_trait_items);
(x.default.is_some(), "associated function")
}
_ => return_spanned_err!(
item,
"Only defaulted associated constant/function are supported.",
),
};
if !is_defaulted {
return_spanned_err!(item, "Expected this {} to be defaulted", item_name)
}
}
for variant in &datatype.variants {
let span = variant.name.span();
let variant_name = variant.name;
let variant_ident = Some(variant.name);
field_bounds.append_all(quote_spanned!(span=>
structural::pmr::IsVariant<#variant_name>+
));
match &variant.replace_bounds {
Some(replace_bounds) => {
replace_bounds.to_tokens(&mut field_bounds, variant.name)?;
}
None if !variant.is_newtype => {
for field in &variant.fields {
process_field(field, variant_ident, &mut field_bounds);
}
}
None => {}
}
}
for field in datatype.fields.iter() {
process_field(field, None, &mut field_bounds);
}
let supertraits_a = supertraits.into_iter();
let supertraits_b = supertraits.into_iter();
let variant_generic_param = match variant_trait {
Some(_) => quote!(__VariantName,),
None => TokenStream2::new(),
};
let impl_generics = {
let after_types = quote!(__This,#variant_generic_param);
GenParamsIn::with_after_types(generics, InWhat::ImplHeader, after_types)
};
let decl_generics =
GenParamsIn::with_after_types(generics, InWhat::ItemDecl, &variant_generic_param);
let ty_generics =
GenParamsIn::with_after_types(generics, InWhat::ItemUse, &variant_generic_param);
let (_, _, where_clause) = generics.split_for_impl();
let empty_preds = Punctuated::new();
let where_preds = where_clause
.as_ref()
.map_or(&empty_preds, |x| &x.predicates);
let where_preds_a = where_preds.into_iter();
let where_preds_b = where_preds.into_iter();
let mut exhaustive_bound = None;
if let Exhaustiveness::Exhaustive | Exhaustiveness::AndExhaustive { .. } = enum_exhaustiveness {
let variant_count_str = tstr_tokens(datatype.variants.len().to_string(), ident.span());
let count_bound = quote!( ::structural::pmr::VariantCount<Count=#variant_count_str>+ );
let attrs = attrs.into_iter();
let where_preds_a = where_preds.into_iter();
let where_preds_b = where_preds.into_iter();
let extra_where_preds_a = extra_where_preds.iter();
let extra_where_preds_b = extra_where_preds.iter();
match enum_exhaustiveness {
Exhaustiveness::Nonexhaustive => unreachable!(),
Exhaustiveness::Exhaustive => {
exhaustive_bound = Some(count_bound);
}
Exhaustiveness::AndExhaustive { name: exhaus_ident } => {
let exh_docs = format!(
"A subtrait of [{NE}](./trait.{NE}.html) with the additional requirement \
that the names and amount of variants must match exactly.",
NE = ident,
);
tokens.append_all(quote_spanned!(span=>
#(#attrs)*
#[doc=#exh_docs]
#[allow(non_camel_case_types)]
#vis trait #exhaus_ident <#decl_generics> :
#ident <#ty_generics>+
#count_bound
where
#(#where_preds_a,)*
#(#extra_where_preds_a,)*
{}
impl<#impl_generics> #exhaus_ident <#ty_generics>
for __This
where
#(#where_preds_b,)*
#(#extra_where_preds_b,)*
__This:
?Sized+
#ident <#ty_generics>+
#count_bound
{}
));
}
}
}
let attrs = attrs.into_iter();
let extra_where_preds_a = extra_where_preds.iter();
let extra_where_preds_b = extra_where_preds.iter();
let docs = docs.into_iter();
tokens.append_all(quote_spanned!(span=>
#(#attrs)*
#( #[doc=#docs] )*
#vis trait #ident <#decl_generics> :
#( #supertraits_a+ )*
#exhaustive_bound
#field_bounds
where
#(#where_preds_a,)*
#(#extra_where_preds_a,)*
{
#out_trait_items
}
impl<#impl_generics> #ident <#ty_generics>
for __This
where
__This:
?Sized+
#( #supertraits_b+ )*
#exhaustive_bound
#field_bounds,
#(#where_preds_b,)*
#(#extra_where_preds_b,)*
{}
));
Ok(tokens)
}