Repository: djc/askama
Pull request #518 (2021-07-18 15:21:04)

 Implement for-else 
This PR implements for-else statements like in Jinja. They make it easy
to print an alternative message if the loop iterator was empty. E.g.

```rs
{% for result in result %}
  <li>{{ result }}</li>
{% else %}
  <li><em>no results</em></li>
{% endfor %}
```

djc: I'm not sure I'm a fan of this, since Rust does not have for-else itself. In most cases, you could probably handle this by calling `is_empty()` on the source of the iterator (for example, for slice or `Vec`). Any thoughts?
Kijewski: I implemented for-else like Jinja does, which is not equivalent to Python's semantics.

In Python the else block is executed unless a `break` statement was hit. That's trivially true for empty ranges, but might also be the case for non-empty ranges.

In Jinja the else block is only executed if the inner block was never entered. I guess I should implement `for … in … if …`, too, then the feature is vastly more useful, too. 
Kijewski: I added `for … in … if …`. I don't quite like how the source for that particular feature looks like, but I don't know to to make it "prettier" right now.
djc: Do you actually have use cases for this syntax, or are you just aiming for feature parity with Jinja?
vallentin: For what it's worth, I technically have a use case for both :)

On one hand, I always think less code is nice. On the other hand, it only requires 1 extra set of `{% if ... %}` + `{% endif %}` (and `{% else %}` for the first one.) So it isn't specifically "needed", but it would be nice, but again, that's just my opinion.
djc: Ah, this has a conflict now. Otherwise looking good; for bonus points, also add some docs?

Patch:
diff --git a/askama_shared/src/generator.rs b/askama_shared/src/generator.rs
index 6c1b151..5c72934 100644
--- a/askama_shared/src/generator.rs
+++ b/askama_shared/src/generator.rs
@@ -2,7 +2,7 @@ use super::{get_template_source, CompileError, Integrations};
 use crate::filters;
 use crate::heritage::{Context, Heritage};
 use crate::input::{Source, TemplateInput};
-use crate::parser::{parse, Cond, CondTest, Expr, Node, Target, When, Ws};
+use crate::parser::{parse, Cond, CondTest, Expr, Loop, Node, Target, When, Ws};
 
 use proc_macro2::Span;
 
@@ -420,8 +420,8 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> {
                 Node::Match(ws1, ref expr, ref arms, ws2) => {
                     self.write_match(ctx, buf, ws1, expr, arms, ws2)?;
                 }
-                Node::Loop(ws1, ref var, ref iter, ref body, ws2) => {
-                    self.write_loop(ctx, buf, ws1, var, iter, body, ws2)?;
+                Node::Loop(ref loop_block) => {
+                    self.write_loop(ctx, buf, loop_block)?;
                 }
                 Node::BlockDef(ws1, name, _, ws2) => {
                     self.write_block(buf, Some(name), Ws(ws1.0, ws2.1))?;
@@ -596,62 +596,69 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> {
         &mut self,
         ctx: &'a Context<'_>,
         buf: &mut Buffer,
-        ws1: Ws,
-        var: &'a Target<'_>,
-        iter: &Expr<'_>,
-        body: &'a [Node<'_>],
-        ws2: Ws,
+        loop_block: &'a Loop<'_>,
     ) -> Result<usize, CompileError> {
-        self.handle_ws(ws1);
+        self.handle_ws(loop_block.ws1);
         self.locals.push();
 
-        let expr_code = self.visit_expr_root(iter)?;
+        let expr_code = self.visit_expr_root(&loop_block.iter)?;
 
         let flushed = self.write_buf_writable(buf)?;
-        buf.write("for (");
-        self.visit_target(buf, true, true, var);
-        match iter {
-            Expr::Range(_, _, _) => buf.writeln(&format!(
-                ", _loop_item) in ::askama::helpers::TemplateLoop::new({}) {{",
-                expr_code
-            )),
-            Expr::Array(..) => buf.writeln(&format!(
-                ", _loop_item) in ::askama::helpers::TemplateLoop::new({}.iter()) {{",
-                expr_code
-            )),
+        buf.writeln("{")?;
+        buf.writeln("let mut _did_loop = false;")?;
+        match loop_block.iter {
+            Expr::Range(_, _, _) => buf.writeln(&format!("let _iter = {};", expr_code)),
+            Expr::Array(..) => buf.writeln(&format!("let _iter = {}.iter();", expr_code)),
             // If `iter` is a call then we assume it's something that returns
             // an iterator. If not then the user can explicitly add the needed
             // call without issues.
-            Expr::MethodCall(..) | Expr::PathCall(..) | Expr::Index(..) => buf.writeln(&format!(
-                ", _loop_item) in ::askama::helpers::TemplateLoop::new(({}).into_iter()) {{",
-                expr_code
-            )),
+            Expr::MethodCall(..) | Expr::PathCall(..) | Expr::Index(..) => {
+                buf.writeln(&format!("let _iter = ({}).into_iter();", expr_code))
+            }
             // If accessing `self` then it most likely needs to be
             // borrowed, to prevent an attempt of moving.
-            _ if expr_code.starts_with("self.") => buf.writeln(&format!(
-                ", _loop_item) in ::askama::helpers::TemplateLoop::new(((&{}).into_iter())) {{",
-                expr_code
-            )),
+            _ if expr_code.starts_with("self.") => {
+                buf.writeln(&format!("let _iter = (&{}).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
-            )),
+            Expr::Attr(..) => buf.writeln(&format!("let _iter = (&{}).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()) {{",
-                expr_code
-            )),
+            _ => buf.writeln(&format!("let _iter = ({}).into_iter();", expr_code)),
         }?;
+        if let Some(cond) = &loop_block.cond {
+            self.locals.push();
+            buf.write("let _iter = _iter.filter(|");
+            self.visit_target(buf, true, true, &loop_block.var);
+            buf.write("| -> bool {");
+            self.visit_expr(buf, cond)?;
+            buf.writeln("});")?;
+            self.locals.pop();
+        }
 
-        let mut size_hint = self.handle(ctx, body, buf, AstLevel::Nested)?;
-        self.handle_ws(ws2);
+        self.locals.push();
+        buf.write("for (");
+        self.visit_target(buf, true, true, &loop_block.var);
+        buf.writeln(", _loop_item) in ::askama::helpers::TemplateLoop::new(_iter) {")?;
 
-        size_hint += self.write_buf_writable(buf)?;
+        buf.writeln("_did_loop = true;")?;
+        let mut size_hint1 = self.handle(ctx, &loop_block.body, buf, AstLevel::Nested)?;
+        self.handle_ws(loop_block.ws2);
+        size_hint1 += self.write_buf_writable(buf)?;
+        self.locals.pop();
         buf.writeln("}")?;
+
+        buf.writeln("if !_did_loop {")?;
+        self.locals.push();
+        let mut size_hint2 = self.handle(ctx, &loop_block.else_block, buf, AstLevel::Nested)?;
+        self.handle_ws(loop_block.ws3);
+        size_hint2 += self.write_buf_writable(buf)?;
         self.locals.pop();
-        Ok(flushed + (size_hint * 3))
+        buf.writeln("}")?;
+
+        buf.writeln("}")?;
+
+        Ok(flushed + ((size_hint1 * 3) + size_hint2) / 2)
     }
 
     fn write_call(
diff --git a/askama_shared/src/heritage.rs b/askama_shared/src/heritage.rs
index 9f90273..a0b5460 100644
--- a/askama_shared/src/heritage.rs
+++ b/askama_shared/src/heritage.rs
@@ -1,7 +1,7 @@
 use std::collections::HashMap;
 use std::path::{Path, PathBuf};
 
-use crate::parser::{Expr, Macro, Node};
+use crate::parser::{Expr, Loop, Macro, Node};
 use crate::{CompileError, Config};
 
 pub struct Heritage<'a> {
@@ -86,8 +86,11 @@ impl<'a> Context<'a> {
                             nested.push(nodes);
                         }
                     }
-                    Node::Loop(_, _, _, nodes, _) => {
-                        nested.push(nodes);
+                    Node::Loop(Loop {
+                        body, else_block, ..
+                    }) => {
+                        nested.push(body);
+                        nested.push(else_block);
                     }
                     Node::Match(_, _, arms, _) => {
                         for (_, _, arm) in arms {
diff --git a/askama_shared/src/parser.rs b/askama_shared/src/parser.rs
index d1bc425..34a391d 100644
--- a/askama_shared/src/parser.rs
+++ b/askama_shared/src/parser.rs
@@ -22,7 +22,7 @@ pub enum Node<'a> {
     Let(Ws, Target<'a>, Expr<'a>),
     Cond(Vec<Cond<'a>>, Ws),
     Match(Ws, Expr<'a>, Vec<When<'a>>, Ws),
-    Loop(Ws, Target<'a>, Expr<'a>, Vec<Node<'a>>, Ws),
+    Loop(Loop<'a>),
     Extends(Expr<'a>),
     BlockDef(Ws, &'a str, Vec<Node<'a>>, Ws),
     Include(Ws, &'a str),
@@ -33,6 +33,18 @@ pub enum Node<'a> {
     Continue(Ws),
 }
 
+#[derive(Debug, PartialEq)]
+pub struct Loop<'a> {
+    pub ws1: Ws,
+    pub var: Target<'a>,
+    pub iter: Expr<'a>,
+    pub cond: Option<Expr<'a>>,
+    pub body: Vec<Node<'a>>,
+    pub ws2: Ws,
+    pub else_block: Vec<Node<'a>>,
+    pub ws3: Ws,
+}
+
 #[derive(Debug, PartialEq)]
 pub enum Expr<'a> {
     BoolLit(&'a str),
@@ -897,12 +909,29 @@ fn block_let(i: &[u8]) -> IResult<&[u8], Node<'_>> {
 
 fn parse_loop_content<'a>(i: &'a [u8], s: &State<'_>) -> IResult<&'a [u8], Vec<Node<'a>>> {
     s.loop_depth.set(s.loop_depth.get() + 1);
-    let (i, node) = parse_template(i, s)?;
+    let result = parse_template(i, s);
     s.loop_depth.set(s.loop_depth.get() - 1);
-    Ok((i, node))
+    result
 }
 
 fn block_for<'a>(i: &'a [u8], s: &State<'_>) -> IResult<&'a [u8], Node<'a>> {
+    let if_cond = preceded(ws(tag("if")), cut(ws(expr_any)));
+    let else_block = |i| {
+        let mut p = preceded(
+            ws(tag("else")),
+            cut(tuple((
+                opt(tag("-")),
+                delimited(
+                    |i| tag_block_end(i, s),
+                    |i| parse_template(i, s),
+                    |i| tag_block_start(i, s),
+                ),
+                opt(tag("-")),
+            ))),
+        );
+        let (i, (pws, nodes, nws)) = p(i)?;
+        Ok((i, (pws.is_some(), nodes, nws.is_some())))
+    };
     let mut p = tuple((
         opt(char('-')),
         ws(tag("for")),
@@ -911,6 +940,7 @@ fn block_for<'a>(i: &'a [u8], s: &State<'_>) -> IResult<&'a [u8], Node<'a>> {
             ws(tag("in")),
             cut(tuple((
                 ws(expr_any),
+                opt(if_cond),
                 opt(char('-')),
                 |i| tag_block_end(i, s),
                 cut(tuple((
@@ -918,6 +948,7 @@ fn block_for<'a>(i: &'a [u8], s: &State<'_>) -> IResult<&'a [u8], Node<'a>> {
                     cut(tuple((
                         |i| tag_block_start(i, s),
                         opt(char('-')),
+                        opt(else_block),
                         ws(tag("endfor")),
                         opt(char('-')),
                     ))),
@@ -925,16 +956,21 @@ fn block_for<'a>(i: &'a [u8], s: &State<'_>) -> IResult<&'a [u8], Node<'a>> {
             ))),
         ))),
     ));
-    let (i, (pws1, _, (var, _, (iter, nws1, _, (block, (_, pws2, _, nws2)))))) = p(i)?;
+    let (i, (pws1, _, (var, _, (iter, cond, nws1, _, (body, (_, pws2, else_block, _, nws2)))))) =
+        p(i)?;
+    let (nws3, else_block, pws3) = else_block.unwrap_or_default();
     Ok((
         i,
-        Node::Loop(
-            Ws(pws1.is_some(), nws1.is_some()),
+        Node::Loop(Loop {
+            ws1: Ws(pws1.is_some(), nws1.is_some()),
             var,
             iter,
-            block,
-            Ws(pws2.is_some(), nws2.is_some()),
-        ),
+            cond,
+            body,
+            ws2: Ws(pws2.is_some(), nws3),
+            else_block,
+            ws3: Ws(pws3, nws2.is_some()),
+        }),
     ))
 }
 


Test patch:
diff --git a/testing/tests/gen_loop_else.py b/testing/tests/gen_loop_else.py
new file mode 100755
index 0000000..30afe09
--- /dev/null
+++ b/testing/tests/gen_loop_else.py
@@ -0,0 +1,58 @@
+#!/usr/bin/env python3
+
+print(r'''use askama::Template;
+
+#[derive(Template)]
+#[template(
+    source = "{% for v in values %}{{v}}{% else %}empty{% endfor %}",
+    ext = "txt"
+)]
+struct ForElse<'a> {
+    values: &'a [i32],
+}
+
+#[test]
+fn test_for_else() {
+    let t = ForElse { values: &[1, 2, 3] };
+    assert_eq!(t.render().unwrap(), "123");
+
+    let t = ForElse { values: &[] };
+    assert_eq!(t.render().unwrap(), "empty");
+}
+''')
+
+for i in range(2**6):
+    a = '-' if i & 2**0 else ' '
+    b = '-' if i & 2**1 else ' '
+    c = '-' if i & 2**2 else ' '
+    d = '-' if i & 2**3 else ' '
+    e = '-' if i & 2**4 else ' '
+    f = '-' if i & 2**5 else ' '
+    source = fr'a {{%{a}for v in values{b}%}}\t{{{{v}}}}\t{{%{c}else{d}%}}\nX\n{{%{e}endfor{f}%}} b'
+
+    a = '' if i & 2**0 else r' '
+    b = '' if i & 2**1 else r'\t'
+    c = '' if i & 2**2 else r'\t'
+    d = '' if i & 2**3 else r'\n'
+    e = '' if i & 2**4 else r'\n'
+    f = '' if i & 2**5 else r' '
+    some = f'a{a}{b}1{c}{f}b'
+    none = f'a{a}{d}X{e}{f}b'
+
+    print(f'''#[derive(Template)]
+#[template(
+    source = "{source}",
+    ext = "txt"
+)]
+struct LoopElseTrim{i:02}<'a> {{
+    values: &'a [i32],
+}}
+
+#[test]
+fn test_loop_else_trim{i:02}() {{
+    let t = LoopElseTrim{i:02} {{ values: &[1] }};
+    assert_eq!(t.render().unwrap(), "{some}");
+
+    let t = LoopElseTrim{i:02} {{ values: &[] }};
+    assert_eq!(t.render().unwrap(), "{none}");
+}}''')
diff --git a/testing/tests/loop_else.rs b/testing/tests/loop_else.rs
new file mode 100644
index 0000000..7673509
--- /dev/null
+++ b/testing/tests/loop_else.rs
@@ -0,0 +1,1108 @@
+use askama::Template;
+
+#[derive(Template)]
+#[template(
+    source = "{% for v in values %}{{ v }}{% else %}empty{% endfor %}",
+    ext = "txt"
+)]
+struct ForElse<'a> {
+    values: &'a [i32],
+}
+
+#[test]
+fn test_for_else() {
+    let t = ForElse { values: &[1, 2, 3] };
+    assert_eq!(t.render().unwrap(), "123");
+
+    let t = ForElse { values: &[] };
+    assert_eq!(t.render().unwrap(), "empty");
+}
+
+#[derive(Template)]
+#[template(
+    source = "a {% for v in values %}\t{{v}}\t{% else %}\nX\n{% endfor %} b",
+    ext = "txt"
+)]
+struct LoopElseTrim00<'a> {
+    values: &'a [i32],
+}
+
+#[test]
+fn test_loop_else_trim00() {
+    let t = LoopElseTrim00 { values: &[1] };
+    assert_eq!(t.render().unwrap(), "a \t1\t b");
+
+    let t = LoopElseTrim00 { values: &[] };
+    assert_eq!(t.render().unwrap(), "a \nX\n b");
+}
+#[derive(Template)]
+#[template(
+    source = "a {%-for v in values %}\t{{v}}\t{% else %}\nX\n{% endfor %} b",
+    ext = "txt"
+)]
+struct LoopElseTrim01<'a> {
+    values: &'a [i32],
+}
+
+#[test]
+fn test_loop_else_trim01() {
+    let t = LoopElseTrim01 { values: &[1] };
+    assert_eq!(t.render().unwrap(), "a\t1\t b");
+
+    let t = LoopElseTrim01 { values: &[] };
+    assert_eq!(t.render().unwrap(), "a\nX\n b");
+}
+#[derive(Template)]
+#[template(
+    source = "a {% for v in values-%}\t{{v}}\t{% else %}\nX\n{% endfor %} b",
+    ext = "txt"
+)]
+struct LoopElseTrim02<'a> {
+    values: &'a [i32],
+}
+
+#[test]
+fn test_loop_else_trim02() {
+    let t = LoopElseTrim02 { values: &[1] };
+    assert_eq!(t.render().unwrap(), "a 1\t b");
+
+    let t = LoopElseTrim02 { values: &[] };
+    assert_eq!(t.render().unwrap(), "a \nX\n b");
+}
+#[derive(Template)]
+#[template(
+    source = "a {%-for v in values-%}\t{{v}}\t{% else %}\nX\n{% endfor %} b",
+    ext = "txt"
+)]
+struct LoopElseTrim03<'a> {
+    values: &'a [i32],
+}
+
+#[test]
+fn test_loop_else_trim03() {
+    let t = LoopElseTrim03 { values: &[1] };
+    assert_eq!(t.render().unwrap(), "a1\t b");
+
+    let t = LoopElseTrim03 { values: &[] };
+    assert_eq!(t.render().unwrap(), "a\nX\n b");
+}
+#[derive(Template)]
+#[template(
+    source = "a {% for v in values %}\t{{v}}\t{%-else %}\nX\n{% endfor %} b",
+    ext = "txt"
+)]
+struct LoopElseTrim04<'a> {
+    values: &'a [i32],
+}
+
+#[test]
+fn test_loop_else_trim04() {
+    let t = LoopElseTrim04 { values: &[1] };
+    assert_eq!(t.render().unwrap(), "a \t1 b");
+
+    let t = LoopElseTrim04 { values: &[] };
+    assert_eq!(t.render().unwrap(), "a \nX\n b");
+}
+#[derive(Template)]
+#[template(
+    source = "a {%-for v in values %}\t{{v}}\t{%-else %}\nX\n{% endfor %} b",
+    ext = "txt"
+)]
+struct LoopElseTrim05<'a> {
+    values: &'a [i32],
+}
+
+#[test]
+fn test_loop_else_trim05() {
+    let t = LoopElseTrim05 { values: &[1] };
+    assert_eq!(t.render().unwrap(), "a\t1 b");
+
+    let t = LoopElseTrim05 { values: &[] };
+    assert_eq!(t.render().unwrap(), "a\nX\n b");
+}
+#[derive(Template)]
+#[template(
+    source = "a {% for v in values-%}\t{{v}}\t{%-else %}\nX\n{% endfor %} b",
+    ext = "txt"
+)]
+struct LoopElseTrim06<'a> {
+    values: &'a [i32],
+}
+
+#[test]
+fn test_loop_else_trim06() {
+    let t = LoopElseTrim06 { values: &[1] };
+    assert_eq!(t.render().unwrap(), "a 1 b");
+
+    let t = LoopElseTrim06 { values: &[] };
+    assert_eq!(t.render().unwrap(), "a \nX\n b");
+}
+#[derive(Template)]
+#[template(
+    source = "a {%-for v in values-%}\t{{v}}\t{%-else %}\nX\n{% endfor %} b",
+    ext = "txt"
+)]
+struct LoopElseTrim07<'a> {
+    values: &'a [i32],
+}
+
+#[test]
+fn test_loop_else_trim07() {
+    let t = LoopElseTrim07 { values: &[1] };
+    assert_eq!(t.render().unwrap(), "a1 b");
+
+    let t = LoopElseTrim07 { values: &[] };
+    assert_eq!(t.render().unwrap(), "a\nX\n b");
+}
+#[derive(Template)]
+#[template(
+    source = "a {% for v in values %}\t{{v}}\t{% else-%}\nX\n{% endfor %} b",
+    ext = "txt"
+)]
+struct LoopElseTrim08<'a> {
+    values: &'a [i32],
+}
+
+#[test]
+fn test_loop_else_trim08() {
+    let t = LoopElseTrim08 { values: &[1] };
+    assert_eq!(t.render().unwrap(), "a \t1\t b");
+
+    let t = LoopElseTrim08 { values: &[] };
+    assert_eq!(t.render().unwrap(), "a X\n b");
+}
+#[derive(Template)]
+#[template(
+    source = "a {%-for v in values %}\t{{v}}\t{% else-%}\nX\n{% endfor %} b",
+    ext = "txt"
+)]
+struct LoopElseTrim09<'a> {
+    values: &'a [i32],
+}
+
+#[test]
+fn test_loop_else_trim09() {
+    let t = LoopElseTrim09 { values: &[1] };
+    assert_eq!(t.render().unwrap(), "a\t1\t b");
+
+    let t = LoopElseTrim09 { values: &[] };
+    assert_eq!(t.render().unwrap(), "aX\n b");
+}
+#[derive(Template)]
+#[template(
+    source = "a {% for v in values-%}\t{{v}}\t{% else-%}\nX\n{% endfor %} b",
+    ext = "txt"
+)]
+struct LoopElseTrim10<'a> {
+    values: &'a [i32],
+}
+
+#[test]
+fn test_loop_else_trim10() {
+    let t = LoopElseTrim10 { values: &[1] };
+    assert_eq!(t.render().unwrap(), "a 1\t b");
+
+    let t = LoopElseTrim10 { values: &[] };
+    assert_eq!(t.render().unwrap(), "a X\n b");
+}
+#[derive(Template)]
+#[template(
+    source = "a {%-for v in values-%}\t{{v}}\t{% else-%}\nX\n{% endfor %} b",
+    ext = "txt"
+)]
+struct LoopElseTrim11<'a> {
+    values: &'a [i32],
+}
+
+#[test]
+fn test_loop_else_trim11() {
+    let t = LoopElseTrim11 { values: &[1] };
+    assert_eq!(t.render().unwrap(), "a1\t b");
+
+    let t = LoopElseTrim11 { values: &[] };
+    assert_eq!(t.render().unwrap(), "aX\n b");
+}
+#[derive(Template)]
+#[template(
+    source = "a {% for v in values %}\t{{v}}\t{%-else-%}\nX\n{% endfor %} b",
+    ext = "txt"
+)]
+struct LoopElseTrim12<'a> {
+    values: &'a [i32],
+}
+
+#[test]
+fn test_loop_else_trim12() {
+    let t = LoopElseTrim12 { values: &[1] };
+    assert_eq!(t.render().unwrap(), "a \t1 b");
+
+    let t = LoopElseTrim12 { values: &[] };
+    assert_eq!(t.render().unwrap(), "a X\n b");
+}
+#[derive(Template)]
+#[template(
+    source = "a {%-for v in values %}\t{{v}}\t{%-else-%}\nX\n{% endfor %} b",
+    ext = "txt"
+)]
+struct LoopElseTrim13<'a> {
+    values: &'a [i32],
+}
+
+#[test]
+fn test_loop_else_trim13() {
+    let t = LoopElseTrim13 { values: &[1] };
+    assert_eq!(t.render().unwrap(), "a\t1 b");
+
+    let t = LoopElseTrim13 { values: &[] };
+    assert_eq!(t.render().unwrap(), "aX\n b");
+}
+#[derive(Template)]
+#[template(
+    source = "a {% for v in values-%}\t{{v}}\t{%-else-%}\nX\n{% endfor %} b",
+    ext = "txt"
+)]
+struct LoopElseTrim14<'a> {
+    values: &'a [i32],
+}
+
+#[test]
+fn test_loop_else_trim14() {
+    let t = LoopElseTrim14 { values: &[1] };
+    assert_eq!(t.render().unwrap(), "a 1 b");
+
+    let t = LoopElseTrim14 { values: &[] };
+    assert_eq!(t.render().unwrap(), "a X\n b");
+}
+#[derive(Template)]
+#[template(
+    source = "a {%-for v in values-%}\t{{v}}\t{%-else-%}\nX\n{% endfor %} b",
+    ext = "txt"
+)]
+struct LoopElseTrim15<'a> {
+    values: &'a [i32],
+}
+
+#[test]
+fn test_loop_else_trim15() {
+    let t = LoopElseTrim15 { values: &[1] };
+    assert_eq!(t.render().unwrap(), "a1 b");
+
+    let t = LoopElseTrim15 { values: &[] };
+    assert_eq!(t.render().unwrap(), "aX\n b");
+}
+#[derive(Template)]
+#[template(
+    source = "a {% for v in values %}\t{{v}}\t{% else %}\nX\n{%-endfor %} b",
+    ext = "txt"
+)]
+struct LoopElseTrim16<'a> {
+    values: &'a [i32],
+}
+
+#[test]
+fn test_loop_else_trim16() {
+    let t = LoopElseTrim16 { values: &[1] };
+    assert_eq!(t.render().unwrap(), "a \t1\t b");
+
+    let t = LoopElseTrim16 { values: &[] };
+    assert_eq!(t.render().unwrap(), "a \nX b");
+}
+#[derive(Template)]
+#[template(
+    source = "a {%-for v in values %}\t{{v}}\t{% else %}\nX\n{%-endfor %} b",
+    ext = "txt"
+)]
+struct LoopElseTrim17<'a> {
+    values: &'a [i32],
+}
+
+#[test]
+fn test_loop_else_trim17() {
+    let t = LoopElseTrim17 { values: &[1] };
+    assert_eq!(t.render().unwrap(), "a\t1\t b");
+
+    let t = LoopElseTrim17 { values: &[] };
+    assert_eq!(t.render().unwrap(), "a\nX b");
+}
+#[derive(Template)]
+#[template(
+    source = "a {% for v in values-%}\t{{v}}\t{% else %}\nX\n{%-endfor %} b",
+    ext = "txt"
+)]
+struct LoopElseTrim18<'a> {
+    values: &'a [i32],
+}
+
+#[test]
+fn test_loop_else_trim18() {
+    let t = LoopElseTrim18 { values: &[1] };
+    assert_eq!(t.render().unwrap(), "a 1\t b");
+
+    let t = LoopElseTrim18 { values: &[] };
+    assert_eq!(t.render().unwrap(), "a \nX b");
+}
+#[derive(Template)]
+#[template(
+    source = "a {%-for v in values-%}\t{{v}}\t{% else %}\nX\n{%-endfor %} b",
+    ext = "txt"
+)]
+struct LoopElseTrim19<'a> {
+    values: &'a [i32],
+}
+
+#[test]
+fn test_loop_else_trim19() {
+    let t = LoopElseTrim19 { values: &[1] };
+    assert_eq!(t.render().unwrap(), "a1\t b");
+
+    let t = LoopElseTrim19 { values: &[] };
+    assert_eq!(t.render().unwrap(), "a\nX b");
+}
+#[derive(Template)]
+#[template(
+    source = "a {% for v in values %}\t{{v}}\t{%-else %}\nX\n{%-endfor %} b",
+    ext = "txt"
+)]
+struct LoopElseTrim20<'a> {
+    values: &'a [i32],
+}
+
+#[test]
+fn test_loop_else_trim20() {
+    let t = LoopElseTrim20 { values: &[1] };
+    assert_eq!(t.render().unwrap(), "a \t1 b");
+
+    let t = LoopElseTrim20 { values: &[] };
+    assert_eq!(t.render().unwrap(), "a \nX b");
+}
+#[derive(Template)]
+#[template(
+    source = "a {%-for v in values %}\t{{v}}\t{%-else %}\nX\n{%-endfor %} b",
+    ext = "txt"
+)]
+struct LoopElseTrim21<'a> {
+    values: &'a [i32],
+}
+
+#[test]
+fn test_loop_else_trim21() {
+    let t = LoopElseTrim21 { values: &[1] };
+    assert_eq!(t.render().unwrap(), "a\t1 b");
+
+    let t = LoopElseTrim21 { values: &[] };
+    assert_eq!(t.render().unwrap(), "a\nX b");
+}
+#[derive(Template)]
+#[template(
+    source = "a {% for v in values-%}\t{{v}}\t{%-else %}\nX\n{%-endfor %} b",
+    ext = "txt"
+)]
+struct LoopElseTrim22<'a> {
+    values: &'a [i32],
+}
+
+#[test]
+fn test_loop_else_trim22() {
+    let t = LoopElseTrim22 { values: &[1] };
+    assert_eq!(t.render().unwrap(), "a 1 b");
+
+    let t = LoopElseTrim22 { values: &[] };
+    assert_eq!(t.render().unwrap(), "a \nX b");
+}
+#[derive(Template)]
+#[template(
+    source = "a {%-for v in values-%}\t{{v}}\t{%-else %}\nX\n{%-endfor %} b",
+    ext = "txt"
+)]
+struct LoopElseTrim23<'a> {
+    values: &'a [i32],
+}
+
+#[test]
+fn test_loop_else_trim23() {
+    let t = LoopElseTrim23 { values: &[1] };
+    assert_eq!(t.render().unwrap(), "a1 b");
+
+    let t = LoopElseTrim23 { values: &[] };
+    assert_eq!(t.render().unwrap(), "a\nX b");
+}
+#[derive(Template)]
+#[template(
+    source = "a {% for v in values %}\t{{v}}\t{% else-%}\nX\n{%-endfor %} b",
+    ext = "txt"
+)]
+struct LoopElseTrim24<'a> {
+    values: &'a [i32],
+}
+
+#[test]
+fn test_loop_else_trim24() {
+    let t = LoopElseTrim24 { values: &[1] };
+    assert_eq!(t.render().unwrap(), "a \t1\t b");
+
+    let t = LoopElseTrim24 { values: &[] };
+    assert_eq!(t.render().unwrap(), "a X b");
+}
+#[derive(Template)]
+#[template(
+    source = "a {%-for v in values %}\t{{v}}\t{% else-%}\nX\n{%-endfor %} b",
+    ext = "txt"
+)]
+struct LoopElseTrim25<'a> {
+    values: &'a [i32],
+}
+
+#[test]
+fn test_loop_else_trim25() {
+    let t = LoopElseTrim25 { values: &[1] };
+    assert_eq!(t.render().unwrap(), "a\t1\t b");
+
+    let t = LoopElseTrim25 { values: &[] };
+    assert_eq!(t.render().unwrap(), "aX b");
+}
+#[derive(Template)]
+#[template(
+    source = "a {% for v in values-%}\t{{v}}\t{% else-%}\nX\n{%-endfor %} b",
+    ext = "txt"
+)]
+struct LoopElseTrim26<'a> {
+    values: &'a [i32],
+}
+
+#[test]
+fn test_loop_else_trim26() {
+    let t = LoopElseTrim26 { values: &[1] };
+    assert_eq!(t.render().unwrap(), "a 1\t b");
+
+    let t = LoopElseTrim26 { values: &[] };
+    assert_eq!(t.render().unwrap(), "a X b");
+}
+#[derive(Template)]
+#[template(
+    source = "a {%-for v in values-%}\t{{v}}\t{% else-%}\nX\n{%-endfor %} b",
+    ext = "txt"
+)]
+struct LoopElseTrim27<'a> {
+    values: &'a [i32],
+}
+
+#[test]
+fn test_loop_else_trim27() {
+    let t = LoopElseTrim27 { values: &[1] };
+    assert_eq!(t.render().unwrap(), "a1\t b");
+
+    let t = LoopElseTrim27 { values: &[] };
+    assert_eq!(t.render().unwrap(), "aX b");
+}
+#[derive(Template)]
+#[template(
+    source = "a {% for v in values %}\t{{v}}\t{%-else-%}\nX\n{%-endfor %} b",
+    ext = "txt"
+)]
+struct LoopElseTrim28<'a> {
+    values: &'a [i32],
+}
+
+#[test]
+fn test_loop_else_trim28() {
+    let t = LoopElseTrim28 { values: &[1] };
+    assert_eq!(t.render().unwrap(), "a \t1 b");
+
+    let t = LoopElseTrim28 { values: &[] };
+    assert_eq!(t.render().unwrap(), "a X b");
+}
+#[derive(Template)]
+#[template(
+    source = "a {%-for v in values %}\t{{v}}\t{%-else-%}\nX\n{%-endfor %} b",
+    ext = "txt"
+)]
+struct LoopElseTrim29<'a> {
+    values: &'a [i32],
+}
+
+#[test]
+fn test_loop_else_trim29() {
+    let t = LoopElseTrim29 { values: &[1] };
+    assert_eq!(t.render().unwrap(), "a\t1 b");
+
+    let t = LoopElseTrim29 { values: &[] };
+    assert_eq!(t.render().unwrap(), "aX b");
+}
+#[derive(Template)]
+#[template(
+    source = "a {% for v in values-%}\t{{v}}\t{%-else-%}\nX\n{%-endfor %} b",
+    ext = "txt"
+)]
+struct LoopElseTrim30<'a> {
+    values: &'a [i32],
+}
+
+#[test]
+fn test_loop_else_trim30() {
+    let t = LoopElseTrim30 { values: &[1] };
+    assert_eq!(t.render().unwrap(), "a 1 b");
+
+    let t = LoopElseTrim30 { values: &[] };
+    assert_eq!(t.render().unwrap(), "a X b");
+}
+#[derive(Template)]
+#[template(
+    source = "a {%-for v in values-%}\t{{v}}\t{%-else-%}\nX\n{%-endfor %} b",
+    ext = "txt"
+)]
+struct LoopElseTrim31<'a> {
+    values: &'a [i32],
+}
+
+#[test]
+fn test_loop_else_trim31() {
+    let t = LoopElseTrim31 { values: &[1] };
+    assert_eq!(t.render().unwrap(), "a1 b");
+
+    let t = LoopElseTrim31 { values: &[] };
+    assert_eq!(t.render().unwrap(), "aX b");
+}
+#[derive(Template)]
+#[template(
+    source = "a {% for v in values %}\t{{v}}\t{% else %}\nX\n{% endfor-%} b",
+    ext = "txt"
+)]
+struct LoopElseTrim32<'a> {
+    values: &'a [i32],
+}
+
+#[test]
+fn test_loop_else_trim32() {
+    let t = LoopElseTrim32 { values: &[1] };
+    assert_eq!(t.render().unwrap(), "a \t1\tb");
+
+    let t = LoopElseTrim32 { values: &[] };
+    assert_eq!(t.render().unwrap(), "a \nX\nb");
+}
+#[derive(Template)]
+#[template(
+    source = "a {%-for v in values %}\t{{v}}\t{% else %}\nX\n{% endfor-%} b",
+    ext = "txt"
+)]
+struct LoopElseTrim33<'a> {
+    values: &'a [i32],
+}
+
+#[test]
+fn test_loop_else_trim33() {
+    let t = LoopElseTrim33 { values: &[1] };
+    assert_eq!(t.render().unwrap(), "a\t1\tb");
+
+    let t = LoopElseTrim33 { values: &[] };
+    assert_eq!(t.render().unwrap(), "a\nX\nb");
+}
+#[derive(Template)]
+#[template(
+    source = "a {% for v in values-%}\t{{v}}\t{% else %}\nX\n{% endfor-%} b",
+    ext = "txt"
+)]
+struct LoopElseTrim34<'a> {
+    values: &'a [i32],
+}
+
+#[test]
+fn test_loop_else_trim34() {
+    let t = LoopElseTrim34 { values: &[1] };
+    assert_eq!(t.render().unwrap(), "a 1\tb");
+
+    let t = LoopElseTrim34 { values: &[] };
+    assert_eq!(t.render().unwrap(), "a \nX\nb");
+}
+#[derive(Template)]
+#[template(
+    source = "a {%-for v in values-%}\t{{v}}\t{% else %}\nX\n{% endfor-%} b",
+    ext = "txt"
+)]
+struct LoopElseTrim35<'a> {
+    values: &'a [i32],
+}
+
+#[test]
+fn test_loop_else_trim35() {
+    let t = LoopElseTrim35 { values: &[1] };
+    assert_eq!(t.render().unwrap(), "a1\tb");
+
+    let t = LoopElseTrim35 { values: &[] };
+    assert_eq!(t.render().unwrap(), "a\nX\nb");
+}
+#[derive(Template)]
+#[template(
+    source = "a {% for v in values %}\t{{v}}\t{%-else %}\nX\n{% endfor-%} b",
+    ext = "txt"
+)]
+struct LoopElseTrim36<'a> {
+    values: &'a [i32],
+}
+
+#[test]
+fn test_loop_else_trim36() {
+    let t = LoopElseTrim36 { values: &[1] };
+    assert_eq!(t.render().unwrap(), "a \t1b");
+
+    let t = LoopElseTrim36 { values: &[] };
+    assert_eq!(t.render().unwrap(), "a \nX\nb");
+}
+#[derive(Template)]
+#[template(
+    source = "a {%-for v in values %}\t{{v}}\t{%-else %}\nX\n{% endfor-%} b",
+    ext = "txt"
+)]
+struct LoopElseTrim37<'a> {
+    values: &'a [i32],
+}
+
+#[test]
+fn test_loop_else_trim37() {
+    let t = LoopElseTrim37 { values: &[1] };
+    assert_eq!(t.render().unwrap(), "a\t1b");
+
+    let t = LoopElseTrim37 { values: &[] };
+    assert_eq!(t.render().unwrap(), "a\nX\nb");
+}
+#[derive(Template)]
+#[template(
+    source = "a {% for v in values-%}\t{{v}}\t{%-else %}\nX\n{% endfor-%} b",
+    ext = "txt"
+)]
+struct LoopElseTrim38<'a> {
+    values: &'a [i32],
+}
+
+#[test]
+fn test_loop_else_trim38() {
+    let t = LoopElseTrim38 { values: &[1] };
+    assert_eq!(t.render().unwrap(), "a 1b");
+
+    let t = LoopElseTrim38 { values: &[] };
+    assert_eq!(t.render().unwrap(), "a \nX\nb");
+}
+#[derive(Template)]
+#[template(
+    source = "a {%-for v in values-%}\t{{v}}\t{%-else %}\nX\n{% endfor-%} b",
+    ext = "txt"
+)]
+struct LoopElseTrim39<'a> {
+    values: &'a [i32],
+}
+
+#[test]
+fn test_loop_else_trim39() {
+    let t = LoopElseTrim39 { values: &[1] };
+    assert_eq!(t.render().unwrap(), "a1b");
+
+    let t = LoopElseTrim39 { values: &[] };
+    assert_eq!(t.render().unwrap(), "a\nX\nb");
+}
+#[derive(Template)]
+#[template(
+    source = "a {% for v in values %}\t{{v}}\t{% else-%}\nX\n{% endfor-%} b",
+    ext = "txt"
+)]
+struct LoopElseTrim40<'a> {
+    values: &'a [i32],
+}
+
+#[test]
+fn test_loop_else_trim40() {
+    let t = LoopElseTrim40 { values: &[1] };
+    assert_eq!(t.render().unwrap(), "a \t1\tb");
+
+    let t = LoopElseTrim40 { values: &[] };
+    assert_eq!(t.render().unwrap(), "a X\nb");
+}
+#[derive(Template)]
+#[template(
+    source = "a {%-for v in values %}\t{{v}}\t{% else-%}\nX\n{% endfor-%} b",
+    ext = "txt"
+)]
+struct LoopElseTrim41<'a> {
+    values: &'a [i32],
+}
+
+#[test]
+fn test_loop_else_trim41() {
+    let t = LoopElseTrim41 { values: &[1] };
+    assert_eq!(t.render().unwrap(), "a\t1\tb");
+
+    let t = LoopElseTrim41 { values: &[] };
+    assert_eq!(t.render().unwrap(), "aX\nb");
+}
+#[derive(Template)]
+#[template(
+    source = "a {% for v in values-%}\t{{v}}\t{% else-%}\nX\n{% endfor-%} b",
+    ext = "txt"
+)]
+struct LoopElseTrim42<'a> {
+    values: &'a [i32],
+}
+
+#[test]
+fn test_loop_else_trim42() {
+    let t = LoopElseTrim42 { values: &[1] };
+    assert_eq!(t.render().unwrap(), "a 1\tb");
+
+    let t = LoopElseTrim42 { values: &[] };
+    assert_eq!(t.render().unwrap(), "a X\nb");
+}
+#[derive(Template)]
+#[template(
+    source = "a {%-for v in values-%}\t{{v}}\t{% else-%}\nX\n{% endfor-%} b",
+    ext = "txt"
+)]
+struct LoopElseTrim43<'a> {
+    values: &'a [i32],
+}
+
+#[test]
+fn test_loop_else_trim43() {
+    let t = LoopElseTrim43 { values: &[1] };
+    assert_eq!(t.render().unwrap(), "a1\tb");
+
+    let t = LoopElseTrim43 { values: &[] };
+    assert_eq!(t.render().unwrap(), "aX\nb");
+}
+#[derive(Template)]
+#[template(
+    source = "a {% for v in values %}\t{{v}}\t{%-else-%}\nX\n{% endfor-%} b",
+    ext = "txt"
+)]
+struct LoopElseTrim44<'a> {
+    values: &'a [i32],
+}
+
+#[test]
+fn test_loop_else_trim44() {
+    let t = LoopElseTrim44 { values: &[1] };
+    assert_eq!(t.render().unwrap(), "a \t1b");
+
+    let t = LoopElseTrim44 { values: &[] };
+    assert_eq!(t.render().unwrap(), "a X\nb");
+}
+#[derive(Template)]
+#[template(
+    source = "a {%-for v in values %}\t{{v}}\t{%-else-%}\nX\n{% endfor-%} b",
+    ext = "txt"
+)]
+struct LoopElseTrim45<'a> {
+    values: &'a [i32],
+}
+
+#[test]
+fn test_loop_else_trim45() {
+    let t = LoopElseTrim45 { values: &[1] };
+    assert_eq!(t.render().unwrap(), "a\t1b");
+
+    let t = LoopElseTrim45 { values: &[] };
+    assert_eq!(t.render().unwrap(), "aX\nb");
+}
+#[derive(Template)]
+#[template(
+    source = "a {% for v in values-%}\t{{v}}\t{%-else-%}\nX\n{% endfor-%} b",
+    ext = "txt"
+)]
+struct LoopElseTrim46<'a> {
+    values: &'a [i32],
+}
+
+#[test]
+fn test_loop_else_trim46() {
+    let t = LoopElseTrim46 { values: &[1] };
+    assert_eq!(t.render().unwrap(), "a 1b");
+
+    let t = LoopElseTrim46 { values: &[] };
+    assert_eq!(t.render().unwrap(), "a X\nb");
+}
+#[derive(Template)]
+#[template(
+    source = "a {%-for v in values-%}\t{{v}}\t{%-else-%}\nX\n{% endfor-%} b",
+    ext = "txt"
+)]
+struct LoopElseTrim47<'a> {
+    values: &'a [i32],
+}
+
+#[test]
+fn test_loop_else_trim47() {
+    let t = LoopElseTrim47 { values: &[1] };
+    assert_eq!(t.render().unwrap(), "a1b");
+
+    let t = LoopElseTrim47 { values: &[] };
+    assert_eq!(t.render().unwrap(), "aX\nb");
+}
+#[derive(Template)]
+#[template(
+    source = "a {% for v in values %}\t{{v}}\t{% else %}\nX\n{%-endfor-%} b",
+    ext = "txt"
+)]
+struct LoopElseTrim48<'a> {
+    values: &'a [i32],
+}
+
+#[test]
+fn test_loop_else_trim48() {
+    let t = LoopElseTrim48 { values: &[1] };
+    assert_eq!(t.render().unwrap(), "a \t1\tb");
+
+    let t = LoopElseTrim48 { values: &[] };
+    assert_eq!(t.render().unwrap(), "a \nXb");
+}
+#[derive(Template)]
+#[template(
+    source = "a {%-for v in values %}\t{{v}}\t{% else %}\nX\n{%-endfor-%} b",
+    ext = "txt"
+)]
+struct LoopElseTrim49<'a> {
+    values: &'a [i32],
+}
+
+#[test]
+fn test_loop_else_trim49() {
+    let t = LoopElseTrim49 { values: &[1] };
+    assert_eq!(t.render().unwrap(), "a\t1\tb");
+
+    let t = LoopElseTrim49 { values: &[] };
+    assert_eq!(t.render().unwrap(), "a\nXb");
+}
+#[derive(Template)]
+#[template(
+    source = "a {% for v in values-%}\t{{v}}\t{% else %}\nX\n{%-endfor-%} b",
+    ext = "txt"
+)]
+struct LoopElseTrim50<'a> {
+    values: &'a [i32],
+}
+
+#[test]
+fn test_loop_else_trim50() {
+    let t = LoopElseTrim50 { values: &[1] };
+    assert_eq!(t.render().unwrap(), "a 1\tb");
+
+    let t = LoopElseTrim50 { values: &[] };
+    assert_eq!(t.render().unwrap(), "a \nXb");
+}
+#[derive(Template)]
+#[template(
+    source = "a {%-for v in values-%}\t{{v}}\t{% else %}\nX\n{%-endfor-%} b",
+    ext = "txt"
+)]
+struct LoopElseTrim51<'a> {
+    values: &'a [i32],
+}
+
+#[test]
+fn test_loop_else_trim51() {
+    let t = LoopElseTrim51 { values: &[1] };
+    assert_eq!(t.render().unwrap(), "a1\tb");
+
+    let t = LoopElseTrim51 { values: &[] };
+    assert_eq!(t.render().unwrap(), "a\nXb");
+}
+#[derive(Template)]
+#[template(
+    source = "a {% for v in values %}\t{{v}}\t{%-else %}\nX\n{%-endfor-%} b",
+    ext = "txt"
+)]
+struct LoopElseTrim52<'a> {
+    values: &'a [i32],
+}
+
+#[test]
+fn test_loop_else_trim52() {
+    let t = LoopElseTrim52 { values: &[1] };
+    assert_eq!(t.render().unwrap(), "a \t1b");
+
+    let t = LoopElseTrim52 { values: &[] };
+    assert_eq!(t.render().unwrap(), "a \nXb");
+}
+#[derive(Template)]
+#[template(
+    source = "a {%-for v in values %}\t{{v}}\t{%-else %}\nX\n{%-endfor-%} b",
+    ext = "txt"
+)]
+struct LoopElseTrim53<'a> {
+    values: &'a [i32],
+}
+
+#[test]
+fn test_loop_else_trim53() {
+    let t = LoopElseTrim53 { values: &[1] };
+    assert_eq!(t.render().unwrap(), "a\t1b");
+
+    let t = LoopElseTrim53 { values: &[] };
+    assert_eq!(t.render().unwrap(), "a\nXb");
+}
+#[derive(Template)]
+#[template(
+    source = "a {% for v in values-%}\t{{v}}\t{%-else %}\nX\n{%-endfor-%} b",
+    ext = "txt"
+)]
+struct LoopElseTrim54<'a> {
+    values: &'a [i32],
+}
+
+#[test]
+fn test_loop_else_trim54() {
+    let t = LoopElseTrim54 { values: &[1] };
+    assert_eq!(t.render().unwrap(), "a 1b");
+
+    let t = LoopElseTrim54 { values: &[] };
+    assert_eq!(t.render().unwrap(), "a \nXb");
+}
+#[derive(Template)]
+#[template(
+    source = "a {%-for v in values-%}\t{{v}}\t{%-else %}\nX\n{%-endfor-%} b",
+    ext = "txt"
+)]
+struct LoopElseTrim55<'a> {
+    values: &'a [i32],
+}
+
+#[test]
+fn test_loop_else_trim55() {
+    let t = LoopElseTrim55 { values: &[1] };
+    assert_eq!(t.render().unwrap(), "a1b");
+
+    let t = LoopElseTrim55 { values: &[] };
+    assert_eq!(t.render().unwrap(), "a\nXb");
+}
+#[derive(Template)]
+#[template(
+    source = "a {% for v in values %}\t{{v}}\t{% else-%}\nX\n{%-endfor-%} b",
+    ext = "txt"
+)]
+struct LoopElseTrim56<'a> {
+    values: &'a [i32],
+}
+
+#[test]
+fn test_loop_else_trim56() {
+    let t = LoopElseTrim56 { values: &[1] };
+    assert_eq!(t.render().unwrap(), "a \t1\tb");
+
+    let t = LoopElseTrim56 { values: &[] };
+    assert_eq!(t.render().unwrap(), "a Xb");
+}
+#[derive(Template)]
+#[template(
+    source = "a {%-for v in values %}\t{{v}}\t{% else-%}\nX\n{%-endfor-%} b",
+    ext = "txt"
+)]
+struct LoopElseTrim57<'a> {
+    values: &'a [i32],
+}
+
+#[test]
+fn test_loop_else_trim57() {
+    let t = LoopElseTrim57 { values: &[1] };
+    assert_eq!(t.render().unwrap(), "a\t1\tb");
+
+    let t = LoopElseTrim57 { values: &[] };
+    assert_eq!(t.render().unwrap(), "aXb");
+}
+#[derive(Template)]
+#[template(
+    source = "a {% for v in values-%}\t{{v}}\t{% else-%}\nX\n{%-endfor-%} b",
+    ext = "txt"
+)]
+struct LoopElseTrim58<'a> {
+    values: &'a [i32],
+}
+
+#[test]
+fn test_loop_else_trim58() {
+    let t = LoopElseTrim58 { values: &[1] };
+    assert_eq!(t.render().unwrap(), "a 1\tb");
+
+    let t = LoopElseTrim58 { values: &[] };
+    assert_eq!(t.render().unwrap(), "a Xb");
+}
+#[derive(Template)]
+#[template(
+    source = "a {%-for v in values-%}\t{{v}}\t{% else-%}\nX\n{%-endfor-%} b",
+    ext = "txt"
+)]
+struct LoopElseTrim59<'a> {
+    values: &'a [i32],
+}
+
+#[test]
+fn test_loop_else_trim59() {
+    let t = LoopElseTrim59 { values: &[1] };
+    assert_eq!(t.render().unwrap(), "a1\tb");
+
+    let t = LoopElseTrim59 { values: &[] };
+    assert_eq!(t.render().unwrap(), "aXb");
+}
+#[derive(Template)]
+#[template(
+    source = "a {% for v in values %}\t{{v}}\t{%-else-%}\nX\n{%-endfor-%} b",
+    ext = "txt"
+)]
+struct LoopElseTrim60<'a> {
+    values: &'a [i32],
+}
+
+#[test]
+fn test_loop_else_trim60() {
+    let t = LoopElseTrim60 { values: &[1] };
+    assert_eq!(t.render().unwrap(), "a \t1b");
+
+    let t = LoopElseTrim60 { values: &[] };
+    assert_eq!(t.render().unwrap(), "a Xb");
+}
+#[derive(Template)]
+#[template(
+    source = "a {%-for v in values %}\t{{v}}\t{%-else-%}\nX\n{%-endfor-%} b",
+    ext = "txt"
+)]
+struct LoopElseTrim61<'a> {
+    values: &'a [i32],
+}
+
+#[test]
+fn test_loop_else_trim61() {
+    let t = LoopElseTrim61 { values: &[1] };
+    assert_eq!(t.render().unwrap(), "a\t1b");
+
+    let t = LoopElseTrim61 { values: &[] };
+    assert_eq!(t.render().unwrap(), "aXb");
+}
+#[derive(Template)]
+#[template(
+    source = "a {% for v in values-%}\t{{v}}\t{%-else-%}\nX\n{%-endfor-%} b",
+    ext = "txt"
+)]
+struct LoopElseTrim62<'a> {
+    values: &'a [i32],
+}
+
+#[test]
+fn test_loop_else_trim62() {
+    let t = LoopElseTrim62 { values: &[1] };
+    assert_eq!(t.render().unwrap(), "a 1b");
+
+    let t = LoopElseTrim62 { values: &[] };
+    assert_eq!(t.render().unwrap(), "a Xb");
+}
+#[derive(Template)]
+#[template(
+    source = "a {%-for v in values-%}\t{{v}}\t{%-else-%}\nX\n{%-endfor-%} b",
+    ext = "txt"
+)]
+struct LoopElseTrim63<'a> {
+    values: &'a [i32],
+}
+
+#[test]
+fn test_loop_else_trim63() {
+    let t = LoopElseTrim63 { values: &[1] };
+    assert_eq!(t.render().unwrap(), "a1b");
+
+    let t = LoopElseTrim63 { values: &[] };
+    assert_eq!(t.render().unwrap(), "aXb");
+}
diff --git a/testing/tests/loops.rs b/testing/tests/loops.rs
index f014068..4a02153 100644
--- a/testing/tests/loops.rs
+++ b/testing/tests/loops.rs
@@ -417,3 +417,21 @@ fn test_for_cycle_empty() {
     };
     assert!(t.render().is_err())
 }
+
+#[derive(Template)]
+#[template(
+    source = "{% for i in 0..limit if i % 2 == 1 %}{{i}}.{% else %}:({% endfor %}",
+    ext = "txt"
+)]
+struct ForInIf {
+    limit: usize,
+}
+
+#[test]
+fn test_for_in_if() {
+    let t = ForInIf { limit: 10 };
+    assert_eq!(t.render().unwrap(), "1.3.5.7.9.");
+
+    let t = ForInIf { limit: 1 };
+    assert_eq!(t.render().unwrap(), ":(");
+}