diff --git a/MODULE.bazel b/MODULE.bazel index 3293bf00..fa07050f 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -98,7 +98,7 @@ bazel_dep(name = "googletest", version = "1.17.0.bcr.2") bazel_dep(name = "google_benchmark", version = "1.9.4") bazel_dep(name = "rules_rust", version = "0.61.0") bazel_dep(name = "buildifier_prebuilt", version = "8.2.0.2") -bazel_dep(name = "score_crates", version = "0.0.5", repo_name = "crate_index") +bazel_dep(name = "score_crates", version = "0.0.6", repo_name = "crate_index") bazel_dep(name = "boost.program_options", version = "1.87.0") bazel_dep(name = "download_utils", version = "1.0.1") diff --git a/score/mw/com/example/com-api-example/com-api-gen/com_api_gen.rs b/score/mw/com/example/com-api-example/com-api-gen/com_api_gen.rs index de22c1e5..41ce27ed 100644 --- a/score/mw/com/example/com-api-example/com-api-gen/com_api_gen.rs +++ b/score/mw/com/example/com-api-example/com-api-gen/com_api_gen.rs @@ -15,13 +15,13 @@ use com_api::{ Consumer, Interface, OfferedProducer, Producer, Publisher, Reloc, Runtime, Subscriber, }; -#[derive(Debug)] +#[derive(Debug, Reloc)] +#[repr(C)] pub struct Tire {} -unsafe impl Reloc for Tire {} -#[derive(Debug)] +#[derive(Debug, Reloc)] +#[repr(C)] pub struct Exhaust {} -unsafe impl Reloc for Exhaust {} pub struct VehicleInterface {} diff --git a/score/mw/com/impl/rust/com-api/com-api-concept-macros/BUILD b/score/mw/com/impl/rust/com-api/com-api-concept-macros/BUILD new file mode 100644 index 00000000..30edc36b --- /dev/null +++ b/score/mw/com/impl/rust/com-api/com-api-concept-macros/BUILD @@ -0,0 +1,31 @@ +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* +load("@rules_rust//rust:defs.bzl", "rust_proc_macro", "rust_doc_test") + +rust_proc_macro( + name = "com-api-concept-macros", + srcs = ["lib.rs"], + crate_name = "com_api_concept_macros", + visibility = [ + "//visibility:public", + ], + deps = [ + "@crate_index//:syn", + "@crate_index//:quote", + ], +) + +rust_doc_test( + name = "com-api-concept-macros-tests", + crate = ":com-api-concept-macros", +) diff --git a/score/mw/com/impl/rust/com-api/com-api-concept-macros/lib.rs b/score/mw/com/impl/rust/com-api/com-api-concept-macros/lib.rs new file mode 100644 index 00000000..9f460355 --- /dev/null +++ b/score/mw/com/impl/rust/com-api/com-api-concept-macros/lib.rs @@ -0,0 +1,247 @@ +use proc_macro::TokenStream; +use quote::quote; +use syn::{parse_macro_input, parse_quote, Data, DeriveInput, Fields, Generics, Meta, Type}; + +/// +/// Derive macro for the `Reloc` trait. This macro automatically implements the `Reloc` trait +/// for structs, ensuring that all field types also implement `Reloc`. This also requires that the +/// struct is marked with `#[repr(C)]`. +/// +#[proc_macro_derive(Reloc)] +pub fn derive_reloc(input: TokenStream) -> TokenStream { + let input_args = parse_macro_input!(input as DeriveInput); + let ident_name = &input_args.ident; + + // Ensure #[repr(C)] on the struct itself + if !has_repr_c(&input_args.attrs) { + return syn::Error::new_spanned( + &ident_name, + "The #[derive(Reloc)] macro requires #[repr(C)] on the type", + ) + .to_compile_error() + .into(); + } + + let mut generics = create_bounds_with_reloc(input_args.generics.clone()); + + // Collect field types. Since we cannot check if given field type has implemented given trait we will use compiler + // to check that generating trait bounds on fields like below: + // + // struct Example { + // field1: SomeType, + // field2: AnotherType, + // } + // unsafe impl Reloc for Example where SomeType: Reloc, AnotherType: Reloc {} + // + let field_types = match collect_field_types(&input_args.data){ + Ok(types) => types, + Err(()) => return syn::Error::new_spanned( + &ident_name, + "The #[derive(Reloc)] macro is supported only for enums(C like), structs and tuple structs", + ) + .to_compile_error() + .into(), + }; + + { + let where_clause_ref = generics.make_where_clause(); + for ty in field_types { + where_clause_ref.predicates.push(parse_quote!(#ty: Reloc)); + } + } + + // Create pieces for final implementation + let (impl_generics, type_generics, where_clause) = generics.split_for_impl(); + + let expanded = quote! { + unsafe impl #impl_generics Reloc for #ident_name #type_generics #where_clause {} + }; + + expanded.into() +} + +/// Add `T: Reloc` bounds to all generic parameters +fn create_bounds_with_reloc(mut generics: Generics) -> Generics { + for param in generics.type_params_mut() { + param.bounds.push(parse_quote!(Reloc)); + } + generics +} + +/// Check for #[repr(C)] existence +fn has_repr_c(attrs: &[syn::Attribute]) -> bool { + 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; + } + } + } + } + false +} + +/// Collect field types, skipping generic parameters and primitives +fn collect_field_types<'a>(data: &'a Data) -> Result, ()> { + let mut out = Vec::new(); + + match data { + Data::Struct(data_struct) => { + out = match &data_struct.fields { + Fields::Named(fields) => fields.named.iter().map(|f| &f.ty).collect(), + Fields::Unnamed(fields) => fields.unnamed.iter().map(|f| &f.ty).collect(), + Fields::Unit => Vec::new(), + }; + } + Data::Enum(data_enum) => { + if !data_enum + .variants + .iter() + .all(|variant| matches!(variant.fields, Fields::Unit)) + { + return Err(()); + } + } + Data::Union(_) => return Err(()), + }; + + Ok(out) +} + +// Use doctest to test failed compilations and successful ones + +/// ``` +/// pub unsafe trait Reloc {} +/// use com_api_concept_macros::Reloc; +/// +/// #[derive(Reloc)] +/// #[repr(C)] +/// pub struct RelocType { t : T} +/// +/// #[derive(Reloc)] +/// #[repr(C)] +/// pub struct RelocTypeTwoGen { t : T, d: D} +/// ``` +#[cfg(doctest)] +fn reloc_works_on_generic_types() {} + +/// ``` +/// pub unsafe trait Reloc {} +/// use com_api_concept_macros::Reloc; +/// +/// #[derive(Reloc)] +/// #[repr(C)] +/// enum EnumPlanType { +/// A, +/// B, +/// C +/// } +/// ``` +#[cfg(doctest)] +fn reloc_works_on_c_like_enums() {} + +/// ``` +/// pub unsafe trait Reloc {} +/// use com_api_concept_macros::Reloc; +/// +/// #[derive(Reloc)] +/// #[repr(C)] +/// struct StructTag{} +/// ``` +#[cfg(doctest)] +fn reloc_works_on_unit() {} + +/// ``` +/// pub unsafe trait Reloc {} +/// unsafe impl Reloc for u32{} +/// unsafe impl Reloc for u64{} +/// +/// use com_api_concept_macros::Reloc; +/// +/// #[derive(Reloc)] +/// #[repr(C)] +/// pub struct RelocType { t : T} +/// +/// #[derive(Reloc)] +/// #[repr(C)] +/// pub struct StructType { +/// a: u32, +/// b: u64, +/// f: RelocType +/// } +/// ``` +#[cfg(doctest)] +fn reloc_works_on_structs() {} + +/// ``` +/// pub unsafe trait Reloc {} +/// unsafe impl Reloc for u32{} +/// unsafe impl Reloc for u64{} +/// +/// use com_api_concept_macros::Reloc; +/// +/// #[derive(Reloc)] +/// #[repr(C)] +/// pub struct RelocType { t : T} +/// +/// #[derive(Reloc)] +/// #[repr(C)] +/// pub struct TupleStructType( u32, u64, RelocType ); +/// ``` +#[cfg(doctest)] +fn reloc_works_on_tuples() {} + +// Failure cases + +/// ```compile_fail +/// pub unsafe trait Reloc {} +/// use com_api_concept_macros::Reloc; +/// +/// #[derive(Reloc)] +/// #[repr(C)] +/// enum EnumPlanType { +/// A(u32), +/// B, +/// C +/// } +/// ``` +#[cfg(doctest)] +fn reloc_fail_on_non_c_like_enums() {} + +/// ```compile_fail +/// pub unsafe trait Reloc {} +/// unsafe impl Reloc for u32{} +/// +/// use com_api_concept_macros::Reloc; +/// +/// #[derive(Reloc)] +/// #[repr(C)] +/// pub struct RelocType { t : T} +/// +/// #[derive(Reloc)] +/// #[repr(C)] +/// pub struct StructType { +/// a: u32, +/// b: u64, +/// f: RelocType +/// } +/// ``` +#[cfg(doctest)] +fn reloc_fails_if_member_is_not_reloc_on_struct() {} + +/// ```compile_fail +/// pub unsafe trait Reloc {} +/// use com_api_concept_macros::Reloc; +/// +/// #[derive(Reloc)] +/// #[repr(C)] +/// pub struct RelocType { t : T} +/// +/// #[derive(Reloc)] +/// #[repr(C)] +/// pub struct ContainsNonRelocType(RelocType); +/// ``` +#[cfg(doctest)] +fn reloc_fails_on_generic_types_that_do_not_impl_reloc() {} diff --git a/score/mw/com/impl/rust/com-api/com-api-concept/BUILD b/score/mw/com/impl/rust/com-api/com-api-concept/BUILD index 294f4893..e34b79ae 100644 --- a/score/mw/com/impl/rust/com-api/com-api-concept/BUILD +++ b/score/mw/com/impl/rust/com-api/com-api-concept/BUILD @@ -23,6 +23,9 @@ rust_library( "//visibility:public", # platform_only ], deps = [], + proc_macro_deps = [ + "//score/mw/com/impl/rust/com-api/com-api-concept-macros", + ], ) rust_test( diff --git a/score/mw/com/impl/rust/com-api/com-api-concept/reloc.rs b/score/mw/com/impl/rust/com-api/com-api-concept/reloc.rs index a7bb5b48..83e27050 100644 --- a/score/mw/com/impl/rust/com-api/com-api-concept/reloc.rs +++ b/score/mw/com/impl/rust/com-api/com-api-concept/reloc.rs @@ -27,6 +27,8 @@ /// is unsafe for now. The expectation is that very few users ever need to implement this manually. pub unsafe trait Reloc {} +pub use com_api_concept_macros::Reloc; + unsafe impl Reloc for () {} unsafe impl Reloc for u8 {} unsafe impl Reloc for u16 {} diff --git a/score/mw/com/impl/rust/com-api/com-api/com_api.rs b/score/mw/com/impl/rust/com-api/com-api/com_api.rs index fa34c4d6..e0b51b4d 100644 --- a/score/mw/com/impl/rust/com-api/com-api/com_api.rs +++ b/score/mw/com/impl/rust/com-api/com-api/com_api.rs @@ -22,3 +22,4 @@ pub use com_api_concept::{ Publisher, Reloc, Result, Runtime, SampleContainer, SampleMaybeUninit, SampleMut, ServiceDiscovery, Subscriber, Subscription, }; +