Rename Expr::Attr into Expr::AssociatedItem

This commit is contained in:
Guillaume Gomez 2025-06-24 11:58:59 +02:00 committed by René Kijewski
parent 338369a424
commit ca6573bf56
5 changed files with 64 additions and 42 deletions

View File

@ -594,16 +594,18 @@ fn is_copyable_within_op(expr: &Expr<'_>, within_op: bool) -> bool {
// will solve that issue. However, if the operand is // will solve that issue. However, if the operand is
// implicitly borrowed, then it's likely not even possible // implicitly borrowed, then it's likely not even possible
// to get the template to compile. // to get the template to compile.
_ => within_op && is_attr_self(expr), _ => within_op && is_associated_item_self(expr),
} }
} }
/// Returns `true` if this is an `Attr` where the `obj` is `"self"`. /// Returns `true` if this is an `AssociatedItem` where the `obj` is `"self"`.
fn is_attr_self(mut expr: &Expr<'_>) -> bool { fn is_associated_item_self(mut expr: &Expr<'_>) -> bool {
loop { loop {
match expr { match expr {
Expr::Attr(obj, _) if matches!(***obj, Expr::Var("self")) => return true, Expr::AssociatedItem(obj, _) if matches!(***obj, Expr::Var("self")) => return true,
Expr::Attr(obj, _) if matches!(***obj, Expr::Attr(..)) => expr = obj, Expr::AssociatedItem(obj, _) if matches!(***obj, Expr::AssociatedItem(..)) => {
expr = obj
}
_ => return false, _ => return false,
} }
} }

View File

@ -1,7 +1,9 @@
use std::borrow::Cow; use std::borrow::Cow;
use parser::node::CondTest; use parser::node::CondTest;
use parser::{Attr, CharLit, CharPrefix, Expr, Span, StrLit, Target, TyGenerics, WithSpan}; use parser::{
AssociatedItem, CharLit, CharPrefix, Expr, Span, StrLit, Target, TyGenerics, WithSpan,
};
use quote::quote; use quote::quote;
use super::{ use super::{
@ -46,7 +48,7 @@ impl<'a> Generator<'a, '_> {
} }
// If accessing a field then it most likely needs to be // If accessing a field then it most likely needs to be
// borrowed, to prevent an attempt of moving. // borrowed, to prevent an attempt of moving.
Expr::Attr(..) => buf.write(format_args!("(&{expr_code}).into_iter()")), Expr::AssociatedItem(..) => buf.write(format_args!("(&{expr_code}).into_iter()")),
// Otherwise, we borrow `iter` assuming that it implements `IntoIterator`. // Otherwise, we borrow `iter` assuming that it implements `IntoIterator`.
_ => buf.write(format_args!("({expr_code}).into_iter()")), _ => buf.write(format_args!("({expr_code}).into_iter()")),
} }
@ -67,7 +69,9 @@ impl<'a> Generator<'a, '_> {
Expr::Var(s) => self.visit_var(buf, s), Expr::Var(s) => self.visit_var(buf, s),
Expr::Path(ref path) => self.visit_path(buf, path), Expr::Path(ref path) => self.visit_path(buf, path),
Expr::Array(ref elements) => self.visit_array(ctx, buf, elements)?, Expr::Array(ref elements) => self.visit_array(ctx, buf, elements)?,
Expr::Attr(ref obj, ref attr) => self.visit_attr(ctx, buf, obj, attr)?, Expr::AssociatedItem(ref obj, ref associated_item) => {
self.visit_associated_item(ctx, buf, obj, associated_item)?
}
Expr::Index(ref obj, ref key) => self.visit_index(ctx, buf, obj, key)?, Expr::Index(ref obj, ref key) => self.visit_index(ctx, buf, obj, key)?,
Expr::Filter(ref v) => { Expr::Filter(ref v) => {
self.visit_filter(ctx, buf, &v.name, &v.arguments, &v.generics, expr.span())? self.visit_filter(ctx, buf, &v.name, &v.arguments, &v.generics, expr.span())?
@ -389,15 +393,15 @@ impl<'a> Generator<'a, '_> {
Ok(()) Ok(())
} }
pub(crate) fn visit_attr( pub(crate) fn visit_associated_item(
&mut self, &mut self,
ctx: &Context<'_>, ctx: &Context<'_>,
buf: &mut Buffer, buf: &mut Buffer,
obj: &WithSpan<'a, Expr<'a>>, obj: &WithSpan<'a, Expr<'a>>,
attr: &Attr<'a>, associated_item: &AssociatedItem<'a>,
) -> Result<DisplayWrap, CompileError> { ) -> Result<DisplayWrap, CompileError> {
if let Expr::Var("loop") = **obj { if let Expr::Var("loop") = **obj {
buf.write(match attr.name { buf.write(match associated_item.name {
"index0" => "__askama_item.index0", "index0" => "__askama_item.index0",
"index" => "(__askama_item.index0 + 1)", "index" => "(__askama_item.index0 + 1)",
"first" => "(__askama_item.index0 == 0)", "first" => "(__askama_item.index0 == 0)",
@ -413,8 +417,11 @@ impl<'a> Generator<'a, '_> {
} }
self.visit_expr(ctx, buf, obj)?; self.visit_expr(ctx, buf, obj)?;
buf.write(format_args!(".{}", normalize_identifier(attr.name))); buf.write(format_args!(
self.visit_call_generics(buf, &attr.generics); ".{}",
normalize_identifier(associated_item.name)
));
self.visit_call_generics(buf, &associated_item.generics);
Ok(DisplayWrap::Unwrapped) Ok(DisplayWrap::Unwrapped)
} }
@ -479,7 +486,9 @@ impl<'a> Generator<'a, '_> {
generics: &[WithSpan<'a, TyGenerics<'a>>], generics: &[WithSpan<'a, TyGenerics<'a>>],
) -> Result<DisplayWrap, CompileError> { ) -> Result<DisplayWrap, CompileError> {
match &**left { match &**left {
Expr::Attr(sub_left, Attr { name, .. }) if ***sub_left == Expr::Var("loop") => { Expr::AssociatedItem(sub_left, AssociatedItem { name, .. })
if ***sub_left == Expr::Var("loop") =>
{
match *name { match *name {
"cycle" => { "cycle" => {
if let [generic, ..] = generics { if let [generic, ..] = generics {

View File

@ -196,7 +196,7 @@ impl<'a> Generator<'a, '_> {
| Expr::Var(_) | Expr::Var(_)
| Expr::Path(_) | Expr::Path(_)
| Expr::Array(_) | Expr::Array(_)
| Expr::Attr(_, _) | Expr::AssociatedItem(_, _)
| Expr::Index(_, _) | Expr::Index(_, _)
| Expr::Filter(_) | Expr::Filter(_)
| Expr::Range(_) | Expr::Range(_)
@ -702,12 +702,12 @@ impl<'a> Generator<'a, '_> {
this.locals this.locals
.insert(Cow::Borrowed(arg), LocalMeta::with_ref(var)); .insert(Cow::Borrowed(arg), LocalMeta::with_ref(var));
} }
Expr::Attr(obj, attr) => { Expr::AssociatedItem(obj, associated_item) => {
let mut attr_buf = Buffer::new(); let mut associated_item_buf = Buffer::new();
this.visit_attr(ctx, &mut attr_buf, obj, attr)?; this.visit_associated_item(ctx, &mut associated_item_buf, obj, associated_item)?;
let attr = attr_buf.into_string(); let associated_item = associated_item_buf.into_string();
let var = this.locals.resolve(&attr).unwrap_or(attr); let var = this.locals.resolve(&associated_item).unwrap_or(associated_item);
this.locals this.locals
.insert(Cow::Borrowed(arg), LocalMeta::with_ref(var)); .insert(Cow::Borrowed(arg), LocalMeta::with_ref(var));
} }
@ -1133,12 +1133,20 @@ impl<'a> Generator<'a, '_> {
this.locals this.locals
.insert(Cow::Borrowed(arg), LocalMeta::with_ref(var)); .insert(Cow::Borrowed(arg), LocalMeta::with_ref(var));
} }
Expr::Attr(obj, attr) => { Expr::AssociatedItem(obj, associated_item) => {
let mut attr_buf = Buffer::new(); let mut associated_item_buf = Buffer::new();
this.visit_attr(ctx, &mut attr_buf, obj, attr)?; this.visit_associated_item(
ctx,
&mut associated_item_buf,
obj,
associated_item,
)?;
let attr = attr_buf.into_string(); let associated_item = associated_item_buf.into_string();
let var = this.locals.resolve(&attr).unwrap_or(attr); let var = this
.locals
.resolve(&associated_item)
.unwrap_or(associated_item);
this.locals this.locals
.insert(Cow::Borrowed(arg), LocalMeta::with_ref(var)); .insert(Cow::Borrowed(arg), LocalMeta::with_ref(var));
} }
@ -1639,7 +1647,7 @@ fn is_cacheable(expr: &WithSpan<'_, Expr<'_>>) -> bool {
Expr::Path(_) => true, Expr::Path(_) => true,
// Check recursively: // Check recursively:
Expr::Array(args) => args.iter().all(is_cacheable), Expr::Array(args) => args.iter().all(is_cacheable),
Expr::Attr(lhs, _) => is_cacheable(lhs), Expr::AssociatedItem(lhs, _) => is_cacheable(lhs),
Expr::Index(lhs, rhs) => is_cacheable(lhs) && is_cacheable(rhs), Expr::Index(lhs, rhs) => is_cacheable(lhs) && is_cacheable(rhs),
Expr::Filter(v) => v.arguments.iter().all(is_cacheable), Expr::Filter(v) => v.arguments.iter().all(is_cacheable),
Expr::Unary(_, arg) => is_cacheable(arg), Expr::Unary(_, arg) => is_cacheable(arg),

View File

@ -84,11 +84,11 @@ fn check_expr<'a>(expr: &WithSpan<'a, Expr<'a>>, allowed: Allowed) -> Result<(),
} }
Ok(()) Ok(())
} }
Expr::Attr(elem, attr) => { Expr::AssociatedItem(elem, associated_item) => {
if attr.name == "_" { if associated_item.name == "_" {
Err(err_underscore_identifier(attr.name)) Err(err_underscore_identifier(associated_item.name))
} else if !crate::can_be_variable_name(attr.name) { } else if !crate::can_be_variable_name(associated_item.name) {
Err(err_reserved_identifier(attr.name)) Err(err_reserved_identifier(associated_item.name))
} else { } else {
check_expr(elem, Allowed::default()) check_expr(elem, Allowed::default())
} }
@ -163,7 +163,7 @@ pub enum Expr<'a> {
Var(&'a str), Var(&'a str),
Path(Vec<&'a str>), Path(Vec<&'a str>),
Array(Vec<WithSpan<'a, Expr<'a>>>), Array(Vec<WithSpan<'a, Expr<'a>>>),
Attr(Box<WithSpan<'a, Expr<'a>>>, Attr<'a>), AssociatedItem(Box<WithSpan<'a, Expr<'a>>>, AssociatedItem<'a>),
Index(Box<WithSpan<'a, Expr<'a>>>, Box<WithSpan<'a, Expr<'a>>>), Index(Box<WithSpan<'a, Expr<'a>>>, Box<WithSpan<'a, Expr<'a>>>),
Filter(Box<Filter<'a>>), Filter(Box<Filter<'a>>),
As(Box<WithSpan<'a, Expr<'a>>>, &'a str), As(Box<WithSpan<'a, Expr<'a>>>, &'a str),
@ -460,7 +460,7 @@ impl<'a> Expr<'a> {
}; };
let var_name = match *lhs { let var_name = match *lhs {
Self::Var(var_name) => var_name, Self::Var(var_name) => var_name,
Self::Attr(_, _) => { Self::AssociatedItem(_, _) => {
return Err(ErrMode::Cut(ErrorContext::new( return Err(ErrMode::Cut(ErrorContext::new(
"`is defined` operator can only be used on variables, not on their fields", "`is defined` operator can only be used on variables, not on their fields",
start, start,
@ -632,7 +632,7 @@ impl<'a> Expr<'a> {
| Self::Try(_) | Self::Try(_)
| Self::NamedArgument(_, _) | Self::NamedArgument(_, _)
| Self::Filter(_) | Self::Filter(_)
| Self::Attr(_, _) | Self::AssociatedItem(_, _)
| Self::Index(_, _) | Self::Index(_, _)
| Self::Tuple(_) | Self::Tuple(_)
| Self::Array(_) | Self::Array(_)
@ -677,13 +677,13 @@ pub struct Filter<'a> {
} }
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct Attr<'a> { pub struct AssociatedItem<'a> {
pub name: &'a str, pub name: &'a str,
pub generics: Vec<WithSpan<'a, TyGenerics<'a>>>, pub generics: Vec<WithSpan<'a, TyGenerics<'a>>>,
} }
enum Suffix<'a> { enum Suffix<'a> {
Attr(Attr<'a>), AssociatedItem(AssociatedItem<'a>),
Index(WithSpan<'a, Expr<'a>>), Index(WithSpan<'a, Expr<'a>>),
Call { Call {
args: Vec<WithSpan<'a, Expr<'a>>>, args: Vec<WithSpan<'a, Expr<'a>>>,
@ -699,7 +699,7 @@ impl<'a> Suffix<'a> {
let mut level_guard = level.guard(); let mut level_guard = level.guard();
let mut expr = Expr::single(i, level)?; let mut expr = Expr::single(i, level)?;
let mut right = opt(alt(( let mut right = opt(alt((
|i: &mut _| Self::attr(i, level), |i: &mut _| Self::associated_item(i, level),
|i: &mut _| Self::index(i, level), |i: &mut _| Self::index(i, level),
|i: &mut _| Self::call(i, level), |i: &mut _| Self::call(i, level),
Self::r#try, Self::r#try,
@ -714,8 +714,11 @@ impl<'a> Suffix<'a> {
level_guard.nest(before_suffix)?; level_guard.nest(before_suffix)?;
match suffix { match suffix {
Self::Attr(attr) => { Self::AssociatedItem(associated_item) => {
expr = WithSpan::new(Expr::Attr(expr.into(), attr), before_suffix) expr = WithSpan::new(
Expr::AssociatedItem(expr.into(), associated_item),
before_suffix,
)
} }
Self::Index(index) => { Self::Index(index) => {
expr = WithSpan::new(Expr::Index(expr.into(), index.into()), before_suffix); expr = WithSpan::new(Expr::Index(expr.into(), index.into()), before_suffix);
@ -1034,7 +1037,7 @@ impl<'a> Suffix<'a> {
(|i: &mut _| macro_arguments(i, open_token)).parse_next(i) (|i: &mut _| macro_arguments(i, open_token)).parse_next(i)
} }
fn attr(i: &mut &'a str, level: Level<'_>) -> ParseResult<'a, Self> { fn associated_item(i: &mut &'a str, level: Level<'_>) -> ParseResult<'a, Self> {
preceded( preceded(
ws(('.', not('.'))), ws(('.', not('.'))),
cut_err(( cut_err((
@ -1053,7 +1056,7 @@ impl<'a> Suffix<'a> {
)), )),
) )
.map(|(name, generics)| { .map(|(name, generics)| {
Self::Attr(Attr { Self::AssociatedItem(AssociatedItem {
name, name,
generics: generics.unwrap_or_default(), generics: generics.unwrap_or_default(),
}) })

View File

@ -27,7 +27,7 @@ use winnow::token::{any, none_of, one_of, take_till, take_while};
use winnow::{ModalParser, Parser}; use winnow::{ModalParser, Parser};
use crate::ascii_str::{AsciiChar, AsciiStr}; use crate::ascii_str::{AsciiChar, AsciiStr};
pub use crate::expr::{Attr, Expr, Filter, TyGenerics}; pub use crate::expr::{AssociatedItem, Expr, Filter, TyGenerics};
pub use crate::node::Node; pub use crate::node::Node;
pub use crate::target::Target; pub use crate::target::Target;