1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
#[cfg(feature = "derive")]
mod derive;
#[cfg(feature = "ccall")]
mod module;
mod version;

use proc_macro::TokenStream;

#[cfg(feature = "derive")]
use self::derive::*;
#[cfg(feature = "ccall")]
use self::module::*;
use self::version::emit_if_compatible;

/// Export functions, types and constants defined in Rust as a Julia module.
///
/// This macro generates an initialization function. This function is used in combination with the
/// macros provided by the `JlrsCore.Wrap` module to automatically generate the content of that
/// module.
///
/// The syntax is as follows:
///
/// ```ignore
/// julia_module! {
///     // init_function_name is the name of the generated initialization function.
///     //
///     // The name of the generated function must be unique, it's recommended you prefix it with
///     // the crate name. If your crate is named foo-jl, you should use a name like
///     // `foo_jl_init`.
///     become init_function_name;
///
///     // Exports the function `foo` as `bar` with documentation.
///     //
///     // The `as <exposed_name>` part is optional, by default the function is exported with the
///     // name it has in Rust, the exposed name can end in an exclamation mark.
///     //
///     // A docstring can be provided with the doc attribute; if multiple functions are exported
///     // with the same name it shoud only be documented once. All exported items can be
///     // documented, a multi-line docstring can be created by providing multiple doc attributes
///     // for the same item.
///     //
///     // If the function doesn't need to call into Julia, you can annotate it with `#[gc_safe]`
///     // to allow the GC to run without having to wait until the function has returned.
///     #[doc = "    bar(arr::Array)"]
///     #[doc = ""]
///     #[doc = "Documentation for this function"]
///     #[gc_safe]
///     fn foo(arr: Array) -> usize as bar;
///
///     // Exports the function `foo` as `bar!` in the `Base` module.
///     //
///     // This syntax can be used to extend existing functions.
///     fn foo(arr: Array) -> usize as Base.bar!;
///
///     // Exports the struct `MyType` as `MyForeignType`. `MyType` must implement `OpaqueType`
///     // or `ForeignType`.
///     struct MyType as MyForeignType;
///
///     // Exports `MyType::new` as `MyForeignType`, turning it into a constructor for that type.
///     in MyType fn new(arg0: TypedValue<u32>) -> TypedValueRet<MyType> as MyForeignType;
///
///     // Exports `MyType::add` as the function `increment!`.
///     //
///     // If a method takes `self` in some way, it is tracked by default. You can opt out of this
///     // behavior with the `#[untracked_self]` attribute.
///     #[untracked_self]
///     in MyType fn add(&mut self, incr: u32) -> RustResultRet<u32>  as increment!;
/// 
///     // Exports the alias `MyTypeAlias` for `MyType`.
///     //
///     // This is exposes as `const MyTypeAlias = MyType`.
///     type MyTypeAlias = MyType;
///
///     // Exports the function `long_running_func`, the returned closure is executed on another
///     // thread.
///     //
///     // After dispatching the closure to another thread, the generated Julia function waits for
///     // the closure to return using an `AsyncCondition`. Because the closure is executed on
///     // another thread you can't call Julia functions or allocate Julia data from it, but it is
///     // possible to (mutably) access Julia data by tracking it.
///     //
///     // In order to be able to use tracked data from the closure,  `Unbound` managed types must
///     // be used. Only `(Typed)ValueUnbound` and `(Typed)ArrayUnbound` exist,  they're aliases
///     // for `(Typed)Value` and `(Typed)Array` with static lifetimes. The generated Julia
///     // function guarantees all data passed as an argument lives at least until the closure has
///     // finished, the tracked data must only be shared with that closure.
///     //
///     // In practice calling a function annotated with `#[gc_safe]` is much more performant.
///     async fn long_running_func(
///         array: ArrayUnbound
///     ) -> JlrsResult<impl AsyncCallback<i32>>;
///
///     // Exports `MY_CONST` as the constant `MY_CONST`, its type must implement `IntoJulia`.
///     // `MY_CONST` can be defined in Rust as either static or constant data, i.e. both
///     // `static MY_CONST: u8 = 1` and `const MY_CONST: u8 = 1` can be exposed this way.
///     const MY_CONST: u8;
///
///     // Exports `MY_CONST` as the global `MY_GLOBAL`, its type must implement `IntoJulia`.
///     // `MY_CONST` can be defined in Rust as either static or constant data, i.e. both
///     // `static MY_CONST: u8 = 1` and `const MY_CONST: u8 = 1` can be exposed this way.
///     static MY_CONST: u8 as MY_GLOBAL;
/// }
/// ```
///
/// And this is all you need to do in Julia:
///
/// ```julia
/// module MyRustModule
/// using JlrsCore.Wrap
///
/// @wrapmodule("path/to/lib", :init_function_name)
///
/// function __init__()
///     @initjlrs
/// end
/// end
/// ```
///
/// It can be rather tricky to figure out how data is passed from Julia to Rust when `ccall`ing
/// a function written in Rust. Primitive and `isbits` types are passed by value, managed types
/// provided directly by jlrs are guaranteed to be boxed, all other types might be passed by
/// value or be boxed.
///
/// In order to avoid figuring out how such data is passed you can work with `TypedValue`, which
/// ensures the data is boxed by using `Any` in the signature of the generated `ccall` invocation,
/// but restricts the type of the data in the generated function to the type constructed from the
/// `TypedValue`'s type parameter.
///
/// [`AsyncCondition`]: https://docs.julialang.org/en/v1/base/base/#Base.AsyncCondition
#[proc_macro]
#[cfg(feature = "ccall")]
pub fn julia_module(item: TokenStream) -> TokenStream {
    let input = syn::parse_macro_input!(item as JuliaModule);
    match input.generate_init_code() {
        Ok(a) => a,
        Err(b) => b.to_compile_error().into(),
    }
}

/// Conditional compilation depending on the used version of Julia.
///
/// This macro can be used instead of a custom `cfg` to conditionally compile code for
/// certain versions of Julia. For example, to enable a function when Julia 1.6, 1.7 or 1.10 is
/// used on Linux, or when Julia 1.7 or 1.10 is used on Windows:
///
/// `#[julia_version(since = "1.6", until = "1.10", except = ["1.8", "1.9"], windows_lts = false)]`
///
/// By default, `since = "1.6"`, `until = "1.10"`, `except = []`, and `windows_lts = None`, so the
/// above can be written more compactly as:
///
/// `#[julia_version(except = ["1.8", "1.9"], windows_lts = false)]`.
#[proc_macro_attribute]
pub fn julia_version(attr: TokenStream, item: TokenStream) -> TokenStream {
    emit_if_compatible(attr, item)
}

/// Derive `IntoJulia`.
///
/// Should only be used in combination with layouts generated by JlrsReflect.jl
#[cfg(feature = "derive")]
#[proc_macro_derive(IntoJulia, attributes(jlrs))]
pub fn into_julia_derive(input: TokenStream) -> TokenStream {
    let ast = syn::parse(input).unwrap();
    impl_into_julia(&ast)
}

/// Derive `IsBits`.
///
/// Should only be used in combination with layouts generated by JlrsReflect.jl
#[cfg(feature = "derive")]
#[proc_macro_derive(IsBits, attributes(jlrs))]
pub fn is_bits_derive(input: TokenStream) -> TokenStream {
    let ast = syn::parse(input).unwrap();
    impl_is_bits(&ast)
}

/// Derive `HasLayout`.
///
/// Should only be used in combination with layouts generated by JlrsReflect.jl
#[cfg(feature = "derive")]
#[proc_macro_derive(HasLayout, attributes(jlrs))]
pub fn is_has_layout(input: TokenStream) -> TokenStream {
    let ast = syn::parse(input).unwrap();
    impl_has_layout(&ast)
}

/// Derive `Unbox`.
///
/// Should only be used in combination with layouts generated by JlrsReflect.jl
#[cfg(feature = "derive")]
#[proc_macro_derive(Unbox, attributes(jlrs))]
pub fn unbox_derive(input: TokenStream) -> TokenStream {
    let ast = syn::parse(input).unwrap();
    impl_unbox(&ast)
}

/// Derive `Typecheck`.
///
/// Should only be used in combination with layouts generated by JlrsReflect.jl
#[cfg(feature = "derive")]
#[proc_macro_derive(Typecheck, attributes(jlrs))]
pub fn typecheck_derive(input: TokenStream) -> TokenStream {
    let ast = syn::parse(input).unwrap();
    impl_typecheck(&ast)
}

/// Derive `ValidLayout`.
///
/// Should only be used in combination with layouts generated by JlrsReflect.jl
#[cfg(feature = "derive")]
#[proc_macro_derive(ValidLayout, attributes(jlrs))]
pub fn valid_layout_derive(input: TokenStream) -> TokenStream {
    let ast = syn::parse(input).unwrap();
    impl_valid_layout(&ast)
}

/// Derive `ValidField`.
///
/// Should only be used in combination with layouts generated by JlrsReflect.jl
#[cfg(feature = "derive")]
#[proc_macro_derive(ValidField, attributes(jlrs))]
pub fn valid_field_derive(input: TokenStream) -> TokenStream {
    let ast = syn::parse(input).unwrap();
    impl_valid_field(&ast)
}

/// Derive `ConstructType`.
///
/// Should only be used in combination with layouts generated by JlrsReflect.jl
#[cfg(feature = "derive")]
#[proc_macro_derive(ConstructType, attributes(jlrs))]
pub fn construct_type_derive(input: TokenStream) -> TokenStream {
    let ast = syn::parse(input).unwrap();
    impl_construct_type(&ast)
}

/// Derive `CCallArg`.
///
/// Should only be used in combination with layouts generated by JlrsReflect.jl
#[cfg(feature = "derive")]
#[proc_macro_derive(CCallArg, attributes(jlrs))]
pub fn ccall_arg_derive(input: TokenStream) -> TokenStream {
    let ast = syn::parse(input).unwrap();
    impl_ccall_arg(&ast)
}

/// Derive `CCallReturn`.
///
/// Should only be used in combination with layouts generated by JlrsReflect.jl
#[cfg(feature = "derive")]
#[proc_macro_derive(CCallReturn, attributes(jlrs))]
pub fn ccall_return_derive(input: TokenStream) -> TokenStream {
    let ast = syn::parse(input).unwrap();
    impl_ccall_return(&ast)
}