Skip to content

Commit

Permalink
Use elided lifetimes instead of 'gc for the short form of Rootable!
Browse files Browse the repository at this point in the history
This allows using the short form even when a `'gc` lifetime is already
in scope.

For ease of future maintenance, the lifetime substitution is done by a
procedural macro instead of a recursive tt-muncher.
  • Loading branch information
moulins committed Jun 2, 2023
1 parent 6e2677b commit 5c5546c
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 27 deletions.
2 changes: 1 addition & 1 deletion src/gc-arena-derive/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,5 @@ proc-macro = true
[dependencies]
proc-macro2 = "1.0.56"
quote = "1.0.26"
syn = "2.0.13"
syn = { version = "2.0.17", features = ["default", "visit-mut"] }
synstructure = "0.13"
41 changes: 40 additions & 1 deletion src/gc-arena-derive/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
use proc_macro2::{Span, TokenStream};
use quote::{quote, quote_spanned, ToTokens};
use syn::spanned::Spanned;
use syn::{
parse::{Parse, ParseStream},
spanned::Spanned,
visit_mut::VisitMut,
};
use synstructure::{decl_derive, AddBounds};

fn find_collect_meta(attrs: &[syn::Attribute]) -> syn::Result<Option<&syn::Attribute>> {
Expand Down Expand Up @@ -259,3 +263,38 @@ decl_derive! {
/// struct/enum is marked with `require_static` then this is unnecessary.
collect_derive
}

// Not public API; implementation detail of `gc_arena::Rootable!`.
// Replaces all `'_` lifetimes in a type by the specified named lifetime.
// Syntax: `__unelide_lifetimes!('lt; SomeType)`.
#[doc(hidden)]
#[proc_macro]
pub fn __unelide_lifetimes(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
struct Input {
lt: syn::Lifetime,
ty: syn::Type,
}

impl Parse for Input {
fn parse(input: ParseStream) -> syn::Result<Self> {
let lt: syn::Lifetime = input.parse()?;
let _: syn::Token!(;) = input.parse()?;
let ty: syn::Type = input.parse()?;
Ok(Self { lt, ty })
}
}

struct UnelideLifetimes(syn::Lifetime);

impl VisitMut for UnelideLifetimes {
fn visit_lifetime_mut(&mut self, i: &mut syn::Lifetime) {
if i.ident == "_" {
*i = self.0.clone();
}
}
}

let mut input = syn::parse_macro_input!(input as Input);
UnelideLifetimes(input.lt).visit_type_mut(&mut input.ty);
input.ty.to_token_stream().into()
}
17 changes: 10 additions & 7 deletions src/gc-arena/src/arena.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,11 @@ impl<'a, T: ?Sized + Rootable<'a>> Rootable<'a> for __DynRootable<T> {
type Root = <T as Rootable<'a>>::Root;
}

/// A convenience macro for quickly creating type that implements of `Rootable`.
/// A convenience macro for quickly creating type that implements `Rootable`.
///
/// The macro takes a single argument, which should be a generic type that references a `'gc`
/// lifetime. When used as a root object, this `'gc` lifetime will be replaced with the branding
/// lifetime.
/// The macro takes a single argument, which should be a generic type with elided lifetimes.
/// When used as a root object, every instances of the elided lifetime will be replaced with
/// the branding lifetime.
///
/// ```
/// # use gc_arena::{Arena, Collect, Gc, Rootable};
Expand All @@ -96,7 +96,10 @@ impl<'a, T: ?Sized + Rootable<'a>> Rootable<'a> for __DynRootable<T> {
/// ptr: Gc<'gc, i32>,
/// }
///
/// type MyArena = Arena<Rootable![MyRoot<'gc>]>;
/// type MyArena = Arena<Rootable![MyRoot<'_>]>;
///
/// // If desired, the branding lifetime can also be explicitely named:
/// type MyArena2 = Arena<Rootable!['gc => MyRoot<'gc>]>;
/// # }
/// ```
///
Expand All @@ -113,7 +116,7 @@ impl<'a, T: ?Sized + Rootable<'a>> Rootable<'a> for __DynRootable<T> {
/// ptr: Gc<'gc, StaticCollect<T>>,
/// }
///
/// type MyGenericArena<T> = Arena<Rootable![MyGenericRoot<'gc, T>]>;
/// type MyGenericArena<T> = Arena<Rootable![MyGenericRoot<'_, T>]>;
/// # }
/// ```
#[macro_export]
Expand All @@ -124,7 +127,7 @@ macro_rules! Rootable {
$crate::__DynRootable::<dyn for<$gc> $crate::Rootable<$gc, Root = $root>>
};
($root:ty) => {
$crate::Rootable!['gc => $root]
$crate::Rootable!['__gc => $crate::__unelide_lifetimes!('__gc; $root)]
};
}

Expand Down
36 changes: 18 additions & 18 deletions src/gc-arena/tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ fn simple_allocation() {
test: Gc<'gc, i32>,
}

let arena = Arena::<Rootable![TestRoot<'gc>]>::new(ArenaParameters::default(), |mc| TestRoot {
let arena = Arena::<Rootable![TestRoot<'_>]>::new(ArenaParameters::default(), |mc| TestRoot {
test: Gc::new(mc, 42),
});

Expand All @@ -36,7 +36,7 @@ fn weak_allocation() {
weak: GcWeak<'gc, i32>,
}

let mut arena = Arena::<Rootable![TestRoot<'gc>]>::new(ArenaParameters::default(), |mc| {
let mut arena = Arena::<Rootable![TestRoot<'_>]>::new(ArenaParameters::default(), |mc| {
let test = Gc::new(mc, 42);
let weak = Gc::downgrade(test);
assert!(weak.upgrade(mc).is_some());
Expand Down Expand Up @@ -87,7 +87,7 @@ fn dyn_sized_allocation() {

let counter = RefCounter(Rc::new(()));

let mut arena = Arena::<Rootable![TestRoot<'gc>]>::new(ArenaParameters::default(), |mc| {
let mut arena = Arena::<Rootable![TestRoot<'_>]>::new(ArenaParameters::default(), |mc| {
let array: [_; SIZE] = core::array::from_fn(|_| Gc::new(mc, counter.clone()));
let slice = unsize!(Gc::new(mc, array) => [_]);
TestRoot { slice }
Expand Down Expand Up @@ -121,7 +121,7 @@ fn repeated_allocation_deallocation() {

let r = RefCounter(Rc::new(()));

let mut arena = Arena::<Rootable![TestRoot<'gc>]>::new(ArenaParameters::default(), |mc| {
let mut arena = Arena::<Rootable![TestRoot<'_>]>::new(ArenaParameters::default(), |mc| {
TestRoot(Gc::new(mc, RefLock::new(HashMap::new())))
});

Expand Down Expand Up @@ -168,7 +168,7 @@ fn all_dropped() {

let r = RefCounter(Rc::new(()));

let arena = Arena::<Rootable![TestRoot<'gc>]>::new(ArenaParameters::default(), |mc| {
let arena = Arena::<Rootable![TestRoot<'_>]>::new(ArenaParameters::default(), |mc| {
TestRoot(Gc::new(mc, RefLock::new(Vec::new())))
});

Expand All @@ -194,7 +194,7 @@ fn all_garbage_collected() {

let r = RefCounter(Rc::new(()));

let mut arena = Arena::<Rootable![TestRoot<'gc>]>::new(ArenaParameters::default(), |mc| {
let mut arena = Arena::<Rootable![TestRoot<'_>]>::new(ArenaParameters::default(), |mc| {
TestRoot(Gc::new(mc, RefLock::new(Vec::new())))
});

Expand Down Expand Up @@ -354,7 +354,7 @@ fn test_map() {
some_complex_state: Vec<Gc<'gc, i32>>,
}

let arena = Arena::<Rootable![Root<'gc>]>::new(ArenaParameters::default(), |mc| Root {
let arena = Arena::<Rootable![Root<'_>]>::new(ArenaParameters::default(), |mc| Root {
some_complex_state: vec![Gc::new(mc, 42), Gc::new(mc, 69)],
});

Expand All @@ -365,7 +365,7 @@ fn test_map() {
state: Gc<'gc, i32>,
}

let arena = arena.map_root::<Rootable![Intermediate<'gc>]>(|_, root| {
let arena = arena.map_root::<Rootable![Intermediate<'_>]>(|_, root| {
let state = root.some_complex_state[0];
Intermediate { root, state }
});
Expand All @@ -376,7 +376,7 @@ fn test_map() {
});

let arena = arena
.try_map_root::<Rootable![Intermediate<'gc>], ()>(|_, intermediate| {
.try_map_root::<Rootable![Intermediate<'_>], ()>(|_, intermediate| {
let state = intermediate.root.some_complex_state[1];
Ok(Intermediate {
root: intermediate.root,
Expand All @@ -393,20 +393,20 @@ fn test_map() {

#[test]
fn test_dynamic_roots() {
let mut arena: Arena<Rootable![DynamicRootSet<'gc>]> =
let mut arena: Arena<Rootable![DynamicRootSet<'_>]> =
Arena::new(ArenaParameters::default(), |mc| DynamicRootSet::new(mc));

let initial_size = arena.total_allocated();

let root1 =
arena.mutate(|mc, root_set| root_set.stash::<Rootable![Gc<'gc, i32>]>(mc, Gc::new(mc, 12)));
arena.mutate(|mc, root_set| root_set.stash::<Rootable![Gc<'_, i32>]>(mc, Gc::new(mc, 12)));

#[derive(Collect)]
#[collect(no_drop)]
struct Root2<'gc>(Gc<'gc, i32>, Gc<'gc, bool>);

let root2 = arena.mutate(|mc, root_set| {
root_set.stash::<Rootable![Root2<'gc>]>(mc, Root2(Gc::new(mc, 27), Gc::new(mc, true)))
root_set.stash::<Rootable![Root2<'_>]>(mc, Root2(Gc::new(mc, 27), Gc::new(mc, true)))
});

arena.collect_all();
Expand Down Expand Up @@ -435,18 +435,18 @@ fn test_dynamic_roots() {
#[test]
#[should_panic]
fn test_dynamic_bad_set() {
let arena1: Arena<Rootable![DynamicRootSet<'gc>]> =
let arena1: Arena<Rootable![DynamicRootSet<'_>]> =
Arena::new(ArenaParameters::default(), |mc| DynamicRootSet::new(mc));

let arena2: Arena<Rootable![DynamicRootSet<'gc>]> =
let arena2: Arena<Rootable![DynamicRootSet<'_>]> =
Arena::new(ArenaParameters::default(), |mc| DynamicRootSet::new(mc));

#[derive(Collect)]
#[collect(no_drop)]
struct Root<'gc>(Gc<'gc, i32>);

let dyn_root =
arena1.mutate(|mc, root| root.stash::<Rootable![Root<'gc>]>(mc, Root(Gc::new(mc, 44))));
arena1.mutate(|mc, root| root.stash::<Rootable![Root<'_>]>(mc, Root(Gc::new(mc, 44))));

arena2.mutate(|_, root| {
root.fetch(&dyn_root);
Expand Down Expand Up @@ -485,7 +485,7 @@ fn test_collect_overflow() {
}

let mut arena =
Arena::<Rootable![TestRoot<'gc>]>::new(ArenaParameters::default(), |mc| TestRoot {
Arena::<Rootable![TestRoot<'_>]>::new(ArenaParameters::default(), |mc| TestRoot {
test: Gc::new(mc, [0; 256]),
});

Expand Down Expand Up @@ -577,7 +577,7 @@ fn okay_panic() {
}
}

let mut arena = Arena::<Rootable![Gc<'gc, Test<'gc>>]>::new(ArenaParameters::default(), |mc| {
let mut arena = Arena::<Rootable![Gc<'_, Test<'_>>]>::new(ArenaParameters::default(), |mc| {
Gc::new(
mc,
Test {
Expand Down Expand Up @@ -622,7 +622,7 @@ fn field_locks() {
nested: Nested<'gc>,
}

let arena = Arena::<Rootable![Gc<'gc, Test<'gc>>]>::new(ArenaParameters::default(), |mc| {
let arena = Arena::<Rootable![Gc<'_, Test<'_>>]>::new(ArenaParameters::default(), |mc| {
Gc::new(
mc,
Test {
Expand Down

0 comments on commit 5c5546c

Please sign in to comment.