mirror of
https://github.com/askama-rs/askama.git
synced 2025-09-30 22:41:13 +00:00
Merge pull request #501 from Kijewski/pr-context_error
parser: un-inline error message generation
This commit is contained in:
commit
73bbfa9200
@ -11,8 +11,8 @@ use winnow::token::take_until;
|
||||
|
||||
use crate::node::CondTest;
|
||||
use crate::{
|
||||
CharLit, ErrorContext, Level, Num, ParseErr, ParseResult, PathOrIdentifier, Span, StrLit,
|
||||
StrPrefix, WithSpan, char_lit, filter, identifier, keyword, not_suffix_with_hash, num_lit,
|
||||
CharLit, Level, Num, ParseResult, PathOrIdentifier, Span, StrLit, StrPrefix, WithSpan,
|
||||
char_lit, cut_error, filter, identifier, keyword, not_suffix_with_hash, num_lit,
|
||||
path_or_identifier, skip_ws0, skip_ws1, str_lit, ws,
|
||||
};
|
||||
|
||||
@ -50,22 +50,22 @@ struct Allowed {
|
||||
super_keyword: bool,
|
||||
}
|
||||
|
||||
fn check_expr<'a>(expr: &WithSpan<'a, Expr<'a>>, allowed: Allowed) -> Result<(), ParseErr<'a>> {
|
||||
fn check_expr<'a>(expr: &WithSpan<'a, Expr<'a>>, allowed: Allowed) -> ParseResult<'a, ()> {
|
||||
match &expr.inner {
|
||||
&Expr::Var(name) => {
|
||||
// List can be found in rust compiler "can_be_raw" function (although in our case, it's
|
||||
// also used in cases like `match`, so `self` is allowed in this case).
|
||||
if (!allowed.super_keyword && name == "super") || matches!(name, "crate" | "Self") {
|
||||
Err(err_reserved_identifier(name))
|
||||
err_reserved_identifier(name)
|
||||
} else if !allowed.underscore && name == "_" {
|
||||
Err(err_underscore_identifier(name))
|
||||
err_underscore_identifier(name)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
&Expr::IsDefined(var) | &Expr::IsNotDefined(var) => {
|
||||
if var == "_" {
|
||||
Err(err_underscore_identifier(var))
|
||||
err_underscore_identifier(var)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
@ -73,7 +73,7 @@ fn check_expr<'a>(expr: &WithSpan<'a, Expr<'a>>, allowed: Allowed) -> Result<(),
|
||||
Expr::Path(path) => {
|
||||
if let &[name] = path.as_slice() {
|
||||
if !crate::can_be_variable_name(name) {
|
||||
return Err(err_reserved_identifier(name));
|
||||
return err_reserved_identifier(name);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
@ -86,9 +86,9 @@ fn check_expr<'a>(expr: &WithSpan<'a, Expr<'a>>, allowed: Allowed) -> Result<(),
|
||||
}
|
||||
Expr::AssociatedItem(elem, associated_item) => {
|
||||
if associated_item.name == "_" {
|
||||
Err(err_underscore_identifier(associated_item.name))
|
||||
err_underscore_identifier(associated_item.name)
|
||||
} else if !crate::can_be_variable_name(associated_item.name) {
|
||||
Err(err_reserved_identifier(associated_item.name))
|
||||
err_reserved_identifier(associated_item.name)
|
||||
} else {
|
||||
check_expr(elem, Allowed::default())
|
||||
}
|
||||
@ -126,7 +126,7 @@ fn check_expr<'a>(expr: &WithSpan<'a, Expr<'a>>, allowed: Allowed) -> Result<(),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Expr::ArgumentPlaceholder => Err(ErrMode::Cut(ErrorContext::new("unreachable", expr.span))),
|
||||
Expr::ArgumentPlaceholder => cut_error!("unreachable", expr.span),
|
||||
Expr::BoolLit(_)
|
||||
| Expr::NumLit(_, _)
|
||||
| Expr::StrLit(_)
|
||||
@ -140,18 +140,14 @@ fn check_expr<'a>(expr: &WithSpan<'a, Expr<'a>>, allowed: Allowed) -> Result<(),
|
||||
}
|
||||
}
|
||||
|
||||
fn err_underscore_identifier(name: &str) -> ErrMode<ErrorContext<'_>> {
|
||||
ErrMode::Cut(ErrorContext::new(
|
||||
"reserved keyword `_` cannot be used here",
|
||||
name,
|
||||
))
|
||||
#[inline(always)]
|
||||
fn err_underscore_identifier<T>(name: &str) -> ParseResult<'_, T> {
|
||||
cut_error!("reserved keyword `_` cannot be used here", name)
|
||||
}
|
||||
|
||||
fn err_reserved_identifier(name: &str) -> ErrMode<ErrorContext<'_>> {
|
||||
ErrMode::Cut(ErrorContext::new(
|
||||
format!("`{name}` cannot be used as an identifier"),
|
||||
name,
|
||||
))
|
||||
#[inline(always)]
|
||||
fn err_reserved_identifier<T>(name: &str) -> ParseResult<'_, T> {
|
||||
cut_error!(format!("`{name}` cannot be used as an identifier"), name)
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
@ -244,10 +240,7 @@ impl<'a> Expr<'a> {
|
||||
))
|
||||
.parse_next(i)?;
|
||||
if has_named_arguments && !matches!(*expr, Self::NamedArgument(_, _)) {
|
||||
Err(ErrMode::Cut(ErrorContext::new(
|
||||
"named arguments must always be passed last",
|
||||
start,
|
||||
)))
|
||||
cut_error!("named arguments must always be passed last", start)
|
||||
} else {
|
||||
Ok(expr)
|
||||
}
|
||||
@ -283,10 +276,10 @@ impl<'a> Expr<'a> {
|
||||
start,
|
||||
))
|
||||
} else {
|
||||
Err(ErrMode::Cut(ErrorContext::new(
|
||||
cut_error!(
|
||||
format!("named argument `{argument}` was passed more than once"),
|
||||
start,
|
||||
)))
|
||||
start
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -357,13 +350,13 @@ impl<'a> Expr<'a> {
|
||||
let expr = WithSpan::new(Expr::BinOp(Box::new(BinOp { op, lhs: expr, rhs })), start);
|
||||
|
||||
if let Some((op2, _)) = opt(right).parse_next(i)? {
|
||||
return Err(ErrMode::Cut(ErrorContext::new(
|
||||
return cut_error!(
|
||||
format!(
|
||||
"comparison operators cannot be chained; \
|
||||
consider using explicit parentheses, e.g. `(_ {op} _) {op2} _`"
|
||||
),
|
||||
op,
|
||||
)));
|
||||
);
|
||||
}
|
||||
|
||||
Ok(expr)
|
||||
@ -386,10 +379,10 @@ impl<'a> Expr<'a> {
|
||||
let data = opt((ws1, '~', ws1, |i: &mut _| Expr::muldivmod(i, level))).parse_next(i)?;
|
||||
if let Some((t1, _, t2, expr)) = data {
|
||||
if t1.is_none() || t2.is_none() {
|
||||
return Err(ErrMode::Cut(ErrorContext::new(
|
||||
return cut_error!(
|
||||
"the concat operator `~` must be surrounded by spaces",
|
||||
start,
|
||||
)));
|
||||
);
|
||||
}
|
||||
Ok(Some(expr))
|
||||
} else {
|
||||
@ -426,18 +419,18 @@ impl<'a> Expr<'a> {
|
||||
if crate::PRIMITIVE_TYPES.contains(&target) {
|
||||
return Ok(WithSpan::new(Self::As(Box::new(lhs), target), start));
|
||||
} else if target.is_empty() {
|
||||
return Err(ErrMode::Cut(ErrorContext::new(
|
||||
return cut_error!(
|
||||
"`as` operator expects the name of a primitive type on its right-hand side",
|
||||
before_keyword.trim_start(),
|
||||
)));
|
||||
);
|
||||
} else {
|
||||
return Err(ErrMode::Cut(ErrorContext::new(
|
||||
return cut_error!(
|
||||
format!(
|
||||
"`as` operator expects the name of a primitive type on its right-hand \
|
||||
side, found `{target}`"
|
||||
),
|
||||
before_keyword.trim_start(),
|
||||
)));
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
@ -449,11 +442,11 @@ impl<'a> Expr<'a> {
|
||||
let rhs = opt(terminated(opt(keyword("not")), ws(keyword("defined")))).parse_next(i)?;
|
||||
let ctor = match rhs {
|
||||
None => {
|
||||
return Err(ErrMode::Cut(ErrorContext::new(
|
||||
return cut_error!(
|
||||
"expected `defined` or `not defined` after `is`",
|
||||
// We use `start` to show the whole `var is` thing instead of the current token.
|
||||
start,
|
||||
)));
|
||||
);
|
||||
}
|
||||
Some(None) => Self::IsDefined,
|
||||
Some(Some(_)) => Self::IsNotDefined,
|
||||
@ -461,16 +454,13 @@ impl<'a> Expr<'a> {
|
||||
let var_name = match *lhs {
|
||||
Self::Var(var_name) => var_name,
|
||||
Self::AssociatedItem(_, _) => {
|
||||
return Err(ErrMode::Cut(ErrorContext::new(
|
||||
return cut_error!(
|
||||
"`is defined` operator can only be used on variables, not on their fields",
|
||||
start,
|
||||
)));
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
return Err(ErrMode::Cut(ErrorContext::new(
|
||||
"`is defined` operator can only be used on variables",
|
||||
start,
|
||||
)));
|
||||
return cut_error!("`is defined` operator can only be used on variables", start);
|
||||
}
|
||||
};
|
||||
Ok(WithSpan::new(ctor(var_name), start))
|
||||
@ -642,10 +632,7 @@ fn token_xor<'a>(i: &mut &'a str) -> ParseResult<'a> {
|
||||
if good {
|
||||
Ok("^")
|
||||
} else {
|
||||
Err(ErrMode::Cut(ErrorContext::new(
|
||||
"the binary XOR operator is called `xor` in askama",
|
||||
*i,
|
||||
)))
|
||||
cut_error!("the binary XOR operator is called `xor` in askama", *i)
|
||||
}
|
||||
}
|
||||
|
||||
@ -654,10 +641,7 @@ fn token_bitand<'a>(i: &mut &'a str) -> ParseResult<'a> {
|
||||
if good {
|
||||
Ok("&")
|
||||
} else {
|
||||
Err(ErrMode::Cut(ErrorContext::new(
|
||||
"the binary AND operator is called `bitand` in askama",
|
||||
*i,
|
||||
)))
|
||||
cut_error!("the binary AND operator is called `bitand` in askama", *i)
|
||||
}
|
||||
}
|
||||
|
||||
@ -784,10 +768,7 @@ impl<'a> Suffix<'a> {
|
||||
let before = *i;
|
||||
let (token, token_span) = ws(opt(token).with_taken()).parse_next(i)?;
|
||||
let Some(token) = token else {
|
||||
return Err(ErrMode::Cut(ErrorContext::new(
|
||||
"expected valid tokens in macro call",
|
||||
token_span,
|
||||
)));
|
||||
return cut_error!("expected valid tokens in macro call", token_span);
|
||||
};
|
||||
let close_token = match token {
|
||||
Token::SomeOther => continue,
|
||||
@ -800,14 +781,14 @@ impl<'a> Suffix<'a> {
|
||||
let open_token = open_list.pop().unwrap();
|
||||
|
||||
if open_token != close_token {
|
||||
return Err(ErrMode::Cut(ErrorContext::new(
|
||||
return cut_error!(
|
||||
format!(
|
||||
"expected `{}` but found `{}`",
|
||||
open_token.as_close_char(),
|
||||
close_token.as_close_char(),
|
||||
),
|
||||
token_span,
|
||||
)));
|
||||
);
|
||||
} else if open_list.is_empty() {
|
||||
return Ok(Suffix::MacroCall(&start[..start.len() - before.len()]));
|
||||
}
|
||||
@ -844,13 +825,13 @@ impl<'a> Suffix<'a> {
|
||||
))
|
||||
.parse_next(i)?;
|
||||
if opt(take_until(.., '\n')).parse_next(i)?.is_none() {
|
||||
return Err(ErrMode::Cut(ErrorContext::new(
|
||||
return cut_error!(
|
||||
format!(
|
||||
"you are probably missing a line break to end {}comment",
|
||||
if is_doc_comment { "doc " } else { "" }
|
||||
),
|
||||
start,
|
||||
)));
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@ -864,13 +845,13 @@ impl<'a> Suffix<'a> {
|
||||
))
|
||||
.parse_next(i)?;
|
||||
if opt(take_until(.., "*/")).parse_next(i)?.is_none() {
|
||||
return Err(ErrMode::Cut(ErrorContext::new(
|
||||
return cut_error!(
|
||||
format!(
|
||||
"missing `*/` to close block {}comment",
|
||||
if is_doc_comment { "doc " } else { "" }
|
||||
),
|
||||
start,
|
||||
)));
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@ -881,10 +862,10 @@ impl<'a> Suffix<'a> {
|
||||
let prefix = identifier.parse_next(i)?;
|
||||
let hashes: usize = repeat(.., '#').parse_next(i)?;
|
||||
if hashes >= 256 {
|
||||
return Err(ErrMode::Cut(ErrorContext::new(
|
||||
return cut_error!(
|
||||
"a maximum of 255 hashes `#` are allowed with raw strings",
|
||||
prefix,
|
||||
)));
|
||||
);
|
||||
}
|
||||
|
||||
let str_kind = match prefix {
|
||||
@ -897,10 +878,10 @@ impl<'a> Suffix<'a> {
|
||||
_ if hashes == 0 => return Ok(()),
|
||||
// reserved prefix: reject
|
||||
_ => {
|
||||
return Err(ErrMode::Cut(ErrorContext::new(
|
||||
return cut_error!(
|
||||
format!("reserved prefix `{}#`", prefix.escape_debug()),
|
||||
prefix,
|
||||
)));
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@ -908,10 +889,7 @@ impl<'a> Suffix<'a> {
|
||||
// got a raw string
|
||||
|
||||
let Some((inner, j)) = i.split_once(&format!("\"{:#<hashes$}", "")) else {
|
||||
return Err(ErrMode::Cut(ErrorContext::new(
|
||||
"unterminated raw string",
|
||||
prefix,
|
||||
)));
|
||||
return cut_error!("unterminated raw string", prefix);
|
||||
};
|
||||
*i = j;
|
||||
|
||||
@ -927,7 +905,7 @@ impl<'a> Suffix<'a> {
|
||||
None => None,
|
||||
};
|
||||
if let Some(msg) = msg {
|
||||
return Err(ErrMode::Cut(ErrorContext::new(msg, prefix)));
|
||||
return cut_error!(msg, prefix);
|
||||
}
|
||||
|
||||
not_suffix_with_hash(i)?;
|
||||
@ -940,31 +918,31 @@ impl<'a> Suffix<'a> {
|
||||
|
||||
if str_kind.is_some() {
|
||||
// an invalid raw identifier like `cr#async`
|
||||
Err(ErrMode::Cut(ErrorContext::new(
|
||||
cut_error!(
|
||||
format!(
|
||||
"reserved prefix `{}#`, only `r#` is allowed with raw identifiers",
|
||||
prefix.escape_debug(),
|
||||
),
|
||||
prefix,
|
||||
)))
|
||||
)
|
||||
} else if hashes > 1 {
|
||||
// an invalid raw identifier like `r##async`
|
||||
Err(ErrMode::Cut(ErrorContext::new(
|
||||
cut_error!(
|
||||
"only one `#` is allowed in raw identifier delimitation",
|
||||
prefix,
|
||||
)))
|
||||
)
|
||||
} else {
|
||||
// a raw identifier like `r#async`
|
||||
Ok(())
|
||||
}
|
||||
} else {
|
||||
Err(ErrMode::Cut(ErrorContext::new(
|
||||
cut_error!(
|
||||
format!(
|
||||
"prefix `{}#` is only allowed with raw identifiers and raw strings",
|
||||
prefix.escape_debug(),
|
||||
),
|
||||
prefix,
|
||||
)))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -972,10 +950,10 @@ impl<'a> Suffix<'a> {
|
||||
let start = *i;
|
||||
'#'.parse_next(i)?;
|
||||
if opt('"').parse_next(i)?.is_some() {
|
||||
return Err(ErrMode::Cut(ErrorContext::new(
|
||||
return cut_error!(
|
||||
"unprefixed guarded string literals are reserved for future use",
|
||||
start,
|
||||
)));
|
||||
);
|
||||
}
|
||||
Ok(Token::SomeOther)
|
||||
}
|
||||
@ -1044,10 +1022,7 @@ impl<'a> Suffix<'a> {
|
||||
|i: &mut _| {
|
||||
let name = alt((digit1, identifier)).parse_next(i)?;
|
||||
if !crate::can_be_variable_name(name) {
|
||||
Err(ErrMode::Cut(ErrorContext::new(
|
||||
format!("`{name}` cannot be used as an identifier"),
|
||||
name,
|
||||
)))
|
||||
cut_error!(format!("`{name}` cannot be used as an identifier"), name)
|
||||
} else {
|
||||
Ok(name)
|
||||
}
|
||||
@ -1094,10 +1069,9 @@ impl<'a> Suffix<'a> {
|
||||
|
||||
fn ensure_macro_name(name: &str) -> ParseResult<'_, ()> {
|
||||
match name {
|
||||
"crate" | "super" | "Self" | "self" => Err(ErrMode::Cut(ErrorContext::new(
|
||||
format!("`{name}` is not a valid macro name"),
|
||||
name,
|
||||
))),
|
||||
"crate" | "super" | "Self" | "self" => {
|
||||
cut_error!(format!("`{name}` is not a valid macro name"), name)
|
||||
}
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
@ -1122,17 +1096,17 @@ impl<'i> TyGenerics<'i> {
|
||||
if let &[name] = path.as_slice() {
|
||||
if matches!(name, "super" | "self" | "crate") {
|
||||
// `Self` and `_` are allowed
|
||||
return Err(err_reserved_identifier(name));
|
||||
return err_reserved_identifier(name);
|
||||
}
|
||||
} else {
|
||||
for (idx, &name) in path.iter().enumerate() {
|
||||
if name == "_" {
|
||||
// `_` is never allowed
|
||||
return Err(err_underscore_identifier(name));
|
||||
return err_underscore_identifier(name);
|
||||
} else if idx > 0 && matches!(name, "super" | "self" | "Self" | "crate") {
|
||||
// At the front of the path, "super" | "self" | "Self" | "crate" are allowed.
|
||||
// Inside the path, they are not allowed.
|
||||
return Err(err_reserved_identifier(name));
|
||||
return err_reserved_identifier(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ use winnow::ascii::take_escaped;
|
||||
use winnow::combinator::{
|
||||
alt, cut_err, delimited, fail, not, opt, peek, preceded, repeat, terminated,
|
||||
};
|
||||
use winnow::error::FromExternalError;
|
||||
use winnow::error::{ErrMode, FromExternalError};
|
||||
use winnow::stream::{AsChar, Stream as _};
|
||||
use winnow::token::{any, none_of, one_of, take_till, take_while};
|
||||
use winnow::{ModalParser, Parser};
|
||||
@ -122,10 +122,10 @@ impl<'a> Ast<'a> {
|
||||
};
|
||||
match Node::parse_template(&mut src, &state) {
|
||||
Ok(nodes) if src.is_empty() => Ok(Self { nodes }),
|
||||
Ok(_) | Err(winnow::error::ErrMode::Incomplete(_)) => unreachable!(),
|
||||
Ok(_) | Err(ErrMode::Incomplete(_)) => unreachable!(),
|
||||
Err(
|
||||
winnow::error::ErrMode::Backtrack(ErrorContext { span, message, .. })
|
||||
| winnow::error::ErrMode::Cut(ErrorContext { span, message, .. }),
|
||||
ErrMode::Backtrack(ErrorContext { span, message, .. })
|
||||
| ErrMode::Cut(ErrorContext { span, message, .. }),
|
||||
) => Err(ParseError {
|
||||
message,
|
||||
offset: span.offset_from(start).unwrap_or_default(),
|
||||
@ -287,7 +287,7 @@ impl fmt::Display for ParseError {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) type ParseErr<'a> = winnow::error::ErrMode<ErrorContext<'a>>;
|
||||
pub(crate) type ParseErr<'a> = ErrMode<ErrorContext<'a>>;
|
||||
pub(crate) type ParseResult<'a, T = &'a str> = Result<T, ParseErr<'a>>;
|
||||
|
||||
/// This type is used to handle `nom` errors and in particular to add custom error messages.
|
||||
@ -313,12 +313,12 @@ impl<'a> ErrorContext<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
fn backtrack(self) -> winnow::error::ErrMode<Self> {
|
||||
winnow::error::ErrMode::Backtrack(self)
|
||||
fn backtrack(self) -> ErrMode<Self> {
|
||||
ErrMode::Backtrack(self)
|
||||
}
|
||||
|
||||
fn cut(self) -> winnow::error::ErrMode<Self> {
|
||||
winnow::error::ErrMode::Cut(self)
|
||||
fn cut(self) -> ErrMode<Self> {
|
||||
ErrMode::Cut(self)
|
||||
}
|
||||
}
|
||||
|
||||
@ -405,10 +405,7 @@ fn num_lit<'a>(i: &mut &'a str) -> ParseResult<'a, Num<'a>> {
|
||||
{
|
||||
Ok(value)
|
||||
} else {
|
||||
Err(winnow::error::ErrMode::Cut(ErrorContext::new(
|
||||
format!("unknown {kind} suffix `{suffix}`"),
|
||||
start,
|
||||
)))
|
||||
cut_error!(format!("unknown {kind} suffix `{suffix}`"), start)
|
||||
}
|
||||
}
|
||||
|
||||
@ -421,10 +418,7 @@ fn num_lit<'a>(i: &mut &'a str) -> ParseResult<'a, Num<'a>> {
|
||||
.parse_next(i)?;
|
||||
match opt(separated_digits(base, false)).parse_next(i)? {
|
||||
Some(_) => Ok(()),
|
||||
None => Err(winnow::error::ErrMode::Cut(ErrorContext::new(
|
||||
format!("expected digits after `{kind}`"),
|
||||
start,
|
||||
))),
|
||||
None => cut_error!(format!("expected digits after `{kind}`"), start),
|
||||
}
|
||||
});
|
||||
|
||||
@ -436,10 +430,12 @@ fn num_lit<'a>(i: &mut &'a str) -> ParseResult<'a, Num<'a>> {
|
||||
let (kind, op) = (one_of(['e', 'E']), opt(one_of(['+', '-']))).parse_next(i)?;
|
||||
match opt(separated_digits(10, op.is_none())).parse_next(i)? {
|
||||
Some(_) => Ok(()),
|
||||
None => Err(winnow::error::ErrMode::Cut(ErrorContext::new(
|
||||
format!("expected decimal digits, `+` or `-` after exponent `{kind}`"),
|
||||
start,
|
||||
))),
|
||||
None => {
|
||||
cut_error!(
|
||||
format!("expected decimal digits, `+` or `-` after exponent `{kind}`"),
|
||||
start,
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
.parse_next(i)?;
|
||||
@ -578,12 +574,12 @@ fn str_lit<'a>(i: &mut &'a str) -> ParseResult<'a, StrLit<'a>> {
|
||||
if has_lf {
|
||||
continue;
|
||||
} else {
|
||||
return Err(winnow::error::ErrMode::Cut(ErrorContext::new(
|
||||
return cut_error!(
|
||||
"a bare CR (Mac linebreak) is not allowed in string literals, \
|
||||
use NL (Unix linebreak) or CRNL (Windows linebreak) instead, \
|
||||
or type `\\r` explicitly",
|
||||
start,
|
||||
)));
|
||||
);
|
||||
}
|
||||
}
|
||||
Sequence::Close => break,
|
||||
@ -611,16 +607,10 @@ fn str_lit<'a>(i: &mut &'a str) -> ParseResult<'a, StrLit<'a>> {
|
||||
match u32::from_str_radix(code, 16).unwrap() {
|
||||
0 => contains_null = true,
|
||||
0xd800..0xe000 => {
|
||||
return Err(winnow::error::ErrMode::Cut(ErrorContext::new(
|
||||
"unicode escape must not be a surrogate",
|
||||
start,
|
||||
)));
|
||||
return cut_error!("unicode escape must not be a surrogate", start);
|
||||
}
|
||||
0x110000.. => {
|
||||
return Err(winnow::error::ErrMode::Cut(ErrorContext::new(
|
||||
"unicode escape must be at most 10FFFF",
|
||||
start,
|
||||
)));
|
||||
return cut_error!("unicode escape must be at most 10FFFF", start);
|
||||
}
|
||||
128.. => contains_unicode_character = true,
|
||||
_ => {}
|
||||
@ -653,10 +643,7 @@ fn str_lit<'a>(i: &mut &'a str) -> ParseResult<'a, StrLit<'a>> {
|
||||
|
||||
let lit = opt(terminated(inner.with_taken(), '"')).parse_next(i)?;
|
||||
let Some((mut lit, content)) = lit else {
|
||||
return Err(winnow::error::ErrMode::Cut(ErrorContext::new(
|
||||
"unclosed or broken string",
|
||||
start,
|
||||
)));
|
||||
return cut_error!("unclosed or broken string", start);
|
||||
};
|
||||
lit.content = content;
|
||||
lit.prefix = prefix;
|
||||
@ -677,7 +664,7 @@ fn str_lit<'a>(i: &mut &'a str) -> ParseResult<'a, StrLit<'a>> {
|
||||
None => lit.contains_high_ascii.then_some("out of range hex escape"),
|
||||
};
|
||||
if let Some(msg) = msg {
|
||||
return Err(winnow::error::ErrMode::Cut(ErrorContext::new(msg, start)));
|
||||
return cut_error!(msg, start);
|
||||
}
|
||||
|
||||
not_suffix_with_hash(i)?;
|
||||
@ -686,10 +673,10 @@ fn str_lit<'a>(i: &mut &'a str) -> ParseResult<'a, StrLit<'a>> {
|
||||
|
||||
fn not_suffix_with_hash<'a>(i: &mut &'a str) -> ParseResult<'a, ()> {
|
||||
if let Some(suffix) = opt((identifier, '#').take()).parse_next(i)? {
|
||||
return Err(winnow::error::ErrMode::Cut(ErrorContext::new(
|
||||
return cut_error!(
|
||||
"you are missing a space to separate two string literals",
|
||||
suffix,
|
||||
)));
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@ -704,10 +691,10 @@ fn str_lit_without_prefix<'a>(i: &mut &'a str) -> ParseResult<'a> {
|
||||
None => None,
|
||||
};
|
||||
if let Some(kind) = kind {
|
||||
return Err(winnow::error::ErrMode::Cut(ErrorContext::new(
|
||||
return cut_error!(
|
||||
format!("expected an unprefixed normal string, not a {kind}"),
|
||||
start,
|
||||
)));
|
||||
);
|
||||
}
|
||||
|
||||
Ok(lit.content)
|
||||
@ -740,18 +727,12 @@ fn char_lit<'a>(i: &mut &'a str) -> ParseResult<'a, CharLit<'a>> {
|
||||
|
||||
let Some(s) = s else {
|
||||
i.reset(&start);
|
||||
return Err(winnow::error::ErrMode::Cut(ErrorContext::new(
|
||||
"empty character literal",
|
||||
*i,
|
||||
)));
|
||||
return cut_error!("empty character literal", *i);
|
||||
};
|
||||
let mut is = s;
|
||||
let Ok(c) = Char::parse(&mut is) else {
|
||||
i.reset(&start);
|
||||
return Err(winnow::error::ErrMode::Cut(ErrorContext::new(
|
||||
"invalid character",
|
||||
*i,
|
||||
)));
|
||||
return cut_error!("invalid character", *i);
|
||||
};
|
||||
|
||||
let (nb, max_value, err1, err2) = match c {
|
||||
@ -779,11 +760,11 @@ fn char_lit<'a>(i: &mut &'a str) -> ParseResult<'a, CharLit<'a>> {
|
||||
|
||||
let Ok(nb) = u32::from_str_radix(nb, 16) else {
|
||||
i.reset(&start);
|
||||
return Err(winnow::error::ErrMode::Cut(ErrorContext::new(err1, *i)));
|
||||
return cut_error!(err1, *i);
|
||||
};
|
||||
if nb > max_value {
|
||||
i.reset(&start);
|
||||
return Err(winnow::error::ErrMode::Cut(ErrorContext::new(err2, *i)));
|
||||
return cut_error!(err2, *i);
|
||||
}
|
||||
|
||||
Ok(CharLit {
|
||||
@ -1120,17 +1101,16 @@ impl Level<'_> {
|
||||
count: 1,
|
||||
})
|
||||
} else {
|
||||
Err(Self::_fail(i))
|
||||
Self::_fail(i)
|
||||
}
|
||||
}
|
||||
|
||||
#[cold]
|
||||
#[inline(never)]
|
||||
fn _fail(i: &str) -> ParseErr<'_> {
|
||||
winnow::error::ErrMode::Cut(ErrorContext::new(
|
||||
#[inline(always)]
|
||||
fn _fail<T>(i: &str) -> ParseResult<'_, T> {
|
||||
cut_error!(
|
||||
"your template code is too deeply nested, or the last expression is too complex",
|
||||
i,
|
||||
))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1156,7 +1136,7 @@ impl LevelGuard<'_> {
|
||||
self.count += 1;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Level::_fail(i))
|
||||
Level::_fail(i)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1440,6 +1420,30 @@ fn is_rust_keyword(ident: &str) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
macro_rules! cut_error {
|
||||
($message:expr, $span:expr $(,)?) => {{
|
||||
use ::std::convert::Into;
|
||||
use ::std::option::Option::Some;
|
||||
|
||||
$crate::cut_context_err(
|
||||
#[cold]
|
||||
#[inline(always)]
|
||||
move || $crate::ErrorContext {
|
||||
span: Into::into($span),
|
||||
message: Some(Into::into($message)),
|
||||
},
|
||||
)
|
||||
}};
|
||||
}
|
||||
|
||||
pub(crate) use cut_error;
|
||||
|
||||
#[cold]
|
||||
#[inline(never)]
|
||||
fn cut_context_err<'a, T>(gen_err: impl FnOnce() -> ErrorContext<'a>) -> ParseResult<'a, T> {
|
||||
Err(ErrMode::Cut(gen_err()))
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
|
@ -5,13 +5,14 @@ use winnow::combinator::{
|
||||
alt, cut_err, delimited, eof, fail, not, opt, peek, preceded, repeat, separated,
|
||||
separated_pair, terminated,
|
||||
};
|
||||
use winnow::error::ErrMode;
|
||||
use winnow::stream::Stream as _;
|
||||
use winnow::token::{any, rest, take_until};
|
||||
use winnow::{ModalParser, Parser};
|
||||
|
||||
use crate::{
|
||||
ErrorContext, Expr, Filter, ParseResult, Span, State, Target, WithSpan, filter, identifier,
|
||||
is_rust_keyword, keyword, skip_ws0, str_lit_without_prefix, ws,
|
||||
ErrorContext, Expr, Filter, ParseResult, Span, State, Target, WithSpan, cut_error, filter,
|
||||
identifier, is_rust_keyword, keyword, skip_ws0, str_lit_without_prefix, ws,
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
@ -41,9 +42,7 @@ impl<'a> Node<'a> {
|
||||
let result = match (|i: &mut _| Self::many(i, s)).parse_next(i) {
|
||||
Ok(result) => result,
|
||||
Err(err) => {
|
||||
if let winnow::error::ErrMode::Backtrack(err) | winnow::error::ErrMode::Cut(err) =
|
||||
&err
|
||||
{
|
||||
if let ErrMode::Backtrack(err) | ErrMode::Cut(err) = &err {
|
||||
if err.message.is_none() {
|
||||
*i = start;
|
||||
if let Some(mut span) = err.span.as_suffix_of(i) {
|
||||
@ -57,12 +56,12 @@ impl<'a> Node<'a> {
|
||||
opt(|i: &mut _| unexpected_tag(i, s)).parse_next(i)?;
|
||||
let is_eof = opt(eof).parse_next(i)?;
|
||||
if is_eof.is_none() {
|
||||
return Err(winnow::error::ErrMode::Cut(ErrorContext::new(
|
||||
return cut_error!(
|
||||
"cannot parse entire template\n\
|
||||
you should never encounter this error\n\
|
||||
please report this error to <https://github.com/askama-rs/askama/issues>",
|
||||
*i,
|
||||
)));
|
||||
);
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
@ -133,10 +132,7 @@ impl<'a> Node<'a> {
|
||||
let start = *i;
|
||||
let (pws, _, nws) = p.parse_next(i)?;
|
||||
if !s.is_in_loop() {
|
||||
return Err(winnow::error::ErrMode::Cut(ErrorContext::new(
|
||||
"you can only `break` inside a `for` loop",
|
||||
start,
|
||||
)));
|
||||
return cut_error!("you can only `break` inside a `for` loop", start);
|
||||
}
|
||||
Ok(Self::Break(WithSpan::new(Ws(pws, nws), start)))
|
||||
}
|
||||
@ -151,10 +147,7 @@ impl<'a> Node<'a> {
|
||||
let start = *i;
|
||||
let (pws, _, nws) = p.parse_next(i)?;
|
||||
if !s.is_in_loop() {
|
||||
return Err(winnow::error::ErrMode::Cut(ErrorContext::new(
|
||||
"you can only `continue` inside a `for` loop",
|
||||
start,
|
||||
)));
|
||||
return cut_error!("you can only `continue` inside a `for` loop", start);
|
||||
}
|
||||
Ok(Self::Continue(WithSpan::new(Ws(pws, nws), start)))
|
||||
}
|
||||
@ -223,9 +216,7 @@ fn cut_node<'a, O>(
|
||||
move |i: &mut &'a str| {
|
||||
let start = *i;
|
||||
let result = inner.parse_next(i);
|
||||
if let Err(winnow::error::ErrMode::Cut(err) | winnow::error::ErrMode::Backtrack(err)) =
|
||||
&result
|
||||
{
|
||||
if let Err(ErrMode::Cut(err) | ErrMode::Backtrack(err)) = &result {
|
||||
if err.message.is_none() {
|
||||
*i = start;
|
||||
if let Some(mut span) = err.span.as_suffix_of(i) {
|
||||
@ -259,7 +250,7 @@ fn unexpected_raw_tag<'a>(kind: Option<&'static str>, i: &mut &'a str) -> ParseR
|
||||
tag if tag.starts_with("end") => format!("unexpected closing tag `{tag}`"),
|
||||
tag => format!("unknown node `{tag}`"),
|
||||
};
|
||||
Err(winnow::error::ErrMode::Cut(ErrorContext::new(msg, *i)))
|
||||
cut_error!(msg, *i)
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
@ -491,10 +482,10 @@ fn check_block_start<'a>(
|
||||
expected: &str,
|
||||
) -> ParseResult<'a, ()> {
|
||||
if i.is_empty() {
|
||||
return Err(winnow::error::ErrMode::Cut(ErrorContext::new(
|
||||
return cut_error!(
|
||||
format!("expected `{expected}` to terminate `{node}` node, found nothing"),
|
||||
start,
|
||||
)));
|
||||
);
|
||||
}
|
||||
(|i: &mut _| s.tag_block_start(i)).parse_next(i)
|
||||
}
|
||||
@ -625,10 +616,7 @@ fn check_duplicated_name<'a>(
|
||||
i: &'a str,
|
||||
) -> Result<(), crate::ParseErr<'a>> {
|
||||
if !names.insert(arg_name) {
|
||||
return Err(winnow::error::ErrMode::Cut(ErrorContext::new(
|
||||
format!("duplicated argument `{arg_name}`"),
|
||||
i,
|
||||
)));
|
||||
return cut_error!(format!("duplicated argument `{arg_name}`"), i);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@ -661,10 +649,7 @@ impl<'a> Macro<'a> {
|
||||
.parse_next(i)?;
|
||||
match args {
|
||||
Some((args, Some(_))) => Ok(args),
|
||||
Some((_, None)) => Err(winnow::error::ErrMode::Cut(ErrorContext::new(
|
||||
"expected `)` to close macro argument list",
|
||||
*i,
|
||||
))),
|
||||
Some((_, None)) => cut_error!("expected `)` to close macro argument list", *i),
|
||||
None => Ok(None),
|
||||
}
|
||||
};
|
||||
@ -685,10 +670,7 @@ impl<'a> Macro<'a> {
|
||||
);
|
||||
let (pws1, _, (name, params, nws1, _)) = start.parse_next(i)?;
|
||||
if is_rust_keyword(name) {
|
||||
return Err(winnow::error::ErrMode::Cut(ErrorContext::new(
|
||||
format!("'{name}' is not a valid name for a macro"),
|
||||
start_s,
|
||||
)));
|
||||
return cut_error!(format!("'{name}' is not a valid name for a macro"), start_s);
|
||||
}
|
||||
|
||||
if let Some(ref params) = params {
|
||||
@ -701,13 +683,13 @@ impl<'a> Macro<'a> {
|
||||
for (new_arg_name, default_value) in iter.by_ref() {
|
||||
check_duplicated_name(&mut names, new_arg_name, start_s)?;
|
||||
if default_value.is_none() {
|
||||
return Err(winnow::error::ErrMode::Cut(ErrorContext::new(
|
||||
return cut_error!(
|
||||
format!(
|
||||
"all arguments following `{arg_name}` should have a default \
|
||||
value, `{new_arg_name}` doesn't have a default value"
|
||||
),
|
||||
start_s,
|
||||
)));
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -880,10 +862,7 @@ impl<'a> Call<'a> {
|
||||
|
||||
match args {
|
||||
Some((args, Some(_))) => Ok(args),
|
||||
Some((_, None)) => Err(winnow::error::ErrMode::Cut(ErrorContext::new(
|
||||
"expected `)` to close call argument list",
|
||||
*i,
|
||||
))),
|
||||
Some((_, None)) => cut_error!("expected `)` to close call argument list", *i),
|
||||
None => Ok(None),
|
||||
}
|
||||
};
|
||||
@ -992,10 +971,10 @@ impl<'a> Match<'a> {
|
||||
arms.push(arm);
|
||||
}
|
||||
if arms.is_empty() {
|
||||
return Err(winnow::error::ErrMode::Cut(ErrorContext::new(
|
||||
return cut_error!(
|
||||
"`match` nodes must contain at least one `when` node and/or an `else` case",
|
||||
start,
|
||||
)));
|
||||
);
|
||||
}
|
||||
|
||||
Ok(WithSpan::new(
|
||||
@ -1082,13 +1061,13 @@ fn check_end_name<'a>(
|
||||
return Ok(end_name);
|
||||
}
|
||||
|
||||
Err(winnow::error::ErrMode::Cut(ErrorContext::new(
|
||||
cut_error!(
|
||||
match name.is_empty() && !end_name.is_empty() {
|
||||
true => format!("unexpected name `{end_name}` in `end{kind}` tag for unnamed `{kind}`"),
|
||||
false => format!("expected name `{name}` in `end{kind}` tag, found `{end_name}`"),
|
||||
},
|
||||
before,
|
||||
)))
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
@ -1234,20 +1213,20 @@ impl<'a> Let<'a> {
|
||||
}
|
||||
};
|
||||
if let Some(kind) = kind {
|
||||
return Err(winnow::error::ErrMode::Cut(ErrorContext::new(
|
||||
return cut_error!(
|
||||
format!(
|
||||
"when you forward-define a variable, you cannot use {kind} in place of \
|
||||
a variable name"
|
||||
),
|
||||
start,
|
||||
)));
|
||||
);
|
||||
}
|
||||
}
|
||||
if is_mut.is_some() && !matches!(var, Target::Name(_)) {
|
||||
return Err(winnow::error::ErrMode::Cut(ErrorContext::new(
|
||||
return cut_error!(
|
||||
"you can only use the `mut` keyword with a variable name",
|
||||
start,
|
||||
)));
|
||||
);
|
||||
}
|
||||
|
||||
Ok(WithSpan::new(
|
||||
@ -1407,7 +1386,7 @@ impl<'a> Comment<'a> {
|
||||
|
||||
let mut ws = Ws(None, None);
|
||||
if content.len() == 1 && matches!(content, "-" | "+" | "~") {
|
||||
return Err(winnow::error::ErrMode::Cut(ErrorContext::new(
|
||||
return cut_error!(
|
||||
format!(
|
||||
"ambiguous whitespace stripping\n\
|
||||
use `{}{content} {content}{}` to apply the same whitespace stripping on both \
|
||||
@ -1415,7 +1394,7 @@ impl<'a> Comment<'a> {
|
||||
s.syntax.comment_start, s.syntax.comment_end,
|
||||
),
|
||||
start,
|
||||
)));
|
||||
);
|
||||
} else if content.len() >= 2 {
|
||||
ws.0 = Whitespace::parse_char(content.chars().next().unwrap_or_default());
|
||||
ws.1 = Whitespace::parse_char(content.chars().next_back().unwrap_or_default());
|
||||
@ -1442,10 +1421,10 @@ fn end_node<'a, 'g: 'a>(
|
||||
Ok(actual)
|
||||
} else if actual.starts_with("end") {
|
||||
i.reset(&start);
|
||||
Err(winnow::error::ErrMode::Cut(ErrorContext::new(
|
||||
cut_error!(
|
||||
format!("expected `{expected}` to terminate `{node}` node, found `{actual}`"),
|
||||
*i,
|
||||
)))
|
||||
)
|
||||
} else {
|
||||
i.reset(&start);
|
||||
fail.parse_next(i)
|
||||
|
@ -1,11 +1,12 @@
|
||||
use winnow::combinator::{alt, opt, peek, preceded, separated};
|
||||
use winnow::error::ErrMode;
|
||||
use winnow::token::one_of;
|
||||
use winnow::{ModalParser, Parser};
|
||||
|
||||
use crate::{
|
||||
CharLit, ErrorContext, Num, ParseErr, ParseResult, PathOrIdentifier, State, StrLit, WithSpan,
|
||||
bool_lit, can_be_variable_name, char_lit, identifier, is_rust_keyword, keyword, num_lit,
|
||||
path_or_identifier, str_lit, ws,
|
||||
bool_lit, can_be_variable_name, char_lit, cut_error, identifier, is_rust_keyword, keyword,
|
||||
num_lit, path_or_identifier, str_lit, ws,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
@ -111,18 +112,12 @@ impl<'a> Target<'a> {
|
||||
if let [name] = path.as_slice() {
|
||||
// If the path only contains one item, we need to check the name.
|
||||
if !can_be_variable_name(name) {
|
||||
return Err(winnow::error::ErrMode::Cut(ErrorContext::new(
|
||||
format!("`{name}` cannot be used as an identifier"),
|
||||
*name,
|
||||
)));
|
||||
return cut_error!(format!("`{name}` cannot be used as an identifier"), *name);
|
||||
}
|
||||
} else {
|
||||
// Otherwise we need to check every element but the first.
|
||||
if let Some(name) = path.iter().skip(1).find(|n| !can_be_variable_name(n)) {
|
||||
return Err(winnow::error::ErrMode::Cut(ErrorContext::new(
|
||||
format!("`{name}` cannot be used as an identifier"),
|
||||
*name,
|
||||
)));
|
||||
return cut_error!(format!("`{name}` cannot be used as an identifier"), *name);
|
||||
}
|
||||
}
|
||||
|
||||
@ -162,20 +157,17 @@ impl<'a> Target<'a> {
|
||||
if let Some(rest) = rest {
|
||||
let chr = peek(ws(opt(one_of([',', ':'])))).parse_next(i)?;
|
||||
if let Some(chr) = chr {
|
||||
return Err(winnow::error::ErrMode::Cut(ErrorContext::new(
|
||||
return cut_error!(
|
||||
format!(
|
||||
"unexpected `{chr}` character after `..`\n\
|
||||
note that in a named struct, `..` must come last to ignore other members"
|
||||
),
|
||||
*i,
|
||||
)));
|
||||
);
|
||||
}
|
||||
if let Target::Rest(ref s) = rest.0 {
|
||||
if s.inner.is_some() {
|
||||
return Err(winnow::error::ErrMode::Cut(ErrorContext::new(
|
||||
"`@ ..` cannot be used in struct",
|
||||
s.span,
|
||||
)));
|
||||
return cut_error!("`@ ..` cannot be used in struct", s.span);
|
||||
}
|
||||
}
|
||||
return Ok((rest.1, rest.0));
|
||||
@ -190,10 +182,7 @@ impl<'a> Target<'a> {
|
||||
|
||||
if src == "_" {
|
||||
*i = start;
|
||||
return Err(winnow::error::ErrMode::Cut(ErrorContext::new(
|
||||
"cannot use placeholder `_` as source in named struct",
|
||||
*i,
|
||||
)));
|
||||
return cut_error!("cannot use placeholder `_` as source in named struct", *i);
|
||||
}
|
||||
|
||||
let target = match target {
|
||||
@ -213,25 +202,19 @@ impl<'a> Target<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
fn verify_name<'a>(
|
||||
input: &'a str,
|
||||
name: &'a str,
|
||||
) -> Result<Target<'a>, winnow::error::ErrMode<ErrorContext<'a>>> {
|
||||
fn verify_name<'a>(input: &'a str, name: &'a str) -> Result<Target<'a>, ErrMode<ErrorContext<'a>>> {
|
||||
if is_rust_keyword(name) {
|
||||
Err(winnow::error::ErrMode::Cut(ErrorContext::new(
|
||||
cut_error!(
|
||||
format!("cannot use `{name}` as a name: it is a rust keyword"),
|
||||
input,
|
||||
)))
|
||||
)
|
||||
} else if !can_be_variable_name(name) {
|
||||
Err(winnow::error::ErrMode::Cut(ErrorContext::new(
|
||||
format!("`{name}` cannot be used as an identifier"),
|
||||
input,
|
||||
)))
|
||||
cut_error!(format!("`{name}` cannot be used as an identifier"), input)
|
||||
} else if name.starts_with("__askama") {
|
||||
Err(winnow::error::ErrMode::Cut(ErrorContext::new(
|
||||
cut_error!(
|
||||
format!("cannot use `{name}` as a name: it is reserved for `askama`"),
|
||||
input,
|
||||
)))
|
||||
)
|
||||
} else {
|
||||
Ok(Target::Name(name))
|
||||
}
|
||||
@ -252,10 +235,7 @@ fn collect_targets<'a, T>(
|
||||
|
||||
let targets = opt(separated(1.., one, ws(',')).map(|v: Vec<_>| v)).parse_next(i)?;
|
||||
let Some(targets) = targets else {
|
||||
return Err(winnow::error::ErrMode::Cut(ErrorContext::new(
|
||||
"expected comma separated list of members",
|
||||
*i,
|
||||
)));
|
||||
return cut_error!("expected comma separated list of members", *i);
|
||||
};
|
||||
|
||||
let (has_comma, has_end) = (opt_comma, opt_end).parse_next(i)?;
|
||||
@ -264,7 +244,7 @@ fn collect_targets<'a, T>(
|
||||
true => format!("expected member, or `{delim}` as terminator"),
|
||||
false => format!("expected `,` for more members, or `{delim}` as terminator"),
|
||||
};
|
||||
return Err(winnow::error::ErrMode::Cut(ErrorContext::new(msg, *i)));
|
||||
return cut_error!(msg, *i);
|
||||
}
|
||||
|
||||
let singleton = !has_comma && targets.len() == 1;
|
||||
@ -281,16 +261,13 @@ fn only_one_rest_pattern<'a>(
|
||||
for target in &targets {
|
||||
if let Target::Rest(s) = target {
|
||||
if !allow_named_rest && s.inner.is_some() {
|
||||
return Err(winnow::error::ErrMode::Cut(ErrorContext::new(
|
||||
"`@ ..` is only allowed in slice patterns",
|
||||
s.span,
|
||||
)));
|
||||
return cut_error!("`@ ..` is only allowed in slice patterns", s.span);
|
||||
}
|
||||
if found_rest {
|
||||
return Err(winnow::error::ErrMode::Cut(ErrorContext::new(
|
||||
return cut_error!(
|
||||
format!("`..` can only be used once per {type_kind} pattern"),
|
||||
s.span,
|
||||
)));
|
||||
);
|
||||
}
|
||||
found_rest = true;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user