use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::parse::{Parse, ParseStream};
use syn::punctuated::Punctuated;
use syn::visit_mut::{self, VisitMut};
use syn::{
Attribute, Expr, GenericArgument, GenericParam, Generics, Ident, ImplItem, ImplItemFn, ItemImpl, PathArguments,
Token, Type, TypeParam, TypePath, Visibility, WherePredicate, parse_quote,
};
pub(crate) struct ExtensionImpl {
pub(crate) visibility: Option<Visibility>,
pub(crate) item: ItemImpl,
}
impl Parse for ExtensionImpl {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
let attrs = syn::Attribute::parse_outer(input)?;
let visibility =
input.parse::<Visibility>().ok().filter(|visibility| !matches!(visibility, Visibility::Inherited));
let mut item = input.parse::<ItemImpl>()?;
item.attrs.extend(attrs);
Ok(Self { visibility, item })
}
}
impl ExtensionImpl {
pub(crate) fn item_visibility(&self) -> Visibility {
self.visibility.clone().unwrap_or(Visibility::Inherited)
}
pub(crate) fn impl_predicates(&self) -> Punctuated<WherePredicate, Token![,]> {
predicates(&self.item.generics)
}
pub(crate) fn unbounded_item(&self) -> ItemImpl {
let mut item = self.item.clone();
unbind(&mut item.generics);
item
}
pub(crate) fn forwarded(&self, attr: TokenStream) -> TokenStream {
match &self.visibility {
Some(visibility) => quote!(#visibility, #attr),
None => attr,
}
}
}
pub(crate) fn predicates(generics: &Generics) -> Punctuated<WherePredicate, Token![,]> {
let mut predicates: Punctuated<WherePredicate, Token![,]> = generics
.params
.iter()
.filter_map(|param| match param {
GenericParam::Type(param) if !param.bounds.is_empty() => {
let (parameter, bounds) = (¶m.ident, ¶m.bounds);
let predicate: WherePredicate = parse_quote!(#parameter: #bounds);
Some(predicate)
}
_ => None,
})
.collect();
if let Some(clause) = &generics.where_clause {
predicates.extend(clause.predicates.clone());
}
predicates
}
pub(crate) fn unbind(generics: &mut Generics) {
generics.where_clause = None;
for param in &mut generics.params {
if let GenericParam::Type(param) = param {
param.bounds.clear();
}
}
}
pub(crate) fn documentation(attrs: &[Attribute]) -> Vec<Attribute> {
attrs.iter().filter(|attr| attr.path().is_ident("doc")).cloned().collect()
}
pub(crate) fn method_names(item: &ItemImpl) -> Vec<Ident> {
item.items
.iter()
.filter_map(|item| match item {
ImplItem::Fn(method) => Some(method.sig.ident.clone()),
_ => None,
})
.collect()
}
pub(crate) fn pascal_ident(name: &Ident, suffix: &str) -> Ident {
let mut pascal = String::new();
for part in name.to_string().split('_') {
let mut chars = part.chars();
if let Some(first) = chars.next() {
pascal.extend(first.to_uppercase());
pascal.extend(chars);
}
}
format_ident!("{pascal}{suffix}", span = name.span())
}
pub(crate) fn operation_ident(method: &Ident) -> Ident {
pascal_ident(method, "Operation")
}
pub(crate) fn program_ident(method: &Ident) -> Ident {
pascal_ident(method, "Program")
}
pub(crate) fn program_type_params(method: &ImplItemFn, rejected: &str) -> syn::Result<Vec<TypeParam>> {
method
.sig
.generics
.params
.iter()
.map(|param| match param {
GenericParam::Type(param) => {
let mut param = param.clone();
param.bounds.clear();
Ok(param)
}
_ => Err(syn::Error::new_spanned(param, rejected)),
})
.collect()
}
pub(crate) struct Reified {
pub(crate) operation: Type,
pub(crate) carrier: Ident,
}
fn carrier_of(operation: &Type) -> Option<Ident> {
let Type::Path(path) = operation else { return None };
let PathArguments::AngleBracketed(arguments) = &path.path.segments.last()?.arguments else {
return None;
};
arguments.args.iter().find_map(|argument| match argument {
GenericArgument::Type(Type::Path(carrier)) => Some(carrier.path.segments.last()?.ident.clone()),
_ => None,
})
}
fn first_order_type(expression: &Expr) -> Option<Type> {
let Expr::Call(call) = expression else { return None };
if !call.args.is_empty() {
return None;
}
let Expr::Path(function) = call.func.as_ref() else { return None };
let mut path = function.path.clone();
if path.segments.last()?.ident != "default" {
return None;
}
path.segments.pop();
path.segments.pop_punct();
Some(Type::Path(TypePath { attrs: Vec::new(), qself: function.qself.clone(), path }))
}
pub(crate) fn lift_operation(declaration: &mut Expr) -> Option<Reified> {
let mut current = declaration;
loop {
let Expr::MethodCall(call) = current else { return None };
if call.method == "op" {
let handler = call.args.first()?;
let (operation, carrier, reify) = match handler {
Expr::Path(handler) => {
let mut segments = handler.path.segments.iter().rev();
let method = segments.next()?.ident.clone();
let carrier = segments.next()?.ident.clone();
let operation = operation_ident(&method);
(parse_quote!(#operation<#carrier>), carrier, true)
}
handler => {
let operation = first_order_type(handler)?;
let carrier = carrier_of(&operation)?;
(operation, carrier, false)
}
};
if reify {
let argument: Expr = parse_quote!(<#operation>::default());
call.args = Punctuated::from_iter([argument]);
}
return Some(Reified { operation, carrier });
}
current = &mut call.receiver;
}
}
pub(crate) struct Subprograms<'a> {
methods: &'a [Ident],
suffix: &'static str,
programs: Vec<TokenStream>,
}
impl<'a> Subprograms<'a> {
pub(crate) fn new(methods: &'a [Ident], suffix: &'static str) -> Self {
Self { methods, suffix, programs: Vec::new() }
}
pub(crate) fn programs(&self) -> &[TokenStream] {
&self.programs
}
}
impl VisitMut for Subprograms<'_> {
fn visit_expr_mut(&mut self, expression: &mut Expr) {
visit_mut::visit_expr_mut(self, expression);
let Expr::MethodCall(call) = expression else { return };
if !(self.methods.contains(&call.method) || call.method.to_string().ends_with(self.suffix))
|| !matches!(call.receiver.as_ref(), Expr::Path(path) if path.path.is_ident("self"))
{
return;
}
let program = program_ident(&call.method);
let arguments = call.turbofish.as_ref().map(|arguments| arguments.args.clone()).unwrap_or_default();
let program_type = if arguments.is_empty() { quote!(#program) } else { quote!(#program<#arguments>) };
self.programs.push(program_type);
let call = call.clone();
*expression = parse_quote!(self.program(#call));
}
}
pub(crate) struct ReplaceSelf;
impl VisitMut for ReplaceSelf {
fn visit_expr_mut(&mut self, expression: &mut Expr) {
if matches!(expression, Expr::Path(path) if path.path.is_ident("self")) {
*expression = parse_quote!(builder);
} else {
visit_mut::visit_expr_mut(self, expression);
}
}
}