Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 125 additions & 11 deletions score/mw/com/rust/score_com_macros/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,8 @@ fn check_attrs(fields: &Fields) -> Result<(), syn::Error> {
///
/// # Validation performed by this macro
///
/// - **`#[repr(C)]`** must be present on the type.
/// - **`#[repr(C)]`** must be present on the type. For C-like enums, a primitive
/// integer repr (e.g. `#[repr(u8)]`) is accepted as well.
/// - It must be a struct, tuple struct, or C-like enum (no unions or non-C-like enums).
///
/// These checks are intentionally kept in `Reloc` rather than `CommData` so that each macro
Expand All @@ -237,7 +238,10 @@ pub fn derive_reloc(input: TokenStream) -> TokenStream {
if !has_repr_c(&input_args.attrs) {
return syn::Error::new_spanned(
ident_name,
"The #[derive(Reloc)] macro requires #[repr(C)] on the type",
concat!(
"The #[derive(Reloc)] macro requires #[repr(C)] or a primitive integer repr ",
"(e.g. #[repr(u8)]) on the type"
),
)
.to_compile_error()
.into();
Expand All @@ -260,8 +264,10 @@ pub fn derive_reloc(input: TokenStream) -> TokenStream {
Err(()) => {
return syn::Error::new_spanned(
ident_name,
"The #[derive(Reloc)] macro is supported only for enums(C like), structs and \
tuple structs",
concat!(
"The #[derive(Reloc)] macro is supported only for enums(C like), structs ",
"and tuple structs"
),
)
Comment thread
NEOatNHNG marked this conversation as resolved.
.to_compile_error()
.into()
Expand Down Expand Up @@ -293,16 +299,45 @@ fn create_bounds_with_reloc(mut generics: Generics) -> Generics {
generics
}

/// Check for #[repr(C)] existence
/// Check whether the type is representable as C struct or enum.
fn has_repr_c(attrs: &[syn::Attribute]) -> bool {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
fn has_repr_c(attrs: &[syn::Attribute]) -> bool {
fn has_repr_c_and_primitive(attrs: &[syn::Attribute]) -> bool {

const INT_REPRS: [&str; 10] = [
"u8", "u16", "u32", "u64", "usize", "i8", "i16", "i32", "i64", "isize",
];

for attr in attrs {
if attr.path().is_ident("repr") {
if let Meta::List(list) = &attr.meta {
let tokens = list.tokens.to_string();
if tokens.split(',').any(|t| t.trim() == "C") {
return true;
}
if !attr.path().is_ident("repr") {
continue;
}

let mut found = false;
let _ = attr.parse_nested_meta(|meta| {
if meta.path.is_ident("C") {
found = true;
return Ok(());
}

if meta
.path
.get_ident()
.is_some_and(|id| INT_REPRS.contains(&id.to_string().as_str()))
{
found = true;
return Ok(());
}

// Unrecognized modifier (e.g. align(4), packed(2)); consume any
// parenthesized arguments so parsing succeeds, then ignore it.
if meta.input.peek(syn::token::Paren) {
let content;
syn::parenthesized!(content in meta.input);
let _ = content;
}
Ok(())
Comment on lines +331 to +336

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think padding should be handled by ABI, possibly through some kind of code generation tool. If we allow padding to be managed on the Rust side, but other applications or components do not apply the exact same rules, it could lead to compatibility issues.

I would recommend making #[repr(packed)] and #[repr(packed(N))] a compilation error when user put this.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have same opinion about align as well

@NEOatNHNG NEOatNHNG Aug 12, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I can also only allow repr(C) and repr(inttype) and forbid everything else if that is what we want. That effectively blocks a user from using align and packed on the data structure if using Rust while still being allowed on the C++ side. And this did work in the code before this PR so it would remove a "feature".

@bharatGoswami8 bharatGoswami8 Aug 12, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My concern is that alignment and padding should be managed from a single source of truth for both C++ and Rust. If you look at the Rust examples, we write the corresponding C++ structure as well for FFI layer translation. If a user applies packing or custom alignment only on the Rust side and forgets to apply the same settings in C++, it can easily result in layout mismatches and misalignment issues.

So, if we want to support this feature, we need to ensure that both the Rust and C++ definitions are generated or configured with identical alignment requirements.

We can enable this feature, but we need a mechanism to guarantee consistency across both sides. A Rust macro can validate attributes present on the Rust type, but currently we do not have a way to verify that the equivalent C++ structure has been defined with the same alignment characteristics. Without such validation, we risk introducing subtle interoperability bugs.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the second thought, I think when an IDL tool is introduced, this issue will be resolved. So maybe we can go with this, but we need to update the documentation and introduce one type like Temperature in the example with alignment and padding, so that we can demonstrate the assumptions of use on both the C++ and Rust sides.
What is your opinion on this?

});

if found {
return true;
}
}
false
Expand Down Expand Up @@ -367,6 +402,70 @@ fn reloc_works_on_generic_types() {}
#[cfg(doctest)]
fn reloc_works_on_c_like_enums() {}

/// ```
/// pub unsafe trait Reloc: Send + Unpin + 'static {}
/// use score_com_macros::Reloc;
///
/// #[derive(Reloc)]
/// #[repr(u8)]
/// enum EnumU8 {
/// A,
/// B,
/// C
/// }
///
/// #[derive(Reloc)]
/// #[repr(u32)]
/// enum EnumU32 {
/// A,
/// B,
/// C
/// }
/// ```
#[cfg(doctest)]
fn reloc_works_on_c_like_enums_with_int_repr() {}

/// ```
/// pub unsafe trait Reloc: Send + Unpin + 'static {}
/// unsafe impl Reloc for u8 {}
/// use score_com_macros::Reloc;
///
/// #[derive(Reloc)]
/// #[repr(C, align(4))]
/// pub struct StructWithAlign {
/// a: u8,
/// }
///
/// #[derive(Reloc)]
/// #[repr(u8, align(4))]
/// enum EnumWithAlign {
/// A,
/// B,
/// C
/// }
/// ```
#[cfg(doctest)]
fn reloc_works_with_repr_c_and_align() {}

/// ```
/// pub unsafe trait Reloc: Send + Unpin + 'static {}
/// pub trait CommData: Reloc {
/// const ID: &'static str;
/// }
/// use score_com_macros::{CommData, Reloc};
///
/// #[derive(Reloc, CommData)]
/// #[repr(u8)]
/// pub enum MyEnum {
/// A,
/// B,
/// C,
/// }
/// // ID will be auto-generated as "module_path::MyEnum"
/// ```
#[cfg(doctest)]
fn comm_data_and_reloc_work_on_enum_with_int_repr() {}

/// ```
/// pub unsafe trait Reloc: Send + Unpin + 'static {}
/// use score_com_macros::Reloc;
Expand Down Expand Up @@ -435,6 +534,21 @@ fn reloc_works_on_tuples() {}
#[cfg(doctest)]
fn reloc_fail_on_non_c_like_enums() {}

/// ```compile_fail
/// pub unsafe trait Reloc: Send + Unpin + 'static {}
/// use score_com_macros::Reloc;
///
/// #[derive(Reloc)]
/// #[repr(u8)]
/// pub struct StructType {
/// a: u32,
/// }
/// ```
/// This will fail because rustc itself rejects a primitive integer repr on structs
/// (it is only legal on enums).
#[cfg(doctest)]
fn reloc_fails_on_struct_with_int_repr() {}

/// ```compile_fail
/// pub unsafe trait Reloc: Send + Unpin + 'static {}
/// unsafe impl Reloc for u32{}
Expand Down
Loading