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)
}