Repository: mitsuhiko/minijinja
Pull request #790 (2025-05-06 10:09:46)

Backport of #787
Create a backport for #787.


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 978b0bb..747aa73 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,11 @@
 
 All notable changes to MiniJinja are documented here.
 
+## 1.0.22
+
+* Fixed an issue with the function bounds that caused the
+  next-generation trait resolver to fail.  #790
+
 ## 1.0.21
 
 - Fixed an issue where `lstrip_blocks` unintentionally also applied to
diff --git a/Cargo.lock b/Cargo.lock
index d71f91f..42c74f2 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -840,9 +840,9 @@ dependencies = [
 
 [[package]]
 name = "deranged"
-version = "0.3.9"
+version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3"
+checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e"
 dependencies = [
  "powerfmt",
  "serde",
@@ -2070,6 +2070,12 @@ dependencies = [
  "windows-sys 0.45.0",
 ]
 
+[[package]]
+name = "num-conv"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
+
 [[package]]
 name = "num-traits"
 version = "0.2.17"
@@ -3050,12 +3056,13 @@ dependencies = [
 
 [[package]]
 name = "time"
-version = "0.3.30"
+version = "0.3.41"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5"
+checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40"
 dependencies = [
  "deranged",
  "itoa",
+ "num-conv",
  "powerfmt",
  "serde",
  "time-core",
@@ -3064,16 +3071,17 @@ dependencies = [
 
 [[package]]
 name = "time-core"
-version = "0.1.2"
+version = "0.1.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
+checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c"
 
 [[package]]
 name = "time-macros"
-version = "0.2.15"
+version = "0.2.22"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20"
+checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49"
 dependencies = [
+ "num-conv",
  "time-core",
 ]
 
diff --git a/minijinja/src/environment.rs b/minijinja/src/environment.rs
index b327b04..805f357 100644
--- a/minijinja/src/environment.rs
+++ b/minijinja/src/environment.rs
@@ -676,9 +676,7 @@ impl<'source> Environment<'source> {
     pub fn add_filter<N, F, Rv, Args>(&mut self, name: N, f: F)
     where
         N: Into<Cow<'source, str>>,
-        // the crazy bounds here exist to enable borrowing in closures
-        F: filters::Filter<Rv, Args>
-            + for<'a> filters::Filter<Rv, <Args as FunctionArgs<'a>>::Output>,
+        F: filters::Filter<Rv, Args>,
         Rv: FunctionResult,
         Args: for<'a> FunctionArgs<'a>,
     {
@@ -699,8 +697,7 @@ impl<'source> Environment<'source> {
     pub fn add_test<N, F, Rv, Args>(&mut self, name: N, f: F)
     where
         N: Into<Cow<'source, str>>,
-        // the crazy bounds here exist to enable borrowing in closures
-        F: tests::Test<Rv, Args> + for<'a> tests::Test<Rv, <Args as FunctionArgs<'a>>::Output>,
+        F: tests::Test<Rv, Args>,
         Rv: tests::TestResult,
         Args: for<'a> FunctionArgs<'a>,
     {
@@ -721,9 +718,7 @@ impl<'source> Environment<'source> {
     pub fn add_function<N, F, Rv, Args>(&mut self, name: N, f: F)
     where
         N: Into<Cow<'source, str>>,
-        // the crazy bounds here exist to enable borrowing in closures
-        F: functions::Function<Rv, Args>
-            + for<'a> functions::Function<Rv, <Args as FunctionArgs<'a>>::Output>,
+        F: functions::Function<Rv, Args>,
         Rv: FunctionResult,
         Args: for<'a> FunctionArgs<'a>,
     {
diff --git a/minijinja/src/filters.rs b/minijinja/src/filters.rs
index 9dc90eb..722e269 100644
--- a/minijinja/src/filters.rs
+++ b/minijinja/src/filters.rs
@@ -199,28 +199,47 @@ pub(crate) struct BoxedFilter(Arc<FilterFunc>);
 /// ```jinja
 /// {{ "|".join(1, 2, 3) }} -> 1|2|3
 /// ```
-pub trait Filter<Rv, Args>: Send + Sync + 'static {
+pub trait Filter<Rv, Args: for<'a> FunctionArgs<'a>>: Send + Sync + 'static {
     /// Applies a filter to value with the given arguments.
     ///
     /// The value is always the first argument.
     #[doc(hidden)]
-    fn apply_to(&self, args: Args, _: SealedMarker) -> Rv;
+    fn apply_to(&self, args: <Args as FunctionArgs<'_>>::Output, _: SealedMarker) -> Rv;
+}
+
+// This is necessary to avoid a bug in the trait solver. See
+// https://github.com/mitsuhiko/minijinja/pull/787 for more details.
+trait FilterHelper<Rv, Args> {
+    fn apply_to_nested(&self, args: Args) -> Rv;
 }
 
 macro_rules! tuple_impls {
     ( $( $name:ident )* ) => {
-        impl<Func, Rv, $($name),*> Filter<Rv, ($($name,)*)> for Func
+        impl<Func, Rv, $($name),*> FilterHelper<Rv, ($($name,)*)> for Func
         where
-            Func: Fn($($name),*) -> Rv + Send + Sync + 'static,
-            Rv: FunctionResult,
-            $($name: for<'a> ArgType<'a>,)*
+            Func: Fn($($name),*) -> Rv,
         {
-            fn apply_to(&self, args: ($($name,)*), _: SealedMarker) -> Rv {
+            fn apply_to_nested(&self, args: ($($name,)*)) -> Rv {
                 #[allow(non_snake_case)]
                 let ($($name,)*) = args;
                 (self)($($name,)*)
             }
         }
+
+        impl<Func, Rv, $($name),*> Filter<Rv, ($($name,)*)> for Func
+        where
+            Func: Send + Sync + 'static,
+            // the crazy bounds here exist to enable borrowing in closures
+            Func: Fn($($name),*) -> Rv + for<'a> FilterHelper<Rv, ($(<$name as ArgType<'a>>::Output,)*)>,
+            Rv: FunctionResult,
+            $($name: for<'a> ArgType<'a>,)*
+        {
+            // Need to allow this lint for the one-element tuple case.
+            #[allow(clippy::needless_lifetimes)]
+            fn apply_to<'a>(&self, args: ($(<$name as ArgType<'a>>::Output,)*), _: SealedMarker) -> Rv {
+                self.apply_to_nested(args)
+            }
+        }
     };
 }
 
@@ -235,7 +254,7 @@ impl BoxedFilter {
     /// Creates a new boxed filter.
     pub fn new<F, Rv, Args>(f: F) -> BoxedFilter
     where
-        F: Filter<Rv, Args> + for<'a> Filter<Rv, <Args as FunctionArgs<'a>>::Output>,
+        F: Filter<Rv, Args>,
         Rv: FunctionResult,
         Args: for<'a> FunctionArgs<'a>,
     {
diff --git a/minijinja/src/functions.rs b/minijinja/src/functions.rs
index 554a557..18eff95 100644
--- a/minijinja/src/functions.rs
+++ b/minijinja/src/functions.rs
@@ -176,26 +176,45 @@ pub(crate) struct BoxedFunction(Arc<FuncFunc>, #[cfg(feature = "debug")] &'stati
 /// ```jinja
 /// {{ sum(1, 2, 3) }} -> 6
 /// ```
-pub trait Function<Rv, Args>: Send + Sync + 'static {
+pub trait Function<Rv, Args: for<'a> FunctionArgs<'a>>: Send + Sync + 'static {
     /// Calls a function with the given arguments.
     #[doc(hidden)]
-    fn invoke(&self, args: Args, _: SealedMarker) -> Rv;
+    fn invoke(&self, args: <Args as FunctionArgs<'_>>::Output, _: SealedMarker) -> Rv;
+}
+
+// This is necessary to avoid a bug in the trait solver. See
+// https://github.com/mitsuhiko/minijinja/pull/787 for more details.
+trait FunctionHelper<Rv, Args> {
+    fn invoke_nested(&self, args: Args) -> Rv;
 }
 
 macro_rules! tuple_impls {
     ( $( $name:ident )* ) => {
-        impl<Func, Rv, $($name),*> Function<Rv, ($($name,)*)> for Func
+        impl<Func, Rv, $($name),*> FunctionHelper<Rv, ($($name,)*)> for Func
         where
-            Func: Fn($($name),*) -> Rv + Send + Sync + 'static,
-            Rv: FunctionResult,
-            $($name: for<'a> ArgType<'a>,)*
+            Func: Fn($($name),*) -> Rv,
         {
-            fn invoke(&self, args: ($($name,)*), _: SealedMarker) -> Rv {
+            fn invoke_nested(&self, args: ($($name,)*)) -> Rv {
                 #[allow(non_snake_case)]
                 let ($($name,)*) = args;
                 (self)($($name,)*)
             }
         }
+
+        impl<Func, Rv, $($name),*> Function<Rv, ($($name,)*)> for Func
+        where
+            Func: Send + Sync + 'static,
+            // the crazy bounds here exist to enable borrowing in closures
+            Func: Fn($($name),*) -> Rv + for<'a> FunctionHelper<Rv, ($(<$name as ArgType<'a>>::Output,)*)>,
+            Rv: FunctionResult,
+            $($name: for<'a> ArgType<'a>,)*
+        {
+            // Need to allow this lint for the one-element tuple case.
+            #[allow(clippy::needless_lifetimes)]
+            fn invoke<'a>(&self, args: ($(<$name as ArgType<'a>>::Output,)*), _: SealedMarker) -> Rv {
+                self.invoke_nested(args)
+            }
+        }
     };
 }
 
@@ -210,7 +229,7 @@ impl BoxedFunction {
     /// Creates a new boxed filter.
     pub fn new<F, Rv, Args>(f: F) -> BoxedFunction
     where
-        F: Function<Rv, Args> + for<'a> Function<Rv, <Args as FunctionArgs<'a>>::Output>,
+        F: Function<Rv, Args>,
         Rv: FunctionResult,
         Args: for<'a> FunctionArgs<'a>,
     {
diff --git a/minijinja/src/value/mod.rs b/minijinja/src/value/mod.rs
index 4d2eaed..0af1247 100644
--- a/minijinja/src/value/mod.rs
+++ b/minijinja/src/value/mod.rs
@@ -747,8 +747,7 @@ impl Value {
     pub fn from_function<F, Rv, Args>(f: F) -> Value
     where
         // the crazy bounds here exist to enable borrowing in closures
-        F: functions::Function<Rv, Args>
-            + for<'a> functions::Function<Rv, <Args as FunctionArgs<'a>>::Output>,
+        F: functions::Function<Rv, Args>,
         Rv: FunctionResult,
         Args: for<'a> FunctionArgs<'a>,
     {


Test patch:
diff --git a/minijinja/src/tests.rs b/minijinja/src/tests.rs
index 8d732ed..70d611d 100644
--- a/minijinja/src/tests.rs
+++ b/minijinja/src/tests.rs
@@ -147,26 +147,45 @@ impl TestResult for bool {
 /// ```jinja
 /// {{ "foo" is containing("o") }} -> true
 /// ```
-pub trait Test<Rv, Args>: Send + Sync + 'static {
+pub trait Test<Rv, Args: for<'a> FunctionArgs<'a>>: Send + Sync + 'static {
     /// Performs a test to value with the given arguments.
     #[doc(hidden)]
-    fn perform(&self, args: Args, _: SealedMarker) -> Rv;
+    fn perform(&self, args: <Args as FunctionArgs<'_>>::Output, _: SealedMarker) -> Rv;
+}
+
+// This is necessary to avoid a bug in the trait solver. See
+// https://github.com/mitsuhiko/minijinja/pull/787 for more details.
+trait TestHelper<Rv, Args> {
+    fn perform_nested(&self, args: Args) -> Rv;
 }
 
 macro_rules! tuple_impls {
     ( $( $name:ident )* ) => {
-        impl<Func, Rv, $($name),*> Test<Rv, ($($name,)*)> for Func
+        impl<Func, Rv, $($name),*> TestHelper<Rv, ($($name,)*)> for Func
         where
-            Func: Fn($($name),*) -> Rv + Send + Sync + 'static,
-            Rv: TestResult,
-            $($name: for<'a> ArgType<'a>),*
+            Func: Fn($($name),*) -> Rv,
         {
-            fn perform(&self, args: ($($name,)*), _: SealedMarker) -> Rv {
+            fn perform_nested(&self, args: ($($name,)*)) -> Rv {
                 #[allow(non_snake_case)]
                 let ($($name,)*) = args;
                 (self)($($name,)*)
             }
         }
+
+        impl<Func, Rv, $($name),*> Test<Rv, ($($name,)*)> for Func
+        where
+            Func: Send + Sync + 'static,
+            // the crazy bounds here exist to enable borrowing in closures
+            Func: Fn($($name),*) -> Rv + for<'a> TestHelper<Rv, ($(<$name as ArgType<'a>>::Output,)*)>,
+            Rv: TestResult,
+            $($name: for<'a> ArgType<'a>),*
+        {
+            // Need to allow this lint for the one-element tuple case.
+            #[allow(clippy::needless_lifetimes)]
+            fn perform<'a>(&self, args: ($(<$name as ArgType<'a>>::Output,)*), _: SealedMarker) -> Rv {
+                self.perform_nested(args)
+            }
+        }
     };
 }
 
@@ -181,7 +200,7 @@ impl BoxedTest {
     /// Creates a new boxed filter.
     pub fn new<F, Rv, Args>(f: F) -> BoxedTest
     where
-        F: Test<Rv, Args> + for<'a> Test<Rv, <Args as FunctionArgs<'a>>::Output>,
+        F: Test<Rv, Args>,
         Rv: TestResult,
         Args: for<'a> FunctionArgs<'a>,
     {