Repository: djc/askama
Pull request #516 (2021-07-17 12:03:40)

 Use "target()" to parse "when" block 
`target()` as used in parsing "let" and "if let" implements parsing nested tuples and structs. But it does not implement parsing literals.

The functions `match_variant()` and `with_parameters()` as used in parsing "when" blocks do not implement parsing nested structs, but it implements parsing literals.

This PR combines `match_variant()` and `with_parameters()` into `target()`, so that all `{%when%}` support nested structs, too.


Patch:
diff --git a/askama_shared/src/filters/mod.rs b/askama_shared/src/filters/mod.rs
index 74486c0..8406dee 100644
--- a/askama_shared/src/filters/mod.rs
+++ b/askama_shared/src/filters/mod.rs
@@ -566,6 +566,7 @@ mod tests {
         };
     }
 
+    #[allow(clippy::needless_borrow)]
     #[test]
     fn test_join() {
         assert_eq!(
diff --git a/askama_shared/src/generator.rs b/askama_shared/src/generator.rs
index 36d1841..dccda93 100644
--- a/askama_shared/src/generator.rs
+++ b/askama_shared/src/generator.rs
@@ -2,10 +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, MatchParameter, MatchParameters, MatchVariant, Node, Target, When,
-    Ws,
-};
+use crate::parser::{parse, Cond, CondTest, Expr, Node, Target, When, Ws};
 
 use proc_macro2::Span;
 
@@ -498,14 +495,11 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> {
                     buf.write("} else if ");
                 }
 
-                if let Some((variant, params)) = target {
+                if let Some(target) = target {
                     let mut expr_buf = Buffer::new(0);
                     self.visit_expr(&mut expr_buf, expr)?;
                     buf.write("let ");
-                    self.visit_match_variant(buf, variant);
-                    if let Some(params) = params {
-                        self.visit_match_params(buf, params);
-                    }
+                    self.visit_target(buf, true, true, target);
                     buf.write(" = &(");
                     buf.write(&expr_buf.buf);
                     buf.write(")");
@@ -562,7 +556,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> {
 
         let mut arm_size = 0;
         for (i, arm) in arms.iter().enumerate() {
-            let &(ws, ref variant, ref params, ref body) = arm;
+            let &(ws, ref target, ref body) = arm;
             self.handle_ws(ws);
 
             if i > 0 {
@@ -573,14 +567,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> {
             }
 
             self.locals.push();
-            match *variant {
-                Some(ref param) => {
-                    self.visit_match_variant(buf, param);
-                }
-                None => buf.write("_"),
-            };
-
-            self.visit_match_params(buf, params);
+            self.visit_target(buf, true, true, target);
             buf.writeln(" => {")?;
 
             arm_size = self.handle(ctx, body, buf, AstLevel::Nested)?;
@@ -614,7 +601,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> {
 
         let flushed = self.write_buf_writable(buf)?;
         buf.write("for (");
-        self.visit_target(buf, true, var);
+        self.visit_target(buf, true, true, var);
         match iter {
             Expr::Range(_, _, _) => buf.writeln(&format!(
                 ", _loop_item) in ::askama::helpers::TemplateLoop::new({}) {{",
@@ -807,7 +794,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> {
         self.handle_ws(ws);
         self.write_buf_writable(buf)?;
         buf.write("let ");
-        self.visit_target(buf, false, var);
+        self.visit_target(buf, false, true, var);
         buf.writeln(";")
     }
 
@@ -830,6 +817,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> {
             Target::Struct(_, named_targets) => named_targets
                 .iter()
                 .any(|(_, target)| self.is_shadowing_variable(target)),
+            _ => panic!("Cannot have literals on the left-hand-side of an assignment."),
         }
     }
 
@@ -857,7 +845,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> {
             buf.write("let ");
         }
 
-        self.visit_target(buf, true, var);
+        self.visit_target(buf, true, true, var);
         buf.writeln(&format!(" = {};", &expr_buf.buf))
     }
 
@@ -1082,84 +1070,6 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> {
         DisplayWrap::Unwrapped
     }
 
-    fn visit_match_variant(&mut self, buf: &mut Buffer, param: &MatchVariant<'_>) -> DisplayWrap {
-        let mut expr_buf = Buffer::new(0);
-        let wrapped = match *param {
-            MatchVariant::StrLit(s) => {
-                expr_buf.write("&");
-                self.visit_str_lit(&mut expr_buf, s)
-            }
-            MatchVariant::CharLit(s) => self.visit_char_lit(&mut expr_buf, s),
-            MatchVariant::NumLit(s) => self.visit_num_lit(&mut expr_buf, s),
-            MatchVariant::Name(s) => {
-                expr_buf.write(s);
-                DisplayWrap::Unwrapped
-            }
-            MatchVariant::Path(ref s) => {
-                expr_buf.write(&s.join("::"));
-                DisplayWrap::Unwrapped
-            }
-        };
-        buf.write(&expr_buf.buf);
-        wrapped
-    }
-
-    fn visit_match_param(&mut self, buf: &mut Buffer, param: &MatchParameter<'_>) -> DisplayWrap {
-        let mut expr_buf = Buffer::new(0);
-        let wrapped = match *param {
-            MatchParameter::NumLit(s) => self.visit_num_lit(&mut expr_buf, s),
-            MatchParameter::StrLit(s) => self.visit_str_lit(&mut expr_buf, s),
-            MatchParameter::CharLit(s) => self.visit_char_lit(&mut expr_buf, s),
-            MatchParameter::Name(s) => {
-                expr_buf.write(s);
-                DisplayWrap::Unwrapped
-            }
-        };
-        buf.write(&expr_buf.buf);
-        wrapped
-    }
-
-    fn visit_match_params(&mut self, buf: &mut Buffer, params: &MatchParameters<'a>) {
-        match params {
-            MatchParameters::Simple(params) => {
-                if !params.is_empty() {
-                    buf.write("(");
-                    for (i, param) in params.iter().enumerate() {
-                        if let MatchParameter::Name(p) = *param {
-                            self.locals.insert_with_default(p);
-                        }
-                        if i > 0 {
-                            buf.write(", ");
-                        }
-                        self.visit_match_param(buf, param);
-                    }
-                    buf.write(")");
-                }
-            }
-            MatchParameters::Named(params) => {
-                buf.write("{");
-                for (i, param) in params.iter().enumerate() {
-                    if let Some(MatchParameter::Name(p)) = param.1 {
-                        let p = normalize_identifier(p);
-                        self.locals.insert_with_default(p);
-                    } else {
-                        self.locals.insert_with_default(param.0);
-                    }
-
-                    if i > 0 {
-                        buf.write(", ");
-                    }
-                    buf.write(param.0);
-                    if let Some(param) = &param.1 {
-                        buf.write(":");
-                        self.visit_match_param(buf, param);
-                    }
-                }
-                buf.write("}");
-            }
-        }
-    }
-
     fn visit_filter(
         &mut self,
         buf: &mut Buffer,
@@ -1514,8 +1424,17 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> {
         DisplayWrap::Unwrapped
     }
 
-    fn visit_target(&mut self, buf: &mut Buffer, initialized: bool, target: &Target<'a>) {
+    fn visit_target(
+        &mut self,
+        buf: &mut Buffer,
+        initialized: bool,
+        first_level: bool,
+        target: &Target<'a>,
+    ) {
         match target {
+            Target::Name("_") => {
+                buf.write("_");
+            }
             Target::Name(name) => {
                 let name = normalize_identifier(name);
                 match initialized {
@@ -1528,7 +1447,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> {
                 buf.write(&path.join("::"));
                 buf.write("(");
                 for target in targets {
-                    self.visit_target(buf, initialized, target);
+                    self.visit_target(buf, initialized, false, target);
                     buf.write(",");
                 }
                 buf.write(")");
@@ -1539,11 +1458,32 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> {
                 for (name, target) in targets {
                     buf.write(normalize_identifier(name));
                     buf.write(": ");
-                    self.visit_target(buf, initialized, target);
+                    self.visit_target(buf, initialized, false, target);
                     buf.write(",");
                 }
                 buf.write(" }");
             }
+            Target::Path(path) => {
+                self.visit_path(buf, path);
+            }
+            Target::StrLit(s) => {
+                if first_level {
+                    buf.write("&");
+                }
+                self.visit_str_lit(buf, s);
+            }
+            Target::NumLit(s) => {
+                if first_level {
+                    buf.write("&");
+                }
+                self.visit_num_lit(buf, s);
+            }
+            Target::CharLit(s) => {
+                if first_level {
+                    buf.write("&");
+                }
+                self.visit_char_lit(buf, s);
+            }
         }
     }
 
diff --git a/askama_shared/src/heritage.rs b/askama_shared/src/heritage.rs
index c03163d..9f90273 100644
--- a/askama_shared/src/heritage.rs
+++ b/askama_shared/src/heritage.rs
@@ -90,7 +90,7 @@ impl<'a> Context<'a> {
                         nested.push(nodes);
                     }
                     Node::Match(_, _, arms, _) => {
-                        for (_, _, _, arm) in arms {
+                        for (_, _, arm) in arms {
                             nested.push(arm);
                         }
                     }
diff --git a/askama_shared/src/parser.rs b/askama_shared/src/parser.rs
index 261887b..a965025 100644
--- a/askama_shared/src/parser.rs
+++ b/askama_shared/src/parser.rs
@@ -91,41 +91,7 @@ impl Expr<'_> {
     }
 }
 
-pub type When<'a> = (
-    Ws,
-    Option<MatchVariant<'a>>,
-    MatchParameters<'a>,
-    Vec<Node<'a>>,
-);
-
-#[derive(Debug, PartialEq)]
-pub enum MatchParameters<'a> {
-    Simple(Vec<MatchParameter<'a>>),
-    Named(Vec<(&'a str, Option<MatchParameter<'a>>)>),
-}
-
-impl<'a> Default for MatchParameters<'a> {
-    fn default() -> Self {
-        MatchParameters::Simple(vec![])
-    }
-}
-
-#[derive(Debug, PartialEq)]
-pub enum MatchParameter<'a> {
-    Name(&'a str),
-    NumLit(&'a str),
-    StrLit(&'a str),
-    CharLit(&'a str),
-}
-
-#[derive(Debug, PartialEq)]
-pub enum MatchVariant<'a> {
-    Path(Vec<&'a str>),
-    Name(&'a str),
-    NumLit(&'a str),
-    StrLit(&'a str),
-    CharLit(&'a str),
-}
+pub type When<'a> = (Ws, Target<'a>, Vec<Node<'a>>);
 
 #[derive(Debug, PartialEq)]
 pub struct Macro<'a> {
@@ -140,6 +106,10 @@ pub enum Target<'a> {
     Name(&'a str),
     Tuple(Vec<&'a str>, Vec<Target<'a>>),
     Struct(Vec<&'a str>, Vec<(&'a str, Target<'a>)>),
+    NumLit(&'a str),
+    StrLit(&'a str),
+    CharLit(&'a str),
+    Path(Vec<&'a str>),
 }
 
 #[derive(Clone, Copy, Debug, PartialEq)]
@@ -149,7 +119,7 @@ pub type Cond<'a> = (Ws, Option<CondTest<'a>>, Vec<Node<'a>>);
 
 #[derive(Debug, PartialEq)]
 pub struct CondTest<'a> {
-    pub target: Option<(MatchVariant<'a>, Option<MatchParameters<'a>>)>,
+    pub target: Option<Target<'a>>,
     pub expr: Expr<'a>,
 }
 
@@ -297,12 +267,8 @@ fn expr_array_lit(i: &[u8]) -> IResult<&[u8], Expr<'_>> {
     )(i)
 }
 
-fn variant_num_lit(i: &[u8]) -> IResult<&[u8], MatchVariant<'_>> {
-    map(num_lit, |s| MatchVariant::NumLit(s))(i)
-}
-
-fn param_num_lit(i: &[u8]) -> IResult<&[u8], MatchParameter<'_>> {
-    map(num_lit, |s| MatchParameter::NumLit(s))(i)
+fn variant_num_lit(i: &[u8]) -> IResult<&[u8], Target<'_>> {
+    map(num_lit, |s| Target::NumLit(s))(i)
 }
 
 fn str_lit(i: &[u8]) -> IResult<&[u8], &str> {
@@ -320,12 +286,8 @@ fn expr_str_lit(i: &[u8]) -> IResult<&[u8], Expr<'_>> {
     map(str_lit, |s| Expr::StrLit(s))(i)
 }
 
-fn variant_str_lit(i: &[u8]) -> IResult<&[u8], MatchVariant<'_>> {
-    map(str_lit, |s| MatchVariant::StrLit(s))(i)
-}
-
-fn param_str_lit(i: &[u8]) -> IResult<&[u8], MatchParameter<'_>> {
-    map(str_lit, |s| MatchParameter::StrLit(s))(i)
+fn variant_str_lit(i: &[u8]) -> IResult<&[u8], Target<'_>> {
+    map(str_lit, |s| Target::StrLit(s))(i)
 }
 
 fn char_lit(i: &[u8]) -> IResult<&[u8], &str> {
@@ -343,12 +305,8 @@ fn expr_char_lit(i: &[u8]) -> IResult<&[u8], Expr<'_>> {
     map(char_lit, |s| Expr::CharLit(s))(i)
 }
 
-fn variant_char_lit(i: &[u8]) -> IResult<&[u8], MatchVariant<'_>> {
-    map(char_lit, |s| MatchVariant::CharLit(s))(i)
-}
-
-fn param_char_lit(i: &[u8]) -> IResult<&[u8], MatchParameter<'_>> {
-    map(char_lit, |s| MatchParameter::CharLit(s))(i)
+fn variant_char_lit(i: &[u8]) -> IResult<&[u8], Target<'_>> {
+    map(char_lit, |s| Target::CharLit(s))(i)
 }
 
 fn expr_var(i: &[u8]) -> IResult<&[u8], Expr<'_>> {
@@ -399,12 +357,6 @@ fn expr_path_call(i: &[u8]) -> IResult<&[u8], Expr<'_>> {
     Ok((i, Expr::PathCall(path, args)))
 }
 
-fn variant_path(i: &[u8]) -> IResult<&[u8], MatchVariant<'_>> {
-    map(separated_list1(ws(tag("::")), identifier), |path| {
-        MatchVariant::Path(path)
-    })(i)
-}
-
 fn named_target(i: &[u8]) -> IResult<&[u8], (&str, Target<'_>)> {
     let (i, (src, target)) = pair(identifier, opt(preceded(ws(tag(":")), target)))(i)?;
     Ok((i, (src, target.unwrap_or(Target::Name(src)))))
@@ -413,6 +365,12 @@ fn named_target(i: &[u8]) -> IResult<&[u8], (&str, Target<'_>)> {
 fn target(i: &[u8]) -> IResult<&[u8], Target<'_>> {
     let mut opt_opening_paren = map(opt(ws(tag("("))), |o| o.is_some());
     let mut opt_closing_paren = map(opt(ws(tag(")"))), |o| o.is_some());
+    let mut opt_opening_brace = map(opt(ws(tag("{"))), |o| o.is_some());
+
+    let (i, lit) = opt(alt((variant_str_lit, variant_char_lit, variant_num_lit)))(i)?;
+    if let Some(lit) = lit {
+        return Ok((i, lit));
+    }
 
     // match tuples and unused parentheses
     let (i, target_is_tuple) = opt_opening_paren(i)?;
@@ -442,6 +400,9 @@ fn target(i: &[u8]) -> IResult<&[u8], Target<'_>> {
     // match structs
     let (i, path) = opt(path)(i)?;
     if let Some(path) = path {
+        let i_before_matching_with = i;
+        let (i, _) = opt(ws(tag("with")))(i)?;
+
         let (i, is_unnamed_struct) = opt_opening_paren(i)?;
         if is_unnamed_struct {
             let (i, targets) = alt((
@@ -452,33 +413,27 @@ fn target(i: &[u8]) -> IResult<&[u8], Target<'_>> {
                 ),
             ))(i)?;
             return Ok((i, Target::Tuple(path, targets)));
-        } else {
-            let (i, targets) = preceded(
-                ws(tag("{")),
-                alt((
-                    map(tag("}"), |_| Vec::new()),
-                    terminated(
-                        separated_list1(ws(tag(",")), named_target),
-                        pair(opt(ws(tag(","))), ws(tag("}"))),
-                    ),
-                )),
-            )(i)?;
+        }
+
+        let (i, is_named_struct) = opt_opening_brace(i)?;
+        if is_named_struct {
+            let (i, targets) = alt((
+                map(tag("}"), |_| Vec::new()),
+                terminated(
+                    separated_list1(ws(tag(",")), named_target),
+                    pair(opt(ws(tag(","))), ws(tag("}"))),
+                ),
+            ))(i)?;
             return Ok((i, Target::Struct(path, targets)));
         }
+
+        return Ok((i_before_matching_with, Target::Path(path)));
     }
 
-    // neither nor struct
+    // neither literal nor struct nor path
     map(identifier, Target::Name)(i)
 }
 
-fn variant_name(i: &[u8]) -> IResult<&[u8], MatchVariant<'_>> {
-    map(identifier, |s| MatchVariant::Name(s))(i)
-}
-
-fn param_name(i: &[u8]) -> IResult<&[u8], MatchParameter<'_>> {
-    map(identifier, |s| MatchParameter::Name(s))(i)
-}
-
 fn arguments(i: &[u8]) -> IResult<&[u8], Vec<Expr<'_>>> {
     delimited(
         ws(tag("(")),
@@ -547,35 +502,6 @@ fn parameters(i: &[u8]) -> IResult<&[u8], Vec<&str>> {
     )(i)
 }
 
-fn with_parameters(i: &[u8]) -> IResult<&[u8], MatchParameters<'_>> {
-    let (i, (_, value)) = tuple((
-        tag("with"),
-        alt((match_simple_parameters, match_named_parameters)),
-    ))(i)?;
-    Ok((i, value))
-}
-
-fn match_simple_parameters(i: &[u8]) -> IResult<&[u8], MatchParameters<'_>> {
-    delimited(
-        ws(tag("(")),
-        map(separated_list0(tag(","), ws(match_parameter)), |mps| {
-            MatchParameters::Simple(mps)
-        }),
-        tag(")"),
-    )(i)
-}
-
-fn match_named_parameters(i: &[u8]) -> IResult<&[u8], MatchParameters<'_>> {
-    delimited(
-        ws(tag("{")),
-        map(
-            separated_list0(tag(","), ws(match_named_parameter)),
-            MatchParameters::Named,
-        ),
-        tag("}"),
-    )(i)
-}
-
 fn expr_group(i: &[u8]) -> IResult<&[u8], Expr<'_>> {
     map(delimited(ws(char('(')), expr_any, ws(char(')'))), |s| {
         Expr::Group(Box::new(s))
@@ -598,26 +524,6 @@ fn expr_single(i: &[u8]) -> IResult<&[u8], Expr<'_>> {
     ))(i)
 }
 
-fn match_variant(i: &[u8]) -> IResult<&[u8], MatchVariant<'_>> {
-    alt((
-        variant_path,
-        variant_name,
-        variant_num_lit,
-        variant_str_lit,
-        variant_char_lit,
-    ))(i)
-}
-
-fn match_parameter(i: &[u8]) -> IResult<&[u8], MatchParameter<'_>> {
-    alt((param_name, param_num_lit, param_str_lit, param_char_lit))(i)
-}
-
-fn match_named_parameter(i: &[u8]) -> IResult<&[u8], (&str, Option<MatchParameter<'_>>)> {
-    let param = tuple((ws(tag(":")), match_parameter));
-    let (i, (name, param)) = tuple((identifier, opt(param)))(i)?;
-    Ok((i, (name, param.map(|s| s.1))))
-}
-
 fn attr(i: &[u8]) -> IResult<&[u8], (&str, Option<Vec<Expr<'_>>>)> {
     let (i, (_, attr, args)) =
         tuple((ws(tag(".")), alt((num_lit, identifier)), ws(opt(arguments))))(i)?;
@@ -789,8 +695,7 @@ fn cond_if(i: &[u8]) -> IResult<&[u8], CondTest<'_>> {
         ws(tag("if")),
         opt(tuple((
             ws(alt((tag("let"), tag("set")))),
-            ws(match_variant),
-            opt(alt((match_simple_parameters, match_named_parameters))),
+            ws(target),
             ws(tag("=")),
         ))),
         ws(expr_any),
@@ -799,7 +704,7 @@ fn cond_if(i: &[u8]) -> IResult<&[u8], CondTest<'_>> {
     Ok((
         i,
         CondTest {
-            target: dest.map(|(_, variant, params, _)| (variant, params)),
+            target: dest.map(|(_, target, _)| target),
             expr,
         },
     ))
@@ -851,12 +756,7 @@ fn match_else_block<'a>(i: &'a [u8], s: &'a Syntax<'a>) -> IResult<&'a [u8], Whe
     let (i, (_, pws, _, nws, _, block)) = p(i)?;
     Ok((
         i,
-        (
-            Ws(pws.is_some(), nws.is_some()),
-            None,
-            MatchParameters::Simple(vec![]),
-            block,
-        ),
+        (Ws(pws.is_some(), nws.is_some()), Target::Name("_"), block),
     ))
 }
 
@@ -865,22 +765,13 @@ fn when_block<'a>(i: &'a [u8], s: &'a Syntax<'a>) -> IResult<&'a [u8], When<'a>>
         |i| tag_block_start(i, s),
         opt(tag("-")),
         ws(tag("when")),
-        ws(match_variant),
-        opt(ws(with_parameters)),
+        ws(target),
         opt(tag("-")),
         |i| tag_block_end(i, s),
         |i| parse_template(i, s),
     ));
-    let (i, (_, pws, _, variant, params, nws, _, block)) = p(i)?;
-    Ok((
-        i,
-        (
-            Ws(pws.is_some(), nws.is_some()),
-            Some(variant),
-            params.unwrap_or_default(),
-            block,
-        ),
-    ))
+    let (i, (_, pws, _, target, nws, _, block)) = p(i)?;
+    Ok((i, (Ws(pws.is_some(), nws.is_some()), target, block)))
 }
 
 fn block_match<'a>(i: &'a [u8], s: &'a Syntax<'a>) -> IResult<&'a [u8], Node<'a>> {


Test patch:
diff --git a/testing/templates/match-option-result-option.html b/testing/templates/match-option-result-option.html
new file mode 100644
index 0000000..25396b6
--- /dev/null
+++ b/testing/templates/match-option-result-option.html
@@ -0,0 +1,10 @@
+{%- match foo -%}
+    {%- when None -%}
+        nothing
+    {%- when Some(Err(err)) -%}
+        err={{err}}
+    {%- when Some(Ok(None)) -%}
+        num=absent
+    {%- when Some(Ok(Some(num))) -%}
+        num={{num}}
+{%- endmatch -%}
diff --git a/testing/tests/let_destructoring.rs b/testing/tests/let_destructoring.rs
index 20f7fff..5cb7ae4 100644
--- a/testing/tests/let_destructoring.rs
+++ b/testing/tests/let_destructoring.rs
@@ -105,3 +105,17 @@ fn test_let_destruct_with_path() {
     };
     assert_eq!(t.render().unwrap(), "hello");
 }
+
+#[derive(Template)]
+#[template(source = "{% let some::path::Struct with (v) = v %}{{v}}", ext = "txt")]
+struct LetDestructoringWithPathAndWithKeyword<'a> {
+    v: some::path::Struct<'a>,
+}
+
+#[test]
+fn test_let_destruct_with_path_and_with_keyword() {
+    let t = LetDestructoringWithPathAndWithKeyword {
+        v: some::path::Struct("hello"),
+    };
+    assert_eq!(t.render().unwrap(), "hello");
+}
diff --git a/testing/tests/matches.rs b/testing/tests/matches.rs
index d75a6c4..c328e39 100644
--- a/testing/tests/matches.rs
+++ b/testing/tests/matches.rs
@@ -113,3 +113,44 @@ fn test_match_no_whitespace() {
     let s = MatchNoWhitespace { foo: Some(1) };
     assert_eq!(s.render().unwrap(), "1");
 }
+
+#[derive(Template)]
+#[template(
+    source = "{% match foo %}{% when Some(bar) %}{{ bar }}{% when None %}{% endmatch %}",
+    ext = "txt"
+)]
+struct MatchWithoutWithKeyword {
+    foo: Option<usize>,
+}
+
+#[test]
+fn test_match_without_with_keyword() {
+    let s = MatchWithoutWithKeyword { foo: Some(1) };
+    assert_eq!(s.render().unwrap(), "1");
+    let s = MatchWithoutWithKeyword { foo: None };
+    assert_eq!(s.render().unwrap(), "");
+}
+
+#[derive(Template)]
+#[template(path = "match-option-result-option.html")]
+struct MatchOptionResultOption {
+    foo: Option<Result<Option<usize>, &'static str>>,
+}
+
+#[test]
+fn test_match_option_result_option() {
+    let s = MatchOptionResultOption { foo: None };
+    assert_eq!(s.render().unwrap(), "nothing");
+    let s = MatchOptionResultOption {
+        foo: Some(Err("fail")),
+    };
+    assert_eq!(s.render().unwrap(), "err=fail");
+    let s = MatchOptionResultOption {
+        foo: Some(Ok(None)),
+    };
+    assert_eq!(s.render().unwrap(), "num=absent");
+    let s = MatchOptionResultOption {
+        foo: Some(Ok(Some(4711))),
+    };
+    assert_eq!(s.render().unwrap(), "num=4711");
+}
diff --git a/testing/tests/ui/lit_on_assignment_lhs.rs b/testing/tests/ui/lit_on_assignment_lhs.rs
new file mode 100644
index 0000000..1793770
--- /dev/null
+++ b/testing/tests/ui/lit_on_assignment_lhs.rs
@@ -0,0 +1,11 @@
+use askama::Template;
+
+#[derive(Template)]
+#[template(
+    source = "{%let 7=x%}",
+    ext = "txt"
+)]
+struct MyTemplate;
+
+fn main() {
+}
diff --git a/testing/tests/ui/lit_on_assignment_lhs.stderr b/testing/tests/ui/lit_on_assignment_lhs.stderr
new file mode 100644
index 0000000..fa488cb
--- /dev/null
+++ b/testing/tests/ui/lit_on_assignment_lhs.stderr
@@ -0,0 +1,7 @@
+error: proc-macro derive panicked
+ --> $DIR/lit_on_assignment_lhs.rs:3:10
+  |
+3 | #[derive(Template)]
+  |          ^^^^^^^^
+  |
+  = help: message: Cannot have literals on the left-hand-side of an assignment.
diff --git a/testing/tests/vars.rs b/testing/tests/vars.rs
index 75d10e5..5447351 100644
--- a/testing/tests/vars.rs
+++ b/testing/tests/vars.rs
@@ -105,3 +105,29 @@ fn test_destruct_tuple() {
     };
     assert_eq!(t.render().unwrap(), "wxyz\nwz\nw");
 }
+
+#[derive(Template)]
+#[template(
+    source = "{% let x = 1 %}{% for x in x..=x %}{{ x }}{% endfor %}",
+    ext = "txt"
+)]
+struct DeclRange;
+
+#[test]
+fn test_decl_range() {
+    let t = DeclRange;
+    assert_eq!(t.render().unwrap(), "1");
+}
+
+#[derive(Template)]
+#[template(
+    source = "{% let x %}{% let x = 1 %}{% for x in x..=x %}{{ x }}{% endfor %}",
+    ext = "txt"
+)]
+struct DeclAssignRange;
+
+#[test]
+fn test_decl_assign_range() {
+    let t = DeclAssignRange;
+    assert_eq!(t.render().unwrap(), "1");
+}