mirror of
https://github.com/askama-rs/askama.git
synced 2025-10-02 07:20:55 +00:00
Merge pull request #36 from Kijewski/rest-pattern-struct#2
Add support for `..` in let pattern matching for structs (alternative take)
This commit is contained in:
commit
eb419049bf
@ -12,10 +12,9 @@ use crate::input::{Source, TemplateInput};
|
||||
use crate::{CompileError, CRATE};
|
||||
|
||||
use parser::node::{
|
||||
Call, Comment, CondTest, FilterBlock, If, Include, Let, Lit, Loop, Match, Target, Whitespace,
|
||||
Ws,
|
||||
Call, Comment, CondTest, FilterBlock, If, Include, Let, Lit, Loop, Match, Whitespace, Ws,
|
||||
};
|
||||
use parser::{Expr, Filter, Node, WithSpan};
|
||||
use parser::{Expr, Filter, Node, Target, WithSpan};
|
||||
use quote::quote;
|
||||
|
||||
pub(crate) struct Generator<'a> {
|
||||
@ -1788,8 +1787,8 @@ impl<'a> Generator<'a> {
|
||||
target: &Target<'a>,
|
||||
) {
|
||||
match target {
|
||||
Target::Name("_") => {
|
||||
buf.write("_");
|
||||
Target::Placeholder(s) | Target::Rest(s) => {
|
||||
buf.write(s);
|
||||
}
|
||||
Target::Name(name) => {
|
||||
let name = normalize_identifier(name);
|
||||
@ -1824,6 +1823,11 @@ impl<'a> Generator<'a> {
|
||||
buf.write(SeparatedPath(path));
|
||||
buf.write(" { ");
|
||||
for (name, target) in targets {
|
||||
if let Target::Rest(s) = target {
|
||||
buf.write(s);
|
||||
continue;
|
||||
}
|
||||
|
||||
buf.write(normalize_identifier(name));
|
||||
buf.write(": ");
|
||||
self.visit_target(buf, initialized, false, target);
|
||||
|
@ -22,6 +22,9 @@ pub mod expr;
|
||||
pub use expr::{Expr, Filter};
|
||||
pub mod node;
|
||||
pub use node::Node;
|
||||
|
||||
mod target;
|
||||
pub use target::Target;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
|
@ -3,19 +3,15 @@ use std::str;
|
||||
use nom::branch::alt;
|
||||
use nom::bytes::complete::{tag, take_till};
|
||||
use nom::character::complete::char;
|
||||
use nom::combinator::{
|
||||
complete, consumed, cut, eof, map, map_res, not, opt, peek, recognize, value,
|
||||
};
|
||||
use nom::combinator::{complete, consumed, cut, eof, map, not, opt, peek, recognize, value};
|
||||
use nom::error::ErrorKind;
|
||||
use nom::error_position;
|
||||
use nom::multi::{fold_many0, many0, many1, separated_list0, separated_list1};
|
||||
use nom::sequence::{delimited, pair, preceded, terminated, tuple};
|
||||
use nom::multi::{many0, many1, separated_list0};
|
||||
use nom::sequence::{delimited, pair, preceded, tuple};
|
||||
|
||||
use crate::{not_ws, ErrorContext, ParseResult, WithSpan};
|
||||
|
||||
use super::{
|
||||
bool_lit, char_lit, filter, identifier, is_ws, keyword, num_lit, path_or_identifier, skip_till,
|
||||
str_lit, ws, Expr, Filter, PathOrIdentifier, State,
|
||||
use crate::{
|
||||
filter, identifier, is_ws, keyword, not_ws, skip_till, str_lit, ws, ErrorContext, Expr, Filter,
|
||||
ParseResult, State, Target, WithSpan,
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
@ -177,150 +173,6 @@ impl<'a> Node<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
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),
|
||||
BoolLit(&'a str),
|
||||
Path(Vec<&'a str>),
|
||||
OrChain(Vec<Target<'a>>),
|
||||
}
|
||||
|
||||
impl<'a> Target<'a> {
|
||||
/// Parses multiple targets with `or` separating them
|
||||
pub(super) fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> {
|
||||
map(
|
||||
separated_list1(ws(tag("or")), |i| s.nest(i, |i| Self::parse_one(i, s))),
|
||||
|mut opts| match opts.len() {
|
||||
1 => opts.pop().unwrap(),
|
||||
_ => Self::OrChain(opts),
|
||||
},
|
||||
)(i)
|
||||
}
|
||||
|
||||
/// Parses a single target without an `or`, unless it is wrapped in parentheses.
|
||||
fn parse_one(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> {
|
||||
let mut opt_opening_paren = map(opt(ws(char('('))), |o| o.is_some());
|
||||
let mut opt_closing_paren = map(opt(ws(char(')'))), |o| o.is_some());
|
||||
let mut opt_opening_brace = map(opt(ws(char('{'))), |o| o.is_some());
|
||||
|
||||
let (i, lit) = opt(Self::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)?;
|
||||
if target_is_tuple {
|
||||
let (i, is_empty_tuple) = opt_closing_paren(i)?;
|
||||
if is_empty_tuple {
|
||||
return Ok((i, Self::Tuple(Vec::new(), Vec::new())));
|
||||
}
|
||||
|
||||
let (i, first_target) = Self::parse(i, s)?;
|
||||
let (i, is_unused_paren) = opt_closing_paren(i)?;
|
||||
if is_unused_paren {
|
||||
return Ok((i, first_target));
|
||||
}
|
||||
|
||||
let mut targets = vec![first_target];
|
||||
let (i, _) = cut(tuple((
|
||||
fold_many0(
|
||||
preceded(ws(char(',')), |i| Self::parse(i, s)),
|
||||
|| (),
|
||||
|_, target| {
|
||||
targets.push(target);
|
||||
},
|
||||
),
|
||||
opt(ws(char(','))),
|
||||
ws(cut(char(')'))),
|
||||
)))(i)?;
|
||||
return Ok((i, Self::Tuple(Vec::new(), targets)));
|
||||
}
|
||||
|
||||
let path = |i| {
|
||||
map_res(path_or_identifier, |v| match v {
|
||||
PathOrIdentifier::Path(v) => Ok(v),
|
||||
PathOrIdentifier::Identifier(v) => Err(v),
|
||||
})(i)
|
||||
};
|
||||
|
||||
// match structs
|
||||
let (i, path) = opt(path)(i)?;
|
||||
if let Some(path) = path {
|
||||
let i_before_matching_with = i;
|
||||
let (i, _) = opt(ws(keyword("with")))(i)?;
|
||||
|
||||
let (i, is_unnamed_struct) = opt_opening_paren(i)?;
|
||||
if is_unnamed_struct {
|
||||
let (i, targets) = alt((
|
||||
map(char(')'), |_| Vec::new()),
|
||||
terminated(
|
||||
cut(separated_list1(ws(char(',')), |i| Self::parse(i, s))),
|
||||
pair(opt(ws(char(','))), ws(cut(char(')')))),
|
||||
),
|
||||
))(i)?;
|
||||
return Ok((i, Self::Tuple(path, targets)));
|
||||
}
|
||||
|
||||
let (i, is_named_struct) = opt_opening_brace(i)?;
|
||||
if is_named_struct {
|
||||
let (i, targets) = alt((
|
||||
map(char('}'), |_| Vec::new()),
|
||||
terminated(
|
||||
cut(separated_list1(ws(char(',')), |i| Self::named(i, s))),
|
||||
pair(opt(ws(char(','))), ws(cut(char('}')))),
|
||||
),
|
||||
))(i)?;
|
||||
return Ok((i, Self::Struct(path, targets)));
|
||||
}
|
||||
|
||||
return Ok((i_before_matching_with, Self::Path(path)));
|
||||
}
|
||||
|
||||
// neither literal nor struct nor path
|
||||
let (new_i, name) = identifier(i)?;
|
||||
Ok((new_i, Self::verify_name(i, name)?))
|
||||
}
|
||||
|
||||
fn lit(i: &'a str) -> ParseResult<'a, Self> {
|
||||
alt((
|
||||
map(str_lit, Self::StrLit),
|
||||
map(char_lit, Self::CharLit),
|
||||
map(num_lit, Self::NumLit),
|
||||
map(bool_lit, Self::BoolLit),
|
||||
))(i)
|
||||
}
|
||||
|
||||
fn named(init_i: &'a str, s: &State<'_>) -> ParseResult<'a, (&'a str, Self)> {
|
||||
let (i, (src, target)) = pair(
|
||||
identifier,
|
||||
opt(preceded(ws(char(':')), |i| Self::parse(i, s))),
|
||||
)(init_i)?;
|
||||
|
||||
let target = match target {
|
||||
Some(target) => target,
|
||||
None => Self::verify_name(init_i, src)?,
|
||||
};
|
||||
|
||||
Ok((i, (src, target)))
|
||||
}
|
||||
|
||||
fn verify_name(input: &'a str, name: &'a str) -> Result<Self, nom::Err<ErrorContext<'a>>> {
|
||||
match name {
|
||||
"self" | "writer" => Err(nom::Err::Failure(ErrorContext::new(
|
||||
format!("cannot use `{name}` as a name"),
|
||||
input,
|
||||
))),
|
||||
_ => Ok(Self::Name(name)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct When<'a> {
|
||||
pub ws: Ws,
|
||||
@ -347,7 +199,7 @@ impl<'a> When<'a> {
|
||||
WithSpan::new(
|
||||
Self {
|
||||
ws: Ws(pws, nws),
|
||||
target: Target::Name("_"),
|
||||
target: Target::Placeholder("_"),
|
||||
nodes,
|
||||
},
|
||||
start,
|
||||
|
213
rinja_parser/src/target.rs
Normal file
213
rinja_parser/src/target.rs
Normal file
@ -0,0 +1,213 @@
|
||||
use nom::branch::alt;
|
||||
use nom::bytes::complete::tag;
|
||||
use nom::character::complete::{char, one_of};
|
||||
use nom::combinator::{consumed, map, map_res, opt};
|
||||
use nom::multi::separated_list1;
|
||||
use nom::sequence::{pair, preceded};
|
||||
|
||||
use crate::{
|
||||
bool_lit, char_lit, identifier, keyword, num_lit, path_or_identifier, str_lit, ws,
|
||||
ErrorContext, ParseErr, ParseResult, PathOrIdentifier, State,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
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),
|
||||
BoolLit(&'a str),
|
||||
Path(Vec<&'a str>),
|
||||
OrChain(Vec<Target<'a>>),
|
||||
Placeholder(&'a str),
|
||||
Rest(&'a str),
|
||||
}
|
||||
|
||||
impl<'a> Target<'a> {
|
||||
/// Parses multiple targets with `or` separating them
|
||||
pub(super) fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> {
|
||||
map(
|
||||
separated_list1(ws(tag("or")), |i| s.nest(i, |i| Self::parse_one(i, s))),
|
||||
|mut opts| match opts.len() {
|
||||
1 => opts.pop().unwrap(),
|
||||
_ => Self::OrChain(opts),
|
||||
},
|
||||
)(i)
|
||||
}
|
||||
|
||||
/// Parses a single target without an `or`, unless it is wrapped in parentheses.
|
||||
fn parse_one(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> {
|
||||
let mut opt_opening_paren = map(opt(ws(char('('))), |o| o.is_some());
|
||||
let mut opt_opening_brace = map(opt(ws(char('{'))), |o| o.is_some());
|
||||
|
||||
let (i, lit) = opt(Self::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)?;
|
||||
if target_is_tuple {
|
||||
let (i, (singleton, mut targets)) = collect_targets(i, s, ')', Self::unnamed)?;
|
||||
if singleton {
|
||||
return Ok((i, targets.pop().unwrap()));
|
||||
}
|
||||
return Ok((i, Self::Tuple(Vec::new(), only_one_rest_pattern(targets)?)));
|
||||
}
|
||||
|
||||
let path = |i| {
|
||||
map_res(path_or_identifier, |v| match v {
|
||||
PathOrIdentifier::Path(v) => Ok(v),
|
||||
PathOrIdentifier::Identifier(v) => Err(v),
|
||||
})(i)
|
||||
};
|
||||
|
||||
// match structs
|
||||
let (i, path) = opt(path)(i)?;
|
||||
if let Some(path) = path {
|
||||
let i_before_matching_with = i;
|
||||
let (i, _) = opt(ws(keyword("with")))(i)?;
|
||||
|
||||
let (i, is_unnamed_struct) = opt_opening_paren(i)?;
|
||||
if is_unnamed_struct {
|
||||
let (i, (_, targets)) = collect_targets(i, s, ')', Self::unnamed)?;
|
||||
return Ok((i, Self::Tuple(path, only_one_rest_pattern(targets)?)));
|
||||
}
|
||||
|
||||
let (i, is_named_struct) = opt_opening_brace(i)?;
|
||||
if is_named_struct {
|
||||
let (i, (_, targets)) = collect_targets(i, s, '}', Self::named)?;
|
||||
return Ok((i, Self::Struct(path, targets)));
|
||||
}
|
||||
|
||||
return Ok((i_before_matching_with, Self::Path(path)));
|
||||
}
|
||||
|
||||
// neither literal nor struct nor path
|
||||
let (new_i, name) = identifier(i)?;
|
||||
let target = match name {
|
||||
"_" => Self::Placeholder(name),
|
||||
_ => verify_name(i, name)?,
|
||||
};
|
||||
Ok((new_i, target))
|
||||
}
|
||||
|
||||
fn lit(i: &'a str) -> ParseResult<'a, Self> {
|
||||
alt((
|
||||
map(str_lit, Self::StrLit),
|
||||
map(char_lit, Self::CharLit),
|
||||
map(num_lit, Self::NumLit),
|
||||
map(bool_lit, Self::BoolLit),
|
||||
))(i)
|
||||
}
|
||||
|
||||
fn unnamed(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> {
|
||||
alt((Self::rest, |i| Self::parse(i, s)))(i)
|
||||
}
|
||||
|
||||
fn named(init_i: &'a str, s: &State<'_>) -> ParseResult<'a, (&'a str, Self)> {
|
||||
let (i, rest) = opt(consumed(Self::rest))(init_i)?;
|
||||
if let Some(rest) = rest {
|
||||
let (_, chr) = ws(opt(one_of(",:")))(i)?;
|
||||
if let Some(chr) = chr {
|
||||
return Err(nom::Err::Failure(ErrorContext::new(
|
||||
format!(
|
||||
"unexpected `{chr}` character after `..`\n\
|
||||
note that in a named struct, `..` must come last to ignore other members"
|
||||
),
|
||||
i,
|
||||
)));
|
||||
}
|
||||
return Ok((i, rest));
|
||||
}
|
||||
|
||||
let (i, (src, target)) = pair(
|
||||
identifier,
|
||||
opt(preceded(ws(char(':')), |i| Self::parse(i, s))),
|
||||
)(init_i)?;
|
||||
|
||||
if src == "_" {
|
||||
return Err(nom::Err::Failure(ErrorContext::new(
|
||||
"cannot use placeholder `_` as source in named struct",
|
||||
init_i,
|
||||
)));
|
||||
}
|
||||
|
||||
let target = match target {
|
||||
Some(target) => target,
|
||||
None => verify_name(init_i, src)?,
|
||||
};
|
||||
Ok((i, (src, target)))
|
||||
}
|
||||
|
||||
fn rest(i: &'a str) -> ParseResult<'a, Self> {
|
||||
map(tag(".."), Self::Rest)(i)
|
||||
}
|
||||
}
|
||||
|
||||
fn verify_name<'a>(
|
||||
input: &'a str,
|
||||
name: &'a str,
|
||||
) -> Result<Target<'a>, nom::Err<ErrorContext<'a>>> {
|
||||
match name {
|
||||
"self" | "writer" => Err(nom::Err::Failure(ErrorContext::new(
|
||||
format!("cannot use `{name}` as a name"),
|
||||
input,
|
||||
))),
|
||||
_ => Ok(Target::Name(name)),
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_targets<'a, T>(
|
||||
i: &'a str,
|
||||
s: &State<'_>,
|
||||
delim: char,
|
||||
mut one: impl FnMut(&'a str, &State<'_>) -> ParseResult<'a, T>,
|
||||
) -> ParseResult<'a, (bool, Vec<T>)> {
|
||||
let opt_comma = |i| map(ws(opt(char(','))), |o| o.is_some())(i);
|
||||
let opt_end = |i| map(ws(opt(char(delim))), |o| o.is_some())(i);
|
||||
|
||||
let (i, has_end) = opt_end(i)?;
|
||||
if has_end {
|
||||
return Ok((i, (false, Vec::new())));
|
||||
}
|
||||
|
||||
let (i, targets) = opt(separated_list1(ws(char(',')), |i| one(i, s)))(i)?;
|
||||
let Some(targets) = targets else {
|
||||
return Err(nom::Err::Failure(ErrorContext::new(
|
||||
"expected comma separated list of members",
|
||||
i,
|
||||
)));
|
||||
};
|
||||
|
||||
let (i, (has_comma, has_end)) = pair(opt_comma, opt_end)(i)?;
|
||||
if !has_end {
|
||||
let msg = match has_comma {
|
||||
true => format!("expected member, or `{delim}` as terminator"),
|
||||
false => format!("expected `,` for more members, or `{delim}` as terminator"),
|
||||
};
|
||||
return Err(nom::Err::Failure(ErrorContext::new(msg, i)));
|
||||
}
|
||||
|
||||
let singleton = !has_comma && targets.len() == 1;
|
||||
Ok((i, (singleton, targets)))
|
||||
}
|
||||
|
||||
fn only_one_rest_pattern(targets: Vec<Target<'_>>) -> Result<Vec<Target<'_>>, ParseErr<'_>> {
|
||||
let snd_wildcard = targets
|
||||
.iter()
|
||||
.filter_map(|t| match t {
|
||||
Target::Rest(s) => Some(s),
|
||||
_ => None,
|
||||
})
|
||||
.nth(1);
|
||||
if let Some(snd_wildcard) = snd_wildcard {
|
||||
return Err(nom::Err::Failure(ErrorContext::new(
|
||||
"`..` can only be used once per tuple pattern",
|
||||
snd_wildcard,
|
||||
)));
|
||||
}
|
||||
Ok(targets)
|
||||
}
|
@ -119,3 +119,73 @@ fn test_let_destruct_with_path_and_with_keyword() {
|
||||
};
|
||||
assert_eq!(t.render().unwrap(), "hello");
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(
|
||||
source = "
|
||||
{%- if let RestPattern2 { a, b } = x -%}hello {{ a }}{%- endif -%}
|
||||
{%- if let RestPattern2 { a, b, } = x -%}hello {{ b }}{%- endif -%}
|
||||
{%- if let RestPattern2 { a, .. } = x -%}hello {{ a }}{%- endif -%}
|
||||
",
|
||||
ext = "html"
|
||||
)]
|
||||
struct RestPattern {
|
||||
x: RestPattern2,
|
||||
}
|
||||
|
||||
struct RestPattern2 {
|
||||
a: u32,
|
||||
b: u32,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_has_rest_pattern() {
|
||||
let t = RestPattern {
|
||||
x: RestPattern2 { a: 0, b: 1 },
|
||||
};
|
||||
assert_eq!(t.render().unwrap(), "hello 0hello 1hello 0");
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
struct X {
|
||||
a: u32,
|
||||
b: u32,
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(
|
||||
source = "
|
||||
{%- if let X { a, .. } = x -%}hello {{ a }}{%- endif -%}
|
||||
",
|
||||
ext = "html"
|
||||
)]
|
||||
struct T1 {
|
||||
x: X,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_t1() {
|
||||
let t = T1 {
|
||||
x: X { a: 1, b: 2 },
|
||||
};
|
||||
assert_eq!(t.render().unwrap(), "hello 1");
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(
|
||||
source = "
|
||||
{%- if let X { .. } = x -%}hello{%- endif -%}
|
||||
",
|
||||
ext = "html"
|
||||
)]
|
||||
struct T2 {
|
||||
x: X,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_t2() {
|
||||
let t = T2 {
|
||||
x: X { a: 1, b: 2 },
|
||||
};
|
||||
assert_eq!(t.render().unwrap(), "hello");
|
||||
}
|
||||
|
78
testing/tests/rest_pattern.rs
Normal file
78
testing/tests/rest_pattern.rs
Normal file
@ -0,0 +1,78 @@
|
||||
use rinja::Template;
|
||||
|
||||
#[test]
|
||||
fn a() {
|
||||
#[derive(Template)]
|
||||
#[template(source = "{% if let (a, ..) = abc %}-{{a}}-{% endif %}", ext = "txt")]
|
||||
struct Tmpl {
|
||||
abc: (u32, u32, u32),
|
||||
}
|
||||
|
||||
assert_eq!(Tmpl { abc: (1, 2, 3) }.to_string(), "-1-");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ab() {
|
||||
#[derive(Template)]
|
||||
#[template(
|
||||
source = "{% if let (a, b, ..) = abc %}-{{a}}{{b}}-{% endif %}",
|
||||
ext = "txt"
|
||||
)]
|
||||
struct Tmpl {
|
||||
abc: (u32, u32, u32),
|
||||
}
|
||||
|
||||
assert_eq!(Tmpl { abc: (1, 2, 3) }.to_string(), "-12-");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn abc() {
|
||||
#[derive(Template)]
|
||||
#[template(
|
||||
source = "{% if let (a, b, c, ..) = abc %}-{{a}}{{b}}{{c}}-{% endif %}",
|
||||
ext = "txt"
|
||||
)]
|
||||
struct Tmpl1 {
|
||||
abc: (u32, u32, u32),
|
||||
}
|
||||
|
||||
assert_eq!(Tmpl1 { abc: (1, 2, 3) }.to_string(), "-123-");
|
||||
|
||||
assert_eq!(Tmpl2 { abc: (1, 2, 3) }.to_string(), "-123-");
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(
|
||||
source = "{% if let (a, b, c, ..) = abc %}-{{a}}{{b}}{{c}}-{% endif %}",
|
||||
ext = "txt"
|
||||
)]
|
||||
struct Tmpl2 {
|
||||
abc: (u32, u32, u32),
|
||||
}
|
||||
|
||||
assert_eq!(Tmpl2 { abc: (1, 2, 3) }.to_string(), "-123-");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bc() {
|
||||
#[derive(Template)]
|
||||
#[template(
|
||||
source = "{% if let (.., b, c) = abc %}-{{b}}{{c}}-{% endif %}",
|
||||
ext = "txt"
|
||||
)]
|
||||
struct Tmpl {
|
||||
abc: (u32, u32, u32),
|
||||
}
|
||||
|
||||
assert_eq!(Tmpl { abc: (1, 2, 3) }.to_string(), "-23-");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn c() {
|
||||
#[derive(Template)]
|
||||
#[template(source = "{% if let (.., c) = abc %}-{{c}}-{% endif %}", ext = "txt")]
|
||||
struct Tmpl {
|
||||
abc: (u32, u32, u32),
|
||||
}
|
||||
|
||||
assert_eq!(Tmpl { abc: (1, 2, 3) }.to_string(), "-3-");
|
||||
}
|
48
testing/tests/ui/let_destructuring_has_rest.rs
Normal file
48
testing/tests/ui/let_destructuring_has_rest.rs
Normal file
@ -0,0 +1,48 @@
|
||||
use rinja::Template;
|
||||
|
||||
struct X {
|
||||
a: u32,
|
||||
b: u32,
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(source = "
|
||||
{%- if let X { a, .., } = x -%}hello {{ a }}{%- endif -%}
|
||||
", ext = "html")]
|
||||
struct T1 {
|
||||
x: X,
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(source = "
|
||||
{%- if let X { a .. } = x -%}hello {{ a }}{%- endif -%}
|
||||
", ext = "html")]
|
||||
struct T2 {
|
||||
x: X,
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(source = "
|
||||
{%- if let X { a, 1 } = x -%}hello {{ a }}{%- endif -%}
|
||||
", ext = "html")]
|
||||
struct T3 {
|
||||
x: X,
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(source = "
|
||||
{%- if let X { a, .., b } = x -%}hello {{ a }}{%- endif -%}
|
||||
", ext = "html")]
|
||||
struct T4 {
|
||||
x: X,
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(source = "
|
||||
{%- if let X { .., b } = x -%}hello {{ a }}{%- endif -%}
|
||||
", ext = "html")]
|
||||
struct T5 {
|
||||
x: X,
|
||||
}
|
||||
|
||||
fn main() {}
|
52
testing/tests/ui/let_destructuring_has_rest.stderr
Normal file
52
testing/tests/ui/let_destructuring_has_rest.stderr
Normal file
@ -0,0 +1,52 @@
|
||||
error: unexpected `,` character after `..`
|
||||
note that in a named struct, `..` must come last to ignore other members
|
||||
failed to parse template source at row 2, column 20 near:
|
||||
", } = x -%}hello {{ a }}{%- endif -%}\n"
|
||||
--> tests/ui/let_destructuring_has_rest.rs:8:10
|
||||
|
|
||||
8 | #[derive(Template)]
|
||||
| ^^^^^^^^
|
||||
|
|
||||
= note: this error originates in the derive macro `Template` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error: expected `,` for more members, or `}` as terminator
|
||||
failed to parse template source at row 2, column 17 near:
|
||||
".. } = x -%}hello {{ a }}{%- endif -%}\n"
|
||||
--> tests/ui/let_destructuring_has_rest.rs:16:10
|
||||
|
|
||||
16 | #[derive(Template)]
|
||||
| ^^^^^^^^
|
||||
|
|
||||
= note: this error originates in the derive macro `Template` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error: expected member, or `}` as terminator
|
||||
failed to parse template source at row 2, column 18 near:
|
||||
"1 } = x -%}hello {{ a }}{%- endif -%}\n"
|
||||
--> tests/ui/let_destructuring_has_rest.rs:24:10
|
||||
|
|
||||
24 | #[derive(Template)]
|
||||
| ^^^^^^^^
|
||||
|
|
||||
= note: this error originates in the derive macro `Template` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error: unexpected `,` character after `..`
|
||||
note that in a named struct, `..` must come last to ignore other members
|
||||
failed to parse template source at row 2, column 20 near:
|
||||
", b } = x -%}hello {{ a }}{%- endif -%}\n"
|
||||
--> tests/ui/let_destructuring_has_rest.rs:32:10
|
||||
|
|
||||
32 | #[derive(Template)]
|
||||
| ^^^^^^^^
|
||||
|
|
||||
= note: this error originates in the derive macro `Template` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error: unexpected `,` character after `..`
|
||||
note that in a named struct, `..` must come last to ignore other members
|
||||
failed to parse template source at row 2, column 17 near:
|
||||
", b } = x -%}hello {{ a }}{%- endif -%}\n"
|
||||
--> tests/ui/let_destructuring_has_rest.rs:40:10
|
||||
|
|
||||
40 | #[derive(Template)]
|
||||
| ^^^^^^^^
|
||||
|
|
||||
= note: this error originates in the derive macro `Template` (in Nightly builds, run with -Z macro-backtrace for more info)
|
Loading…
x
Reference in New Issue
Block a user