Skip to main content

opendp_derive/
lib.rs

1use proc_macro::TokenStream;
2
3#[cfg(feature = "full")]
4mod full;
5
6/// When the opendp crate is compiled with the "derive" feature,
7/// the bootstrap procedural macro is executed on the function it decorates, before the library is compiled.
8///
9/// We use this to insert a link to the proof in the documentation of the function, if an adjacent proof exists.
10///
11/// We also use this to extract the rust documentation, so that we can reuse it in external language bindings.
12/// The bootstrap macro also looks at the names and types of arguments, the generics used, and the return type
13/// to automatically generate additional documentation and technical information needed for language bindings.
14///
15/// External language bindings (like python) oftentimes need some additional metadata--
16/// like the default value of arguments or types, or how to infer type information from arguments.
17/// This additional metadata is passed directly to the proc-macro itself.
18///
19/// # Linking Proofs
20///
21/// The proc-macro will look for a proof file for your function. If you are proving `fn my_function`,
22/// and a file named `my_function.tex` is found on the filesystem,
23/// the bootstrap macro will insert a link to that file into the documentation.
24/// Depending on the environment, this link will go directly to a versioned docs site, or to a local file.
25///
26/// you can also specify the location of the proof file directly:
27/// ```compile_fail
28/// #[bootstrap(
29///     proof = "transformations/make_clamp.tex"
30/// )]
31/// fn my_func() {}
32/// ```
33/// Expands to:
34/// ```no_run
35/// /// [(Proof Link)](link/to/proof.pdf)
36/// fn my_func() {}
37/// ```
38///
39/// Note that when you specify the location of the proof file, it should be relative to the src/ directory.
40///
41/// # Features
42/// You can indicate a list of features that must be enabled by the user for the function to exist.
43/// ```no_run
44/// #[bootstrap(
45///     features("contrib", "floating-point")
46/// )]
47/// ```
48/// It is recommended to specify features through the `bootstrap` function, not via `cfg` attributes,
49/// because features listed via `bootstrap` are present in external language bindings.
50///
51/// # Name
52/// In some situations, you want the name of the function in external languages to be different
53/// from the name of the function in the rust code.
54/// This is useful in situations where the bootstrap macro is invoked directly on extern "C" functions,
55/// like in the core and data modules. You can ignore this in most contexts.
56/// ```compile_fail
57/// #[bootstrap(
58///     name = "my_func_renamed"
59/// )]
60/// fn my_func() {}
61/// ```
62/// Generates in Python:
63/// ```compile_fail
64/// def my_func_renamed():
65///     pass
66/// ```
67///
68/// # Rust Link
69/// Set to the expected path of Rust documentation, if it can't be correctly inferred.
70///
71/// ```compile_fail
72/// #[bootstrap(rust_path = "domains/struct.AtomDomain")]
73/// fn my_func() {}
74/// ```
75///
76/// This gives a manual way to fix dead links to rust documentation in bindings language documentation.
77///
78/// # Arguments, Generics and Return
79/// You can pass additional metadata that is specific to each argument or generic.
80///
81/// ```no_run
82/// #[bootstrap(
83///     // can specify multiple. Everything is optional
84///     arguments(my_arg1(default = "example"), my_arg2(hint = "int")),
85///     // can specify multiple. Everything is optional
86///     generics(T(default = "AtomDomain<f64>")),
87///     // same syntax as inside an argument or generic. Optional
88///     returns(c_type = "FfiResult<AnyTransformation *>")
89/// )]
90/// ```
91///
92/// The rest of this doc comment documents how to specify metadata for
93/// specific arguments, specific generics and the return value.
94///
95/// ## Default Value
96/// You can set the default value for arguments and generics in bindings languages:
97/// ```compile_fail
98/// #[bootstrap(
99///     arguments(value(default = -1074)),
100///     generics(T(default = "i32"))
101/// )]
102/// fn my_func<T: Number>(value: T) -> T {
103///     value
104/// }
105/// ```
106/// This can make the library more accessible by making some arguments optional with sensible defaults.
107///
108/// When specifying the default value for types, you can also specify "int" or "float" instead of concrete types like "i8" or "f32".
109/// "int" refers to the default concrete type for ints, and respectively for floats.
110/// The default concrete types for ints and floats can be configured by users.
111///
112/// ## Generics in Default Values
113/// This metadata is specific to default values for generic arguments.
114/// It is used when you want to specify a default type,
115/// but let the atomic type be filled in based on context.
116/// ```compile_fail
117/// #[bootstrap(
118///     generics(
119///         D(default = "AtomDomain<T>", generics = "T"),
120///         M(default = "AbsoluteDistance<T>", generics = "T")),
121///     derived_types(T = "$get_atom_or_infer(D, constant)")
122/// )]
123/// fn my_func<D: SomeDomain, M>(constant: D::Atom) {
124///     unimplemented!()
125/// }
126/// ```
127/// In the above example, when you pass a constant to the function, the type T is first derived via the "$" macro.
128/// It is then substituted in-place of T in the generics `D` and `M`.
129/// You can separate multiple generics with commas.
130///
131/// ## Type Example
132/// It is often unnecessary for a user to specify the type T, because it can be inferred from other arguments.
133/// In the previous example, the generated bindings for `my_func` will treat the argument `T` as optional,
134/// because it can be inferred from `value`.
135///
136/// This breaks down when the public example is embedded in another data structure, like a tuple.
137/// You can provide instructions on how to retrieve an example that can be used to infer a type:
138/// ```compile_fail
139/// #[bootstrap(
140///     generics(T(example = "$get_first(bounds)"))
141/// )]
142/// fn my_func<T>(bounds: (T, T)) -> T {
143///     unimplemented!()
144/// }
145/// ```
146/// In the generated code, the type argument T becomes optional.
147/// If T is not set, T is inferred from the output of the "$" macro-- inferred from the first bound.
148/// The generated code will run a function, `get_first`, that has been defined locally in the bindings language.
149///
150/// You can specify a null default value with the byte-string literal `b"null"`.
151///
152/// ## C Type
153/// When used by other languages, data is passed to and from the OpenDP library via C interfaces.
154///
155/// The bootstrap macro will infer the correct C type in most situations by inspecting the types in the Rust signature.
156/// There are some cases that are ambiguous, though.
157/// For example, the inferred C type is always "AnyObject *" if the rust type is generic.
158/// However, it is not always necessary to construct an AnyObject; a raw pointer will do.
159///
160/// In the following example, the generated bindings would expect `value` to be passed as an "AnyObject *" because it is generic.
161/// ```compile_fail
162/// #[bootstrap()]
163/// fn my_func<T: Number>(value: T) -> T {
164///     value
165/// }
166/// ```
167/// However, we know `value` is a number that we can place behind a simple raw pointer "void *".
168/// ```compile_fail
169/// #[bootstrap(
170///     arguments(value(c_type = "void *"))
171/// )]
172/// fn my_func<T: Number>(value: T) -> T {
173///     value
174/// }
175/// ```
176/// With this annotation, the extern function associated with `my_func` should accept a "value: c_void".
177/// The extern function will downcast the pointer to a concrete rust type based on the argument "T".
178///
179/// Note that it is not meaningful to set the C type on generics, as generics are always passed as a "char *".
180///
181/// ## Rust Type
182/// For generic arguments, when unpacking data behind "void *" or "AnyObject *",
183/// it is necessary to know the concrete type to downcast to.
184/// This is the purpose of the "rust_type" metadata.
185/// Generally speaking, the bootstrap macro infers the rust type by reading the function arguments in the Rust signature.
186/// In the previous example, the rust type for `value` is automatically inferred to be "T".
187///
188/// The rust type for `bounds` in the following function is `(T, T)`.
189/// ```compile_fail
190/// #[bootstrap()]
191/// fn my_func<T>(bounds: (T, T)) -> T {
192///     unimplemented!()
193/// }
194/// ```
195/// It is again unnecessary to pass additional metadata about the bounds' rust_type because it is inferred from the Rust signature.
196///
197/// Now consider if each bound is wrapped in an enum that indicates if the value is inclusive or exclusive.
198/// It is unclear what the memory layout of the bound enum is in C.
199/// For simplicity, the extern C function slightly changes the interface to be less general by assuming that the bounds are inclusive.
200/// We can communicate this to the bindings generation by setting the rust type manually.
201/// ```compile_fail
202/// #[bootstrap(
203///     arguments(bounds(rust_type = "(T, T)")),
204///     generics(TA(example = "$get_first(bounds)"))
205/// )]
206/// fn make_unclamp<T>(bounds: (Bound<T>, Bound<T>)) -> S::Atom {
207///     unimplemented!()
208/// }
209/// ```
210/// In this case, the extern C function is written to expect `bounds: AnyObject *`,
211/// the default C type for any nontrivial or generic data structure.
212/// The extern function downcasts said AnyObject to the rust type `(T, T)` and wraps each bound in Bound::Inclusive.
213/// This constructor manually specified the rust_type to smooth over a small difference between the api of the rust function,
214/// and the api of the extern function.
215///
216///
217/// In a slightly more complicated example, consider the case where the function is generic over some type `S`.
218/// `S` has an associated type `S::Atom`, and the function expects bounds of type `S::Atom`.
219/// The bootstrap macro doesn't know how to handle these associated types.
220///
221/// We can instead derive a type, by saying there exists some type T that is inferred from the first bound.
222/// As a developer we have knowledge that our naive tooling doesn't-- that T is the same as S::Atom.
223/// We can then use this inferred type to specify the rust type.
224/// ```compile_fail
225/// #[bootstrap(
226///     arguments(bounds(rust_type = "(T, T)")),
227///     derived_types(T = "$get_first(bounds)")
228/// )]
229/// fn my_func<S: Summable>(bounds: (S::Atom, S::Atom)) -> S::Atom {
230///     unimplemented!()
231/// }
232/// ```
233/// In the bindings, the type T is inferred from the first bound,
234/// and it packs the tuple data structure into an AnyObject of type `(T, T)`.
235/// The implementation of the rust extern "C" function downcasts the AnyObject to an `(S::Item, S::Item)`.
236/// This pattern is used often in the sum constructors.
237///
238/// It is also possible to specify the rust type via a macro.
239/// Here is an example for the invoke function:
240/// ```compile_fail
241/// #[bootstrap(
242///     name = "measurement_invoke",
243///     arguments(
244///         this(rust_type = b"null"),
245///         arg(rust_type = "$measurement_input_carrier_type(this)")
246///     )
247/// )]
248/// #[unsafe(no_mangle)]
249/// pub extern "C" fn opendp_core__measurement_invoke(
250///     this: *const AnyMeasurement,
251///     arg: *const AnyObject
252/// ) -> FfiResult<*mut AnyObject> {
253///     unimplemented!()
254/// }
255/// ```
256/// First off, the rust type of `this` is marked as null to indicate that we don't want to perform any conversions between C and rust types.
257/// Secondly, the rust type of the argument should always be the input carrier type on the measurement.
258/// We use another function `measurement_input_carrier_type`, defined in the bindings, to retrieve this value.
259/// Thus `arg` is always an instance of the Rust type that the measurement expects.
260///
261/// Note that it is not meaningful to set the rust type on generics,
262/// as generics are just type information, passed as a string.
263///
264/// ## Hints
265/// This metadata contains a fair amount of Python-only syntax.
266/// It is added as a type hint to the argument in Python.
267/// ```compile_fail
268/// #[bootstrap(
269///     generics(MO(hint = "SensitivityMetric"))
270/// )]
271/// fn my_func<MO: SensitivityMetric>() {}
272/// ```
273/// The above type MO has a trait bound `SensitivityMetric` that restricts the set of possible types MO can be.
274///
275/// The generated code downgrades this to just a type hint.
276/// ```compile_fail
277/// def my_func(
278///     MO: SensitivityMetric
279/// ):
280///     pass
281/// ```
282/// The extern "C" rust function would throw an error if any type that is not a sensitivity metric were passed.
283///
284/// ## Suppress
285/// In some cases you want generated code not to include a type argument for a particular generic,
286/// because it is unambiguously determined by another argument.
287/// This comes up often when the type is already captured in the `input_domain` or `input_metric` argument.
288/// ```compile_fail
289/// #[bootstrap(
290///     generics(DI(suppress))
291/// )]
292/// fn my_func<DI>(input_domain: DI) {}
293/// ```
294///
295/// In this case, the generated code will not include the type argument for DI.
296/// The extern "C" function will get a handle on DI by introspecting the received `AnyDomain` struct.
297///
298/// ## Do Not Convert
299/// By default, generated bindings code always calls a function to convert between rust types and C types.
300/// This can be disabled on individual arguments by specifying `do_not_convert = true`.
301/// This is typically only useful on the innermost structural utilities,
302/// like when converting from an FfiSlice to an AnyObject or vice versa.
303///
304/// ## Dependencies
305/// When the return value of the function contains data whose memory has been allocated by a foreign language,
306/// the data is at risk of being freed.
307/// This is because foreign languages may not know the return value is still holding a reference to the data.
308///
309/// For example, when you construct a domain that holds a member check function that was allocated in Python.
310/// ```compile_fail
311/// #[bootstrap(
312///     dependencies(member)
313/// )]
314/// fn example_user_domain<DI>(member: CallbackFn) -> AnyDomain {
315///     pack_function_into_domain(member)
316/// }
317/// ```
318///
319/// The generated code will add a reference to `member` to the return type, which keeps the refcount up.
320/// ```compile_fail
321/// def example_user_domain(
322///     member: Callable[[Any], bool]
323/// ):
324///     result = lib.example_user_domain(member)
325///     setattr(result, "_dependencies", member)
326///     return result
327/// ```
328///
329#[cfg(feature = "full")]
330#[proc_macro_attribute]
331pub fn bootstrap(attr_args: TokenStream, input: TokenStream) -> TokenStream {
332    full::bootstrap(attr_args, input)
333}
334
335/// When the "derive" crate feature is not enabled, no work is done, and dependencies are simplified.
336///
337#[cfg(not(feature = "full"))]
338#[proc_macro_attribute]
339pub fn bootstrap(_attr_args: TokenStream, input: TokenStream) -> TokenStream {
340    input
341}
342
343/// This shares the same interface as the bootstrap macro, but only accepts the first two arguments:
344/// proof_link, and features.
345///
346/// This macro differs from bootstrap in that it is much simpler,
347/// and is meant to be used on internal functions that don't get foreign language bindings.
348/// This macro throws a compile error if the proof file cannot be found.
349///
350/// This macro can also be affixed on trait and struct impls.
351/// When used on a trait impl, it looks for `TraitName.tex`,
352/// and when used on a struct impl, it looks for `StructName.tex`.
353///
354#[cfg(feature = "full")]
355#[proc_macro_attribute]
356pub fn proven(attr_args: TokenStream, input: TokenStream) -> TokenStream {
357    full::proven(attr_args, input)
358}
359
360#[cfg(not(feature = "full"))]
361#[proc_macro_attribute]
362pub fn proven(_attr_args: TokenStream, input: TokenStream) -> TokenStream {
363    input
364}