jlrs_macros/
lib.rs

1mod constant_bytes;
2#[cfg(feature = "derive")]
3mod derive;
4#[cfg(feature = "ccall")]
5mod module;
6mod version;
7
8use proc_macro::TokenStream;
9
10#[cfg(feature = "ccall")]
11use self::module::*;
12use self::{constant_bytes::*, version::emit_if_compatible};
13
14/// Export functions, types and constants defined in Rust as a Julia module.
15///
16/// This macro generates an initialization function. This function is used in combination with the
17/// macros provided by the `JlrsCore.Wrap` module to automatically generate the content of that
18/// module.
19///
20/// The syntax is as follows:
21///
22/// ```ignore
23/// julia_macro! {
24///     // init_function_name is the name of the generated initialization function.
25///     //
26///     // The name of the generated function must be unique, it's recommended you prefix it with
27///     // the crate name. If your crate is named foo-jl, you should use a name like
28///     // `foo_jl_init`.
29///     become init_function_name;
30///
31///     // Exports the function `foo` as `bar` with documentation.
32///     //
33///     // The `as <exposed_name>` part is optional, by default the function is exported with the
34///     // name it has in Rust, the exposed name can end in an exclamation mark.
35///     //
36///     // A docstring can be provided with a doc comment; if multiple functions are exported
37///     // with the same name it shoud only be documented once. All exported items can be
38///     // documented.
39///     //
40///     // If the function doesn't need to call into Julia, you can annotate it with `#[gc_safe]`
41///     // to allow the GC to run without having to wait until the function has returned.
42///
43///     ///     bar(arr::Array)
44///     ///
45///     /// Documentation for this function"]
46///     #[gc_safe]
47///     fn foo(arr: Array) -> usize as bar;
48///
49///     // Exports the function `foo` as `bar!` in the `Base` module.
50///     //
51///     // This syntax can be used to extend existing functions.
52///     fn foo(arr: Array) -> usize as Base.bar!;
53///
54///     // Exports the struct `MyType` as `MyForeignType`. `MyType` must implement `OpaqueType`
55///     // or `ForeignType`.
56///     struct MyType as MyForeignType;
57///
58///     // Exports `MyType::new` as `MyForeignType`, turning it into a constructor for that type.
59///     in MyType fn new(arg0: TypedValue<u32>) -> TypedValueRet<MyType> as MyForeignType;
60///
61///     // Exports `MyType::add` as the function `increment!`.
62///     //
63///     // If a method takes `self` in some way, it is tracked by default. You can opt out of this
64///     // behavior with the `#[untracked_self]` attribute.
65///     #[untracked_self]
66///     in MyType fn add(&mut self, incr: u32) -> JlrsResult<u32>  as increment!;
67///
68///     // Exports the alias `MyTypeAlias` for `MyType`.
69///     //
70///     // This is exposes as `const MyTypeAlias = MyType`.
71///     type MyTypeAlias = MyType;
72///
73///     // Exports `MY_CONST` as the constant `MY_CONST`, its type must implement `IntoJulia`.
74///     // `MY_CONST` can be defined in Rust as either static or constant data, i.e. both
75///     // `static MY_CONST: u8 = 1` and `const MY_CONST: u8 = 1` can be exposed this way.
76///     const MY_CONST: u8;
77///
78///     // You can loop over types to export types and functions multiple times with
79///     // different type parameters.
80///     for T in [f32, f64] {
81///         fn has_generic(t: T) -> T;
82///
83///         // POpaque<T> must implement `OpaqueType`.
84///         struct POpaque<T>;
85///
86///         in POpaque<T> fn new(value: T) -> TypedValueRet<POpaque<T>> as POpaque;
87///     }
88///
89///     // You can use an environment of type parameters to define generic functions.
90///     // type GenericEnv = tvars!(tvar!('T'; AbstractFloat), tvar!('N'), tvar!('A'; AbstractArray<tvar!('T'), tvar!('N')>));
91///     fn takes_generics_from_env(array: TypedValue<tvar!('A')>, data: TypedValue<tvar!('T')>) use GenericEnv;
92/// }
93/// ```
94///
95/// And this is all you need to do in Julia:
96///
97/// ```julia
98/// module MyRustModule
99/// using JlrsCore.Wrap
100///
101/// @wrapmodule("path/to/lib", :init_function_name)
102///
103/// function __init__()
104///     @initjlrs
105/// end
106/// end
107/// ```
108///
109/// It can be rather tricky to figure out how data is passed from Julia to Rust when `ccall`ing
110/// a function written in Rust. Primitive and `isbits` types are passed by value, managed types
111/// provided directly by jlrs are guaranteed to be boxed, all other types might be passed by
112/// value or be boxed.
113///
114/// In order to avoid figuring out how such data is passed you can work with `TypedValue`, which
115/// ensures the data is boxed by using `Any` in the signature of the generated `ccall` invocation,
116/// but restricts the type of the data in the generated function to the type constructed from the
117/// `TypedValue`'s type parameter.
118///
119/// [`AsyncCondition`]: https://docs.julialang.org/en/v1/base/base/#Base.AsyncCondition
120#[proc_macro]
121#[cfg(feature = "ccall")]
122pub fn julia_module(item: TokenStream) -> TokenStream {
123    let input = syn::parse_macro_input!(item as JuliaModule);
124    match input.generate_init_code() {
125        Ok(a) => a,
126        Err(b) => b.to_compile_error().into(),
127    }
128}
129
130/// Encode the literal string passed to this macro as [`ConstantBytes`].
131///
132/// [`ConstantBytes`]: jlrs::data::types::construct_type::ConstantBytes
133#[proc_macro]
134pub fn encode_as_constant_bytes(item: TokenStream) -> TokenStream {
135    let s: syn::LitStr = syn::parse_macro_input!(item as syn::LitStr);
136    let input = s.value();
137    convert_to_constant_bytes(input)
138}
139
140/// Conditional compilation depending on the used version of Julia.
141///
142/// This macro can be used instead of a custom `cfg` to conditionally compile code for
143/// certain versions of Julia. For example, to enable a function when Julia 1.10 or 1.12 is
144/// used:
145///
146/// `#[julia_version(since = "1.10", until = "1.12", except = ["1.11"])]`
147#[proc_macro_attribute]
148pub fn julia_version(attr: TokenStream, item: TokenStream) -> TokenStream {
149    emit_if_compatible(attr, item)
150}
151
152/// Derive `IntoJulia`.
153///
154/// Should only be used in combination with layouts generated by JlrsReflect.jl
155#[cfg(feature = "derive")]
156#[proc_macro_derive(IntoJulia, attributes(jlrs))]
157pub fn into_julia_derive(input: TokenStream) -> TokenStream {
158    use derive::into_julia::impl_into_julia;
159
160    let ast = syn::parse(input).unwrap();
161    match impl_into_julia(&ast) {
162        Ok(tokens) => tokens,
163        Err(err) => err.to_compile_error().into(),
164    }
165}
166
167/// Derive `IsBits`.
168///
169/// Should only be used in combination with layouts generated by JlrsReflect.jl
170#[cfg(feature = "derive")]
171#[proc_macro_derive(IsBits, attributes(jlrs))]
172pub fn is_bits_derive(input: TokenStream) -> TokenStream {
173    use derive::is_bits::impl_is_bits;
174
175    let ast = syn::parse(input).unwrap();
176    match impl_is_bits(&ast) {
177        Ok(tokens) => tokens,
178        Err(err) => err.to_compile_error().into(),
179    }
180}
181
182/// Derive `HasLayout`.
183///
184/// Should only be used in combination with layouts generated by JlrsReflect.jl
185#[cfg(feature = "derive")]
186#[proc_macro_derive(HasLayout, attributes(jlrs))]
187pub fn is_has_layout(input: TokenStream) -> TokenStream {
188    use derive::has_layout::impl_has_layout;
189
190    let ast = syn::parse(input).unwrap();
191    match impl_has_layout(&ast) {
192        Ok(tokens) => tokens,
193        Err(err) => err.to_compile_error().into(),
194    }
195}
196
197/// Derive `Unbox`.
198///
199/// Should only be used in combination with layouts generated by JlrsReflect.jl
200#[cfg(feature = "derive")]
201#[proc_macro_derive(Unbox, attributes(jlrs))]
202pub fn unbox_derive(input: TokenStream) -> TokenStream {
203    use derive::unbox::impl_unbox;
204
205    let ast = syn::parse(input).unwrap();
206    match impl_unbox(&ast) {
207        Ok(tokens) => tokens,
208        Err(err) => err.to_compile_error().into(),
209    }
210}
211
212/// Derive `Typecheck`.
213///
214/// Should only be used in combination with layouts generated by JlrsReflect.jl
215#[cfg(feature = "derive")]
216#[proc_macro_derive(Typecheck, attributes(jlrs))]
217pub fn typecheck_derive(input: TokenStream) -> TokenStream {
218    use derive::typecheck::impl_typecheck;
219
220    let ast = syn::parse(input).unwrap();
221    match impl_typecheck(&ast) {
222        Ok(tokens) => tokens,
223        Err(err) => err.to_compile_error().into(),
224    }
225}
226
227/// Derive `ValidLayout`.
228///
229/// Should only be used in combination with layouts generated by JlrsReflect.jl
230#[cfg(feature = "derive")]
231#[proc_macro_derive(ValidLayout, attributes(jlrs))]
232pub fn valid_layout_derive(input: TokenStream) -> TokenStream {
233    use derive::valid_layout::impl_valid_layout;
234
235    let ast = syn::parse(input).unwrap();
236    match impl_valid_layout(&ast) {
237        Ok(tokens) => tokens,
238        Err(err) => err.to_compile_error().into(),
239    }
240}
241
242/// Derive `ValidField`.
243///
244/// Should only be used in combination with layouts generated by JlrsReflect.jl
245#[cfg(feature = "derive")]
246#[proc_macro_derive(ValidField, attributes(jlrs))]
247pub fn valid_field_derive(input: TokenStream) -> TokenStream {
248    use derive::valid_field::impl_valid_field;
249
250    let ast = syn::parse(input).unwrap();
251    match impl_valid_field(&ast) {
252        Ok(tokens) => tokens,
253        Err(err) => err.to_compile_error().into(),
254    }
255}
256
257/// Derive `ConstructType`.
258///
259/// Should only be used in combination with layouts generated by JlrsReflect.jl
260#[cfg(feature = "derive")]
261#[proc_macro_derive(ConstructType, attributes(jlrs))]
262pub fn construct_type_derive(input: TokenStream) -> TokenStream {
263    use derive::construct_type::impl_construct_type;
264
265    let ast = syn::parse(input).unwrap();
266    match impl_construct_type(&ast) {
267        Ok(tokens) => tokens,
268        Err(err) => err.to_compile_error().into(),
269    }
270}
271
272/// Derive `CCallArg`.
273///
274/// Should only be used in combination with layouts generated by JlrsReflect.jl
275#[cfg(feature = "derive")]
276#[proc_macro_derive(CCallArg, attributes(jlrs))]
277pub fn ccall_arg_derive(input: TokenStream) -> TokenStream {
278    use derive::ccall_arg::impl_ccall_arg;
279
280    let ast = syn::parse(input).unwrap();
281    match impl_ccall_arg(&ast) {
282        Ok(tokens) => tokens,
283        Err(err) => err.to_compile_error().into(),
284    }
285}
286
287/// Derive `CCallReturn`.
288///
289/// Should only be used in combination with layouts generated by JlrsReflect.jl
290#[cfg(feature = "derive")]
291#[proc_macro_derive(CCallReturn, attributes(jlrs))]
292pub fn ccall_return_derive(input: TokenStream) -> TokenStream {
293    use derive::ccall_return::impl_ccall_return;
294
295    let ast = syn::parse(input).unwrap();
296    match impl_ccall_return(&ast) {
297        Ok(tokens) => tokens,
298        Err(err) => err.to_compile_error().into(),
299    }
300}
301
302/// Derive `Enum`.
303///
304/// Should only be used in combination with layouts generated by JlrsReflect.jl
305#[cfg(feature = "derive")]
306#[proc_macro_derive(Enum, attributes(jlrs))]
307pub fn enum_derive(input: TokenStream) -> TokenStream {
308    use derive::enum_impl::impl_enum;
309
310    let ast = syn::parse(input).unwrap();
311    match impl_enum(&ast) {
312        Ok(tokens) => tokens,
313        Err(err) => err.to_compile_error().into(),
314    }
315}
316
317/// Derive `OpaqueType`.
318#[cfg(feature = "derive")]
319#[proc_macro_derive(OpaqueType, attributes(jlrs))]
320pub fn opaque_type_derive(input: TokenStream) -> TokenStream {
321    use derive::opaque_type::impl_opaque_type;
322
323    let ast = syn::parse(input).unwrap();
324    match impl_opaque_type(&ast) {
325        Ok(tokens) => tokens,
326        Err(err) => err.to_compile_error().into(),
327    }
328}
329
330/// Derive `ForeignType`.
331#[cfg(feature = "derive")]
332#[proc_macro_derive(ForeignType, attributes(jlrs))]
333pub fn foreign_type_derive(input: TokenStream) -> TokenStream {
334    use derive::foreign_type::impl_foreign_type;
335
336    let ast = syn::parse(input).unwrap();
337    match impl_foreign_type(&ast) {
338        Ok(tokens) => tokens,
339        Err(err) => err.to_compile_error().into(),
340    }
341}