Repository: djc/askama (rust)

Issue:
Calling a function with a literal and a variable requires manually writing .borrow()
Using the latest release, we could write code like this (untested, from memory):

```jinja2
{{ foo.bar(value) }}
{{ foo.bar(1) }}
```

```rust
#[derive(Template)]
struct MyTemplate {
    foo: Foo,
    value: u32
}

struct Foo;
impl Foo {
    fn bar(value: &u32) -> String { format!("{}", value) }
}
```

Using the code currently on the main branch, `foo.bar(1)` gets translated to `self.foo.bar(1)`, whereas `foo.bar(value)` gets translated to `self.foo.bar(&self.value)`. This makes it impossible to write such a function without introducing a trait that is implemented for both `u32` and `&u32`, as the function now needs to accept both values and references.

The only other workaround I could find was to use `{{ foo.bar(1.borrow()) }}` in the template code, which requires an explicit `use std::borrow::Borrow` in the file that invokes the derive macro. Still less code than defining my own trait.

Pull request description:
Fixes loop generation when accessing field
Fixes #492

Straightforward to fix, added a case when `iter` is a `Expr::Attr`, so it borrows the field.

Added a few tests, to assert it works regardless if the inner type is `Vec<_>` or `&[_]`. I also added a case for `Range`, though since `Range` is not `Copy` then it requires `.clone()`, so it was added more to ensure that continues to work.

_Not a big fan of the naming of the tests and structs._


Patch:
diff --git a/askama_shared/src/generator.rs b/askama_shared/src/generator.rs
index b0489ec..0ce7df3 100644
--- a/askama_shared/src/generator.rs
+++ b/askama_shared/src/generator.rs
@@ -658,6 +658,12 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> {
                 ", _loop_item) in ::askama::helpers::TemplateLoop::new(((&{}).into_iter())) {{",
                 expr_code
             )),
+            // If accessing a field then it most likely needs to be
+            // borrowed, to prevent an attempt of moving.
+            Expr::Attr(..) => buf.writeln(&format!(
+                ", _loop_item) in ::askama::helpers::TemplateLoop::new(((&{}).into_iter())) {{",
+                expr_code
+            )),
             // Otherwise, we borrow `iter` assuming that it implements `IntoIterator`.
             _ => buf.writeln(&format!(
                 ", _loop_item) in ::askama::helpers::TemplateLoop::new(({}).into_iter()) {{",


Test patch:
diff --git a/testing/tests/loops.rs b/testing/tests/loops.rs
index b324ae3..3bba428 100644
--- a/testing/tests/loops.rs
+++ b/testing/tests/loops.rs
@@ -1,3 +1,5 @@
+use std::ops::Range;
+
 use askama::Template;
 
 #[derive(Template)]
@@ -131,3 +133,84 @@ fn test_for_zip_ranges() {
         "0 10 30 1 11 31 2 12 32 3 13 33 4 14 34 5 15 35 6 16 36 7 17 37 8 18 38 9 19 39 "
     );
 }
+
+struct ForVecAttrVec {
+    iterable: Vec<i32>,
+}
+
+#[derive(Template)]
+#[template(
+    source = "{% for x in v %}{% for y in x.iterable %}{{ y }} {% endfor %}{% endfor %}",
+    ext = "txt"
+)]
+struct ForVecAttrVecTemplate {
+    v: Vec<ForVecAttrVec>,
+}
+
+#[test]
+fn test_for_vec_attr_vec() {
+    let t = ForVecAttrVecTemplate {
+        v: vec![
+            ForVecAttrVec {
+                iterable: vec![1, 2],
+            },
+            ForVecAttrVec {
+                iterable: vec![3, 4],
+            },
+            ForVecAttrVec {
+                iterable: vec![5, 6],
+            },
+        ],
+    };
+    assert_eq!(t.render().unwrap(), "1 2 3 4 5 6 ");
+}
+
+struct ForVecAttrSlice {
+    iterable: &'static [i32],
+}
+
+#[derive(Template)]
+#[template(
+    source = "{% for x in v %}{% for y in x.iterable %}{{ y }} {% endfor %}{% endfor %}",
+    ext = "txt"
+)]
+struct ForVecAttrSliceTemplate {
+    v: Vec<ForVecAttrSlice>,
+}
+
+#[test]
+fn test_for_vec_attr_slice() {
+    let t = ForVecAttrSliceTemplate {
+        v: vec![
+            ForVecAttrSlice { iterable: &[1, 2] },
+            ForVecAttrSlice { iterable: &[3, 4] },
+            ForVecAttrSlice { iterable: &[5, 6] },
+        ],
+    };
+    assert_eq!(t.render().unwrap(), "1 2 3 4 5 6 ");
+}
+
+struct ForVecAttrRange {
+    iterable: Range<usize>,
+}
+
+#[derive(Template)]
+#[template(
+    source = "{% for x in v %}{% for y in x.iterable.clone() %}{{ y }} {% endfor %}{% endfor %}",
+    ext = "txt"
+)]
+struct ForVecAttrRangeTemplate {
+    v: Vec<ForVecAttrRange>,
+}
+
+#[test]
+fn test_for_vec_attr_range() {
+    let t = ForVecAttrRangeTemplate {
+        v: vec![
+            ForVecAttrRange { iterable: 1..3 },
+            ForVecAttrRange { iterable: 3..5 },
+            ForVecAttrRange { iterable: 5..7 },
+        ],
+    };
+    assert_eq!(t.render().unwrap(), "1 2 3 4 5 6 ");
+}