mod context_map;
mod cx_fn;
mod id;
mod lookup;
mod tracking;
use std::{
any::{Any, TypeId},
iter,
panic::Location,
sync::Arc,
};
pub use context_map::*;
pub use cx_fn::*;
pub use id::*;
pub use lookup::*;
pub(crate) use tracking::*;
pub use crate::memoize::MemoizeAsRef;
use crate::{
abort::AbortStore,
identity::{AmbiguousIdentityError, Identity, IdentityKey, SiteKey},
memoize::MemoizeCache,
};
#[derive(Debug, Default, Clone)]
pub struct Cx {
scope: Arc<Scope>,
tracker: Option<Arc<ContextTracker>>,
identity: Identity,
}
impl Cx {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[inline]
#[must_use]
pub fn id(&self) -> CxId {
self.scope.request.id
}
#[must_use]
#[track_caller]
pub fn keyed(&self, key: impl IdentityKey) -> Self {
let site = SiteKey::from_location(Location::caller());
Self {
identity: self.identity.keyed_child(site, key),
..self.clone()
}
}
#[inline]
pub(crate) fn scope(&self) -> &Scope {
&self.scope
}
#[inline]
pub(crate) fn tracker(&self) -> Option<&ContextTracker> {
self.tracker.as_deref()
}
#[must_use]
pub fn with<T>(&self, value: T) -> Cx
where
T: Any + Send + Sync,
{
let mut values = self.scope.values.clone();
values.insert(value);
self.derive(values)
}
#[must_use]
pub fn with_many<V>(&self, values: V) -> Cx
where
V: ContextValues,
{
let mut derived = self.scope.values.clone();
values.install(&mut derived);
self.derive(derived)
}
#[must_use]
pub fn with_shared(&self, shared: Arc<ContextMap>) -> Cx {
Cx {
scope: Arc::new(Scope {
request: Arc::clone(&self.scope.request),
values: self.scope.values.clone(),
sealed: Some(Arc::new(Sealed {
id: BindingId::new(),
values: shared,
parent: self.scope.sealed.clone(),
})),
}),
tracker: self.tracker.clone(),
identity: self.identity,
}
}
fn derive(&self, values: ContextMap) -> Cx {
Cx {
scope: Arc::new(Scope {
request: Arc::clone(&self.scope.request),
values,
sealed: self.scope.sealed.clone(),
}),
tracker: self.tracker.clone(),
identity: self.identity,
}
}
pub(crate) fn track(&self) -> (Cx, Arc<ContextTracker>) {
let tracker = Arc::new(ContextTracker::new(Arc::clone(&self.scope)));
let child = Cx {
scope: Arc::clone(&self.scope),
tracker: Some(Arc::clone(&tracker)),
identity: self.identity,
};
(child, tracker)
}
}
#[derive(Debug, Default)]
pub(crate) struct Scope {
request: Arc<RequestState>,
values: ContextMap,
sealed: Option<Arc<Sealed>>,
}
impl Scope {
pub(crate) fn lookup<T>(&self) -> Option<(BindingId, &T)>
where
T: Any + Send + Sync,
{
self.values
.lookup::<T>()
.or_else(|| self.sealed().find_map(ContextMap::lookup::<T>))
}
pub(crate) fn iter<T>(&self) -> impl Iterator<Item = &T>
where
T: Any + Send + Sync,
{
self.values
.iter::<T>()
.chain(self.sealed().flat_map(ContextMap::iter::<T>))
}
pub(crate) fn binding_id(&self, type_id: TypeId) -> Option<BindingId> {
self.values
.binding_id(type_id)
.or_else(|| self.sealed().find_map(|values| values.binding_id(type_id)))
}
pub(crate) fn request_binding_id(&self, type_id: TypeId) -> Option<BindingId> {
self.values.binding_id(type_id)
}
pub(crate) fn sealed_id(&self) -> Option<BindingId> {
self.sealed.as_ref().map(|layer| layer.id)
}
fn sealed(&self) -> impl Iterator<Item = &ContextMap> {
iter::successors(self.sealed.as_deref(), |layer| layer.parent.as_deref())
.map(|layer| &*layer.values)
}
}
#[derive(Debug)]
struct Sealed {
id: BindingId,
values: Arc<ContextMap>,
parent: Option<Arc<Sealed>>,
}
#[derive(Debug, Default)]
struct RequestState {
id: CxId,
memoize_cache: MemoizeCache,
abort_store: AbortStore,
}
#[must_use]
#[track_caller]
pub fn identity(cx: &Cx) -> Identity {
match try_identity(cx) {
Ok(identity) => identity,
Err(error) => panic!("{error}"),
}
}
#[track_caller]
pub fn try_identity(cx: &Cx) -> Result<Identity, AmbiguousIdentityError> {
assert!(
cx.tracker.is_none(),
"identity cannot be read inside memoized functions"
);
cx.identity.checked()
}
#[doc(hidden)]
#[must_use]
pub fn identity_raw(cx: &Cx) -> Identity {
cx.identity
}
#[doc(hidden)]
#[must_use]
pub fn with_identity(cx: Cx, identity: Identity) -> Cx {
Cx { identity, ..cx }
}
#[inline]
#[must_use]
#[doc(hidden)]
pub fn memoize_cache(cx: &Cx) -> &MemoizeCache {
&cx.scope.request.memoize_cache
}
#[inline]
#[must_use]
#[doc(hidden)]
pub fn abort_store(cx: &Cx) -> &AbortStore {
&cx.scope.request.abort_store
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug, PartialEq)]
struct Marker(u32);
#[derive(Debug, PartialEq)]
struct Other(&'static str);
fn map<T: Any + Send + Sync>(value: T) -> Arc<ContextMap> {
let mut values = ContextMap::new();
values.insert(value);
Arc::new(values)
}
fn shared<T: Any + Send + Sync>(value: T) -> Cx {
Cx::new().with_shared(map(value))
}
fn sealed_layers(layers: Vec<ContextMap>) -> Cx {
let sealed = layers.into_iter().rev().fold(None, |parent, values| {
Some(Arc::new(Sealed {
id: BindingId::new(),
values: Arc::new(values),
parent,
}))
});
Cx {
scope: Arc::new(Scope {
sealed,
..Scope::default()
}),
..Cx::new()
}
}
fn markers(cx: &Cx) -> Vec<u32> {
cx.scope.iter::<Marker>().map(|marker| marker.0).collect()
}
#[test]
fn a_fresh_context_has_a_unique_id() {
let first = Cx::new();
let second = Cx::new();
assert_ne!(first.id(), second.id());
}
#[test]
fn keys_distinguish_locations_and_repetitions() {
fn child(cx: &Cx, key: u32) -> Cx {
cx.keyed(key)
}
let cx = Cx::new();
assert_ne!(identity(&cx.keyed(())), identity(&cx.keyed(())));
assert_eq!(identity(&child(&cx, 1)), identity(&child(&Cx::new(), 1)));
assert_ne!(identity(&child(&cx, 1)), identity(&child(&cx, 2)));
assert_ne!(
identity(&child(&child(&cx, 1), 2)),
identity(&child(&cx, 2))
);
assert_eq!(identity(&cx), Identity::ROOT);
}
#[test]
fn keyed_contexts_share_request_state_and_preserve_their_scope() {
let cx = Cx::new().with(Marker(7));
let child = cx.keyed("child");
assert!(Arc::ptr_eq(&child.scope, &cx.scope));
assert_eq!(child.id(), cx.id());
assert!(std::ptr::eq(memoize_cache(&child), memoize_cache(&cx)));
assert!(std::ptr::eq(
context::<Marker>(&child),
context::<Marker>(&cx)
));
let expected = identity(&child);
assert_eq!(identity(&child.clone()), expected);
assert_eq!(identity(&child.with(Other("value"))), expected);
assert_eq!(identity(&child.with_many((Other("value"),))), expected);
assert_eq!(identity_raw(&child.track().0), expected);
}
#[test]
#[should_panic(expected = "identity cannot be read inside memoized functions")]
fn memoized_functions_cannot_read_identity() {
let cx = Cx::new();
memoize_cache(&cx).memoize(&cx, (), (), |cx, ()| {
identity(&cx.keyed(()).with(Marker(7)))
});
}
#[test]
#[should_panic(expected = "identity cannot be read inside memoized functions")]
fn memoized_functions_cannot_try_identity() {
let cx = Cx::new();
memoize_cache(&cx).memoize(&cx, (), (), |cx, ()| try_identity(&cx));
}
#[tokio::test]
#[should_panic(expected = "identity cannot be read inside memoized functions")]
async fn memoized_functions_cannot_read_identity_after_suspension() {
let cx = Cx::new();
memoize_cache(&cx)
.memoize_async(&cx, (), (), |cx, ()| async move {
tokio::task::yield_now().await;
identity(&cx)
})
.await;
}
#[test]
fn with_registers_a_value_on_the_child() {
let cx = Cx::new();
let child = cx.with(Marker(1));
assert_eq!(try_context::<Marker>(&cx), None);
assert_eq!(context::<Marker>(&child), &Marker(1));
}
#[test]
fn with_shadows_without_touching_the_parent() {
let cx = Cx::new().with(Marker(1));
let child = cx.with(Marker(2));
assert_eq!(context::<Marker>(&cx), &Marker(1));
assert_eq!(context::<Marker>(&child), &Marker(2));
}
#[test]
fn a_child_inherits_the_parent_context() {
let cx = shared(Other("router")).with(Marker(7));
let child = cx.with(Other("scoped"));
assert_eq!(context::<Marker>(&child), &Marker(7));
assert_eq!(context::<Other>(&child), &Other("scoped"));
assert_eq!(context::<Other>(&cx), &Other("router"));
}
#[test]
fn with_many_registers_every_value() {
let cx = Cx::new().with_many((Marker(1), Other("many")));
assert_eq!(context::<Marker>(&cx), &Marker(1));
assert_eq!(context::<Other>(&cx), &Other("many"));
}
#[test]
fn a_child_shares_the_request_state() {
let cx = Cx::new();
let child = cx.with(Marker(1));
assert_eq!(child.id(), cx.id());
assert!(std::ptr::eq(memoize_cache(&child), memoize_cache(&cx)));
assert!(std::ptr::eq(abort_store(&child), abort_store(&cx)));
}
#[test]
fn clones_outlive_the_original() {
let cx = Cx::new().with(Marker(7));
let id = cx.id();
let handle = cx.clone();
drop(cx);
assert_eq!(context::<Marker>(&handle).0, 7);
assert_eq!(handle.id(), id);
}
#[test]
fn with_shares_sealed_values_instead_of_copying_them() {
let cx = shared(Other("router"));
let child = cx.with(Marker(1)).with_many((Marker(2),));
assert!(std::ptr::eq(
context::<Other>(&child),
context::<Other>(&cx)
));
}
#[test]
fn request_values_shadow_shared_values_added_later() {
let cx = Cx::new().with(Marker(1)).with_shared(map(Marker(2)));
assert_eq!(context::<Marker>(&cx), &Marker(1));
assert_eq!(markers(&cx), [1, 2]);
}
#[test]
fn newer_shared_maps_shadow_older_ones() {
let cx = shared(Marker(1)).with_shared(map(Marker(2)));
assert_eq!(context::<Marker>(&cx), &Marker(2));
assert_eq!(markers(&cx), [2, 1]);
}
#[test]
fn with_shared_keeps_the_request_state() {
let cx = Cx::new().with(Marker(1));
let child = cx.with_shared(map(Other("shared")));
assert_eq!(child.id(), cx.id());
assert!(std::ptr::eq(memoize_cache(&child), memoize_cache(&cx)));
assert_eq!(try_context::<Other>(&cx), None);
}
#[test]
fn iter_visits_request_values_before_sealed_values() {
let cx = shared(Marker(1))
.with(Marker(2))
.with_many((Marker(3), Other("many")));
assert_eq!(markers(&cx), [3, 2, 1]);
}
#[test]
fn iter_visits_sealed_layers_newest_first() {
let mut older = ContextMap::new();
older.insert(Marker(0));
older.insert(Marker(1));
let mut newer = ContextMap::new();
newer.insert(Marker(2));
newer.insert(Other("newer"));
let cx = sealed_layers(vec![newer, ContextMap::new(), older]).with(Marker(3));
assert_eq!(markers(&cx), [3, 2, 1, 0]);
}
#[test]
fn iter_leaves_the_parent_unchanged() {
let cx = shared(Marker(1)).with(Marker(2));
let child = cx.with(Marker(3));
assert_eq!(markers(&cx), [2, 1]);
assert_eq!(markers(&child), [3, 2, 1]);
}
#[test]
fn iter_is_empty_for_unregistered_type() {
let cx = shared(Other("router")).with(Other("request"));
assert_eq!(markers(&cx), Vec::<u32>::new());
}
#[test]
fn reads_of_sealed_values_are_tracked() {
let cx = shared(Other("router"));
let (child, tracker) = cx.track();
let _ = context::<Other>(&child);
let reads = tracker.take_reads();
assert_eq!(reads.len(), 1);
assert!(reads[0].matches(cx.scope()));
assert!(!reads[0].matches(cx.with(Other("scoped")).scope()));
}
#[test]
fn track_shares_the_scope() {
let cx = Cx::new().with(Marker(1));
let (child, _tracker) = cx.track();
assert!(Arc::ptr_eq(&child.scope, &cx.scope));
assert!(cx.tracker.is_none());
assert!(child.tracker.is_some());
}
#[test]
fn derived_handles_keep_the_tracker() {
let cx = Cx::new();
let (child, tracker) = cx.track();
let derived = child.with(Marker(1)).keyed(());
assert!(Arc::ptr_eq(
derived.tracker.as_ref().expect("tracker is kept"),
&tracker
));
}
#[test]
fn handles_are_send_and_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<Cx>();
}
}