diff --git a/askama_parser/src/expr.rs b/askama_parser/src/expr.rs index 3e9574b6..74ab92ea 100644 --- a/askama_parser/src/expr.rs +++ b/askama_parser/src/expr.rs @@ -39,6 +39,14 @@ fn check_expr<'a>( allow_underscore: bool, ) -> Result<(), ParseErr<'a>> { match &expr.inner { + // 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). + Expr::Var(name @ ("crate" | "super" | "Self")) => { + Err(winnow::error::ErrMode::Cut(ErrorContext::new( + format!("`{name}` cannot be used as an identifier"), + expr.span, + ))) + } Expr::Var("_") if !allow_underscore => Err(winnow::error::ErrMode::Cut(ErrorContext::new( "reserved keyword `_` cannot be used here", expr.span, @@ -53,11 +61,21 @@ fn check_expr<'a>( Ok(()) } } + Expr::Path(path) => { + if let [name] = path.as_slice() { + if !crate::can_be_variable_name(name) { + return Err(winnow::error::ErrMode::Cut(ErrorContext::new( + format!("`{name}` cannot be used as an identifier"), + expr.span, + ))); + } + } + Ok(()) + } Expr::BoolLit(_) | Expr::NumLit(_, _) | Expr::StrLit(_) | Expr::CharLit(_) - | Expr::Path(_) | Expr::Attr(_, _) | Expr::Filter(_) | Expr::NamedArgument(_, _) @@ -702,7 +720,17 @@ impl<'a> Suffix<'a> { preceded( ws(('.', not('.'))), cut_err(( - alt((digit1, identifier)), + |i: &mut _| { + let name = alt((digit1, identifier)).parse_next(i)?; + if !crate::can_be_variable_name(name) { + Err(winnow::error::ErrMode::Cut(ErrorContext::new( + format!("`{name}` cannot be used as an identifier"), + *i, + ))) + } else { + Ok(name) + } + }, opt(|i: &mut _| call_generics(i, level)), )), ) diff --git a/askama_parser/src/lib.rs b/askama_parser/src/lib.rs index 6fc01f1e..7f8c2f39 100644 --- a/askama_parser/src/lib.rs +++ b/askama_parser/src/lib.rs @@ -1077,6 +1077,11 @@ pub fn strip_common(base: &Path, path: &Path) -> String { } } +#[inline] +pub(crate) fn can_be_variable_name(name: &str) -> bool { + !matches!(name, "self" | "Self" | "super" | "crate") +} + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum IntKind { I8, diff --git a/askama_parser/src/target.rs b/askama_parser/src/target.rs index 59ca14a0..a2178adf 100644 --- a/askama_parser/src/target.rs +++ b/askama_parser/src/target.rs @@ -108,6 +108,15 @@ impl<'a> Target<'a> { return Ok(Self::Struct(path, targets)); } + // If the path only contains one item, we need to check the name. + if let [name] = path.as_slice() { + if !crate::can_be_variable_name(name) { + return Err(winnow::error::ErrMode::Cut(ErrorContext::new( + format!("`{name}` cannot be used as an identifier"), + i_before_matching_with, + ))); + } + } *i = i_before_matching_with; return Ok(Self::Path(path)); } @@ -204,6 +213,11 @@ fn verify_name<'a>( format!("cannot use `{name}` as a name: it is a rust keyword"), input, ))) + } else if !crate::can_be_variable_name(name) { + Err(winnow::error::ErrMode::Cut(ErrorContext::new( + format!("`{name}` cannot be used as an identifier"), + input, + ))) } else if name.starts_with("__askama") { Err(winnow::error::ErrMode::Cut(ErrorContext::new( format!("cannot use `{name}` as a name: it is reserved for `askama`"),