From 772ffbd971cde8b46e2ad030b2b72cd871b6b5f7 Mon Sep 17 00:00:00 2001 From: Techcable Date: Sat, 9 Jan 2021 19:13:18 -0700 Subject: [PATCH] [api] Fix doc tests 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 :) --- libs/derive/tests/basic.rs | 82 +++-------------------- src/dummy_impl.rs | 131 +++++++++++++++++++++++++++++++++++++ src/lib.rs | 20 ++++-- src/manually_traced/mod.rs | 27 ++++++-- 4 files changed, 174 insertions(+), 86 deletions(-) create mode 100644 src/dummy_impl.rs diff --git a/libs/derive/tests/basic.rs b/libs/derive/tests/basic.rs index 8e86e75..44bb8d8 100644 --- a/libs/derive/tests/basic.rs +++ b/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; @@ -41,84 +41,18 @@ struct NopTrace { #[test] fn basic() { - let _b = Basic:: { + let _b = Basic:: { value: String::new(), parent: None, children: vec![] }; - assert!( as Trace>::NEEDS_TRACE); - assert!( as Trace>::NEEDS_TRACE); - assert!( as GcSafe>::NEEDS_DROP); - assert!(! as GcSafe>::NEEDS_DROP); - assert_copy::>(); + assert!( as Trace>::NEEDS_TRACE); + assert!( as Trace>::NEEDS_TRACE); + assert!( as GcSafe>::NEEDS_DROP); + assert!(! as GcSafe>::NEEDS_DROP); + assert_copy::>(); assert_null_trace::(); assert!(!::NEEDS_TRACE); - check_id::(); + check_id::(); } - -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(&mut self, _value: &mut &mut T) { - unimplemented!() - } - - unsafe fn freeze(&mut self) { - unimplemented!() - } - - unsafe fn unfreeze(&mut self) { - unimplemented!() - } - - unsafe fn recurse_context(&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(&mut self, _visitor: &mut V) -> Result<(), ::Err> { - Ok(()) - } - } - unsafe impl TraceImmutable for DummyCollectorId { - fn visit_immutable(&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!() - } - } -} \ No newline at end of file diff --git a/src/dummy_impl.rs b/src/dummy_impl.rs new file mode 100644 index 0000000..68d6216 --- /dev/null +++ b/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(&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(&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(&mut self, _visitor: &mut V) -> Result<(), ::Err> { + Ok(()) + } +} +unsafe impl TraceImmutable for DummyCollectorId { + fn visit_immutable(&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!() + } +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 990604d..dd7fc9c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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. @@ -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. @@ -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 diff --git a/src/manually_traced/mod.rs b/src/manually_traced/mod.rs index c646038..9805a88 100644 --- a/src/manually_traced/mod.rs +++ b/src/manually_traced/mod.rs @@ -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()); @@ -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); @@ -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!(! as Trace>::NEEDS_TRACE); +/// assert!(! as Trace>::NEEDS_TRACE); +/// // Box needs to be dropped +/// assert!( as GcSafe>::NEEDS_DROP); +/// // but Cell doesn't need to be dropped +/// assert!( as GcSafe>::NEEDS_DROP); +/// +/// // if the inside needs tracing, the outside does +/// assert!(> as Trace>::NEEDS_TRACE); +/// assert!(> as Trace>::NEEDS_TRACE); /// ```` /// /// ## Safety @@ -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!(! as Trace>::NEEDS_TRACE); +/// assert!(> as Trace>::NEEDS_TRACE); +/// assert!( as GcSafe>::NEEDS_DROP); /// ```` /// /// ## Safety @@ -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),*>; }