Skip to content

Commit

Permalink
[api] Fix doc tests
Browse files Browse the repository at this point in the history
Now we have a 'dummy_impl' module that has a fake
GC implementation. Maybe someday we should actually implement
a real nop gc, like Java is adding.

Right now the only doc-test we have is for `safepoint!`
but at least that works :)
  • Loading branch information
Techcable committed Jan 10, 2021
1 parent 4ef8388 commit 772ffbd
Show file tree
Hide file tree
Showing 4 changed files with 174 additions and 86 deletions.
82 changes: 8 additions & 74 deletions libs/derive/tests/basic.rs
@@ -1,4 +1,4 @@
use zerogc::{Gc, CollectorId, Trace, GcSafe, NullTrace};
use zerogc::{Gc, CollectorId, Trace, GcSafe, NullTrace, dummy_impl};

use zerogc_derive::Trace;

Expand Down Expand Up @@ -41,84 +41,18 @@ struct NopTrace {

#[test]
fn basic() {
let _b = Basic::<dummy::DummyCollectorId> {
let _b = Basic::<dummy_impl::DummyCollectorId> {
value: String::new(),
parent: None,
children: vec![]
};
assert!(<Basic::<dummy::DummyCollectorId> as Trace>::NEEDS_TRACE);
assert!(<BasicCopy::<dummy::DummyCollectorId> as Trace>::NEEDS_TRACE);
assert!(<Basic::<dummy::DummyCollectorId> as GcSafe>::NEEDS_DROP);
assert!(!<BasicCopy::<dummy::DummyCollectorId> as GcSafe>::NEEDS_DROP);
assert_copy::<BasicCopy::<dummy::DummyCollectorId>>();
assert!(<Basic::<dummy_impl::DummyCollectorId> as Trace>::NEEDS_TRACE);
assert!(<BasicCopy::<dummy_impl::DummyCollectorId> as Trace>::NEEDS_TRACE);
assert!(<Basic::<dummy_impl::DummyCollectorId> as GcSafe>::NEEDS_DROP);
assert!(!<BasicCopy::<dummy_impl::DummyCollectorId> as GcSafe>::NEEDS_DROP);
assert_copy::<BasicCopy::<dummy_impl::DummyCollectorId>>();
assert_null_trace::<NopTrace>();
assert!(!<NopTrace as Trace>::NEEDS_TRACE);

check_id::<dummy::DummyCollectorId>();
check_id::<dummy_impl::DummyCollectorId>();
}

mod dummy {
use zerogc::{
Gc, Trace, GcSafe, GcSystem, GcContext, CollectorId,
NullTrace, TraceImmutable, GcVisitor
};

pub struct DummyContext {}
unsafe impl GcContext for DummyContext {
type System = DummySystem;
type Id = DummyCollectorId;

unsafe fn basic_safepoint<T: Trace>(&mut self, _value: &mut &mut T) {
unimplemented!()
}

unsafe fn freeze(&mut self) {
unimplemented!()
}

unsafe fn unfreeze(&mut self) {
unimplemented!()
}

unsafe fn recurse_context<T, F, R>(&self, _value: &mut &mut T, _func: F) -> R where T: Trace, F: for<'gc> FnOnce(&'gc mut Self, &'gc mut T) -> R {
unimplemented!()
}
}


pub struct DummySystem {}
unsafe impl GcSystem for DummySystem {
type Id = DummyCollectorId;
type Context = DummyContext;
}

#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct DummyCollectorId {}
unsafe impl Trace for DummyCollectorId {
const NEEDS_TRACE: bool = false;

fn visit<V: GcVisitor>(&mut self, _visitor: &mut V) -> Result<(), <V as GcVisitor>::Err> {
Ok(())
}
}
unsafe impl TraceImmutable for DummyCollectorId {
fn visit_immutable<V: GcVisitor>(&self, _visitor: &mut V) -> Result<(), V::Err> {
Ok(())
}
}

unsafe impl NullTrace for DummyCollectorId {}
unsafe impl CollectorId for DummyCollectorId {
type System = DummySystem;

unsafe fn gc_write_barrier<'gc, T, V>(
_owner: &Gc<'gc, T, Self>,
_value: &Gc<'gc, V, Self>,
_field_offset: usize
) where T: GcSafe + ?Sized + 'gc, V: GcSafe + ?Sized + 'gc {}

unsafe fn assume_valid_system(&self) -> &Self::System {
unimplemented!()
}
}
}
131 changes: 131 additions & 0 deletions src/dummy_impl.rs
@@ -0,0 +1,131 @@
//! Dummy collector implementation for testing

use crate::{
Trace, TraceImmutable, GcVisitor, NullTrace, CollectorId,
GcSafe, GcSystem, GcContext,
};
use std::ptr::NonNull;

/// Fake a [Gc] that points to the specified value
///
/// This will never actually be collected
/// and will always be valid
pub fn gc<'gc, T: GcSafe + 'static>(ptr: &'static T) -> Gc<'gc, T> {
unsafe {
Gc::from_raw(
DummyCollectorId { _priv: () },
NonNull::from(ptr)
)
}
}

/// Allocate a [(fake) Gc](Gc) that points to the specified
/// value and leak it.
///
/// Since collection is unimplemented,
/// this intentionally leaks memory.
pub fn leaked<'gc, T: GcSafe + 'static>(value: T) -> Gc<'gc, T> {
gc(Box::leak(Box::new(value)))
}

/// An fake [garbage collected pointer](::zerogc::Gc)
/// that uses the dummy collector system
///
/// This never actually collects any garbage
pub type Gc<'gc, T> = crate::Gc<'gc, T, DummyCollectorId>;

/// A dummy implementation of [crate::GcSystem]
/// which is useful for testing
///
/// This just blindly allocates memory and doesn't
/// actually do any collection.
pub struct DummyContext {
_priv: ()
}
unsafe impl GcContext for DummyContext {
type System = DummySystem;
type Id = DummyCollectorId;

unsafe fn basic_safepoint<T: Trace>(&mut self, _value: &mut &mut T) {
// safepoints are a nop since there is nothing to track
}

unsafe fn freeze(&mut self) {
unimplemented!()
}

unsafe fn unfreeze(&mut self) {
unimplemented!()
}

unsafe fn recurse_context<T, F, R>(&self, value: &mut &mut T, func: F) -> R
where T: Trace, F: for<'gc> FnOnce(&'gc mut Self, &'gc mut T) -> R {
// safepoints are a nop since there is nothing to track
let mut child = DummyContext { _priv: () };
func(&mut child, &mut *value)
}
}


/// A dummy implementation of [::zerogc::GcSystem]
/// which is useful for testing
///
/// All methods panic and this should never be used
/// in actual code.
#[derive(Default)]
pub struct DummySystem {
_priv: ()
}
impl DummySystem {
/// Create a new fake system for testing
pub fn new() -> Self {
DummySystem::default()
}

/// Create a [DummyContext]
///
/// There are few restrictions on this
/// because it doesn't actually do anything
pub fn new_context(&self) -> DummyContext {
DummyContext {
_priv: ()
}
}
}
unsafe impl GcSystem for DummySystem {
type Id = DummyCollectorId;
type Context = DummyContext;
}

/// The id for a [dummy gc pointer](Gc)
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct DummyCollectorId {
_priv: ()
}
unsafe impl Trace for DummyCollectorId {
const NEEDS_TRACE: bool = false;

fn visit<V: GcVisitor>(&mut self, _visitor: &mut V) -> Result<(), <V as GcVisitor>::Err> {
Ok(())
}
}
unsafe impl TraceImmutable for DummyCollectorId {
fn visit_immutable<V: GcVisitor>(&self, _visitor: &mut V) -> Result<(), V::Err> {
Ok(())
}
}

unsafe impl NullTrace for DummyCollectorId {}
unsafe impl CollectorId for DummyCollectorId {
type System = DummySystem;

unsafe fn gc_write_barrier<'gc, T, V>(
_owner: &Gc<'gc, T>,
_value: &Gc<'gc, V>,
_field_offset: usize
) where T: GcSafe + ?Sized + 'gc, V: GcSafe + ?Sized + 'gc {}

unsafe fn assume_valid_system(&self) -> &Self::System {
unimplemented!()
}
}
20 changes: 14 additions & 6 deletions src/lib.rs
Expand Up @@ -34,6 +34,7 @@ use core::fmt::{self, Debug, Formatter};
mod manually_traced;
pub mod cell;
pub mod prelude;
pub mod dummy_impl;

/// Invoke the closure with a temporary [GcContext],
/// then perform a safepoint afterwards.
Expand Down Expand Up @@ -92,10 +93,11 @@ macro_rules! safepoint_recurse {

/// Create a new sub-context for the duration of the closure
///
/// The specified `root` object will be appended to the shadowstack
/// and is guarenteed to live for the entire lifetime of the closure (and the created sub-context).
/// The specified `root` object will be appended to the shadow-stack
/// and is guarenteed to live for the entire lifetime of the closure
/// (and the created sub-context).
///
/// Unlike `safepoint_recurse!` this doesn't imply a safepoint anywhere.
/// Unlike [safepoint_recurse!] this doesn't imply a safepoint anywhere.
///
/// # Safety
/// This doesn't actually mutate the original collector.
Expand Down Expand Up @@ -124,15 +126,21 @@ macro_rules! __recurse_context {
/// Indicate it's safe to begin a garbage collection,
/// while keeping the specified root alive.
///
/// All other garbage collected pointers that aren't reachable from the root are invalidated.
/// They have a lifetime that references the [GcRef]
/// All other garbage collected pointers that aren't reachable
/// from the root are invalidated.
/// They have a lifetime that references the [GcContext]
/// and the borrow checker considers the safepoint a 'mutation'.
///
/// The root is exempted from the "mutation" and rebound to the new lifetime.
///
/// ## Example
/// ```
/// let root = safepoint!(collector, root);
/// # use ::zerogc::safepoint;
/// # let mut context = zerogc::dummy_impl::DummySystem::new().new_context();
/// # // TODO: Can we please get support for non-Sized types like `String`?!?!?!
/// let root = zerogc::dummy_impl::leaked(String::from("potato"));
/// let root = safepoint!(context, root);
/// assert_eq!(**root, "potato");
/// ```
///
/// ## Safety
Expand Down
27 changes: 21 additions & 6 deletions src/manually_traced/mod.rs
Expand Up @@ -30,7 +30,7 @@
/// However, using this macro is always better than a manual implementation, since it makes your intent clearer.
///
/// ## Usage
/// ````
/// ````no_test
/// // You can use an arbitrary expression to acquire a lock's guard
/// unsafe_trace_lock!(RefCell, target = T, |cell| cell.borrow());
/// unsafe_trace_lock!(Mutex, target = T, |lock| lock.lock().unwrap());
Expand Down Expand Up @@ -97,7 +97,7 @@ macro_rules! unsafe_trace_lock {
/// However, using this macro is always better than a manual implementation, since it makes your intent clearer.
///
/// ## Usage
/// ````
/// ````no_test
/// // Easy to use for wrappers that `Deref` to their type parameter
/// unsafe_trace_deref!(Box, target = T);
/// unsafe_trace_deref!(Rc, target = T);
Expand All @@ -110,7 +110,18 @@ macro_rules! unsafe_trace_lock {
/// */
/// unsafe_trace_deref!(Cell, T; |cell| cell.get());
/// unsafe_trace_deref!(Wrapping, T; |wrapping| &wrapping.0);
/// unsafe_trace_deref!(NonZero, T; |nonzero| &nonzero.get());
///
/// // wrappers shouldn't need tracing if their innards don't
/// assert!(!<Box<i32> as Trace>::NEEDS_TRACE);
/// assert!(!<Cell<i32> as Trace>::NEEDS_TRACE);
/// // Box needs to be dropped
/// assert!(<Box<i32> as GcSafe>::NEEDS_DROP);
/// // but Cell doesn't need to be dropped
/// assert!(<Cell<i32> as GcSafe>::NEEDS_DROP);
///
/// // if the inside needs tracing, the outside does
/// assert!(<Box<dummy_impl::Gc<'static, i32>> as Trace>::NEEDS_TRACE);
/// assert!(<Cell<dummy_impl::Gc<'static, i32>> as Trace>::NEEDS_TRACE);
/// ````
///
/// ## Safety
Expand Down Expand Up @@ -219,10 +230,14 @@ macro_rules! unsafe_trace_deref {
/// In order to prevent ambiguity, this always requires the type of the element being traced.
///
/// ## Usage
/// ````
/// ````no_test
/// unsafe_trace_iterable!(Vec, element = T);
/// unsafe_trace_iterable!(HashMap, element = { (&K, &V) }; K, V);
/// unsafe_trace_iterable!(HashSet, element = T);
///
/// assert!(!<Vec<i32> as Trace>::NEEDS_TRACE);
/// assert!(<Vec<dummy_impl::Gc<'static, i32>> as Trace>::NEEDS_TRACE);
/// assert!(<Vec<i32> as GcSafe>::NEEDS_DROP);
/// ````
///
/// ## Safety
Expand Down Expand Up @@ -348,14 +363,14 @@ macro_rules! unsafe_gc_brand {
};
($target:ident, $($param:ident),+) => {
unsafe impl<'new_gc, Id, $($param),*> $crate::GcBrand<'new_gc, Id> for $target<$($param),*>
where Id: crate::CollectorId, $($param: $crate::GcBrand<'new_gc, Id>,)*
where Id: $crate::CollectorId, $($param: $crate::GcBrand<'new_gc, Id>,)*
$(<$param as $crate::GcBrand<'new_gc, Id>>::Branded: Trace,)* {
type Branded = $target<$(<$param as $crate::GcBrand<'new_gc, Id>>::Branded),*>;
}
};
($target:tt, immut = required; $($param:ident),+) => {
unsafe impl<'new_gc, Id, $($param),*> $crate::GcBrand<'new_gc, Id> for $target<$($param),*>
where Id: crate::CollectorId, $($param: $crate::GcBrand<'new_gc, Id> + TraceImmutable,)*
where Id: $crate::CollectorId, $($param: $crate::GcBrand<'new_gc, Id> + TraceImmutable,)*
$(<$param as $crate::GcBrand<'new_gc, Id>>::Branded: TraceImmutable,)* {
type Branded = $target<$(<$param as $crate::GcBrand<'new_gc, Id>>::Branded),*>;
}
Expand Down

0 comments on commit 772ffbd

Please sign in to comment.