mod attachment;
mod erasable;
mod kind;
mod vtable;
use alloc::boxed::Box;
use core::{fmt, mem, mem::ManuallyDrop, panic::Location, ptr::NonNull};
pub use self::kind::{AttachmentKind, FrameKind};
use self::{erasable::ErasableFrame, vtable::VTable};
#[cfg(nightly)]
use crate::provider::{self, Demand, Provider};
use crate::{frame::attachment::AttachmentProvider, Context};
pub struct Frame {
erased_frame: ManuallyDrop<Box<ErasableFrame>>,
location: &'static Location<'static>,
source: Option<Box<Frame>>,
}
impl Frame {
fn from_unerased<T>(
object: T,
location: &'static Location<'static>,
source: Option<Box<Self>>,
vtable: &'static VTable,
) -> Self {
Self {
erased_frame: unsafe { ManuallyDrop::new(ErasableFrame::new(object, vtable)) },
location,
source,
}
}
pub(crate) fn from_context<C>(
context: C,
location: &'static Location<'static>,
source: Option<Box<Self>>,
) -> Self
where
C: Context,
{
Self::from_unerased(context, location, source, VTable::new_context::<C>())
}
pub(crate) fn from_attachment<A>(
attachment: A,
location: &'static Location<'static>,
source: Option<Box<Self>>,
) -> Self
where
A: Send + Sync + 'static,
{
Self::from_unerased(
AttachmentProvider::new(attachment),
location,
source,
VTable::new_attachment::<A>(),
)
}
pub(crate) fn from_printable_attachment<A>(
attachment: A,
location: &'static Location<'static>,
source: Option<Box<Self>>,
) -> Self
where
A: fmt::Display + fmt::Debug + Send + Sync + 'static,
{
Self::from_unerased(
AttachmentProvider::new(attachment),
location,
source,
VTable::new_printable_attachment::<A>(),
)
}
#[must_use]
pub const fn location(&self) -> &'static Location<'static> {
self.location
}
#[must_use]
pub const fn source(&self) -> Option<&Self> {
#[cfg_attr(not(nightly), allow(clippy::needless_match))]
match &self.source {
Some(source) => Some(source),
None => None,
}
}
#[must_use]
pub fn source_mut(&mut self) -> Option<&mut Self> {
self.source.as_mut().map(Box::as_mut)
}
#[must_use]
pub fn kind(&self) -> FrameKind<'_> {
self.erased_frame.vtable().unerase(&self.erased_frame)
}
#[must_use]
#[cfg(nightly)]
pub fn request_ref<T>(&self) -> Option<&T>
where
T: ?Sized + 'static,
{
provider::request_ref(self)
}
#[must_use]
#[cfg(nightly)]
pub fn request_value<T>(&self) -> Option<T>
where
T: 'static,
{
provider::request_value(self)
}
#[must_use]
pub fn is<T: Send + Sync + 'static>(&self) -> bool {
self.downcast_ref::<T>().is_some()
}
#[must_use]
pub fn downcast_ref<T: Send + Sync + 'static>(&self) -> Option<&T> {
self.erased_frame.vtable().downcast_ref(&self.erased_frame)
}
#[must_use]
pub fn downcast_mut<T: Send + Sync + 'static>(&mut self) -> Option<&mut T> {
self.erased_frame
.vtable()
.downcast_mut(&mut self.erased_frame)
}
}
#[cfg(nightly)]
impl Provider for Frame {
fn provide<'a>(&'a self, demand: &mut Demand<'a>) {
self.erased_frame
.vtable()
.provide(&self.erased_frame, demand);
demand.provide_value(|| self.location);
if let Some(source) = &self.source {
demand.provide_ref::<Self>(source);
}
}
}
impl fmt::Debug for Frame {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut debug = fmt.debug_struct("Frame");
debug.field("location", &self.location);
match self.kind() {
FrameKind::Context(context) => {
debug.field("context", &context);
}
FrameKind::Attachment(AttachmentKind::Printable(attachment)) => {
debug.field("attachment", &attachment);
}
FrameKind::Attachment(AttachmentKind::Opaque(_)) => {
debug.field("attachment", &"Opaque");
}
}
debug.finish()
}
}
impl Drop for Frame {
fn drop(&mut self) {
let erased = unsafe { ManuallyDrop::take(&mut self.erased_frame) };
let ptr = NonNull::from(&*erased);
mem::forget(erased);
self.erased_frame.vtable().drop(ptr);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[allow(clippy::wildcard_imports)]
use crate::test_helper::*;
use crate::Report;
#[test]
fn downcast_ref() {
struct Attached;
let report = Report::new(ContextA)
.attach_printable(String::from("Hello"))
.attach(Attached);
let attachment = report.downcast_ref::<String>().unwrap();
assert!(report.contains::<ContextA>());
assert_eq!(attachment, "Hello");
assert!(report.contains::<Attached>());
}
#[test]
fn downcast_mut() {
let mut report = Report::new(ContextA).attach_printable(String::from("Hello"));
let attachment = report.downcast_mut::<String>().unwrap();
attachment.push_str(" World!");
let messages: Vec<_> = report
.frames_mut()
.filter_map(|frame| match frame.kind() {
FrameKind::Context(context) => Some(context.to_string()),
FrameKind::Attachment(AttachmentKind::Printable(attachment)) => {
Some(attachment.to_string())
}
FrameKind::Attachment(AttachmentKind::Opaque(_)) => None,
})
.collect();
assert_eq!(messages, ["Hello World!", "Context A"]);
}
#[test]
fn kinds() {
let report = Report::new(ContextA);
let report = report.attach_printable("A1");
let report = report.attach_printable("A2");
let report = report.change_context(ContextB);
let report = report.attach("B1");
let report = report.attach_printable("B2");
let kinds_a = frame_kinds(&report);
assert!(matches!(
kinds_a[0],
FrameKind::Attachment(AttachmentKind::Printable(_))
));
assert!(matches!(
kinds_a[1],
FrameKind::Attachment(AttachmentKind::Opaque(_))
));
assert!(matches!(kinds_a[2], FrameKind::Context(_)));
assert!(matches!(
kinds_a[3],
FrameKind::Attachment(AttachmentKind::Printable(_))
));
assert!(matches!(
kinds_a[4],
FrameKind::Attachment(AttachmentKind::Printable(_))
));
assert!(matches!(kinds_a[5], FrameKind::Context(_)));
assert_eq!(messages(&report), [
"B2",
"Opaque",
"Context B",
"A2",
"A1",
"Context A"
]);
let report = Report::new(ContextA);
let report = report.change_context(ContextB);
let kinds_b = frame_kinds(&report);
assert!(matches!(kinds_b[0], FrameKind::Context(_)));
assert!(matches!(kinds_b[1], FrameKind::Context(_)));
assert_eq!(messages(&report), ["Context B", "Context A"]);
}
}