cubecl-macros-internal 0.9.0

Internal procedural macros for CubeCL
Documentation
use darling::{FromDeriveInput, util::Flag};
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::DeriveInput;

use crate::parse::operation::{OpCode, Operation, OperationVariant};

impl Operation {
    fn variants(&self) -> Vec<&OperationVariant> {
        self.data.as_ref().take_enum().unwrap()
    }

    fn generate_opcode_impl(&self) -> TokenStream {
        let opcode = &self.opcode_name;
        let variants = self.variants();
        let match_variants = variants.iter().map(|variant| {
            let ident = &variant.ident;
            if variant.nested.is_present() {
                quote![Self::#ident(child) => #opcode::#ident(crate::OperationReflect::op_code(child))]
            } else if variant.fields.is_empty() {
                quote![Self::#ident => #opcode::#ident]
            } else if variant.fields.fields[0].ident.is_some() {
                quote![Self::#ident { .. } => #opcode::#ident]
            } else {
                let args = variant.fields.fields.iter().map(|_| quote![_]);
                quote![Self::#ident(#(#args),*) => #opcode::#ident]
            }
        });
        quote! {
            match self {
                #(#match_variants),*
            }
        }
    }

    fn generate_args_impl(&self) -> TokenStream {
        let variants = self.variants();
        let match_variants = variants.iter().map(|variant| {
            let ident = &variant.ident;
            if variant.nested.is_present() {
                quote![Self::#ident(child) => crate::OperationReflect::args(child)]
            } else if variant.fields.is_empty() {
                quote![Self::#ident => Some(alloc::vec::Vec::new())]
            } else if variant.fields.fields[0].ident.is_some() {
                let names = variant
                    .fields
                    .fields
                    .iter()
                    .map(|it| it.ident.clone().unwrap())
                    .collect::<Vec<_>>();
                let body = quote![{
                    let mut args = alloc::vec::Vec::new();
                    #(args.extend(crate::FromArgList::as_arg_list(#names));)*
                    Some(args)
                }];
                quote![Self::#ident { #(#names),* } => #body]
            } else {
                quote![Self::#ident(args) => crate::OperationArgs::as_args(args)]
            }
        });
        quote! {
            match self {
                #(#match_variants),*
            }
        }
    }

    fn generate_from_args_impl(&self) -> TokenStream {
        let opcode = &self.opcode_name;
        let variants = self.variants();
        let match_variants = variants.iter().map(|variant| {
            let ident = &variant.ident;
            if variant.nested.is_present() {
                quote![#opcode::#ident(child) => Some(Self::#ident(crate::OperationReflect::from_code_and_args(child, args)?))]
            } else if variant.fields.is_empty() {
                quote![#opcode::#ident => Some(Self::#ident)]
            } else if variant.fields.fields[0].ident.is_some() {
                let fields = variant
                    .fields
                    .fields
                    .iter()
                    .map(|it| it.ident.clone().unwrap())
                    .map(|it| quote![#it: crate::FromArgList::from_arg_list(&mut args)]);
                quote![#opcode::#ident => {
                    let mut args: alloc::collections::VecDeque<crate::Variable> = args.iter().cloned().collect();
                    Some(Self::#ident {
                        #(#fields),*
                    })
                }]
            } else {
                quote![#opcode::#ident => crate::OperationArgs::from_args(args).map(Self::#ident)]
            }
        });
        quote! {
            match op_code {
                #(#match_variants),*
            }
        }
    }

    fn generate_bool_property(
        &self,
        flag_global: impl Fn(&Self) -> Flag,
        flag_local: impl Fn(&OperationVariant) -> Flag,
        func_ident: &str,
    ) -> TokenStream {
        let func_ident = format_ident!("{func_ident}");
        if flag_global(self).is_present() {
            quote! {
                fn #func_ident(&self) -> bool { true }
            }
        } else {
            let variants = self.variants();
            let variants = variants.iter().map(|variant| {
                let ident = &variant.ident;
                let value = flag_local(variant).is_present();
                if variant.nested.is_present() && !value {
                    quote![Self::#ident(child) => crate::OperationReflect::#func_ident(child)]
                } else if variant.fields.is_empty() {
                    quote![Self::#ident => #value]
                } else if variant.fields.is_struct() {
                    quote![Self::#ident { .. } => #value]
                } else {
                    let args = variant.fields.fields.iter().map(|_| quote![_]);
                    quote![Self::#ident(#(#args),*) => #value]
                }
            });
            quote! {
                fn #func_ident(&self) -> bool {
                    match self {
                        #(#variants),*
                    }
                }
            }
        }
    }

    fn generate_operation_impl(&self) -> TokenStream {
        let name = &self.ident;
        let opcode_name = &self.opcode_name;
        let (generics, generic_names, where_clause) = self.generics.split_for_impl();

        let opcode_impl = self.generate_opcode_impl();
        let args_impl = self.generate_args_impl();
        let from_args_impl = self.generate_from_args_impl();
        let commutative =
            self.generate_bool_property(|x| x.commutative, |x| x.commutative, "is_commutative");
        let pure = self.generate_bool_property(|x| x.pure, |x| x.pure, "is_pure");

        quote![impl #generics crate::OperationReflect for #name #generic_names #where_clause {
            type OpCode = #opcode_name;

            fn op_code(&self) -> Self::OpCode {
                #opcode_impl
            }
            fn args(&self) -> Option<alloc::vec::Vec<crate::Variable>> {
                #args_impl
            }
            fn from_code_and_args(op_code: Self::OpCode, args: &[crate::Variable]) -> Option<Self> {
                #from_args_impl
            }

            #commutative
            #pure
        }]
    }

    fn generate_opcode(&self) -> TokenStream {
        let vis = &self.vis;
        let name = &self.opcode_name;
        let variants = self.variants();
        let variants = variants.iter().map(|variant| {
            let ident = &variant.ident;
            if variant.nested.is_present() {
                let child_ty = &variant.fields.fields[0].ty;
                quote![#ident(<#child_ty as crate::OperationReflect>::OpCode)]
            } else {
                quote![#ident]
            }
        });
        quote! {
            #[derive(Debug, Clone, Copy, TypeHash, PartialEq, Eq, Hash, PartialOrd, Ord, derive_more::From)]
            #vis enum #name {
                #(#variants),*
            }
        }
    }
}

pub fn generate_operation(input: DeriveInput) -> syn::Result<TokenStream> {
    let operation = Operation::from_derive_input(&input)?;

    let opcode = operation.generate_opcode();
    let operation_impl = operation.generate_operation_impl();

    Ok(quote! {
        #opcode
        #operation_impl
    })
}

pub fn generate_opcode(input: DeriveInput) -> syn::Result<TokenStream> {
    let operation = OpCode::from_derive_input(&input)?;
    let operation = Operation {
        ident: operation.ident,
        vis: operation.vis,
        generics: operation.generics,
        data: operation.data,
        opcode_name: operation.opcode_name,
        commutative: Flag::default(),
        pure: Flag::default(),
    };

    let name = &operation.ident;
    let generics = &operation.generics;
    let opcode_name = &operation.opcode_name;
    let opcode = operation.generate_opcode();
    let match_opcode = operation.generate_opcode_impl();

    Ok(quote! {
        #opcode

        impl #generics #name {
            fn __match_opcode(&self) -> #opcode_name {
                #match_opcode
            }
        }
    })
}