diff --git a/askama_parser/src/expr.rs b/askama_parser/src/expr.rs index 5d59edd2..af9e1e78 100644 --- a/askama_parser/src/expr.rs +++ b/askama_parser/src/expr.rs @@ -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> { - ErrMode::Cut(ErrorContext::new( - "reserved keyword `_` cannot be used here", - name, - )) +#[inline(always)] +fn err_underscore_identifier(name: &str) -> ParseResult<'_, T> { + cut_error!("reserved keyword `_` cannot be used here", name) } -fn err_reserved_identifier(name: &str) -> ErrMode> { - ErrMode::Cut(ErrorContext::new( - format!("`{name}` cannot be used as an identifier"), - name, - )) +#[inline(always)] +fn err_reserved_identifier(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!("\"{:# 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); } } } diff --git a/askama_parser/src/lib.rs b/askama_parser/src/lib.rs index 0e0ef035..4e4f450b 100644 --- a/askama_parser/src/lib.rs +++ b/askama_parser/src/lib.rs @@ -405,10 +405,7 @@ fn num_lit<'a>(i: &mut &'a str) -> ParseResult<'a, Num<'a>> { { Ok(value) } else { - Err(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(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(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(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(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(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(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(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(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(ErrMode::Cut(ErrorContext::new( + return cut_error!( format!("expected an unprefixed normal string, not a {kind}"), start, - ))); + ); } Ok(lit.content) @@ -740,15 +727,12 @@ fn char_lit<'a>(i: &mut &'a str) -> ParseResult<'a, CharLit<'a>> { let Some(s) = s else { i.reset(&start); - return Err(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(ErrMode::Cut(ErrorContext::new("invalid character", *i))); + return cut_error!("invalid character", *i); }; let (nb, max_value, err1, err2) = match c { @@ -776,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(ErrMode::Cut(ErrorContext::new(err1, *i))); + return cut_error!(err1, *i); }; if nb > max_value { i.reset(&start); - return Err(ErrMode::Cut(ErrorContext::new(err2, *i))); + return cut_error!(err2, *i); } Ok(CharLit { @@ -1117,17 +1101,16 @@ impl Level<'_> { count: 1, }) } else { - Err(Self::_fail(i)) + Self::_fail(i) } } - #[cold] - #[inline(never)] - fn _fail(i: &str) -> ParseErr<'_> { - ErrMode::Cut(ErrorContext::new( + #[inline(always)] + fn _fail(i: &str) -> ParseResult<'_, T> { + cut_error!( "your template code is too deeply nested, or the last expression is too complex", i, - )) + ) } } @@ -1153,7 +1136,7 @@ impl LevelGuard<'_> { self.count += 1; Ok(()) } else { - Err(Level::_fail(i)) + Level::_fail(i) } } } @@ -1437,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 { diff --git a/askama_parser/src/node.rs b/askama_parser/src/node.rs index 2599c8de..a92020ad 100644 --- a/askama_parser/src/node.rs +++ b/askama_parser/src/node.rs @@ -11,8 +11,8 @@ 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)] @@ -56,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(ErrMode::Cut(ErrorContext::new( + return cut_error!( "cannot parse entire template\n\ you should never encounter this error\n\ please report this error to ", *i, - ))); + ); } Ok(result) } @@ -132,10 +132,7 @@ impl<'a> Node<'a> { let start = *i; let (pws, _, nws) = p.parse_next(i)?; if !s.is_in_loop() { - return Err(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))) } @@ -150,10 +147,7 @@ impl<'a> Node<'a> { let start = *i; let (pws, _, nws) = p.parse_next(i)?; if !s.is_in_loop() { - return Err(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))) } @@ -256,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(ErrMode::Cut(ErrorContext::new(msg, *i))) + cut_error!(msg, *i) } #[derive(Debug, PartialEq)] @@ -488,10 +482,10 @@ fn check_block_start<'a>( expected: &str, ) -> ParseResult<'a, ()> { if i.is_empty() { - return Err(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) } @@ -622,10 +616,7 @@ fn check_duplicated_name<'a>( i: &'a str, ) -> Result<(), crate::ParseErr<'a>> { if !names.insert(arg_name) { - return Err(ErrMode::Cut(ErrorContext::new( - format!("duplicated argument `{arg_name}`"), - i, - ))); + return cut_error!(format!("duplicated argument `{arg_name}`"), i); } Ok(()) } @@ -658,10 +649,7 @@ impl<'a> Macro<'a> { .parse_next(i)?; match args { Some((args, Some(_))) => Ok(args), - Some((_, None)) => Err(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), } }; @@ -682,10 +670,7 @@ impl<'a> Macro<'a> { ); let (pws1, _, (name, params, nws1, _)) = start.parse_next(i)?; if is_rust_keyword(name) { - return Err(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 { @@ -698,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(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, - ))); + ); } } } @@ -877,10 +862,7 @@ impl<'a> Call<'a> { match args { Some((args, Some(_))) => Ok(args), - Some((_, None)) => Err(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), } }; @@ -989,10 +971,10 @@ impl<'a> Match<'a> { arms.push(arm); } if arms.is_empty() { - return Err(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( @@ -1079,13 +1061,13 @@ fn check_end_name<'a>( return Ok(end_name); } - Err(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)] @@ -1231,20 +1213,20 @@ impl<'a> Let<'a> { } }; if let Some(kind) = kind { - return Err(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(ErrMode::Cut(ErrorContext::new( + return cut_error!( "you can only use the `mut` keyword with a variable name", start, - ))); + ); } Ok(WithSpan::new( @@ -1404,7 +1386,7 @@ impl<'a> Comment<'a> { let mut ws = Ws(None, None); if content.len() == 1 && matches!(content, "-" | "+" | "~") { - return Err(ErrMode::Cut(ErrorContext::new( + return cut_error!( format!( "ambiguous whitespace stripping\n\ use `{}{content} {content}{}` to apply the same whitespace stripping on both \ @@ -1412,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()); @@ -1439,10 +1421,10 @@ fn end_node<'a, 'g: 'a>( Ok(actual) } else if actual.starts_with("end") { i.reset(&start); - Err(ErrMode::Cut(ErrorContext::new( + cut_error!( format!("expected `{expected}` to terminate `{node}` node, found `{actual}`"), *i, - ))) + ) } else { i.reset(&start); fail.parse_next(i) diff --git a/askama_parser/src/target.rs b/askama_parser/src/target.rs index e6c24f57..6b999e09 100644 --- a/askama_parser/src/target.rs +++ b/askama_parser/src/target.rs @@ -5,8 +5,8 @@ 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)] @@ -112,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(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(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); } } @@ -163,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(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(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)); @@ -191,10 +182,7 @@ impl<'a> Target<'a> { if src == "_" { *i = start; - return Err(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 { @@ -216,20 +204,17 @@ impl<'a> Target<'a> { fn verify_name<'a>(input: &'a str, name: &'a str) -> Result, ErrMode>> { if is_rust_keyword(name) { - Err(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(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(ErrMode::Cut(ErrorContext::new( + cut_error!( format!("cannot use `{name}` as a name: it is reserved for `askama`"), input, - ))) + ) } else { Ok(Target::Name(name)) } @@ -250,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(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)?; @@ -262,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(ErrMode::Cut(ErrorContext::new(msg, *i))); + return cut_error!(msg, *i); } let singleton = !has_comma && targets.len() == 1; @@ -279,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(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(ErrMode::Cut(ErrorContext::new( + return cut_error!( format!("`..` can only be used once per {type_kind} pattern"), s.span, - ))); + ); } found_rest = true; }