Repository: mitsuhiko/minijinja (language: rust)

Issue:
Expose call() on Value
It's currently not possible through public APIs to call a value. This is done because a call requires the `State`, most specifically a mutable state. While it's also not possible yet to get hold of a state, the main blocker is that before performing a call the `current_call` property needs to be set which requires a mutable state.

If `current_state` is removed, it should be possible to expose this.

Refs https://github.com/mitsuhiko/minijinja/issues/266


Discussion / hints:

Fix patch:
diff --git a/minijinja/src/compiler/instructions.rs b/minijinja/src/compiler/instructions.rs
index 26212c62..9a7bfe63 100644
--- a/minijinja/src/compiler/instructions.rs
+++ b/minijinja/src/compiler/instructions.rs
@@ -255,6 +255,7 @@ pub struct Instructions<'source> {
     source: &'source str,
 }
 
+#[cfg(any(test, feature = "testutils"))]
 pub(crate) static EMPTY_INSTRUCTIONS: Instructions<'static> = Instructions {
     instructions: Vec::new(),
     line_infos: Vec::new(),
diff --git a/minijinja/src/template.rs b/minijinja/src/template.rs
index c4cf331d..653af4a6 100644
--- a/minijinja/src/template.rs
+++ b/minijinja/src/template.rs
@@ -13,7 +13,7 @@ use crate::error::{attach_basic_debug_info, Error};
 use crate::output::{Output, WriteWrapper};
 use crate::utils::AutoEscape;
 use crate::value::{self, Value};
-use crate::vm::Vm;
+use crate::vm::{prepare_blocks, Vm};
 use crate::{ErrorKind, State};
 
 /// Represents a handle to a template.
@@ -188,6 +188,17 @@ impl<'env> Template<'env> {
         }
     }
 
+    /// Creates an empty [`State`] for this template.
+    pub fn new_state(&self) -> State<'_, 'env> {
+        State::new(
+            self.env,
+            Default::default(),
+            self.initial_auto_escape,
+            &self.compiled.instructions,
+            prepare_blocks(&self.compiled.blocks),
+        )
+    }
+
     /// Returns the root instructions.
     #[cfg(feature = "multi_template")]
     pub(crate) fn instructions(&self) -> &'env Instructions<'env> {
@@ -287,15 +298,13 @@ impl<'template, 'env> TemplateModule<'template, 'env> {
             .map_err(|err| wrapper.take_err(err))
     }
 
-    /// Looks up a global macro and invokes it.
+    /// Looks up a global macro and calls it.
     ///
     /// This looks up a value like [`get_global`](Self::get_global) does and
-    /// invokes it.  It's not possible to call values directly under normal
-    /// circumstances as the state is not available that is necessary to
-    /// trigger a call.
+    /// calls it with the module's [`state`](Self::state) and the passed args.
     #[cfg(feature = "macros")]
     #[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
-    pub fn invoke_macro(&self, name: &str, args: &[Value]) -> Result<String, Error> {
+    pub fn call_macro(&self, name: &str, args: &[Value]) -> Result<String, Error> {
         let f = ok!(self
             .get_global(name)
             .ok_or_else(|| Error::new(ErrorKind::UnknownFunction, "macro not found")));
diff --git a/minijinja/src/value/argtypes.rs b/minijinja/src/value/argtypes.rs
index ce5a7c31..18f4c41a 100644
--- a/minijinja/src/value/argtypes.rs
+++ b/minijinja/src/value/argtypes.rs
@@ -625,7 +625,19 @@ impl<'a, T: ArgType<'a, Output = T>> ArgType<'a> for Rest<T> {
 /// ```
 ///
 /// If for whatever reason you need a value again you can use [`Into`] to
-/// convert it back into a [`Value`].
+/// convert it back into a [`Value`].  This is particularly useful when performing
+/// calls into values.  To create a [`Kwargs`] object from scratch you can use
+/// [`FromIterator`]:
+///
+/// ```
+/// use minijinja::value::{Value, Kwargs};
+/// let kwargs = Kwargs::from_iter([
+///     ("foo", Value::from(true)),
+///     ("bar", Value::from(42)),
+/// ]);
+/// let value = Value::from(kwargs);
+/// assert!(value.is_kwargs());
+/// ```
 #[derive(Debug, Clone)]
 pub struct Kwargs {
     values: Arc<ValueMap>,
@@ -761,6 +773,28 @@ impl Kwargs {
     }
 }
 
+impl FromIterator<(String, Value)> for Kwargs {
+    fn from_iter<T>(iter: T) -> Self
+    where
+        T: IntoIterator<Item = (String, Value)>,
+    {
+        Kwargs::new(Arc::new(
+            iter.into_iter().map(|(k, v)| (Key::from(k), v)).collect(),
+        ))
+    }
+}
+
+impl<'a> FromIterator<(&'a str, Value)> for Kwargs {
+    fn from_iter<T>(iter: T) -> Self
+    where
+        T: IntoIterator<Item = (&'a str, Value)>,
+    {
+        Kwargs::new(Arc::new(
+            iter.into_iter().map(|(k, v)| (Key::from(k), v)).collect(),
+        ))
+    }
+}
+
 impl From<Kwargs> for Value {
     fn from(value: Kwargs) -> Self {
         Value(ValueRepr::Map(value.values, MapType::Kwargs))
diff --git a/minijinja/src/value/mod.rs b/minijinja/src/value/mod.rs
index 7ce2d6ea..3a76832e 100644
--- a/minijinja/src/value/mod.rs
+++ b/minijinja/src/value/mod.rs
@@ -916,7 +916,38 @@ impl Value {
     }
 
     /// Calls the value directly.
-    pub(crate) fn call(&self, state: &State, args: &[Value]) -> Result<Value, Error> {
+    ///
+    /// If the value holds a function or macro, this invokes it.  Note that in
+    /// MiniJinja there is a separate namespace for methods on objects and callable
+    /// items.  To call methods (which should be a rather rare occurrence) you
+    /// have to use [`call_method`](Self::call_method).
+    ///
+    /// The `args` slice is for the arguments of the function call.  To pass
+    /// keyword arguments use the [`Kwargs`](crate::value::Kwargs) type.
+    ///
+    /// Usually the state is already available when it's useful to call this method,
+    /// but when it's not available you can get a fresh template state straight
+    /// from the [`Template`](crate::Template) via [`new_state`](crate::Template::new_state).
+    ///
+    /// ```
+    /// # use minijinja::{Environment, value::{Value, Kwargs}};
+    /// # let mut env = Environment::new();
+    /// # env.add_template("foo", "").unwrap();
+    /// # let tmpl = env.get_template("foo").unwrap();
+    /// # let state = tmpl.new_state(); let state = &state;
+    /// let func = Value::from_function(|v: i64, kwargs: Kwargs| {
+    ///     v * kwargs.get::<i64>("mult").unwrap_or(1)
+    /// });
+    /// let rv = func.call(
+    ///     state,
+    ///     &[
+    ///         Value::from(42),
+    ///         Value::from(Kwargs::from_iter([("mult", Value::from(2))])),
+    ///     ],
+    /// ).unwrap();
+    /// assert_eq!(rv, Value::from(84));
+    /// ```
+    pub fn call(&self, state: &State, args: &[Value]) -> Result<Value, Error> {
         if let ValueRepr::Dynamic(ref dy) = self.0 {
             dy.call(state, args)
         } else {
@@ -928,12 +959,10 @@ impl Value {
     }
 
     /// Calls a method on the value.
-    pub(crate) fn call_method(
-        &self,
-        state: &State,
-        name: &str,
-        args: &[Value],
-    ) -> Result<Value, Error> {
+    ///
+    /// The name of the method is `name`, the arguments passed are in the `args`
+    /// slice.
+    pub fn call_method(&self, state: &State, name: &str, args: &[Value]) -> Result<Value, Error> {
         match self.0 {
             ValueRepr::Dynamic(ref dy) => return dy.call_method(state, name, args),
             ValueRepr::Map(ref map, _) => {
diff --git a/minijinja/src/vm/mod.rs b/minijinja/src/vm/mod.rs
index c6b2cffd..2b3368ab 100644
--- a/minijinja/src/vm/mod.rs
+++ b/minijinja/src/vm/mod.rs
@@ -43,7 +43,7 @@ pub struct Vm<'env> {
     env: &'env Environment<'env>,
 }
 
-fn prepare_blocks<'env, 'template>(
+pub(crate) fn prepare_blocks<'env, 'template>(
     blocks: &'template BTreeMap<&'env str, Instructions<'env>>,
 ) -> BTreeMap<&'env str, BlockStack<'template, 'env>> {
     blocks
diff --git a/minijinja/src/vm/state.rs b/minijinja/src/vm/state.rs
index 5d0ea8ef..b82895f4 100644
--- a/minijinja/src/vm/state.rs
+++ b/minijinja/src/vm/state.rs
@@ -21,6 +21,12 @@ use crate::vm::fuel::FuelTracker;
 /// variables of the engine, the current auto escaping behavior as well as the
 /// auto escape flag.
 ///
+/// In some testing scenarios or more advanced use cases you might need to get
+/// a [`State`].  The state is managed as part of the template execution but the
+/// initial state can be retrieved via [`Template::new_state`](crate::Template::new_state)
+/// or [`TemplateModule::state`](crate::TemplateModule::state).  The most
+/// common way to get hold of the state however is via functions of filters.
+///
 /// **Notes on lifetimes:** the state object exposes some of the internal
 /// lifetimes through the type.  You should always elide these lifetimes
 /// as there might be lifetimes added or removed between releases.
@@ -76,7 +82,7 @@ impl<'template, 'env> State<'template, 'env> {
     }
 
     /// Creates an empty state for an environment.
-    #[allow(unused)]
+    #[cfg(any(test, feature = "testutils"))]
     pub(crate) fn new_for_env(env: &'env Environment) -> State<'env, 'env> {
         State::new(
             env,


Test patch:
diff --git a/minijinja/src/testutils.rs b/minijinja/src/testutils.rs
index 7b8bd70e..37b811d7 100644
--- a/minijinja/src/testutils.rs
+++ b/minijinja/src/testutils.rs
@@ -1,8 +1,7 @@
 //! Utilities for unit testing.
 //!
-//! MiniJinja intentionally hides some unstable internal API.  However this hiding can be a
-//! hindrance when doing unittests.  For this reason this module optionally exposes some
-//! common utilities to test filters, tests, formatters and global functions.
+//! This module optionally exposes some common utilities to test filters, tests, formatters
+//! and global functions.
 //!
 //! All these functions generally assume that the filter, test or else was first registered
 //! on the environment before it's invoked.  While some filters can be invoked without being
diff --git a/minijinja/tests/test_templates.rs b/minijinja/tests/test_templates.rs
index 44b0aaee..6db912c4 100644
--- a/minijinja/tests/test_templates.rs
+++ b/minijinja/tests/test_templates.rs
@@ -480,6 +480,6 @@ fn test_module() {
         .unwrap();
     assert_eq!(module.get_global("range"), None);
     assert_eq!(module.get_global("global"), Some(Value::from(23 * 2)));
-    assert_eq!(module.invoke_macro("something", &[]).unwrap(), "46");
+    assert_eq!(module.call_macro("something", &[]).unwrap(), "46");
     assert_eq!(module.render_block("baz").unwrap(), "[46]");
 }
diff --git a/minijinja/tests/test_value.rs b/minijinja/tests/test_value.rs
index 1265fae3..4d20ec76 100644
--- a/minijinja/tests/test_value.rs
+++ b/minijinja/tests/test_value.rs
@@ -3,7 +3,8 @@ use std::fmt;
 use std::sync::Arc;
 
 use insta::assert_snapshot;
-use minijinja::value::{Object, ObjectKind, SeqObject, StructObject, Value};
+use minijinja::value::{Kwargs, Object, ObjectKind, SeqObject, StructObject, Value};
+use minijinja::Environment;
 
 #[test]
 fn test_sort() {
@@ -239,3 +240,19 @@ fn test_value_cmp() {
     assert_ne!(Value::from(&[1][..]), Value::from(&[2][..]));
     assert_eq!(Value::UNDEFINED, Value::UNDEFINED);
 }
+
+#[test]
+fn test_call_kwargs() {
+    let mut env = Environment::new();
+    env.add_template("foo", "").unwrap();
+    let tmpl = env.get_template("foo").unwrap();
+    let state = tmpl.new_state();
+    let val = Value::from_function(|kwargs: Kwargs| kwargs.get::<i32>("foo"));
+    let rv = val
+        .call(
+            &state,
+            &[Kwargs::from_iter([("foo", Value::from(42))]).into()],
+        )
+        .unwrap();
+    assert_eq!(rv, Value::from(42));
+}