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::{
Expr, GenericParam, Ident, ImplItem, ImplItemFn, ItemImpl, 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![,]> {
self.item.generics.where_clause.as_ref().map(|clause| clause.predicates.clone()).unwrap_or_default()
}
pub(crate) fn forwarded(&self, attr: TokenStream) -> TokenStream {
match &self.visibility {
Some(visibility) => quote!(#visibility, #attr),
None => attr,
}
}
}
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}")
}
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) => Ok(param.clone()),
_ => Err(syn::Error::new_spanned(param, rejected)),
})
.collect()
}
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<Type> {
let mut current = declaration;
loop {
let Expr::MethodCall(call) = current else { return None };
if call.method == "op" {
let handler = call.args.first()?;
let (operation, reified) = match handler {
Expr::Path(handler) => {
let method = handler.path.segments.last()?.ident.clone();
let operation = operation_ident(&method);
(parse_quote!(#operation<Alg>), true)
}
handler => (first_order_type(handler)?, false),
};
if reified {
let argument: Expr = parse_quote!(<#operation>::default());
call.args = Punctuated::from_iter([argument]);
}
return Some(operation);
}
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);
}
}
}