crossflow_derive 0.0.6

Procedural macros for crossflow
Documentation
/*
 * Copyright (C) 2025 Open Source Robotics Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
*/

use std::iter::zip;

use proc_macro2::{Span, TokenStream};
use quote::{quote, quote_spanned};
use syn::{Field, Ident, ItemStruct, Member, Type, parse_quote_spanned, spanned::Spanned};

use crate::Result;

pub(crate) fn impl_section(input_struct: &ItemStruct) -> Result<TokenStream> {
    let struct_ident = &input_struct.ident;
    let (impl_generics, ty_generics, where_clause) = input_struct.generics.split_for_impl();
    let field_ident: Vec<Ident> = input_struct
        .fields
        .members()
        .filter_map(|m| match m {
            Member::Named(m) => Some(m),
            _ => None,
        })
        .collect();
    let field_name_str: Vec<String> = field_ident.iter().map(|ident| ident.to_string()).collect();
    let field_type: Vec<&Type> = input_struct.fields.iter().map(|f| &f.ty).collect();
    let field_configs: Vec<(FieldConfig, Span)> = input_struct
        .fields
        .iter()
        .map(|f| (FieldConfig::from_field(f), f.ty.span()))
        .collect();

    let register_deserialize = gen_register_deserialize(&field_configs);
    let register_serialize = gen_register_serialize(&field_configs);
    let register_fork_clone = gen_register_fork_clone(&field_configs);
    let register_unzip = gen_register_unzip(&field_configs);
    let register_result = gen_register_result(&field_configs);
    let register_split = gen_register_split(&field_configs);
    let register_join = gen_register_join(&field_configs);
    let register_buffer_access = gen_register_buffer_access(&field_configs);
    let register_listen = gen_register_listen(&field_configs);

    let register_message: Vec<TokenStream> = zip(&field_type, &field_configs)
        .map(|(field_type, (_config, span))| {
            quote_spanned! {*span=>
                let mut _message = _opt_out.register_message::<<#field_type as ::crossflow::SectionItem>::MessageType>();
            }
        })
        .collect();

    let tokens = quote! {
        impl #impl_generics ::crossflow::Section for #struct_ident #ty_generics #where_clause {
            fn into_slots(
                self: Box<Self>,
            ) -> ::crossflow::SectionSlots {
                let mut slots = ::crossflow::SectionSlots::new();
                #(
                    self.#field_ident.insert_into_slots(&#field_name_str, &mut slots);
                )*
                slots
            }

            fn on_register(registry: &mut DiagramElementRegistry)
            where
                Self: Sized,
            {
                #({
                    let _opt_out = registry.opt_out();
                    #register_deserialize
                    #register_serialize
                    #register_fork_clone

                    #register_message

                    #register_unzip
                    #register_result
                    #register_split
                    #register_join
                    #register_buffer_access
                    #register_listen
                })*
            }
        }

        impl #impl_generics ::crossflow::SectionMetadataProvider for #struct_ident #ty_generics #where_clause {
            fn metadata() -> &'static ::crossflow::SectionMetadata {
                static METADATA: ::std::sync::OnceLock<::crossflow::SectionMetadata> = ::std::sync::OnceLock::new();
                METADATA.get_or_init(|| {
                    let mut metadata = ::crossflow::SectionMetadata::new();
                    #(
                        <#field_type as ::crossflow::SectionItem>::build_metadata(
                            &mut metadata,
                            &#field_name_str,
                        );
                    )*
                    metadata
                })
            }
        }
    };

    Ok(tokens)
}

struct FieldConfig {
    no_deserialize: bool,
    no_serialize: bool,
    no_clone: bool,
    unzip: bool,
    unzip_minimal: bool,
    result: bool,
    result_minimal: bool,
    split: bool,
    split_minimal: bool,
    join: bool,
    buffer_access: bool,
    listen: bool,
}

impl FieldConfig {
    fn from_field(field: &Field) -> Self {
        let mut config = Self {
            no_deserialize: false,
            no_serialize: false,
            no_clone: false,
            unzip: false,
            unzip_minimal: false,
            result: false,
            result_minimal: false,
            split: false,
            split_minimal: false,
            join: false,
            buffer_access: false,
            listen: false,
        };

        for attr in field
            .attrs
            .iter()
            .filter(|attr| attr.path().is_ident("message"))
        {
            attr.parse_nested_meta(|meta| {
                if meta.path.is_ident("no_deserialize") {
                    config.no_deserialize = true;
                } else if meta.path.is_ident("no_serialize") {
                    config.no_serialize = true;
                } else if meta.path.is_ident("no_clone") {
                    config.no_clone = true;
                } else if meta.path.is_ident("unzip") {
                    config.unzip = true;
                } else if meta.path.is_ident("unzip_minimal") {
                    config.unzip_minimal = true;
                } else if meta.path.is_ident("result") {
                    config.result = true;
                } else if meta.path.is_ident("result_minimal") {
                    config.result_minimal = true;
                } else if meta.path.is_ident("split") {
                    config.split = true;
                } else if meta.path.is_ident("split_minimal") {
                    config.split_minimal = true;
                } else if meta.path.is_ident("join") {
                    config.join = true;
                } else if meta.path.is_ident("buffer_access") {
                    config.buffer_access = true;
                } else if meta.path.is_ident("listen") {
                    config.listen = true;
                }
                Ok(())
            })
            // panic if attribute is malformed, this will result in a compile error which is intended.
            .unwrap();
        }

        config
    }
}

fn gen_register_deserialize(fields: &Vec<(FieldConfig, Span)>) -> Vec<TokenStream> {
    fields
        .into_iter()
        .map(|(config, span)| {
            if config.no_deserialize {
                quote_spanned! {*span=>
                    let _opt_out = _opt_out.no_deserializing();
                }
            } else {
                TokenStream::new()
            }
        })
        .collect()
}

fn gen_register_serialize(fields: &Vec<(FieldConfig, Span)>) -> Vec<TokenStream> {
    fields
        .into_iter()
        .map(|(config, span)| {
            if config.no_serialize {
                parse_quote_spanned! {*span=>
                    let _opt_out = _opt_out.no_serializing();
                }
            } else {
                TokenStream::new()
            }
        })
        .collect()
}

fn gen_register_fork_clone(fields: &Vec<(FieldConfig, Span)>) -> Vec<TokenStream> {
    fields
        .into_iter()
        .map(|(config, span)| {
            if config.no_clone {
                quote_spanned! {*span=>
                    let _opt_out = _opt_out.no_cloning();
                }
            } else {
                TokenStream::new()
            }
        })
        .collect()
}

fn gen_register_unzip(fields: &Vec<(FieldConfig, Span)>) -> Vec<TokenStream> {
    fields
        .into_iter()
        .map(|(config, span)| {
            if config.unzip {
                quote_spanned! {*span=>
                    _message.with_unzip();
                }
            } else if config.unzip_minimal {
                quote_spanned! {*span=>
                    _message.with_unzip_minimal();
                }
            } else {
                TokenStream::new()
            }
        })
        .collect()
}

fn gen_register_result(fields: &Vec<(FieldConfig, Span)>) -> Vec<TokenStream> {
    fields
        .into_iter()
        .map(|(config, span)| {
            if config.result {
                quote_spanned! {*span=>
                    _message.with_result();
                }
            } else if config.result_minimal {
                quote_spanned! {*span=>
                    _message.with_result_minimal();
                }
            } else {
                TokenStream::new()
            }
        })
        .collect()
}

fn gen_register_split(fields: &Vec<(FieldConfig, Span)>) -> Vec<TokenStream> {
    fields
        .into_iter()
        .map(|(config, span)| {
            if config.split {
                quote_spanned! {*span=>
                    _message.with_split();
                }
            } else if config.split_minimal {
                quote_spanned! {*span=>
                    _message.with_split_minimal();
                }
            } else {
                TokenStream::new()
            }
        })
        .collect()
}

fn gen_register_join(fields: &Vec<(FieldConfig, Span)>) -> Vec<TokenStream> {
    fields
        .into_iter()
        .map(|(config, span)| {
            if config.join {
                quote_spanned! {*span=>
                    _message.with_join();
                }
            } else {
                TokenStream::new()
            }
        })
        .collect()
}

fn gen_register_buffer_access(fields: &Vec<(FieldConfig, Span)>) -> Vec<TokenStream> {
    fields
        .into_iter()
        .map(|(config, span)| {
            if config.buffer_access {
                quote_spanned! {*span=>
                    _message.with_buffer_access();
                }
            } else {
                TokenStream::new()
            }
        })
        .collect()
}

fn gen_register_listen(fields: &Vec<(FieldConfig, Span)>) -> Vec<TokenStream> {
    fields
        .into_iter()
        .map(|(config, span)| {
            if config.listen {
                quote_spanned! {*span=>
                    _message.with_listen();
                }
            } else {
                TokenStream::new()
            }
        })
        .collect()
}