Skip to content

Commit

Permalink
Added as_object and downcast_ref to &Object
Browse files Browse the repository at this point in the history
  • Loading branch information
mitsuhiko committed Dec 4, 2022
1 parent 4b48cff commit 0a8b338
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 12 deletions.
23 changes: 12 additions & 11 deletions minijinja/src/value/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,6 @@
// this module is based on the content module in insta which in turn is based
// on the content module in serde::private::ser.

use std::any::TypeId;
use std::borrow::Cow;
use std::cell::RefCell;
use std::cmp::Ordering;
Expand Down Expand Up @@ -637,6 +636,14 @@ impl Value {
}
}

/// If the valuen object, it's returned as [`Object`].
pub fn as_object(&self) -> Option<&dyn Object> {
match self.0 {
ValueRepr::Dynamic(ref dy) => Some(&**dy as &dyn Object),
_ => None,
}
}

/// If the value is a sequence it's returned as [`SeqObject`].
pub fn as_seq(&self) -> Option<&dyn SeqObject> {
match self.0 {
Expand Down Expand Up @@ -788,7 +795,9 @@ impl Value {

/// Returns some reference to the boxed object if it is of type `T`, or None if it isn’t.
///
/// This is basically the "reverse" of [`from_object`](Self::from_object).
/// This is basically the "reverse" of [`from_object`](Self::from_object). It's also
/// a shortcut for [`downcast_ref`](trait.Object.html#method.downcast_ref)
/// on the return value of [`as_object`](Self::as_object).
///
/// # Example
///
Expand All @@ -814,15 +823,7 @@ impl Value {
/// assert_eq!(thing.id, 42);
/// ```
pub fn downcast_object_ref<T: Object>(&self) -> Option<&T> {
if let ValueRepr::Dynamic(ref obj) = self.0 {
if (**obj).type_id() == TypeId::of::<T>() {
unsafe {
let raw: *const (dyn Object) = Arc::as_ptr(obj);
return (raw as *const u8 as *const T).as_ref();
}
}
}
None
self.as_object().and_then(|x| x.downcast_ref())
}

fn get_item_opt(&self, key: &Value) -> Option<Value> {
Expand Down
42 changes: 41 additions & 1 deletion minijinja/src/value/object.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::any::Any;
use std::any::{Any, TypeId};
use std::fmt;
use std::ops::Range;
use std::sync::Arc;
Expand Down Expand Up @@ -76,6 +76,46 @@ pub trait Object: fmt::Display + fmt::Debug + Any + Sync + Send {
}
}

impl dyn Object + '_ {
/// Returns some reference to the boxed object if it is of type `T`, or None if it isn’t.
///
/// This is basically the "reverse" of [`from_object`](Value::from_object).
///
/// # Example
///
/// ```rust
/// # use minijinja::value::{Value, Object};
/// use std::fmt;
///
/// #[derive(Debug)]
/// struct Thing {
/// id: usize,
/// }
///
/// impl fmt::Display for Thing {
/// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
/// fmt::Debug::fmt(self, f)
/// }
/// }
///
/// impl Object for Thing {}
///
/// let x_value = Value::from_object(Thing { id: 42 });
/// let value_as_obj = x_value.as_object().unwrap();
/// let thing = value_as_obj.downcast_ref::<Thing>().unwrap();
/// assert_eq!(thing.id, 42);
/// ```
pub fn downcast_ref<T: Object>(&self) -> Option<&T> {
if (*self).type_id() == TypeId::of::<T>() {
unsafe {
let raw: *const (dyn Object) = self;
return (raw as *const u8 as *const T).as_ref();
}
}
None
}
}

impl<T: Object> Object for Arc<T> {
#[inline]
fn kind(&self) -> ObjectKind<'_> {
Expand Down
14 changes: 14 additions & 0 deletions minijinja/tests/test_value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,3 +195,17 @@ fn test_value_string_interop() {
let v = Value::from(s);
assert_eq!(v.as_str(), Some("Hello"));
}

#[test]
fn test_value_object_interface() {
let val = Value::from_seq_object(vec![1u32, 2, 3, 4]);
let seq = val.as_seq().unwrap();
assert_eq!(seq.item_count(), 4);
let obj = val.as_object().unwrap();
let seq2 = match obj.kind() {
ObjectKind::Seq(s) => s,
_ => panic!("did not expect this"),
};
assert_eq!(seq2.item_count(), 4);
assert_eq!(obj.to_string(), "[1, 2, 3, 4]");
}

0 comments on commit 0a8b338

Please sign in to comment.