r2r_msg_gen/
lib.rs

1#![allow(non_upper_case_globals)]
2#![allow(non_camel_case_types)]
3#![allow(non_snake_case)]
4#![allow(improper_ctypes)]
5#![allow(dead_code)]
6#![allow(clippy::all)]
7
8#[cfg(feature = "doc-only")]
9include!(concat!(env!("OUT_DIR"), "/msg_bindings_doc_only.rs"));
10
11#[cfg(not(feature = "doc-only"))]
12include!(concat!(env!("OUT_DIR"), "/msg_bindings.rs"));
13
14include!(concat!(env!("OUT_DIR"), "/introspection_functions.rs"));
15include!(concat!(env!("OUT_DIR"), "/constants.rs"));
16
17mod introspection;
18
19#[cfg(not(feature = "doc-only"))]
20use {
21    crate::introspection::{MemberType, MessageMember},
22    rayon::prelude::*,
23    std::mem,
24};
25
26use quote::{format_ident, quote};
27use r2r_common::RosMsg;
28use r2r_rcl::*;
29
30use std::{borrow::Cow, ffi::CStr};
31
32use std::slice;
33
34// Copied from bindgen.
35// https://github.com/rust-lang/rust-bindgen/blob/e68b8c0e2b2ceeb42c35e74bd9344a1a99ec2e0c/src/ir/context.rs#L817
36fn rust_mangle<'a>(name: &'a str) -> Cow<'a, str> {
37    if name.contains('@')
38        || name.contains('?')
39        || name.contains('$')
40        || matches!(
41            name,
42            "abstract"
43                | "alignof"
44                | "as"
45                | "async"
46                | "await"
47                | "become"
48                | "box"
49                | "break"
50                | "const"
51                | "continue"
52                | "crate"
53                | "do"
54                | "dyn"
55                | "else"
56                | "enum"
57                | "extern"
58                | "false"
59                | "final"
60                | "fn"
61                | "for"
62                | "if"
63                | "impl"
64                | "in"
65                | "let"
66                | "loop"
67                | "macro"
68                | "match"
69                | "mod"
70                | "move"
71                | "mut"
72                | "offsetof"
73                | "override"
74                | "priv"
75                | "proc"
76                | "pub"
77                | "pure"
78                | "ref"
79                | "return"
80                | "Self"
81                | "self"
82                | "sizeof"
83                | "static"
84                | "struct"
85                | "super"
86                | "trait"
87                | "true"
88                | "try"
89                | "type"
90                | "typeof"
91                | "unsafe"
92                | "unsized"
93                | "use"
94                | "virtual"
95                | "where"
96                | "while"
97                | "yield"
98                | "str"
99                | "bool"
100                | "f32"
101                | "f64"
102                | "usize"
103                | "isize"
104                | "u128"
105                | "i128"
106                | "u64"
107                | "i64"
108                | "u32"
109                | "i32"
110                | "u16"
111                | "i16"
112                | "u8"
113                | "i8"
114                | "_"
115        )
116    {
117        let mut s = name.to_owned();
118        s = s.replace('@', "_");
119        s = s.replace('?', "_");
120        s = s.replace('$', "_");
121        s.push('_');
122        return Cow::Owned(s);
123    }
124    Cow::Borrowed(name)
125}
126
127unsafe fn introspection<'a>(
128    ptr: *const rosidl_message_type_support_t,
129) -> (
130    String,
131    String,
132    String,
133    String,
134    &'a [rosidl_typesupport_introspection_c__MessageMember],
135) {
136    let members = (*ptr).data as *const rosidl_typesupport_introspection_c__MessageMembers;
137    let namespace = CStr::from_ptr((*members).message_namespace_)
138        .to_str()
139        .unwrap();
140    let name = CStr::from_ptr((*members).message_name_).to_str().unwrap();
141    let nn: Vec<&str> = namespace.split("__").into_iter().take(2).collect();
142    let (module, prefix) = (nn[0], nn[1]);
143    let c_struct = format!(
144        "{module}__{prefix}__{msgname}",
145        module = module,
146        prefix = prefix,
147        msgname = name
148    );
149    let memberslice = slice::from_raw_parts((*members).members_, (*members).member_count_ as usize);
150    (module.to_owned(), prefix.to_owned(), name.to_owned(), c_struct, memberslice)
151}
152
153#[cfg(not(feature = "doc-only"))]
154pub fn generate_rust_service(
155    module_: &str, prefix_: &str, name_: &str,
156) -> proc_macro2::TokenStream {
157    let ident = format_ident!(
158        "rosidl_typesupport_c__\
159         get_service_type_support_handle__\
160         {module_}__\
161         {prefix_}__\
162         {name_}"
163    );
164
165    quote!(
166        #[derive(Clone,Debug,PartialEq,Serialize,Deserialize)]
167        pub struct Service();
168        impl WrappedServiceTypeSupport for Service {
169            type Request = Request;
170            type Response = Response;
171
172            fn get_ts() -> &'static rosidl_service_type_support_t {
173                unsafe {
174                    &* #ident ()
175                }
176            }
177        }
178
179    )
180}
181
182#[cfg(not(feature = "doc-only"))]
183pub fn generate_rust_action(module_: &str, prefix_: &str, name_: &str) -> proc_macro2::TokenStream {
184    let ident = format_ident!(
185        "rosidl_typesupport_c__\
186         get_action_type_support_handle__\
187         {module_}__\
188         {prefix_}__\
189         {name_}"
190    );
191
192    quote! {
193        #[derive(Clone,Debug,PartialEq,Serialize,Deserialize)]
194        pub struct Action();
195        impl WrappedActionTypeSupport for Action {
196            type Goal = Goal;
197            type Result = Result;
198            type Feedback = Feedback;
199
200            // internal structs
201            type FeedbackMessage = FeedbackMessage;
202            type SendGoal = SendGoal::Service;
203            type GetResult = GetResult::Service;
204
205            fn get_ts() -> &'static rosidl_action_type_support_t {
206                unsafe {
207                    &* #ident ()
208                }
209            }
210
211            fn make_goal_request_msg(goal_id: unique_identifier_msgs::msg::UUID, goal: Goal) -> SendGoal::Request {
212                SendGoal::Request {
213                     goal_id,
214                     goal
215                }
216            }
217
218            fn make_goal_response_msg(accepted: bool, stamp: builtin_interfaces::msg::Time) -> SendGoal::Response {
219                SendGoal::Response {
220                     accepted,
221                     stamp
222                }
223            }
224
225            fn make_feedback_msg(goal_id: unique_identifier_msgs::msg::UUID, feedback: Feedback) -> FeedbackMessage {
226                FeedbackMessage {
227                     goal_id,
228                     feedback
229                }
230            }
231
232            fn make_result_request_msg(goal_id: unique_identifier_msgs::msg::UUID) -> GetResult::Request {
233                GetResult::Request {
234                     goal_id,
235                }
236            }
237
238            fn make_result_response_msg(status: i8, result: Result) -> GetResult::Response {
239                GetResult::Response {
240                     status,
241                     result,
242                }
243            }
244
245            fn destructure_goal_request_msg(msg: SendGoal::Request) -> (unique_identifier_msgs::msg::UUID, Goal) {
246                (msg.goal_id, msg.goal)
247            }
248
249            fn destructure_goal_response_msg(msg: SendGoal::Response) -> (bool, builtin_interfaces::msg::Time) {
250                (msg.accepted, msg.stamp)
251            }
252
253            fn destructure_feedback_msg(msg: FeedbackMessage) -> (unique_identifier_msgs::msg::UUID, Feedback) {
254                (msg.goal_id, msg.feedback)
255            }
256
257            fn destructure_result_response_msg(msg: GetResult::Response) -> (i8, Result) {
258                (msg.status, msg.result)
259            }
260
261            fn destructure_result_request_msg(msg: GetResult::Request) -> unique_identifier_msgs::msg::UUID {
262                msg.goal_id
263            }
264        }
265    }
266}
267
268#[cfg(not(feature = "doc-only"))]
269pub fn generate_rust_msg(module_: &str, prefix_: &str, name_: &str) -> proc_macro2::TokenStream {
270    let key = format!("{}__{}__{}", module_, prefix_, name_);
271    let func = *INTROSPECTION_FNS
272        .get(key.as_str())
273        .unwrap_or_else(|| panic!("code generation error: {}", key));
274    let ptr = unsafe { func() };
275
276    let (module, prefix, name, c_struct, c_members) = unsafe { introspection(ptr) };
277    // let members: &[MessageMember] = unsafe { mem::transmute(c_members) };
278    assert_eq!(module, module_);
279    assert_eq!(prefix, prefix_);
280    assert_eq!(name, name_);
281
282    let name = if ["srv", "action"].contains(&prefix.as_str()) {
283        // for srv, the message name is both the service name and _Request or _Respone
284        // we only want to keep the last part.
285        // same for actions with _Goal, _Result, _Feedback
286        // TODO: refactor...
287        // handle special case of ActionName_ServiceName_Response
288        let nn: Vec<&str> = name.splitn(3, '_').collect();
289        if let [_mod_name, _srv_name, msg_name] = &nn[..] {
290            msg_name.to_string()
291        } else if let [_mod_name, msg_name] = &nn[..] {
292            msg_name.to_string()
293        } else {
294            panic!("malformed service name {}", name);
295        }
296    } else {
297        name
298    };
299
300    // let module = format_ident!("{module}");
301    // let prefix = format_ident!("{prefix}");
302    let name = format_ident!("{name}");
303    let c_struct = format_ident!("{c_struct}");
304
305    let fields = {
306        let fields: Vec<_> = c_members
307            .into_iter()
308            .filter_map(|c_member| {
309                let member: &MessageMember = unsafe { mem::transmute(c_member) };
310
311                let actual_field_name = member.name();
312                let field_name = member.rust_name();
313                let got_mangled = field_name != actual_field_name;
314                if field_name == "structure_needs_at_least_one_member" {
315                    // Yay we can have empty structs in rust
316                    return None;
317                }
318
319                let field_name = format_ident!("{field_name}");
320
321                let rust_field_type = member.type_id();
322                let rust_field_type = if rust_field_type == MemberType::Message {
323                    let (module, prefix, name, _, _) = unsafe { introspection(c_member.members_) };
324                    let module = format_ident!("{module}");
325                    let prefix = format_ident!("{prefix}");
326
327                    // hack here to rustify nested action type names
328                    if prefix == "action" {
329                        if let Some((n1, n2)) = name.rsplit_once("_") {
330                            let n1 = format_ident!("{n1}");
331                            let n2 = format_ident!("{n2}");
332                            quote! { #module :: #prefix :: #n1  :: #n2 }
333                        } else {
334                            let name = format_ident!("{name}");
335                            quote! { #module :: #prefix :: #name }
336                        }
337                    } else {
338                        let name = format_ident!("{name}");
339                        quote! { #module :: #prefix :: #name }
340                    }
341                } else {
342                    rust_field_type.to_rust_type()
343                };
344
345                let field = if c_member.is_array_ {
346                    // if member.array_size_ > 0 {
347                    // fixed size array
348                    // format!("pub {}: [{};{}usize],\n",field_name, rust_field_type, array_size)
349                    // actually lets use a vector anyway because its more convenient with traits. assert on the fixed size instead!
350                    //} else {
351                    // vector type
352                    quote! { pub #field_name : Vec< #rust_field_type > }
353                //}
354                } else {
355                    quote! { pub #field_name : #rust_field_type }
356                };
357
358                let attr = got_mangled.then(|| {
359                    quote! { #[serde(rename = #actual_field_name )] }
360                });
361
362                Some(quote! {
363                    #attr
364                    #field
365                })
366            })
367            .collect();
368        quote! { #(#fields),* }
369    };
370
371    let from_native = {
372        let fields = c_members.into_iter().filter_map(|c_member| {
373            let member: &MessageMember = unsafe { mem::transmute(c_member) };
374            let field_name = member.rust_name();
375            if field_name == "structure_needs_at_least_one_member" {
376                // Yay we can have empty structs in rust
377                return None;
378            }
379            let field_name = format_ident!("{field_name}");
380            let rust_field_type = member.type_id();
381            let array_info = member.array_info();
382
383            let fields = if let Some(array_info) = array_info {
384                if array_info.size > 0 && !array_info.is_upper_bound {
385                    // these are plain arrays
386                    // let is_upper_bound = member.is_upper_bound();
387                    // let array_size = array_info.size;
388                    let fields1 = quote! {
389                        // is_upper_bound_: #is_upper_bound,
390                        // member.array_size_ : #array_size,
391                    };
392
393                    let field2 = match rust_field_type {
394                        MemberType::Message => {
395                            let (module, prefix, name, _, _) =
396                                unsafe { introspection(c_member.members_) };
397                            let module = format_ident!("{module}");
398                            let prefix = format_ident!("{prefix}");
399                            let name = format_ident!("{name}");
400
401                            quote! {
402                                #field_name: {
403                                    let vec: Vec<_> = msg
404                                        .#field_name
405                                        .iter()
406                                        .map(|s| #module :: #prefix :: #name :: from_native(s))
407                                        .collect();
408                                    vec
409                                },
410                            }
411                        }
412                        MemberType::String | MemberType::WString => {
413                            quote! {
414                                #field_name :
415                                msg. #field_name .iter().map(|s|s.to_str().to_owned()).collect(),
416                            }
417                        }
418                        _ => {
419                            quote! {
420                                #field_name : msg. #field_name .to_vec(),
421                            }
422                        }
423                    };
424
425                    quote! {
426                        #fields1
427                        #field2
428                    }
429                } else {
430                    // these are __Sequence:s
431                    // let is_upper_bound = member.is_upper_bound();
432                    // let array_size = array_info.size;
433
434                    let fields1 = quote! {
435                        // is_upper_bound_: #is_upper_bound
436                        // member.array_size_ : #array_size
437                    };
438
439                    let field2 = if rust_field_type == MemberType::Message {
440                        let (module, prefix, name, _, _) =
441                            unsafe { introspection(c_member.members_) };
442                        let module = format_ident!("{module}");
443                        let prefix = format_ident!("{prefix}");
444                        let name = format_ident!("{name}");
445
446                        quote! {
447                            #field_name : {
448                                let mut temp = Vec::with_capacity(msg. #field_name .size);
449                                if msg. #field_name .data != std::ptr::null_mut() {
450                                    let slice = unsafe {
451                                        std::slice::from_raw_parts(
452                                            msg. #field_name .data,
453                                            msg. #field_name .size
454                                        )
455                                    };
456                                    for s in slice {
457                                        temp.push( #module :: #prefix :: #name :: from_native(s));
458                                    }
459                                }
460                                temp
461                            },
462                        }
463                    } else {
464                        quote! {
465                            #field_name: msg. #field_name .to_vec(),
466                        }
467                    };
468
469                    quote! {
470                        #fields1
471                        #field2
472                    }
473                }
474            } else {
475                match rust_field_type {
476                    MemberType::String | MemberType::WString => {
477                        quote! {
478                            #field_name: msg. #field_name .to_str().to_owned(),
479                        }
480                    }
481                    MemberType::Message => {
482                        let (module, prefix, name, _, _) =
483                            unsafe { introspection(c_member.members_) };
484                        let module = format_ident!("{module}");
485                        let prefix = format_ident!("{prefix}");
486
487                        // same hack as above to rustify message type names
488                        if prefix == "action" {
489                            let (srvname, msgname) =
490                                name.rsplit_once("_").expect("ooops at from_native");
491                            let srvname = format_ident!("{srvname}");
492                            let msgname = format_ident!("{msgname}");
493
494                            quote! {
495                                #field_name:
496                                #module ::
497                                #prefix ::
498                                #srvname ::
499                                #msgname ::
500                                from_native(&msg. #field_name ),
501                            }
502                        } else {
503                            let name = format_ident!("{name}");
504
505                            quote! {
506                                #field_name :
507                                #module :: #prefix :: #name ::from_native(&msg. #field_name),
508                            }
509                        }
510                    }
511                    _ => {
512                        quote! {
513                            #field_name : msg. #field_name,
514                        }
515                    }
516                }
517            };
518
519            Some(fields)
520        });
521        // let name = name;
522        quote! {
523            fn from_native(#[allow(unused)] msg: &Self::CStruct) -> #name {
524                #name {
525                    #(#fields)*
526                }
527            }
528        }
529    };
530
531    let copy_to_native = {
532        let stmts = c_members.into_iter().filter_map(|c_member| {
533            let member: &MessageMember = unsafe { mem::transmute(c_member) };
534            let field_name_str = member.rust_name();
535            if field_name_str == "structure_needs_at_least_one_member" {
536                // Yay we can have empty structs in rust
537                return None
538            }
539            let rust_field_type = member.type_id();
540            let array_info = member.array_info();
541            let field_name = format_ident!("{field_name_str}");
542
543            let stmts = if let Some(array_info) = array_info {
544                let array_size = array_info.size;
545
546                if array_info.size > 0 && !array_info.is_upper_bound {
547                    // these are plain arrays
548                    // fixed size array, just copy but first check the size!
549
550                    let content = match rust_field_type {
551                        MemberType::Message => {
552                            quote! {
553                                for (t, s) in msg. #field_name .iter_mut().zip(&self. #field_name ) {
554                                    s.copy_to_native(t);
555                                }
556                            }
557                        }
558                        MemberType::String | MemberType::WString => {
559                            quote! {
560                                for (t, s) in msg. #field_name .iter_mut().zip(&self. #field_name ) {
561                                    t.assign(&s);
562                                }
563                            }
564                        }
565                        _ => {
566                            quote! {
567                                msg. #field_name .copy_from_slice(&self. #field_name [.. #array_size ]);
568                            }
569                        }
570                    };
571
572                    quote! {
573                        assert_eq!(
574                            self. #field_name .len(),
575                            #array_size ,
576                            "Field {} is fixed size of {}!", #field_name_str, #array_size
577                        );
578                        #content
579                    }
580                } else {
581                    // these are __Sequence:s
582
583                    if rust_field_type == MemberType::Message {
584                        let (_, _, _, c_struct, _) = unsafe { introspection(c_member.members_) };
585                        let init_func = format_ident!("{c_struct}__Sequence__init");
586                        let fini_func = format_ident!("{c_struct}__Sequence__fini");
587                        let field_name = format_ident!("{field_name}");
588
589                        quote! {
590                            unsafe {
591                                #fini_func (&mut msg. #field_name);
592                                #init_func (&mut msg. #field_name , self. #field_name .len());
593
594                                if msg. #field_name .data != std::ptr::null_mut() {
595                                    let slice = std::slice::from_raw_parts_mut(msg. #field_name .data, msg. #field_name .size);
596                                    for (t, s) in slice.iter_mut().zip(&self. #field_name ) {
597                                        s.copy_to_native(t);
598                                    }
599                                }
600                            }
601                        }
602                    } else {
603                        // extra assertion
604                        let assert_stmt = array_info.is_upper_bound.then(|| {
605                            quote! {
606                                assert!(
607                                    self. #field_name .len() <= #array_size,
608                                    "Field {} is upper bounded by {}!",
609                                    #field_name_str, #array_size
610                                );
611                            }
612                        });
613
614                        quote! {
615                            #assert_stmt
616                            msg. #field_name .update(&self. #field_name );
617                        }
618                    }
619                }
620            } else {
621                match rust_field_type {
622                    MemberType::String | MemberType::WString => {
623                        quote! {
624                            msg. #field_name .assign(&self. #field_name );
625                        }
626                    }
627                    MemberType::Message => {
628                        quote! {
629                            self. #field_name .copy_to_native(&mut msg. #field_name );
630                        }
631                    }
632                    _ => {
633                        quote! {
634                            msg. #field_name = self. #field_name;
635                        }
636                    }
637                }
638            };
639
640            Some(stmts)
641        });
642
643        quote! {
644            fn copy_to_native(&self, #[allow(unused)] msg: &mut Self::CStruct) {
645                #(#stmts)*
646            }
647        }
648    };
649
650    let typesupport = {
651        let type_support_handle =
652            format_ident!("rosidl_typesupport_c__get_message_type_support_handle__{c_struct}");
653        let create_func = format_ident!("{c_struct}__create");
654        let destroy_func = format_ident!("{c_struct}__destroy");
655
656        quote! {
657            impl WrappedTypesupport for #name {
658                type CStruct = #c_struct;
659
660                fn get_ts() -> &'static rosidl_message_type_support_t {
661                    unsafe {
662                        &* #type_support_handle()
663                    }
664                }
665
666                fn create_msg() -> *mut #c_struct {
667                    #[cfg(not(feature = "doc-only"))]
668                    unsafe {
669                        #create_func ()
670                    }
671                    #[cfg(feature = "doc-only")]
672                    #create_func ()
673                }
674
675                fn destroy_msg(msg: *mut #c_struct) -> () {
676                    #[cfg(not(feature = "doc-only"))]
677                    unsafe {
678                        #destroy_func (msg)
679                    };
680                    #[cfg(feature = "doc-only")]
681                    #destroy_func (msg)
682                }
683
684                #from_native
685                #copy_to_native
686            }
687        }
688    };
689
690    let impl_default = quote! {
691        impl Default for #name {
692            fn default() -> Self {
693                let msg_native = WrappedNativeMsg::< #name >::new();
694                #name :: from_native(&msg_native)
695            }
696        }
697    };
698
699    let constant_items: Vec<_> = CONSTANTS_MAP
700        .get(&key)
701        .cloned()
702        .into_iter()
703        .flatten()
704        .map(|(const_name, typ)| {
705            let const_name = format_ident!("{const_name}");
706            let value = format_ident!("{key}__{const_name}");
707            if let Ok(mut typ) = syn::parse_str::<Box<syn::TypeReference>>(typ) {
708                // If the constant is a reference, rustc needs it to be static.
709                // (see https://github.com/rust-lang/rust/issues/115010)
710                typ.lifetime = Some(syn::Lifetime::new("'static", proc_macro2::Span::call_site()));
711                quote! { pub const #const_name: #typ = #value; }
712            } else if let Ok(typ) = syn::parse_str::<Box<syn::Type>>(typ) {
713                // Value
714                quote! { pub const #const_name: #typ = #value; }
715            } else {
716                // Something else, hope for the best but will most likely fail to compile.
717                quote! { pub const #const_name: #typ = #value; }
718            }
719        })
720        .collect();
721
722    let impl_constants = if constant_items.is_empty() {
723        quote! {}
724    } else {
725        quote! {
726            #[allow(non_upper_case_globals)]
727            impl #name {
728                #(#constant_items)*
729            }
730        }
731    };
732
733    quote! {
734        #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
735        #[serde(default)]
736        pub struct #name {
737            #fields
738        }
739
740        #typesupport
741        #impl_default
742        #impl_constants
743    }
744}
745
746#[cfg(not(feature = "doc-only"))]
747pub fn generate_untyped_helper(msgs: &[RosMsg]) -> proc_macro2::TokenStream {
748    let (funcs, entries): (Vec<_>, Vec<_>) = msgs
749        .into_par_iter()
750        .filter(|msg| !["srv", "action"].contains(&msg.prefix.as_str()))
751        .map(|msg| {
752            let RosMsg {
753                module,
754                prefix,
755                name,
756            } = msg;
757            let typename = type_name(&msg);
758            let rustname = {
759                let module = format_ident!("{module}");
760                let prefix = format_ident!("{prefix}");
761                let name = format_ident!("{name}");
762                quote! { #module :: #prefix :: #name }
763            };
764
765            let func_ident =
766                format_ident!("new_wrapped_native_msg_untyped_{module}_{prefix}_{name}");
767            let func = quote! {
768                #[allow(non_snake_case)]
769                fn #func_ident() -> WrappedNativeMsgUntyped {
770                    WrappedNativeMsgUntyped::new::< #rustname >()
771                }
772            };
773            let entry = quote! { #typename => #func_ident };
774
775            (unsafe { force_send(func) }, unsafe { force_send(entry) })
776        })
777        .unzip();
778    let funcs = funcs.into_iter().map(|tokens| tokens.unwrap());
779    let entries = entries.into_iter().map(|tokens| tokens.unwrap());
780
781    quote! {
782        impl WrappedNativeMsgUntyped {
783            pub fn new_from(typename: &str) -> Result<Self> {
784                #(#funcs)*
785
786                static MAP: phf::Map<&'static str, fn() -> WrappedNativeMsgUntyped> = phf::phf_map! { #(#entries),* };
787
788                let func = MAP.get(typename)
789                    .ok_or_else(|| Error::InvalidMessageType{ msgtype: typename.into() })?;
790                Ok(func())
791            }
792        }
793    }
794}
795
796#[cfg(not(feature = "doc-only"))]
797pub fn generate_untyped_service_helper(msgs: &[RosMsg]) -> proc_macro2::TokenStream {
798    let (funcs, entries): (Vec<_>, Vec<_>) = msgs
799        .into_par_iter()
800        .filter(|msg| msg.prefix == "srv")
801        .map(|msg| {
802            let RosMsg {
803                module,
804                prefix,
805                name,
806            } = msg;
807            let typename = type_name(&msg);
808            let rustname = {
809                let module = format_ident!("{module}");
810                let prefix = format_ident!("{prefix}");
811                let name = format_ident!("{name}");
812                quote! { #module :: #prefix :: #name :: Service }
813            };
814            let func_ident = format_ident!("new_untyped_service_support_{module}_{prefix}_{name}");
815
816            let func = quote! {
817                #[allow(non_snake_case)]
818                fn #func_ident() -> UntypedServiceSupport {
819                    UntypedServiceSupport::new::< #rustname  >()
820                }
821            };
822            let entry = quote! { #typename => #func_ident };
823
824            unsafe { (force_send(func), force_send(entry)) }
825        })
826        .unzip();
827    let funcs = funcs.into_iter().map(|tokens| tokens.unwrap());
828    let entries = entries.into_iter().map(|tokens| tokens.unwrap());
829
830    quote! {
831        impl UntypedServiceSupport {
832            pub fn new_from(typename: &str) -> Result<Self> {
833                #(#funcs)*
834
835                static MAP: phf::Map<&'static str, fn() -> UntypedServiceSupport> = phf::phf_map! { #(#entries),* };
836
837                let func = MAP.get(typename)
838                    .ok_or_else(|| Error::InvalidMessageType{ msgtype: typename.into() })?;
839                Ok(func())
840            }
841        }
842    }
843}
844
845#[cfg(not(feature = "doc-only"))]
846pub fn generate_untyped_action_helper(msgs: &[RosMsg]) -> proc_macro2::TokenStream {
847    let (funcs, entries): (Vec<_>, Vec<_>) = msgs
848        .into_par_iter()
849        .filter(|msg| msg.prefix == "action")
850        .map(|msg| {
851            let RosMsg {
852                module,
853                prefix,
854                name,
855            } = msg;
856            let typename = type_name(&msg);
857            let rustname = {
858                let module = format_ident!("{module}");
859                let prefix = format_ident!("{prefix}");
860                let name = format_ident!("{name}");
861                quote! { #module :: #prefix :: #name :: Action }
862            };
863            let func_ident = format_ident!("new_untyped_service_support_{module}_{prefix}_{name}");
864
865            let func = quote! {
866                #[allow(non_snake_case)]
867                fn #func_ident() -> UntypedActionSupport {
868                    UntypedActionSupport::new::< #rustname  >()
869                }
870            };
871            let entry = quote! { #typename => #func_ident };
872
873            unsafe { (force_send(func), force_send(entry)) }
874        })
875        .unzip();
876
877    let funcs = funcs.into_iter().map(|tokens| tokens.unwrap());
878    let entries = entries.into_iter().map(|tokens| tokens.unwrap());
879
880    quote! {
881        impl UntypedActionSupport {
882            pub fn new_from(typename: &str) -> Result<Self> {
883                #(#funcs)*
884
885                static MAP: phf::Map<&'static str, fn() -> UntypedActionSupport> = phf::phf_map! { #(#entries),* };
886
887                let func = MAP.get(typename)
888                    .ok_or_else(|| Error::InvalidMessageType{ msgtype: typename.into() })?;
889                Ok(func())
890            }
891        }
892    }
893}
894
895fn type_name(msg: &RosMsg) -> String {
896    let RosMsg {
897        module,
898        prefix,
899        name,
900    } = msg;
901    format!("{module}/{prefix}/{name}")
902}
903
904fn rust_name(msg: &RosMsg) -> proc_macro2::TokenStream {
905    let RosMsg {
906        module,
907        prefix,
908        name,
909    } = msg;
910    let module = format_ident!("{module}");
911    let prefix = format_ident!("{prefix}");
912    let name = format_ident!("{name}");
913    quote! { #module :: #prefix :: #name :: Action }
914}
915
916unsafe fn force_send<T>(value: T) -> force_send_sync::Send<T> {
917    force_send_sync::Send::new(value)
918}