veecle_os_runtime_macros/
lib.rs

1//! This crate provides runtime macros.
2
3#![forbid(unsafe_code)]
4#![cfg_attr(coverage_nightly, feature(coverage_attribute))]
5
6mod actor;
7mod storable;
8
9/// Generates an [`Actor`] from a function.
10///
11/// [`Actor`]: https://docs.rs/veecle-os/latest/veecle_os/runtime/trait.Actor.html
12///
13/// ```rust
14/// use veecle_os_runtime::{Reader, Writer};
15/// # use std::convert::Infallible;
16/// # use veecle_os_runtime::Storable;
17/// #
18/// # #[derive(Debug, PartialEq, Clone, Default, Storable)]
19/// # pub struct Sensor(pub u8);
20///
21/// #[veecle_os_runtime::actor]
22/// async fn macro_test_actor(
23///     _sensor_reader: Reader<'_, Sensor>,
24///     _sensor_writer: Writer<'_, Sensor>,
25///     #[init_context] _my_init_context: u32,
26/// ) -> Infallible {
27///     loop {
28///         // Do things.
29///     }
30/// }
31/// ```
32///
33/// # Attribute Arguments
34///
35/// ## `crate`
36///
37/// If necessary the path to [`veecle-os-runtime`] can be overridden by passing a `crate = ::some::path` argument.
38///
39/// [`veecle-os-runtime`]: https://docs.rs/veecle-os-runtime/latest/veecle_os_runtime/
40///
41/// ```rust
42/// extern crate veecle_os_runtime as my_veecle_os_runtime;
43///
44/// use my_veecle_os_runtime::{Reader, Writer};
45/// # use std::convert::Infallible;
46/// # use my_veecle_os_runtime::Storable;
47/// #
48/// # #[derive(Debug, PartialEq, Clone, Default, Storable)]
49/// # pub struct Sensor(pub u8);
50///
51/// #[my_veecle_os_runtime::actor(crate = my_veecle_os_runtime)]
52/// async fn macro_test_actor(
53///     _sensor_reader: Reader<'_, Sensor>,
54///     _sensor_writer: Writer<'_, Sensor>,
55///     #[init_context] _my_init_context: u32,
56/// ) -> Infallible {
57///     loop {
58///         // Do things.
59///     }
60/// }
61/// ```
62#[proc_macro_attribute]
63pub fn actor(
64    meta: proc_macro::TokenStream,
65    item: proc_macro::TokenStream,
66) -> proc_macro::TokenStream {
67    actor2(meta.into(), item.into()).into()
68}
69
70/// `proc_macro2` implementation of [`actor()`] to allow executing outside the compiler.
71///
72/// The actual implementation is in the module, this just maps any errors into `compile_error!`s to allow using `?` in
73/// the implementation while giving the expected infallible function signature.
74fn actor2(
75    meta: proc_macro2::TokenStream,
76    item: proc_macro2::TokenStream,
77) -> proc_macro2::TokenStream {
78    actor::impl_actor(meta, item).unwrap_or_else(|error| error.into_compile_error())
79}
80
81/// Implements [`Storable`] for a struct or enum.
82///
83/// # Attributes
84///
85/// * `data_type = "Type"`: Sets the [`Storable::DataType`]. Defaults to `Self`.
86/// * `crate = ::veecle_os_runtime`: Overrides the path to the `veecle-os-runtime` crate in case the import was renamed.
87///
88/// [`Storable`]: https://docs.rs/veecle-os/latest/veecle_os/runtime/trait.Storable.html
89/// [`Storable::DataType`]: https://docs.rs/veecle-os/latest/veecle_os/runtime/trait.Storable.html#associatedtype.DataType
90///
91/// ```
92/// use core::fmt::Debug;
93/// use veecle_os_runtime::Storable;
94///
95/// // `DataType = Self`
96/// #[derive(Debug, Storable)]
97/// pub struct Sensor<T>
98/// where
99///     T: Debug,
100/// {
101///     test: u8,
102///     test0: u8,
103///     test1: T,
104/// }
105///
106/// // `DataType = Self`
107/// #[derive(Debug, Storable)]
108/// pub struct Motor {
109///     test: u8,
110/// }
111///
112/// // `DataType = Self`
113/// #[derive(Debug, Storable)]
114/// pub enum Actuator {
115///     Variant1,
116///     Variant2(u8),
117///     Variant3 { test: u8 },
118/// }
119///
120/// // `DataType = u8`
121/// #[derive(Storable)]
122/// #[storable(data_type = "u8")]
123/// pub struct EventId;
124/// ```
125#[proc_macro_derive(Storable, attributes(storable))]
126pub fn derive_storable(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
127    derive_storable2(input.into()).into()
128}
129
130/// `proc_macro2` implementation of [`derive_storable`] to allow executing outside the compiler.
131///
132/// The actual implementation is in the module, this just maps any errors into `compile_error!`s to allow using `?` in
133/// the implementation while giving the expected infallible function signature.
134fn derive_storable2(input: proc_macro2::TokenStream) -> proc_macro2::TokenStream {
135    storable::impl_derive_storable(input).unwrap_or_else(|error| error.write_errors())
136}
137
138/// Returns a path to the `veecle_os_runtime` crate for use when macro users don't set it explicitly.
139fn veecle_os_runtime_path() -> syn::Result<syn::Path> {
140    proc_macro_crate::crate_name("veecle-os-runtime")
141        .map(|found| match found {
142            proc_macro_crate::FoundCrate::Itself => {
143                // The only place we use `veecle-os-runtime` within "itself" is doc-tests, where it needs to be an external
144                // path anyway.
145                syn::parse_quote!(::veecle_os_runtime)
146            }
147            proc_macro_crate::FoundCrate::Name(name) => {
148                let ident = syn::Ident::new(&name, proc_macro2::Span::call_site());
149                syn::parse_quote!(::#ident)
150            }
151        })
152        .or_else(|_| {
153            proc_macro_crate::crate_name("veecle-os").map(|found| match found {
154                proc_macro_crate::FoundCrate::Itself => {
155                    todo!("unused currently, not sure what behavior will be wanted")
156                }
157                proc_macro_crate::FoundCrate::Name(name) => {
158                    let ident = syn::Ident::new(&name, proc_macro2::Span::call_site());
159                    syn::parse_quote!(::#ident::runtime)
160                }
161            })
162        })
163        .map_err(|_| {
164            syn::Error::new(
165                proc_macro2::Span::call_site(),
166                "could not find either veecle-os-runtime or veecle-os crates",
167            )
168        })
169}
170
171#[cfg(test)]
172#[cfg_attr(coverage_nightly, coverage(off))]
173mod tests {
174    use std::fs::File;
175
176    #[test]
177    fn test_for_code_coverage() -> Result<(), Box<dyn std::error::Error>> {
178        for entry in walkdir::WalkDir::new("tests/ui") {
179            let entry = entry?;
180            if entry.path().extension().unwrap_or_default() == "rs" {
181                runtime_macros::emulate_attributelike_macro_expansion(
182                    File::open(entry.path())?,
183                    &[
184                        ("actor", super::actor2),
185                        ("veecle_os_runtime::actor", super::actor2),
186                        ("veecle_os_runtime_macros::actor", super::actor2),
187                    ],
188                )?;
189                runtime_macros::emulate_derive_macro_expansion(
190                    File::open(entry.path())?,
191                    &[
192                        ("Storable", super::derive_storable2),
193                        ("veecle_os_runtime::Storable", super::derive_storable2),
194                        (
195                            "veecle_os_runtime_macros::Storable",
196                            super::derive_storable2,
197                        ),
198                    ],
199                )?;
200            }
201        }
202
203        Ok(())
204    }
205}