Skip to content

Commit 14a7f51

Browse files
Rust::com Derive macro for CommData
* Implemented derive macro for CommData trait impl
1 parent cd18300 commit 14a7f51

3 files changed

Lines changed: 291 additions & 11 deletions

File tree

score/mw/com/example/com-api-example/com-api-gen/com_api_gen.rs

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,24 +16,18 @@ use com_api::{
1616
Runtime, Subscriber,
1717
};
1818

19-
#[derive(Debug, Reloc)]
19+
#[derive(Debug, CommData)]
2020
#[repr(C)]
21+
#[comm_data(id = "Tire")]
2122
pub struct Tire {
2223
pub pressure: f32,
2324
}
2425

25-
impl CommData for Tire {
26-
const ID: &'static str = "Tire";
27-
}
28-
29-
#[derive(Debug, Reloc)]
26+
#[derive(Debug, CommData)]
3027
#[repr(C)]
28+
// No explicit ID provided, so it will be auto-generated as "com_api_gen::Exhaust"
3129
pub struct Exhaust {}
3230

33-
impl CommData for Exhaust {
34-
const ID: &'static str = "Exhaust";
35-
}
36-
3731
pub struct VehicleInterface {}
3832

3933
/// Generic

score/mw/com/impl/rust/com-api/com-api-concept-macros/lib.rs

Lines changed: 278 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,140 @@
1212
********************************************************************************/
1313

1414
use proc_macro::TokenStream;
15-
use quote::quote;
15+
use quote::{quote, ToTokens};
1616
use syn::{parse_macro_input, parse_quote, Data, DeriveInput, Fields, Generics, Meta, Type};
1717

18+
/// Derive macro for the `CommData` trait. This macro automatically implements the `CommData` trait
19+
/// for structs and C-like enums, ensuring that the type is marked with `#[repr(C)]`.
20+
/// It internally also derives the `Reloc` trait for the type.
21+
///
22+
/// # Important
23+
/// Users must NOT implement the `CommData` trait manually. Always use this derive macro instead.
24+
///
25+
/// It also supports an optional attribute to specify a custom ID string:
26+
/// ```
27+
/// #[comm_data(id = "CustomID_String")]
28+
/// ```
29+
/// If no custom ID is provided, a fully qualified type name is used as the ID.
30+
/// Example usage:
31+
/// ```
32+
/// pub trait CommData {
33+
/// const ID: &'static str;
34+
/// }
35+
/// use com_api_concept_macros::CommData;
36+
/// #[derive(CommData)]
37+
/// #[repr(C)]
38+
/// pub struct MyData {
39+
/// value: u32,
40+
/// }
41+
/// ```
42+
/// with custom ID:
43+
/// ```
44+
/// pub trait CommData {
45+
/// const ID: &'static str;
46+
/// }
47+
/// use com_api_concept_macros::CommData;
48+
/// #[derive(CommData)]
49+
/// #[repr(C)]
50+
/// #[comm_data(id = "CustomID_MyData")]
51+
/// pub struct MyData {
52+
/// value: u32,
53+
/// }
54+
/// ```
55+
/// ```
56+
#[proc_macro_derive(CommData, attributes(comm_data))]
57+
pub fn derive_comm_data(input: TokenStream) -> TokenStream {
58+
let input_args = parse_macro_input!(input as DeriveInput);
59+
let ident_name = &input_args.ident;
60+
61+
// Call derive_reloc to generate Reloc implementation
62+
let reloc_impl_tokens = derive_reloc(input_args.to_token_stream().into());
63+
64+
//Add where clause if there are generics
65+
let generics = input_args.generics.clone();
66+
let (impl_generics, type_generics, where_clause) = generics.split_for_impl();
67+
68+
// Generate ID string: use user-provided ID if available, otherwise use fully qualified type name
69+
let id_str = match extract_id_from_attribute(&input_args.attrs) {
70+
Ok(Some(user_id)) => {
71+
// User-provided ID
72+
quote! { #user_id }
73+
}
74+
Ok(None) => {
75+
// Auto-generated: use fully qualified type name with module_path!()
76+
quote! { concat!(module_path!(), "::", stringify!(#ident_name)) }
77+
}
78+
Err(err) => {
79+
// Propagate the error to the compiler
80+
return err.to_compile_error().into();
81+
}
82+
};
83+
84+
let comm_data_impl = quote! {
85+
impl #impl_generics CommData for #ident_name #type_generics #where_clause {
86+
const ID: &'static str = #id_str;
87+
}
88+
};
89+
// Merge the TokenStreams
90+
let mut result = TokenStream::from(quote! { #comm_data_impl });
91+
result.extend(reloc_impl_tokens);
92+
result
93+
}
94+
95+
/// Extract custom ID from #[comm_data(id = "...")] attribute
96+
fn extract_id_from_attribute(attrs: &[syn::Attribute]) -> Result<Option<String>, syn::Error> {
97+
for attr in attrs {
98+
if !attr.path().is_ident("comm_data") {
99+
continue;
100+
}
101+
102+
let Meta::List(list) = &attr.meta else {
103+
return Err(syn::Error::new_spanned(
104+
&attr,
105+
"Expected #[comm_data(id = \"...\")]",
106+
));
107+
};
108+
109+
let Ok(Meta::NameValue(nv)) = list.parse_args::<Meta>() else {
110+
return Err(syn::Error::new_spanned(
111+
&attr,
112+
"Expected #[comm_data(id = \"...\")]",
113+
));
114+
};
115+
116+
if !nv.path.is_ident("id") {
117+
return Err(syn::Error::new_spanned(
118+
&nv.path,
119+
"Expected #[comm_data(id = \"...\")]",
120+
));
121+
}
122+
123+
let syn::Expr::Lit(expr_lit) = &nv.value else {
124+
return Err(syn::Error::new_spanned(
125+
&nv.value,
126+
"Expected a string literal value",
127+
));
128+
};
129+
130+
let syn::Lit::Str(lit_str) = &expr_lit.lit else {
131+
return Err(syn::Error::new_spanned(
132+
&expr_lit,
133+
"Expected a string literal value",
134+
));
135+
};
136+
137+
let id_value = lit_str.value();
138+
if id_value.is_empty() {
139+
return Err(syn::Error::new_spanned(
140+
&expr_lit,
141+
"The id cannot be an empty string",
142+
));
143+
}
144+
145+
return Ok(Some(id_value));
146+
}
147+
Ok(None)
148+
}
18149
///
19150
/// Derive macro for the `Reloc` trait. This macro automatically implements the `Reloc` trait
20151
/// for structs, ensuring that all field types also implement `Reloc`. This also requires that the
@@ -258,3 +389,149 @@ fn reloc_fails_if_member_is_not_reloc_on_struct() {}
258389
/// ```
259390
#[cfg(doctest)]
260391
fn reloc_fails_on_generic_types_that_do_not_impl_reloc() {}
392+
393+
/// ```compile_fail
394+
/// pub trait CommData {
395+
/// const ID: &'static str;
396+
/// }
397+
/// use com_api_concept_macros::CommData;
398+
/// #[derive(CommData)]
399+
/// pub struct MyData {
400+
/// value: u32,
401+
/// }
402+
/// ```
403+
/// This will fail because of missing #[repr(C)]
404+
#[cfg(doctest)]
405+
fn comm_data_fails_without_repr_c() {}
406+
407+
/// ```compile_fail
408+
/// pub trait CommData {
409+
/// const ID: &'static str;
410+
/// }
411+
/// use com_api_concept_macros::CommData;
412+
/// #[derive(CommData)]
413+
/// #[repr(C)]
414+
/// pub enum MyEnum {
415+
/// A,
416+
/// B,
417+
/// C(u32),
418+
/// }
419+
/// This will fail because of non C-like enum
420+
/// ```
421+
#[cfg(doctest)]
422+
fn comm_data_fails_on_non_c_like_enum() {}
423+
424+
/// ```
425+
/// pub trait CommData {
426+
/// const ID: &'static str;
427+
/// }
428+
/// use com_api_concept_macros::CommData;
429+
/// #[derive(CommData)]
430+
/// #[repr(C)]
431+
/// pub enum MyEnum {
432+
/// A,
433+
/// B,
434+
/// C,
435+
/// }
436+
/// // ID will be auto-generated as "module_path::MyEnum"
437+
/// ```
438+
#[cfg(doctest)]
439+
fn comm_data_works_on_c_like_enum() {}
440+
441+
/// ```
442+
/// pub trait CommData {
443+
/// const ID: &'static str;
444+
/// }
445+
/// use com_api_concept_macros::CommData;
446+
/// #[derive(CommData)]
447+
/// #[repr(C)]
448+
/// pub struct MyStruct {
449+
/// value: u32,
450+
/// }
451+
///
452+
/// // This will succeed because of struct with repr(C)
453+
/// // ID will be auto-generated as fully qualified type name: "module_path::MyStruct"
454+
/// ```
455+
#[cfg(doctest)]
456+
fn comm_data_works_on_struct() {}
457+
458+
/// ```
459+
/// pub trait CommData {
460+
/// const ID: &'static str;
461+
/// }
462+
/// use com_api_concept_macros::CommData;
463+
/// #[derive(CommData)]
464+
/// #[repr(C)]
465+
/// #[comm_data(id = "CustomID_MyStruct")]
466+
/// pub struct MyStruct {
467+
/// value: u32,
468+
/// }
469+
/// // This will succeed because of struct with repr(C) and custom ID
470+
/// // ID will be the user-provided string: "CustomID_MyStruct"
471+
/// ```
472+
#[cfg(doctest)]
473+
fn comm_data_works_on_struct_with_custom_id() {}
474+
475+
/// ```compile_fail
476+
/// pub trait CommData {
477+
/// const ID: &'static str;
478+
/// }
479+
/// use com_api_concept_macros::CommData;
480+
/// #[derive(CommData)]
481+
/// #[repr(C)]
482+
/// #[comm_data(wrong = "value")]
483+
/// pub struct MyData {
484+
/// value: u32,
485+
/// }
486+
/// ```
487+
/// This will fail because of invalid attribute key (should be "id")
488+
#[cfg(doctest)]
489+
fn comm_data_fails_with_invalid_attribute_key() {}
490+
491+
/// ```compile_fail
492+
/// pub trait CommData {
493+
/// const ID: &'static str;
494+
/// }
495+
/// use com_api_concept_macros::CommData;
496+
/// #[derive(CommData)]
497+
/// #[repr(C)]
498+
/// #[comm_data(id = 123)]
499+
/// pub struct MyData {
500+
/// value: u32,
501+
/// }
502+
/// ```
503+
/// This will fail because id value must be a string literal, not a number
504+
#[cfg(doctest)]
505+
fn comm_data_fails_with_non_string_id() {}
506+
507+
/// ```compile_fail
508+
/// pub trait CommData {
509+
/// const ID: &'static str;
510+
/// }
511+
/// use com_api_concept_macros::CommData;
512+
/// #[derive(CommData)]
513+
/// #[repr(C)]
514+
/// #[comm_data]
515+
/// pub struct MyData {
516+
/// value: u32,
517+
/// }
518+
/// ```
519+
/// This will fail because of malformed attribute (missing id = "...")
520+
#[cfg(doctest)]
521+
fn comm_data_fails_with_malformed_attribute() {}
522+
523+
/// ```compile_fail
524+
/// pub trait CommData {
525+
/// const ID: &'static str;
526+
/// }
527+
/// use com_api_concept_macros::CommData;
528+
/// #[derive(CommData)]
529+
/// #[repr(C)]
530+
/// #[comm_data(id = "")]
531+
/// pub struct MyData {
532+
/// value: u32,
533+
/// }
534+
/// ```
535+
/// This will fail because id cannot be an empty string
536+
#[cfg(doctest)]
537+
fn comm_data_fails_with_empty_string_id() {}

score/mw/com/impl/rust/com-api/com-api-concept/com_api_concept.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ use core::fmt::Debug;
5050
use core::future::Future;
5151
use core::ops::{Deref, DerefMut};
5252
pub mod reloc;
53+
pub use com_api_concept_macros::CommData;
5354
use containers::fixed_capacity::FixedCapacityQueue;
5455
pub use reloc::Reloc;
5556
use std::path::Path;
@@ -199,6 +200,14 @@ where
199200
/// Bounds the data type to be relocatable and sendable across threads.
200201
/// Also requires the data type to implement Debug for logging and debugging purposes.
201202
/// 'static' lifetime ensures the data type does not contain non-static references.
203+
/// # Important
204+
/// Users must NOT implement the `CommData` trait manually. Always use this derive macro instead.
205+
/// like `#[derive(CommData)]` on the struct definition.
206+
/// This is to ensure that all necessary trait bounds and metadata are correctly applied to the communication data types.
207+
/// Also if user wants to specify a custom ID for the communication data type,
208+
/// they can use the `#[comm_data(id = "CustomID")]` attribute on the struct definition.
209+
/// The custom ID must be unique across all communication data types to avoid conflicts in the system.
210+
/// If no custom ID is provided, the struct name will be used as the default ID with module path as prefix to ensure uniqueness.
202211
pub trait CommData: Reloc + Send + Debug + 'static {
203212
const ID: &'static str;
204213
}

0 commit comments

Comments
 (0)