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}