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
extern crate proc_macro;
/// Return an error at the given item.
macro_rules! bail {
($item:expr, $fmt:literal $($tts:tt)*) => {
return Err(Error::new_spanned(
&$item,
format!(concat!("comemo: ", $fmt) $($tts)*)
))
}
}
mod memoize;
mod track;
use proc_macro::TokenStream as BoundaryStream;
use proc_macro2::TokenStream;
use quote::{quote, quote_spanned, ToTokens};
use syn::spanned::Spanned;
use syn::{parse_quote, Error, Result};
/// Memoize a function.
///
/// Memoized functions can take two kinds of arguments:
/// - _Hashed:_ This is the default. These arguments are hashed into a
/// high-quality 128-bit hash, which is used as a cache key. For this to be
/// correct, the hash implementations of your arguments **must feed all the
/// information your arguments expose to the hasher**. Otherwise, memoized
/// results might get reused invalidly.
/// - _Tracked:_ The argument is of the form `Tracked<T>`. These arguments enjoy
/// fine-grained access tracking and needn't be exactly the same for a cache
/// hit to occur. They only need to be used equivalently.
///
/// You can also add the `#[memoize]` attribute to methods in inherent and trait
/// impls.
///
/// # Example
/// ```
/// /// Evaluate a `.calc` script.
/// #[comemo::memoize]
/// fn evaluate(script: &str, files: comemo::Tracked<Files>) -> i32 {
/// script
/// .split('+')
/// .map(str::trim)
/// .map(|part| match part.strip_prefix("eval ") {
/// Some(path) => evaluate(&files.read(path), files),
/// None => part.parse::<i32>().unwrap(),
/// })
/// .sum()
/// }
/// ```
///
/// # Restrictions
/// There are certain restrictions that apply to memoized functions. Most of
/// these are checked by comemo, but some are your responsibility:
/// - They must be **pure**, that is, **free of observable side effects**. This
/// is **your responsibility** as comemo can't check it.
/// - They must have an explicit return type.
/// - They cannot have mutable parameters (conflicts with purity).
/// - They cannot use destructuring patterns in their arguments.
#[proc_macro_attribute]
pub fn memoize(_: BoundaryStream, stream: BoundaryStream) -> BoundaryStream {
let func = syn::parse_macro_input!(stream as syn::Item);
memoize::expand(&func)
.unwrap_or_else(|err| err.to_compile_error())
.into()
}
/// Make a type trackable.
///
/// Adding this to an impl block of a type `T` implements the `Track` trait for
/// `T`. This lets you call `.track()` on that type, producing a `Tracked<T>`.
/// When such a tracked type is used an argument to a memoized function it
/// enjoys fine-grained access tracking instead of being bluntly hashed.
///
/// You can also add the `#[track]` attribute to a trait to make its trait
/// object trackable.
///
/// # Example
/// ```
/// /// File storage.
/// struct Files(HashMap<PathBuf, String>);
///
/// #[comemo::track]
/// impl Files {
/// /// Load a file from storage.
/// fn read(&self, path: &str) -> String {
/// self.0.get(Path::new(path)).cloned().unwrap_or_default()
/// }
/// }
///
/// impl Files {
/// /// Write a file to storage.
/// fn write(&mut self, path: &str, text: &str) {
/// self.0.insert(path.into(), text.into());
/// }
/// }
/// ```
///
/// # Restrictions
/// Tracked impl blocks or traits may not be generic and may only contain
/// methods. Just like with memoized functions, certain restrictions apply to
/// tracked methods:
/// - They must be **pure**, that is, **free of observable side effects**. This
/// is **your responsibility** as comemo can't check it. You can use interior
/// mutability as long as the method stays idempotent.
/// - Their **return values must implement `Hash`** and **must feed all the
/// information they expose to the hasher**. Otherwise, memoized results might
/// get reused invalidly.
/// - They cannot be generic.
/// - They can only be private or public not `pub(...)`.
/// - They cannot be `unsafe`, `async` or `const`.
/// - They must take an `&self` parameter.
/// - They must have an explicit return type.
/// - They cannot have mutable parameters (conflicts with purity).
/// - They cannot use destructuring patterns in their arguments.
#[proc_macro_attribute]
pub fn track(_: BoundaryStream, stream: BoundaryStream) -> BoundaryStream {
let block = syn::parse_macro_input!(stream as syn::Item);
track::expand(&block)
.unwrap_or_else(|err| err.to_compile_error())
.into()
}