Repository: mitsuhiko/minijinja
Pull request #465 (2024-04-01 22:54:50)

Implement enumerators
This implements the concept of an `Enumerator` as an alternative to the current `Enumeration` system as outlined in https://github.com/mitsuhiko/minijinja/issues/456#issuecomment-2030630560.

It's mostly what's there with some minor differences that I ran into during implementation:

* `iter` -> `try_iter` as an extension method
* I ended up adding `Enumerator::NonEnumerable` which I believe fits nicer
* `Enumerator::Seq` instead of `Enumerator::Sequential`

Personally I quite like this, but I would like to play with this in practice more first.

It still retains `Value::from_iterator` which I think can be seen as a mistake but I want to fix this later.  It also renames `mapped_enumeration` to `mapped_enumerator`.


Patch:
diff --git a/examples/dynamic-context/src/main.rs b/examples/dynamic-context/src/main.rs
index 52b3f2f..0031995 100644
--- a/examples/dynamic-context/src/main.rs
+++ b/examples/dynamic-context/src/main.rs
@@ -2,7 +2,7 @@ use std::collections::HashMap;
 use std::env;
 use std::sync::Arc;
 
-use minijinja::value::{Enumeration, Object, Value};
+use minijinja::value::{Enumerator, Object, Value};
 use minijinja::Environment;
 
 #[derive(Debug)]
@@ -25,8 +25,8 @@ impl Object for DynamicContext {
     /// This implementation is not needed for the example.  However
     /// returning known keys here has the benefit that `{{ debug() }}`
     /// can show the context.
-    fn enumeration(self: &Arc<Self>) -> Enumeration {
-        Enumeration::Static(&["pid", "cwd", "env"])
+    fn enumerate(self: &Arc<Self>) -> Enumerator {
+        Enumerator::Str(&["pid", "cwd", "env"])
     }
 }
 
diff --git a/examples/dynamic-objects/src/main.rs b/examples/dynamic-objects/src/main.rs
index fbf9ee0..a8c1a88 100644
--- a/examples/dynamic-objects/src/main.rs
+++ b/examples/dynamic-objects/src/main.rs
@@ -3,7 +3,7 @@ use std::fmt;
 use std::sync::atomic::{AtomicUsize, Ordering};
 use std::sync::Arc;
 
-use minijinja::value::{from_args, Enumeration, Object, ObjectRepr, Value};
+use minijinja::value::{from_args, Enumerator, Object, ObjectRepr, Value};
 use minijinja::{Environment, Error, State};
 
 #[derive(Debug)]
@@ -70,8 +70,8 @@ impl Object for SimpleDynamicSeq {
         ['a', 'b', 'c', 'd'].get(idx).copied().map(Value::from)
     }
 
-    fn enumeration(self: &Arc<Self>) -> Enumeration {
-        Enumeration::Sized(4)
+    fn enumerate(self: &Arc<Self>) -> Enumerator {
+        Enumerator::Seq(4)
     }
 }
 
diff --git a/examples/self-referential-context/src/main.rs b/examples/self-referential-context/src/main.rs
index e4dceb1..c08ff36 100644
--- a/examples/self-referential-context/src/main.rs
+++ b/examples/self-referential-context/src/main.rs
@@ -1,6 +1,6 @@
 use std::sync::Arc;
 
-use minijinja::value::{Enumeration, Object, Value, ValueKind};
+use minijinja::value::{Enumerator, Object, Value, ValueKind};
 use minijinja::{context, Environment};
 
 #[derive(Debug)]
@@ -16,13 +16,13 @@ impl Object for SelfReferentialContext {
         self.ctx.get_item(name).ok().filter(|x| !x.is_undefined())
     }
 
-    fn enumeration(self: &Arc<Self>) -> Enumeration {
+    fn enumerate(self: &Arc<Self>) -> Enumerator {
         if self.ctx.kind() == ValueKind::Map {
             if let Ok(keys) = self.ctx.try_iter() {
-                return Enumeration::Values(keys.collect());
+                return Enumerator::Values(keys.collect());
             }
         }
-        Enumeration::Sized(0)
+        Enumerator::Seq(0)
     }
 }
 
diff --git a/examples/undefined-tracking/src/main.rs b/examples/undefined-tracking/src/main.rs
index 10ec269..2f35b6f 100644
--- a/examples/undefined-tracking/src/main.rs
+++ b/examples/undefined-tracking/src/main.rs
@@ -1,7 +1,7 @@
 use std::collections::HashSet;
 use std::sync::{Arc, Mutex};
 
-use minijinja::value::{Enumeration, Object, Value, ValueKind};
+use minijinja::value::{Enumerator, Object, Value, ValueKind};
 use minijinja::{context, Environment};
 
 #[derive(Debug)]
@@ -33,13 +33,13 @@ impl Object for TrackedContext {
         }
     }
 
-    fn enumeration(self: &Arc<Self>) -> Enumeration {
+    fn enumerate(self: &Arc<Self>) -> Enumerator {
         if self.enclosed.kind() == ValueKind::Map {
             if let Ok(keys) = self.enclosed.try_iter() {
-                return Enumeration::Values(keys.collect());
+                return Enumerator::Values(keys.collect());
             }
         }
-        Enumeration::Sized(0)
+        Enumerator::Seq(0)
     }
 }
 
diff --git a/examples/value-tracking/src/main.rs b/examples/value-tracking/src/main.rs
index 013cbda..07ea32d 100644
--- a/examples/value-tracking/src/main.rs
+++ b/examples/value-tracking/src/main.rs
@@ -1,7 +1,7 @@
 use std::collections::HashSet;
 use std::sync::{Arc, Mutex};
 
-use minijinja::value::{Enumeration, Object, Value, ValueKind};
+use minijinja::value::{Enumerator, Object, Value, ValueKind};
 use minijinja::{context, Environment};
 
 #[derive(Debug)]
@@ -26,13 +26,13 @@ impl Object for TrackedContext {
         }
     }
 
-    fn enumeration(self: &Arc<Self>) -> Enumeration {
+    fn enumerate(self: &Arc<Self>) -> Enumerator {
         if self.enclosed.kind() == ValueKind::Map {
             if let Ok(keys) = self.enclosed.try_iter() {
-                return Enumeration::Values(keys.collect());
+                return Enumerator::Values(keys.collect());
             }
         }
-        Enumeration::Sized(0)
+        Enumerator::Seq(0)
     }
 }
 
diff --git a/minijinja/src/filters.rs b/minijinja/src/filters.rs
index 8d54903..6923af5 100644
--- a/minijinja/src/filters.rs
+++ b/minijinja/src/filters.rs
@@ -764,7 +764,7 @@ mod builtins {
             .as_object()
             .filter(|x| matches!(x.repr(), ObjectRepr::Seq))
         {
-            Ok(match obj.enumeration().len() {
+            Ok(match obj.len() {
                 Some(0) | None => None,
                 Some(idx) => obj.get_value(&Value::from(idx - 1)),
             }
diff --git a/minijinja/src/functions.rs b/minijinja/src/functions.rs
index 101f83a..5520ef2 100644
--- a/minijinja/src/functions.rs
+++ b/minijinja/src/functions.rs
@@ -101,7 +101,7 @@ use std::sync::Arc;
 
 use crate::error::Error;
 use crate::utils::SealedMarker;
-use crate::value::{ArgType, Enumeration, FunctionArgs, FunctionResult, Object, ObjectRepr, Value};
+use crate::value::{ArgType, FunctionArgs, FunctionResult, Object, ObjectRepr, Value};
 use crate::vm::State;
 
 type FuncFunc = dyn Fn(&State, &[Value]) -> Result<Value, Error> + Sync + Send + 'static;
@@ -252,10 +252,6 @@ impl Object for BoxedFunction {
         ObjectRepr::Plain
     }
 
-    fn enumeration(self: &Arc<Self>) -> Enumeration {
-        Enumeration::NonEnumerable
-    }
-
     fn call(self: &Arc<Self>, state: &State, args: &[Value]) -> Result<Value, Error> {
         self.invoke(state, args)
     }
diff --git a/minijinja/src/value/argtypes.rs b/minijinja/src/value/argtypes.rs
index f856058..01c077f 100644
--- a/minijinja/src/value/argtypes.rs
+++ b/minijinja/src/value/argtypes.rs
@@ -11,7 +11,7 @@ use crate::value::{
 };
 use crate::vm::State;
 
-use super::{Enumeration, Object};
+use super::{Enumerator, Object};
 
 /// A utility trait that represents the return value of functions and filters.
 ///
@@ -751,8 +751,8 @@ impl Object for KwargsValues {
         self.as_value_map().get_value(key)
     }
 
-    fn enumeration(self: &Arc<Self>) -> Enumeration {
-        self.as_value_map().enumeration()
+    fn enumerate(self: &Arc<Self>) -> Enumerator {
+        self.as_value_map().enumerate()
     }
 }
 
diff --git a/minijinja/src/value/deserialize.rs b/minijinja/src/value/deserialize.rs
index 5d5499b..c530ef5 100644
--- a/minijinja/src/value/deserialize.rs
+++ b/minijinja/src/value/deserialize.rs
@@ -181,7 +181,7 @@ impl<'de> de::Deserializer<'de> for ValueDeserializer {
             ValueRepr::Bytes(ref v) => visitor.visit_bytes(v),
             ValueRepr::Object(o) => match o.repr() {
                 ObjectRepr::Plain => Err(de::Error::custom("cannot deserialize plain objects")),
-                ObjectRepr::Seq | ObjectRepr::Iterator => {
+                ObjectRepr::Seq | ObjectRepr::Iterable => {
                     visitor.visit_seq(de::value::SeqDeserializer::new(
                         o.try_iter()
                             .into_iter()
diff --git a/minijinja/src/value/merge_object.rs b/minijinja/src/value/merge_object.rs
index de5a837..e1d1d3d 100644
--- a/minijinja/src/value/merge_object.rs
+++ b/minijinja/src/value/merge_object.rs
@@ -1,7 +1,7 @@
 use std::collections::BTreeSet;
 use std::sync::Arc;
 
-use crate::value::{Enumeration, Object, ObjectExt, Value};
+use crate::value::{Enumerator, Object, ObjectExt, Value};
 
 /// Utility struct used by [`context!`](crate::context) to merge
 /// multiple values.
@@ -20,8 +20,8 @@ impl Object for MergeObject {
         None
     }
 
-    fn enumeration(self: &Arc<Self>) -> Enumeration {
-        self.mapped_enumeration(|this| {
+    fn enumerate(self: &Arc<Self>) -> Enumerator {
+        self.mapped_enumerator(|this| {
             let mut seen = BTreeSet::new();
             let iter = this
                 .0
diff --git a/minijinja/src/value/mod.rs b/minijinja/src/value/mod.rs
index f9189bd..7285079 100644
--- a/minijinja/src/value/mod.rs
+++ b/minijinja/src/value/mod.rs
@@ -140,13 +140,12 @@ use serde::ser::{Serialize, Serializer};
 use crate::error::{Error, ErrorKind, Result};
 use crate::functions;
 use crate::utils::OnDrop;
-use crate::value::object::ValueDbgRender;
 use crate::value::ops::as_f64;
 use crate::value::serialize::transform;
 use crate::vm::State;
 
 pub use crate::value::argtypes::{from_args, ArgType, FunctionArgs, FunctionResult, Kwargs, Rest};
-pub use crate::value::object::{DynObject, Enumeration, Object, ObjectExt, ObjectRepr};
+pub use crate::value::object::{DynObject, Enumerator, Object, ObjectExt, ObjectRepr};
 
 #[macro_use]
 mod type_erase;
@@ -264,8 +263,8 @@ pub enum ValueKind {
     Seq,
     /// The value is a key/value mapping.
     Map,
-    /// An iterator
-    Iterator,
+    /// An iterable
+    Iterable,
     /// A plain object without specific behavior.
     Plain,
 }
@@ -281,7 +280,7 @@ impl fmt::Display for ValueKind {
             ValueKind::Bytes => "bytes",
             ValueKind::Seq => "sequence",
             ValueKind::Map => "map",
-            ValueKind::Iterator => "iterator",
+            ValueKind::Iterable => "iterator",
             ValueKind::Plain => "plain object",
         })
     }
@@ -640,12 +639,14 @@ impl Value {
             T: Into<Value> + 'static,
         {
             fn repr(self: &Arc<Self>) -> ObjectRepr {
-                ObjectRepr::Iterator
+                ObjectRepr::Iterable
             }
 
-            fn custom_iter(
-                self: &Arc<Self>,
-            ) -> Option<Box<dyn Iterator<Item = Value> + Send + Sync>> {
+            fn len(self: &Arc<Self>) -> Option<usize> {
+                None
+            }
+
+            fn enumerate(self: &Arc<Self>) -> Enumerator {
                 let iter = self.0.lock().unwrap().take();
 
                 struct Iter<I, T>(Option<I>)
@@ -675,7 +676,7 @@ impl Value {
                     }
                 }
 
-                Some(Box::new(Iter(iter)))
+                Enumerator::Iter(Box::new(Iter(iter)))
             }
         }
         Value::from_object(SimpleIteratorObject(Mutex::new(Some(iter.fuse()))))
@@ -722,23 +723,10 @@ impl Value {
                 + 'static,
         {
             fn repr(self: &Arc<Self>) -> ObjectRepr {
-                ObjectRepr::Iterator
-            }
-
-            fn render(self: &Arc<Self>, f: &mut fmt::Formatter<'_>) -> fmt::Result
-            where
-                Self: Sized + 'static,
-            {
-                let mut dbg = f.debug_list();
-                for value in self.try_iter().into_iter().flatten() {
-                    dbg.entry(&ValueDbgRender(&value));
-                }
-                dbg.finish()
+                ObjectRepr::Iterable
             }
 
-            fn custom_iter(
-                self: &Arc<Self>,
-            ) -> Option<Box<dyn Iterator<Item = Value> + Send + Sync>> {
+            fn enumerate(self: &Arc<Self>) -> Enumerator {
                 struct IterWrapper {
                     iter: Box<dyn Iterator<Item = Value> + Send + Sync + 'static>,
                     _object: DynObject,
@@ -760,7 +748,7 @@ impl Value {
                     (self.maker)(&self.object);
                 let iter = unsafe { std::mem::transmute(iter) };
                 let _object = DynObject::new(self.clone());
-                Some(Box::new(IterWrapper { iter, _object }))
+                Enumerator::Iter(Box::new(IterWrapper { iter, _object }))
             }
         }
 
@@ -803,7 +791,7 @@ impl Value {
             ValueRepr::Object(ref obj) => match obj.repr() {
                 ObjectRepr::Map => ValueKind::Map,
                 ObjectRepr::Seq => ValueKind::Seq,
-                ObjectRepr::Iterator => ValueKind::Iterator,
+                ObjectRepr::Iterable => ValueKind::Iterable,
                 ObjectRepr::Plain => ValueKind::Plain,
             },
         }
@@ -840,7 +828,7 @@ impl Value {
             ValueRepr::String(ref x, _) => !x.is_empty(),
             ValueRepr::Bytes(ref x) => !x.is_empty(),
             ValueRepr::None | ValueRepr::Undefined | ValueRepr::Invalid(_) => false,
-            ValueRepr::Object(ref x) => !x.enumeration().is_empty(),
+            ValueRepr::Object(ref x) => !x.is_empty(),
         }
     }
 
@@ -914,7 +902,7 @@ impl Value {
     pub fn len(&self) -> Option<usize> {
         match self.0 {
             ValueRepr::String(ref s, _) => Some(s.chars().count()),
-            ValueRepr::Object(ref dy) => dy.enumeration().len(),
+            ValueRepr::Object(ref dy) => dy.len(),
             _ => None,
         }
     }
@@ -1094,9 +1082,9 @@ impl Value {
     }
 
     pub(crate) fn get_item_opt(&self, key: &Value) -> Option<Value> {
-        fn index(value: &Value, len: impl Fn() -> usize) -> Option<usize> {
+        fn index(value: &Value, len: impl Fn() -> Option<usize>) -> Option<usize> {
             match value.as_i64().and_then(|v| isize::try_from(v).ok()) {
-                Some(i) if i < 0 => len().checked_sub(i.unsigned_abs()),
+                Some(i) if i < 0 => some!(len()).checked_sub(i.unsigned_abs()),
                 Some(i) => Some(i as usize),
                 None => None,
             }
@@ -1104,16 +1092,14 @@ impl Value {
 
         match self.0 {
             ValueRepr::Object(ref dy) => match dy.repr() {
-                ObjectRepr::Map | ObjectRepr::Plain | ObjectRepr::Iterator => dy.get_value(key),
+                ObjectRepr::Map | ObjectRepr::Plain | ObjectRepr::Iterable => dy.get_value(key),
                 ObjectRepr::Seq => {
-                    let len = dy.enumeration().len();
-                    let idx = len.and_then(|n| index(key, || n));
-                    let value = idx.map(Value::from);
-                    dy.get_value(value.as_ref().unwrap_or(key))
+                    let idx = index(key, || dy.len()).map(Value::from);
+                    dy.get_value(idx.as_ref().unwrap_or(key))
                 }
             },
             ValueRepr::String(ref s, _) => {
-                let idx = some!(index(key, || s.chars().count()));
+                let idx = some!(index(key, || Some(s.chars().count())));
                 s.chars().nth(idx).map(Value::from)
             }
             _ => None,
@@ -1260,10 +1246,9 @@ impl Serialize for Value {
             ValueRepr::Bytes(ref b) => serializer.serialize_bytes(b),
             ValueRepr::Object(ref o) => match o.repr() {
                 ObjectRepr::Plain => serializer.serialize_str(&o.to_string()),
-                ObjectRepr::Seq | ObjectRepr::Iterator => {
+                ObjectRepr::Seq | ObjectRepr::Iterable => {
                     use serde::ser::SerializeSeq;
-                    let enumeration = o.enumeration();
-                    let mut seq = ok!(serializer.serialize_seq(enumeration.len()));
+                    let mut seq = ok!(serializer.serialize_seq(o.len()));
                     if let Some(iter) = o.try_iter() {
                         for item in iter {
                             ok!(seq.serialize_element(&item));
@@ -1358,8 +1343,8 @@ mod tests {
                 }
             }
 
-            fn enumeration(self: &Arc<Self>) -> Enumeration {
-                Enumeration::Static(&["value"])
+            fn enumerate(self: &Arc<Self>) -> Enumerator {
+                Enumerator::Str(&["value"])
             }
 
             fn render(self: &Arc<Self>, f: &mut fmt::Formatter<'_>) -> fmt::Result {
diff --git a/minijinja/src/value/namespace_object.rs b/minijinja/src/value/namespace_object.rs
index aa13a50..378d5bd 100644
--- a/minijinja/src/value/namespace_object.rs
+++ b/minijinja/src/value/namespace_object.rs
@@ -1,7 +1,7 @@
 use std::collections::BTreeMap;
 use std::sync::{Arc, Mutex};
 
-use crate::value::{Enumeration, Object, Value};
+use crate::value::{Enumerator, Object, Value};
 
 /// This object exists for the `namespace` function.
 ///
@@ -15,13 +15,13 @@ pub(crate) struct Namespace {
 
 impl Object for Namespace {
     fn get_value(self: &Arc<Self>, key: &Value) -> Option<Value> {
-        self.data.lock().unwrap().get(key.as_str()?).cloned()
+        self.data.lock().unwrap().get(some!(key.as_str())).cloned()
     }
 
-    fn enumeration(self: &Arc<Self>) -> Enumeration {
+    fn enumerate(self: &Arc<Self>) -> Enumerator {
         let data = self.data.lock().unwrap();
         let keys = data.keys().cloned().map(Value::from);
-        Enumeration::Values(keys.collect())
+        Enumerator::Values(keys.collect())
     }
 }
 
diff --git a/minijinja/src/value/object.rs b/minijinja/src/value/object.rs
index 352c14a..22acc81 100644
--- a/minijinja/src/value/object.rs
+++ b/minijinja/src/value/object.rs
@@ -2,7 +2,6 @@ use std::borrow::Borrow;
 use std::collections::HashMap;
 use std::fmt;
 use std::hash::Hash;
-use std::ops::Range;
 use std::sync::Arc;
 
 use crate::error::{Error, ErrorKind, Result};
@@ -11,6 +10,77 @@ use crate::vm::State;
 
 /// A trait that represents a dynamic object.
 ///
+/// # Basic Struct
+///
+/// The following example shows how to implement a dynamic object which
+/// represents a struct.  All that's needed is to implement
+/// [`get_value`](Self::get_value) to look up a field by name as well as
+/// [`enumerate`](Self::enumerate) to return an enumerator over the known keys.
+/// The [`repr`](Self::repr) defaults to `Map` so nothing needs to be done here.
+///
+/// ```
+/// use std::sync::Arc;
+/// use minijinja::value::{Value, Object, Enumerator};
+///
+/// #[derive(Debug)]
+/// struct Point(f32, f32, f32);
+///
+/// impl Object for Point {
+///     fn get_value(self: &Arc<Self>, key: &Value) -> Option<Value> {
+///         match key.as_str()? {
+///             "x" => Some(Value::from(self.0)),
+///             "y" => Some(Value::from(self.1)),
+///             "z" => Some(Value::from(self.2)),
+///             _ => None,
+///         }
+///     }
+///
+///     fn enumerate(self: &Arc<Self>) -> Enumerator {
+///         Enumerator::Str(&["x", "y", "z"])
+///     }
+/// }
+///
+/// let value = Value::from_object(Point(1.0, 2.5, 3.0));
+/// ```
+///
+/// # Basic Sequence
+///
+/// The following example shows how to implement a dynamic object which
+/// represents a sequence.  All that's needed is to implement
+/// [`repr`](Self::repr) to indicate that this is a sequence,
+/// [`get_value`](Self::get_value) to look up a field by index, and
+/// [`enumerate`](Self::enumerate) to return a sequential enumerator.
+/// This enumerator will automatically call `get_value` from `0..length`.
+///
+/// ```
+/// use std::sync::Arc;
+/// use minijinja::value::{Value, Object, ObjectRepr, Enumerator};
+///
+/// #[derive(Debug)]
+/// struct Point(f32, f32, f32);
+///
+/// impl Object for Point {
+///     fn repr(self: &Arc<Self>) -> ObjectRepr {
+///         ObjectRepr::Seq
+///     }
+///
+///     fn get_value(self: &Arc<Self>, key: &Value) -> Option<Value> {
+///         match key.as_usize()? {
+///             0 => Some(Value::from(self.0)),
+///             1 => Some(Value::from(self.1)),
+///             2 => Some(Value::from(self.2)),
+///             _ => None,
+///         }
+///     }
+///
+///     fn enumerate(self: &Arc<Self>) -> Enumerator {
+///         Enumerator::Seq(3)
+///     }
+/// }
+///
+/// let value = Value::from_object(Point(1.0, 2.5, 3.0));
+/// ```
+///
 /// # Map As Context
 ///
 /// Map can also be used as template rendering context.  This has a lot of
@@ -19,7 +89,7 @@ use crate::vm::State;
 /// values, MiniJinja does not spend time eagerly converting them into values.
 ///
 /// Here is a very basic example of how a template can be rendered with a dynamic
-/// context.  Note that the implementation of [`enumeration`](Self::enumeration)
+/// context.  Note that the implementation of [`enumerate`](Self::enumerate)
 /// is optional for this to work.  It's in fact not used by the engine during
 /// rendering but it is necessary for the [`debug()`](crate::functions::debug)
 /// function to be able to show which values exist in the context.
@@ -70,28 +140,34 @@ pub trait Object: fmt::Debug + Send + Sync {
         None
     }
 
-    /// Returns the enumeration of the object.
+    /// Enumerates the object.
     ///
-    /// For more information see [`Enumeration`].  The default implementation
-    /// returns a empty enumeration if the object repr is a `Map` or `Seq`,
+    /// For more information see [`Enumerator`].  The default implementation
+    /// returns an `Empty` enumerator if the object repr is a `Map` or `Seq`,
     /// and `NonEnumerable` for `Plain` objects or `Iterator`s.
-    fn enumeration(self: &Arc<Self>) -> Enumeration {
+    fn enumerate(self: &Arc<Self>) -> Enumerator {
         match self.repr() {
-            ObjectRepr::Plain | ObjectRepr::Iterator => Enumeration::NonEnumerable,
-            ObjectRepr::Map | ObjectRepr::Seq => Enumeration::Sized(0),
+            ObjectRepr::Plain | ObjectRepr::Iterable => Enumerator::NonEnumerable,
+            ObjectRepr::Map | ObjectRepr::Seq => Enumerator::Empty,
         }
     }
 
-    /// Overrides the default iteration behavior.
+    /// Returns the length of the object.
     ///
-    /// If this returns `None` then the default object iteration as
-    /// defined by the object's `repr` and `enumeration` is used.
-    /// When this is implemented it's recommended that the object
-    /// repr is set to [`ObjectRepr::Iterator`].  The engine does
-    /// ensure that it can also be implemented for other object types
-    /// but the behavior can be confusing.
-    fn custom_iter(self: &Arc<Self>) -> Option<Box<dyn Iterator<Item = Value> + Send + Sync>> {
-        None
+    /// By default the length is taken from the `Enumerator`.  This means that in order
+    /// to determine the length, an iteration is started.  If you this is a problem for
+    /// your uses, you can manually implement this.  This might for instance be needed
+    /// if your type can only be iterated over once.
+    fn len(self: &Arc<Self>) -> Option<usize> {
+        self.enumerate().len()
+    }
+
+    /// Returns `true` if this object is considered empty.
+    ///
+    /// The default implementation checks if the length of the object is `Some(0)` which
+    /// is the recommended behavior for objects.
+    fn is_empty(self: &Arc<Self>) -> bool {
+        self.len() == Some(0)
     }
 
     /// The engine calls this to invoke the object itself.
@@ -137,22 +213,37 @@ pub trait Object: fmt::Debug + Send + Sync {
     where
         Self: Sized + 'static,
     {
+        struct Dbg<'a>(pub &'a Value);
+
+        impl<'a> fmt::Debug for Dbg<'a> {
+            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+                if let ValueRepr::Object(ref obj) = self.0 .0 {
+                    obj.render(f)
+                } else {
+                    fmt::Debug::fmt(&self.0, f)
+                }
+            }
+        }
+
         match self.repr() {
             ObjectRepr::Map => {
                 let mut dbg = f.debug_map();
                 for (key, value) in self.try_iter_pairs().into_iter().flatten() {
-                    dbg.entry(&ValueDbgRender(&key), &ValueDbgRender(&value));
+                    dbg.entry(&Dbg(&key), &Dbg(&value));
                 }
                 dbg.finish()
             }
-            ObjectRepr::Seq => {
+            // for either sequences or iterables, a length is needed, otherwise we
+            // don't want to risk iteration during printing and fall back to the
+            // debug print.
+            ObjectRepr::Seq | ObjectRepr::Iterable if self.len().is_some() => {
                 let mut dbg = f.debug_list();
                 for value in self.try_iter().into_iter().flatten() {
-                    dbg.entry(&ValueDbgRender(&value));
+                    dbg.entry(&Dbg(&value));
                 }
                 dbg.finish()
             }
-            ObjectRepr::Plain | ObjectRepr::Iterator => {
+            _ => {
                 write!(f, "{self:?}")
             }
         }
@@ -161,22 +252,26 @@ pub trait Object: fmt::Debug + Send + Sync {
 
 macro_rules! impl_object_helpers {
     ($vis:vis $self_ty: ty) => {
-        /// Iterates over an object.
-        $vis fn try_iter(self: $self_ty) -> Option<Box<dyn Iterator<Item = Value> + Send + Sync>> {
-            if let Some(iter) = self.custom_iter() {
-                Some(iter)
-            } else {
-                let iter = some!(self.clone().enumeration().try_into_iter());
-                Some(match self.repr() {
-                    ObjectRepr::Plain | ObjectRepr::Iterator => return None,
-                    ObjectRepr::Map => Box::new(iter),
-                    ObjectRepr::Seq => {
-                        let self_clone = self.clone();
-                        Box::new(
-                            iter.map(move |key| self_clone.get_value(&key).unwrap_or_default()),
-                        )
-                    }
-                })
+        /// Iterates over this object.
+        ///
+        /// If this returns `None` then the default object iteration as defined by
+        /// the object's `enumeration` is used.
+        $vis fn try_iter(self: $self_ty) -> Option<Box<dyn Iterator<Item = Value> + Send + Sync>>
+        where
+            Self: 'static,
+        {
+            match self.enumerate() {
+                Enumerator::NonEnumerable => None,
+                Enumerator::Empty => Some(Box::new(None::<Value>.into_iter())),
+                Enumerator::Seq(l) => {
+                    let self_clone = self.clone();
+                    Some(Box::new((0..l).map(move |idx| {
+                        self_clone.get_value(&Value::from(idx)).unwrap_or_default()
+                    })))
+                }
+                Enumerator::Iter(iter) => Some(iter),
+                Enumerator::Str(s) => Some(Box::new(s.iter().copied().map(intern).map(Value::from))),
+                Enumerator::Values(v) => Some(Box::new(v.into_iter())),
             }
         }
 
@@ -184,35 +279,26 @@ macro_rules! impl_object_helpers {
         $vis fn try_iter_pairs(
             self: $self_ty,
         ) -> Option<Box<dyn Iterator<Item = (Value, Value)> + Send + Sync>> {
+            let iter = some!(self.try_iter());
+            let repr = self.repr();
             let self_clone = self.clone();
-            if let Some(iter) = self.custom_iter() {
-                let repr = self.repr();
-                Some(Box::new(
-                    iter.enumerate().map(move |(idx, item)| {
-                        match repr {
-                            ObjectRepr::Seq | ObjectRepr::Plain | ObjectRepr::Iterator => (Value::from(idx), item),
-                            ObjectRepr::Map => {
-                                let value = self_clone.get_value(&item);
-                                (item, value.unwrap_or_default())
-                            }
-                        }
-                    })
-                ))
-            } else {
-                let iter = some!(self.clone().enumeration().try_into_iter());
-                Some(Box::new(iter.map(move |key| {
-                    let value = self_clone.get_value(&key);
-                    (key, value.unwrap_or_default())
-                })))
-            }
+            Some(Box::new(iter.enumerate().map(move |(idx, item)| {
+                match repr {
+                    ObjectRepr::Map => {
+                        let value = self_clone.get_value(&item);
+                        (item, value.unwrap_or_default())
+                    }
+                    _ => (Value::from(idx), item)
+                }
+            })))
         }
     };
 }
 
 /// Provides utility methods for working with objects.
 pub trait ObjectExt: Object + Send + Sync + 'static {
-    /// Creates a new iterator enumeration that projects into the given object.
-    fn mapped_enumeration<F>(self: &Arc<Self>, maker: F) -> Enumeration
+    /// Creates a new enumeration that projects into the given object.
+    fn mapped_enumerator<F>(self: &Arc<Self>, maker: F) -> Enumerator
     where
         F: for<'a> FnOnce(&'a Self) -> Box<dyn Iterator<Item = Value> + Send + Sync + 'a>
             + Send
@@ -242,7 +328,7 @@ pub trait ObjectExt: Object + Send + Sync + 'static {
         // SAFETY: this is safe because the `IterObject` will keep our object alive.
         let iter = unsafe { std::mem::transmute(iter) };
         let _object = self.clone();
-        Enumeration::Iterator(Box::new(IterObject { iter, _object }))
+        Enumerator::Iter(Box::new(IterObject { iter, _object }))
     }
 
     impl_object_helpers!(&Arc<Self>);
@@ -251,39 +337,30 @@ pub trait ObjectExt: Object + Send + Sync + 'static {
 impl<T: Object + Send + Sync + 'static> ObjectExt for T {}
 
 /// Utility type to enumerate an object.
-///
-/// The purpose of this type is to reveal the contents of an object by the key.  An
-/// enumeration always reveals the indexes or keys of an object.  The user of
-/// such an enumeration must thus call into [`get_value`](Object::get_value) to
-/// reveal the associated value to that key.
-///
-/// Enumerations are used as the primary method to automatically derive the
-/// iteration behavior of an object.
 #[non_exhaustive]
-pub enum Enumeration {
-    /// A list of known values.
+pub enum Enumerator {
+    /// A non enumerable enumeration.
     ///
-    /// If the object is a sequence these are the values, if the object is a
-    /// map this are actually the keys.
-    Values(Vec<Value>),
-    /// A slice of static string keys.
-    Static(&'static [&'static str]),
-    /// A dynamic iterator over keys.
-    Iterator(Box<dyn Iterator<Item = Value> + Send + Sync>),
-    /// Indicates indexes from 0 to `usize`.
-    Sized(usize),
-    /// A non enumerable enumeration.  This fails iteration.
+    /// This fails iteration and the object has no known length.
     NonEnumerable,
-}
-
-/// Iterates over an enumeration.
-pub(crate) struct EnumerationIter(EnumerationIterRepr);
-
-enum EnumerationIterRepr {
-    Values(std::vec::IntoIter<Value>),
-    Static(std::slice::Iter<'static, &'static str>),
-    Iterator(Box<dyn Iterator<Item = Value> + Send + Sync>),
-    Sized(Range<usize>),
+    /// The empty enumeration.  It yields no elements.
+    ///
+    /// It has a known length of 0.
+    Empty,
+    /// A slice of static string keys.
+    ///
+    /// This has a known length which is the length of the slice.
+    Str(&'static [&'static str]),
+    /// A dynamic iterator over keys.  Length is known if the size hint has matching lower and upper bounds.
+    Iter(Box<dyn Iterator<Item = Value> + Send + Sync>),
+    /// Instructs the engine to yield values by calling `get_value` from 0 to `usize`.
+    ///
+    /// This has a known legth of `usize`.
+    Seq(usize),
+    /// A vector of known values to iterate over.
+    ///
+    /// This has a known length which is the length of the vector.
+    Values(Vec<Value>),
 }
 
 /// Defines the natural representation of this object.
@@ -296,7 +373,7 @@ pub enum ObjectRepr {
     /// serializes to [...] over its values
     Seq,
     /// Similar to `Seq` but without indexing
-    Iterator,
+    Iterable,
 }
 
 type_erase! {
@@ -305,9 +382,11 @@ type_erase! {
 
         fn get_value(&self, key: &Value) -> Option<Value>;
 
-        fn enumeration(&self) -> Enumeration;
+        fn enumerate(&self) -> Enumerator;
+
+        fn is_empty(&self) -> bool;
 
-        fn custom_iter(&self) -> Option<Box<dyn Iterator<Item = Value> + Send + Sync>>;
+        fn len(&self) -> Option<usize>;
 
         fn call(
             &self,
@@ -358,18 +437,19 @@ impl fmt::Display for DynObject {
     }
 }
 
-impl Enumeration {
+impl Enumerator {
     /// Returns the length if the object has one.
     pub fn len(&self) -> Option<usize> {
         Some(match self {
-            Enumeration::Values(v) => v.len(),
-            Enumeration::Static(v) => v.len(),
-            Enumeration::Iterator(i) => match i.size_hint() {
+            Enumerator::Empty => 0,
+            Enumerator::Values(v) => v.len(),
+            Enumerator::Str(v) => v.len(),
+            Enumerator::Iter(i) => match i.size_hint() {
                 (a, Some(b)) if a == b => a,
                 _ => return None,
             },
-            Enumeration::Sized(v) => *v,
-            Enumeration::NonEnumerable => return None,
+            Enumerator::Seq(v) => *v,
+            Enumerator::NonEnumerable => return None,
         })
     }
 
@@ -377,39 +457,6 @@ impl Enumeration {
     pub fn is_empty(&self) -> bool {
         self.len() == Some(0)
     }
-
-    /// Converts the enumeration into an iterator if possible.
-    fn try_into_iter(self) -> Option<EnumerationIter> {
-        Some(EnumerationIter(match self {
-            Enumeration::Values(v) => EnumerationIterRepr::Values(v.into_iter()),
-            Enumeration::Static(v) => EnumerationIterRepr::Static(v.iter()),
-            Enumeration::Iterator(i) => EnumerationIterRepr::Iterator(i),
-            Enumeration::Sized(i) => EnumerationIterRepr::Sized(0..i),
-            Enumeration::NonEnumerable => return None,
-        }))
-    }
-}
-
-impl Iterator for EnumerationIter {
-    type Item = Value;
-
-    fn next(&mut self) -> Option<Self::Item> {
-        match &mut self.0 {
-            EnumerationIterRepr::Values(iter) => iter.next(),
-            EnumerationIterRepr::Static(iter) => iter.next().copied().map(intern).map(Value::from),
-            EnumerationIterRepr::Iterator(iter) => iter.next(),
-            EnumerationIterRepr::Sized(iter) => iter.next().map(Value::from),
-        }
-    }
-
-    fn size_hint(&self) -> (usize, Option<usize>) {
-        match &self.0 {
-            EnumerationIterRepr::Values(iter) => iter.size_hint(),
-            EnumerationIterRepr::Static(iter) => iter.size_hint(),
-            EnumerationIterRepr::Iterator(iter) => iter.size_hint(),
-            EnumerationIterRepr::Sized(iter) => iter.size_hint(),
-        }
-    }
 }
 
 impl<T: Into<Value> + Clone + Send + Sync + fmt::Debug> Object for Vec<T> {
@@ -418,11 +465,11 @@ impl<T: Into<Value> + Clone + Send + Sync + fmt::Debug> Object for Vec<T> {
     }
 
     fn get_value(self: &Arc<Self>, key: &Value) -> Option<Value> {
-        self.get(key.as_usize()?).cloned().map(|v| v.into())
+        self.get(some!(key.as_usize())).cloned().map(|v| v.into())
     }
 
-    fn enumeration(self: &Arc<Self>) -> Enumeration {
-        Enumeration::Sized(self.len())
+    fn enumerate(self: &Arc<Self>) -> Enumerator {
+        Enumerator::Seq(Vec::len(self))
     }
 }
 
@@ -431,8 +478,8 @@ impl Object for ValueMap {
         self.get(key).cloned()
     }
 
-    fn enumeration(self: &Arc<Self>) -> Enumeration {
-        self.mapped_enumeration(|this| Box::new(this.keys().cloned()))
+    fn enumerate(self: &Arc<Self>) -> Enumerator {
+        self.mapped_enumerator(|this| Box::new(this.keys().cloned()))
     }
 }
 
@@ -451,25 +498,12 @@ where
     V: Into<Value> + Clone + Send + Sync + fmt::Debug + 'static,
 {
     fn get_value(self: &Arc<Self>, key: &Value) -> Option<Value> {
-        self.get(key.as_str()?).cloned().map(|v| v.into())
+        self.get(some!(key.as_str())).cloned().map(|v| v.into())
     }
 
-    fn enumeration(self: &Arc<Self>) -> Enumeration {
-        self.mapped_enumeration(|this| {
+    fn enumerate(self: &Arc<Self>) -> Enumerator {
+        self.mapped_enumerator(|this| {
             Box::new(this.keys().map(|k| intern(k.as_ref())).map(Value::from))
         })
     }
 }
-
-/// Utility type that displays a value in debug except for objects which are rendered.
-pub(crate) struct ValueDbgRender<'a>(pub &'a Value);
-
-impl<'a> fmt::Debug for ValueDbgRender<'a> {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        if let ValueRepr::Object(ref obj) = self.0 .0 {
-            obj.render(f)
-        } else {
-            fmt::Debug::fmt(&self.0, f)
-        }
-    }
-}
diff --git a/minijinja/src/value/ops.rs b/minijinja/src/value/ops.rs
index d6b6e00..63496eb 100644
--- a/minijinja/src/value/ops.rs
+++ b/minijinja/src/value/ops.rs
@@ -114,7 +114,7 @@ pub fn slice(value: Value, start: Value, stop: Value, step: Value) -> Result<Val
         }
         ValueRepr::Undefined | ValueRepr::None => Ok(Value::from(Vec::<Value>::new())),
         ValueRepr::Object(obj) if obj.repr() == ObjectRepr::Seq => {
-            let len = obj.enumeration().len().unwrap_or_default();
+            let len = obj.len().unwrap_or_default();
             let (start, len) = get_offset_and_len(start, stop, || len);
             Ok(Value::from_iter(
                 obj.try_iter()
@@ -273,7 +273,7 @@ pub fn contains(container: &Value, value: &Value) -> Result<Value, Error> {
         match obj.repr() {
             ObjectRepr::Plain => false,
             ObjectRepr::Map => obj.get_value(value).is_some(),
-            ObjectRepr::Seq | ObjectRepr::Iterator => {
+            ObjectRepr::Seq | ObjectRepr::Iterable => {
                 obj.try_iter().into_iter().flatten().any(|v| &v == value)
             }
         }
diff --git a/minijinja/src/vm/closure_object.rs b/minijinja/src/vm/closure_object.rs
index 1ad707b..96d6c54 100644
--- a/minijinja/src/vm/closure_object.rs
+++ b/minijinja/src/vm/closure_object.rs
@@ -1,7 +1,7 @@
 use std::collections::BTreeMap;
 use std::sync::{Arc, Mutex};
 
-use crate::value::{Enumeration, Object, Value};
+use crate::value::{Enumerator, Object, Value};
 
 /// Closure cycle breaker utility.
 ///
@@ -65,9 +65,9 @@ impl Object for Closure {
         self.values.lock().unwrap().get(key.as_str()?).cloned()
     }
 
-    fn enumeration(self: &Arc<Self>) -> Enumeration {
+    fn enumerate(self: &Arc<Self>) -> Enumerator {
         let values = self.values.lock().unwrap();
         let keys = values.keys().cloned().map(Value::from);
-        Enumeration::Values(keys.collect())
+        Enumerator::Values(keys.collect())
     }
 }
diff --git a/minijinja/src/vm/loop_object.rs b/minijinja/src/vm/loop_object.rs
index 829dea1..c597012 100644
--- a/minijinja/src/vm/loop_object.rs
+++ b/minijinja/src/vm/loop_object.rs
@@ -3,7 +3,7 @@ use std::sync::atomic::{AtomicUsize, Ordering};
 use std::sync::{Arc, Mutex};
 
 use crate::error::{Error, ErrorKind};
-use crate::value::{Enumeration, Object, Value};
+use crate::value::{Enumerator, Object, Value};
 use crate::vm::state::State;
 
 pub(crate) struct Loop {
@@ -135,12 +135,12 @@ impl Object for Loop {
         }
     }
 
-    fn enumeration(self: &Arc<Self>) -> Enumeration {
-        Enumeration::Static(self.keys())
+    fn enumerate(self: &Arc<Self>) -> Enumerator {
+        Enumerator::Str(self.keys())
     }
 
     fn get_value(self: &Arc<Self>, key: &Value) -> Option<Value> {
-        self.get(key.as_str()?)
+        self.get(some!(key.as_str()))
     }
 
     fn render(self: &Arc<Self>, f: &mut fmt::Formatter<'_>) -> fmt::Result {
diff --git a/minijinja/src/vm/macro_object.rs b/minijinja/src/vm/macro_object.rs
index 6790b88..c9b5aba 100644
--- a/minijinja/src/vm/macro_object.rs
+++ b/minijinja/src/vm/macro_object.rs
@@ -5,7 +5,7 @@ use std::sync::Arc;
 use crate::error::{Error, ErrorKind};
 use crate::output::Output;
 use crate::utils::AutoEscape;
-use crate::value::{Enumeration, Object, Value, ValueRepr};
+use crate::value::{Enumerator, Object, Value, ValueRepr};
 use crate::vm::state::State;
 use crate::vm::Vm;
 
@@ -29,19 +29,17 @@ impl fmt::Debug for Macro {
 }
 
 impl Object for Macro {
-    fn enumeration(self: &Arc<Self>) -> Enumeration {
-        Enumeration::Static(&["name", "arguments", "caller"])
+    fn enumerate(self: &Arc<Self>) -> Enumerator {
+        Enumerator::Str(&["name", "arguments", "caller"])
     }
 
     fn get_value(self: &Arc<Self>, key: &Value) -> Option<Value> {
-        match key.as_str()? {
-            "name" => Some(Value::from(self.name.clone())),
-            "arguments" => Some(Value::from_iter(
-                self.arg_spec.iter().cloned().map(Value::from),
-            )),
-            "caller" => Some(Value::from(self.caller_reference)),
-            _ => None,
-        }
+        Some(match some!(key.as_str()) {
+            "name" => Value::from(self.name.clone()),
+            "arguments" => Value::from_iter(self.arg_spec.iter().cloned().map(Value::from)),
+            "caller" => Value::from(self.caller_reference),
+            _ => return None,
+        })
     }
 
     fn call(self: &Arc<Self>, state: &State<'_, '_>, args: &[Value]) -> Result<Value, Error> {


Test patch:
diff --git a/minijinja/tests/test_deserialization.rs b/minijinja/tests/test_deserialization.rs
index 5dc67ab..e2f8992 100644
--- a/minijinja/tests/test_deserialization.rs
+++ b/minijinja/tests/test_deserialization.rs
@@ -4,7 +4,7 @@ use std::{collections::BTreeMap, sync::Arc};
 use serde::{Deserialize, Serialize};
 use similar_asserts::assert_eq;
 
-use minijinja::value::{Enumeration, Object, ObjectRepr, Value};
+use minijinja::value::{Enumerator, Object, ObjectRepr, Value};
 
 #[test]
 fn test_seq() {
@@ -31,8 +31,8 @@ fn test_seq_object() {
             }
         }
 
-        fn enumeration(self: &Arc<Self>) -> Enumeration {
-            Enumeration::Sized(3)
+        fn enumerate(self: &Arc<Self>) -> Enumerator {
+            Enumerator::Seq(3)
         }
     }
 
@@ -67,8 +67,8 @@ fn test_struct_object() {
             }
         }
 
-        fn enumeration(self: &Arc<Self>) -> Enumeration {
-            Enumeration::Static(&["a", "b"])
+        fn enumerate(self: &Arc<Self>) -> Enumerator {
+            Enumerator::Str(&["a", "b"])
         }
     }
 
diff --git a/minijinja/tests/test_templates.rs b/minijinja/tests/test_templates.rs
index 41ed7ed..cf6953e 100644
--- a/minijinja/tests/test_templates.rs
+++ b/minijinja/tests/test_templates.rs
@@ -10,7 +10,7 @@ use std::sync::Arc;
 use std::{env, fs};
 
 use insta::assert_snapshot;
-use minijinja::value::{Enumeration, Object, Value};
+use minijinja::value::{Enumerator, Object, Value};
 use minijinja::{context, render, Environment, Error, State};
 
 use similar_asserts::assert_eq;
@@ -181,8 +181,8 @@ fn test_items_and_dictsort_with_structs() {
             }
         }
 
-        fn enumeration(self: &Arc<Self>) -> Enumeration {
-            Enumeration::Static(&["b", "a"])
+        fn enumerate(self: &Arc<Self>) -> Enumerator {
+            Enumerator::Str(&["b", "a"])
         }
     }
 
@@ -210,8 +210,8 @@ fn test_urlencode_with_struct() {
             }
         }
 
-        fn enumeration(self: &Arc<Self>) -> Enumeration {
-            Enumeration::Static(&["a", "b"])
+        fn enumerate(self: &Arc<Self>) -> Enumerator {
+            Enumerator::Str(&["a", "b"])
         }
     }
 
diff --git a/minijinja/tests/test_value.rs b/minijinja/tests/test_value.rs
index 7deeb39..b01cccb 100644
--- a/minijinja/tests/test_value.rs
+++ b/minijinja/tests/test_value.rs
@@ -3,7 +3,7 @@ use std::sync::Arc;
 use insta::assert_snapshot;
 use similar_asserts::assert_eq;
 
-use minijinja::value::{DynObject, Enumeration, Kwargs, Object, ObjectRepr, Rest, Value};
+use minijinja::value::{DynObject, Enumerator, Kwargs, Object, ObjectRepr, Rest, Value};
 use minijinja::{args, render, Environment, Error};
 
 #[test]
@@ -179,8 +179,8 @@ fn test_map_object_iteration_and_indexing() {
             }
         }
 
-        fn enumeration(self: &Arc<Self>) -> Enumeration {
-            Enumeration::Static(&["x", "y", "z"])
+        fn enumerate(self: &Arc<Self>) -> Enumerator {
+            Enumerator::Str(&["x", "y", "z"])
         }
     }
 
@@ -220,8 +220,8 @@ fn test_seq_object_iteration_and_indexing() {
             }
         }
 
-        fn enumeration(self: &Arc<Self>) -> Enumeration {
-            Enumeration::Sized(3)
+        fn enumerate(self: &Arc<Self>) -> Enumerator {
+            Enumerator::Seq(3)
         }
     }
 
@@ -261,7 +261,7 @@ fn test_builtin_seq_objects() {
 fn test_value_object_interface() {
     let val = Value::from_object(vec![1u32, 2, 3, 4]);
     let obj = val.as_object().unwrap();
-    assert_eq!(obj.enumeration().len(), Some(4));
+    assert_eq!(obj.len(), Some(4));
     assert_eq!(obj.to_string(), "[1, 2, 3, 4]");
 }
 
@@ -297,8 +297,8 @@ fn test_seq_object_downcast() {
             }
         }
 
-        fn enumeration(self: &Arc<Self>) -> Enumeration {
-            Enumeration::Sized(3)
+        fn enumerate(self: &Arc<Self>) -> Enumerator {
+            Enumerator::Seq(3)
         }
     }
 
@@ -580,8 +580,8 @@ fn test_seq_custom_iter() {
             ObjectRepr::Seq
         }
 
-        fn enumeration(self: &Arc<Self>) -> Enumeration {
-            Enumeration::NonEnumerable
+        fn enumerate(self: &Arc<Self>) -> Enumerator {
+            Enumerator::Iter(Box::new(('a'..='b').map(Value::from)))
         }
 
         fn get_value(self: &Arc<Self>, key: &Value) -> Option<Value> {
@@ -591,10 +591,6 @@ fn test_seq_custom_iter() {
                 _ => None,
             }
         }
-
-        fn custom_iter(self: &Arc<Self>) -> Option<Box<dyn Iterator<Item = Value> + Send + Sync>> {
-            Some(Box::new(('a'..='b').map(Value::from)))
-        }
     }
 
     let v = Value::from_object(WeirdSeq);
@@ -625,8 +621,8 @@ fn test_map_custom_iter() {
             ObjectRepr::Map
         }
 
-        fn enumeration(self: &Arc<Self>) -> Enumeration {
-            Enumeration::NonEnumerable
+        fn enumerate(self: &Arc<Self>) -> Enumerator {
+            Enumerator::Iter(Box::new(('a'..='b').map(Value::from)))
         }
 
         fn get_value(self: &Arc<Self>, key: &Value) -> Option<Value> {
@@ -636,10 +632,6 @@ fn test_map_custom_iter() {
                 _ => None,
             }
         }
-
-        fn custom_iter(self: &Arc<Self>) -> Option<Box<dyn Iterator<Item = Value> + Send + Sync>> {
-            Some(Box::new(('a'..='b').map(Value::from)))
-        }
     }
 
     let v = Value::from_object(WeirdMap);