diff options
-rw-r--r-- | askama_shared/src/generator.rs | 144 | ||||
-rw-r--r-- | askama_shared/src/heritage.rs | 2 | ||||
-rw-r--r-- | askama_shared/src/parser.rs | 190 | ||||
-rw-r--r-- | testing/templates/match-option-result-option.html | 10 | ||||
-rw-r--r-- | testing/tests/matches.rs | 24 | ||||
-rw-r--r-- | testing/tests/ui/lit_on_assignment_lhs.rs | 11 | ||||
-rw-r--r-- | testing/tests/ui/lit_on_assignment_lhs.stderr | 7 | ||||
-rw-r--r-- | testing/tests/vars.rs | 26 |
8 files changed, 161 insertions, 253 deletions
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) = ¶m.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 29b325d..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,7 +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(( @@ -453,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("(")), @@ -548,35 +502,6 @@ fn parameters(i: &[u8]) -> IResult<&[u8], Vec<&str>> { )(i) } -fn with_parameters(i: &[u8]) -> IResult<&[u8], MatchParameters<'_>> { - let (i, (_, value)) = tuple(( - opt(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)) @@ -599,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)?; @@ -790,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), @@ -800,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, }, )) @@ -852,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), )) } @@ -866,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>> { 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/matches.rs b/testing/tests/matches.rs index 380a69c..c328e39 100644 --- a/testing/tests/matches.rs +++ b/testing/tests/matches.rs @@ -130,3 +130,27 @@ fn test_match_without_with_keyword() { 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"); +} |