minijinja/compiler/
ast.rs

1use std::ops::Deref;
2
3#[cfg(feature = "internal_debug")]
4use std::fmt;
5
6use crate::compiler::tokens::Span;
7use crate::value::{ops, value_map_with_capacity, Value};
8
9/// Container for nodes with location info.
10///
11/// This container fulfills two purposes: it adds location information
12/// to nodes, but it also ensures the nodes is heap allocated.  The
13/// latter is useful to ensure that enum variants do not cause the enum
14/// to become too large.
15#[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))]
16pub struct Spanned<T> {
17    inner: Box<(T, Span)>,
18}
19
20impl<T> Spanned<T> {
21    /// Creates a new spanned node.
22    pub fn new(node: T, span: Span) -> Spanned<T> {
23        Spanned {
24            inner: Box::new((node, span)),
25        }
26    }
27
28    /// Accesses the span.
29    pub fn span(&self) -> Span {
30        self.inner.1
31    }
32}
33
34impl<T> Deref for Spanned<T> {
35    type Target = T;
36
37    fn deref(&self) -> &Self::Target {
38        &self.inner.0
39    }
40}
41
42#[cfg(feature = "internal_debug")]
43impl<T: fmt::Debug> fmt::Debug for Spanned<T> {
44    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45        ok!(fmt::Debug::fmt(&self.inner.0, f));
46        write!(f, "{:?}", self.inner.1)
47    }
48}
49
50/// A statement node.
51#[cfg_attr(
52    feature = "unstable_machinery_serde",
53    derive(serde::Serialize),
54    serde(tag = "stmt")
55)]
56pub enum Stmt<'a> {
57    Template(Spanned<Template<'a>>),
58    EmitExpr(Spanned<EmitExpr<'a>>),
59    EmitRaw(Spanned<EmitRaw<'a>>),
60    ForLoop(Spanned<ForLoop<'a>>),
61    IfCond(Spanned<IfCond<'a>>),
62    WithBlock(Spanned<WithBlock<'a>>),
63    Set(Spanned<Set<'a>>),
64    SetBlock(Spanned<SetBlock<'a>>),
65    AutoEscape(Spanned<AutoEscape<'a>>),
66    FilterBlock(Spanned<FilterBlock<'a>>),
67    #[cfg(feature = "multi_template")]
68    Block(Spanned<Block<'a>>),
69    #[cfg(feature = "multi_template")]
70    Import(Spanned<Import<'a>>),
71    #[cfg(feature = "multi_template")]
72    FromImport(Spanned<FromImport<'a>>),
73    #[cfg(feature = "multi_template")]
74    Extends(Spanned<Extends<'a>>),
75    #[cfg(feature = "multi_template")]
76    Include(Spanned<Include<'a>>),
77    #[cfg(feature = "macros")]
78    Macro(Spanned<Macro<'a>>),
79    #[cfg(feature = "macros")]
80    CallBlock(Spanned<CallBlock<'a>>),
81    #[cfg(feature = "loop_controls")]
82    Continue(Spanned<Continue>),
83    #[cfg(feature = "loop_controls")]
84    Break(Spanned<Break>),
85    Do(Spanned<Do<'a>>),
86}
87
88#[cfg(feature = "internal_debug")]
89impl fmt::Debug for Stmt<'_> {
90    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91        match self {
92            Stmt::Template(s) => fmt::Debug::fmt(s, f),
93            Stmt::EmitExpr(s) => fmt::Debug::fmt(s, f),
94            Stmt::EmitRaw(s) => fmt::Debug::fmt(s, f),
95            Stmt::ForLoop(s) => fmt::Debug::fmt(s, f),
96            Stmt::IfCond(s) => fmt::Debug::fmt(s, f),
97            Stmt::WithBlock(s) => fmt::Debug::fmt(s, f),
98            Stmt::Set(s) => fmt::Debug::fmt(s, f),
99            Stmt::SetBlock(s) => fmt::Debug::fmt(s, f),
100            Stmt::AutoEscape(s) => fmt::Debug::fmt(s, f),
101            Stmt::FilterBlock(s) => fmt::Debug::fmt(s, f),
102            #[cfg(feature = "multi_template")]
103            Stmt::Block(s) => fmt::Debug::fmt(s, f),
104            #[cfg(feature = "multi_template")]
105            Stmt::Extends(s) => fmt::Debug::fmt(s, f),
106            #[cfg(feature = "multi_template")]
107            Stmt::Include(s) => fmt::Debug::fmt(s, f),
108            #[cfg(feature = "multi_template")]
109            Stmt::Import(s) => fmt::Debug::fmt(s, f),
110            #[cfg(feature = "multi_template")]
111            Stmt::FromImport(s) => fmt::Debug::fmt(s, f),
112            #[cfg(feature = "macros")]
113            Stmt::Macro(s) => fmt::Debug::fmt(s, f),
114            #[cfg(feature = "macros")]
115            Stmt::CallBlock(s) => fmt::Debug::fmt(s, f),
116            #[cfg(feature = "loop_controls")]
117            Stmt::Continue(s) => fmt::Debug::fmt(s, f),
118            #[cfg(feature = "loop_controls")]
119            Stmt::Break(s) => fmt::Debug::fmt(s, f),
120            Stmt::Do(s) => fmt::Debug::fmt(s, f),
121        }
122    }
123}
124
125/// An expression node.
126#[allow(clippy::enum_variant_names)]
127#[cfg_attr(
128    feature = "unstable_machinery_serde",
129    derive(serde::Serialize),
130    serde(tag = "expr")
131)]
132pub enum Expr<'a> {
133    Var(Spanned<Var<'a>>),
134    Const(Spanned<Const>),
135    Slice(Spanned<Slice<'a>>),
136    UnaryOp(Spanned<UnaryOp<'a>>),
137    BinOp(Spanned<BinOp<'a>>),
138    IfExpr(Spanned<IfExpr<'a>>),
139    Filter(Spanned<Filter<'a>>),
140    Test(Spanned<Test<'a>>),
141    GetAttr(Spanned<GetAttr<'a>>),
142    GetItem(Spanned<GetItem<'a>>),
143    Call(Spanned<Call<'a>>),
144    List(Spanned<List<'a>>),
145    Map(Spanned<Map<'a>>),
146}
147
148#[cfg(feature = "internal_debug")]
149impl fmt::Debug for Expr<'_> {
150    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
151        match self {
152            Expr::Var(s) => fmt::Debug::fmt(s, f),
153            Expr::Const(s) => fmt::Debug::fmt(s, f),
154            Expr::Slice(s) => fmt::Debug::fmt(s, f),
155            Expr::UnaryOp(s) => fmt::Debug::fmt(s, f),
156            Expr::BinOp(s) => fmt::Debug::fmt(s, f),
157            Expr::IfExpr(s) => fmt::Debug::fmt(s, f),
158            Expr::Filter(s) => fmt::Debug::fmt(s, f),
159            Expr::Test(s) => fmt::Debug::fmt(s, f),
160            Expr::GetAttr(s) => fmt::Debug::fmt(s, f),
161            Expr::GetItem(s) => fmt::Debug::fmt(s, f),
162            Expr::Call(s) => fmt::Debug::fmt(s, f),
163            Expr::List(s) => fmt::Debug::fmt(s, f),
164            Expr::Map(s) => fmt::Debug::fmt(s, f),
165        }
166    }
167}
168
169impl Expr<'_> {
170    pub fn description(&self) -> &'static str {
171        match self {
172            Expr::Var(_) => "variable",
173            Expr::Const(_) => "constant",
174            Expr::Slice(_)
175            | Expr::UnaryOp(_)
176            | Expr::BinOp(_)
177            | Expr::IfExpr(_)
178            | Expr::GetAttr(_)
179            | Expr::GetItem(_) => "expression",
180            Expr::Call(_) => "call",
181            Expr::List(_) => "list literal",
182            Expr::Map(_) => "map literal",
183            Expr::Test(_) => "test expression",
184            Expr::Filter(_) => "filter expression",
185        }
186    }
187
188    pub fn span(&self) -> Span {
189        match self {
190            Expr::Var(s) => s.span(),
191            Expr::Const(s) => s.span(),
192            Expr::Slice(s) => s.span(),
193            Expr::UnaryOp(s) => s.span(),
194            Expr::BinOp(s) => s.span(),
195            Expr::IfExpr(s) => s.span(),
196            Expr::Filter(s) => s.span(),
197            Expr::Test(s) => s.span(),
198            Expr::GetAttr(s) => s.span(),
199            Expr::GetItem(s) => s.span(),
200            Expr::Call(s) => s.span(),
201            Expr::List(s) => s.span(),
202            Expr::Map(s) => s.span(),
203        }
204    }
205
206    pub fn as_const(&self) -> Option<Value> {
207        match self {
208            Expr::Const(c) => Some(c.value.clone()),
209            Expr::List(l) => l.as_const(),
210            Expr::Map(m) => m.as_const(),
211            Expr::UnaryOp(c) => match c.op {
212                UnaryOpKind::Not => c.expr.as_const().map(|value| Value::from(!value.is_true())),
213                UnaryOpKind::Neg => c.expr.as_const().and_then(|v| ops::neg(&v).ok()),
214            },
215            Expr::BinOp(c) => {
216                let (Some(left), Some(right)) = (c.left.as_const(), c.right.as_const()) else {
217                    return None;
218                };
219                match c.op {
220                    BinOpKind::Add => ops::add(&left, &right).ok(),
221                    BinOpKind::Sub => ops::sub(&left, &right).ok(),
222                    BinOpKind::Mul => ops::mul(&left, &right).ok(),
223                    BinOpKind::Div => ops::div(&left, &right).ok(),
224                    BinOpKind::FloorDiv => ops::int_div(&left, &right).ok(),
225                    BinOpKind::Rem => ops::rem(&left, &right).ok(),
226                    BinOpKind::Pow => ops::pow(&left, &right).ok(),
227                    BinOpKind::Concat => Some(ops::string_concat(left, &right)),
228                    BinOpKind::Eq => Some(Value::from(left == right)),
229                    BinOpKind::Ne => Some(Value::from(left != right)),
230                    BinOpKind::Lt => Some(Value::from(left < right)),
231                    BinOpKind::Lte => Some(Value::from(left <= right)),
232                    BinOpKind::Gt => Some(Value::from(left > right)),
233                    BinOpKind::Gte => Some(Value::from(left >= right)),
234                    BinOpKind::In => ops::contains(&right, &left).ok(),
235                    BinOpKind::ScAnd => Some(if left.is_true() && right.is_true() {
236                        right
237                    } else {
238                        Value::from(false)
239                    }),
240                    BinOpKind::ScOr => Some(if left.is_true() { left } else { right }),
241                }
242            }
243            _ => None,
244        }
245    }
246}
247
248/// Root template node.
249#[cfg_attr(feature = "internal_debug", derive(Debug))]
250#[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))]
251pub struct Template<'a> {
252    pub children: Vec<Stmt<'a>>,
253}
254
255/// A for loop.
256#[cfg_attr(feature = "internal_debug", derive(Debug))]
257#[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))]
258pub struct ForLoop<'a> {
259    pub target: Expr<'a>,
260    pub iter: Expr<'a>,
261    pub filter_expr: Option<Expr<'a>>,
262    pub recursive: bool,
263    pub body: Vec<Stmt<'a>>,
264    pub else_body: Vec<Stmt<'a>>,
265}
266
267/// An if/else condition.
268#[cfg_attr(feature = "internal_debug", derive(Debug))]
269#[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))]
270pub struct IfCond<'a> {
271    pub expr: Expr<'a>,
272    pub true_body: Vec<Stmt<'a>>,
273    pub false_body: Vec<Stmt<'a>>,
274}
275
276/// A with block.
277#[cfg_attr(feature = "internal_debug", derive(Debug))]
278#[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))]
279pub struct WithBlock<'a> {
280    pub assignments: Vec<(Expr<'a>, Expr<'a>)>,
281    pub body: Vec<Stmt<'a>>,
282}
283
284/// A set statement.
285#[cfg_attr(feature = "internal_debug", derive(Debug))]
286#[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))]
287pub struct Set<'a> {
288    pub target: Expr<'a>,
289    pub expr: Expr<'a>,
290}
291
292/// A set capture statement.
293#[cfg_attr(feature = "internal_debug", derive(Debug))]
294#[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))]
295pub struct SetBlock<'a> {
296    pub target: Expr<'a>,
297    pub filter: Option<Expr<'a>>,
298    pub body: Vec<Stmt<'a>>,
299}
300
301/// A block for inheritance elements.
302#[cfg_attr(feature = "internal_debug", derive(Debug))]
303#[cfg(feature = "multi_template")]
304#[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))]
305pub struct Block<'a> {
306    pub name: &'a str,
307    pub body: Vec<Stmt<'a>>,
308}
309
310/// An extends block.
311#[cfg_attr(feature = "internal_debug", derive(Debug))]
312#[cfg(feature = "multi_template")]
313#[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))]
314pub struct Extends<'a> {
315    pub name: Expr<'a>,
316}
317
318/// An include block.
319#[cfg_attr(feature = "internal_debug", derive(Debug))]
320#[cfg(feature = "multi_template")]
321#[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))]
322pub struct Include<'a> {
323    pub name: Expr<'a>,
324    pub ignore_missing: bool,
325}
326
327/// An auto escape control block.
328#[cfg_attr(feature = "internal_debug", derive(Debug))]
329#[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))]
330pub struct AutoEscape<'a> {
331    pub enabled: Expr<'a>,
332    pub body: Vec<Stmt<'a>>,
333}
334
335/// Applies filters to a block.
336#[cfg_attr(feature = "internal_debug", derive(Debug))]
337#[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))]
338pub struct FilterBlock<'a> {
339    pub filter: Expr<'a>,
340    pub body: Vec<Stmt<'a>>,
341}
342
343/// Declares a macro.
344#[cfg_attr(feature = "internal_debug", derive(Debug))]
345#[cfg(feature = "macros")]
346#[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))]
347pub struct Macro<'a> {
348    pub name: &'a str,
349    pub args: Vec<Expr<'a>>,
350    pub defaults: Vec<Expr<'a>>,
351    pub body: Vec<Stmt<'a>>,
352}
353
354/// A call block
355#[cfg_attr(feature = "internal_debug", derive(Debug))]
356#[cfg(feature = "macros")]
357#[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))]
358pub struct CallBlock<'a> {
359    pub call: Spanned<Call<'a>>,
360    pub macro_decl: Spanned<Macro<'a>>,
361}
362
363/// Continue
364#[cfg_attr(feature = "internal_debug", derive(Debug))]
365#[cfg(feature = "loop_controls")]
366#[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))]
367pub struct Continue;
368
369/// Break
370#[cfg_attr(feature = "internal_debug", derive(Debug))]
371#[cfg(feature = "loop_controls")]
372#[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))]
373pub struct Break;
374
375/// A call block
376#[cfg_attr(feature = "internal_debug", derive(Debug))]
377#[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))]
378pub struct Do<'a> {
379    pub call: Spanned<Call<'a>>,
380}
381
382/// A "from" import
383#[cfg_attr(feature = "internal_debug", derive(Debug))]
384#[cfg(feature = "multi_template")]
385#[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))]
386pub struct FromImport<'a> {
387    pub expr: Expr<'a>,
388    pub names: Vec<(Expr<'a>, Option<Expr<'a>>)>,
389}
390
391/// A full module import
392#[cfg_attr(feature = "internal_debug", derive(Debug))]
393#[cfg(feature = "multi_template")]
394#[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))]
395pub struct Import<'a> {
396    pub expr: Expr<'a>,
397    pub name: Expr<'a>,
398}
399
400/// Outputs the expression.
401#[cfg_attr(feature = "internal_debug", derive(Debug))]
402#[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))]
403pub struct EmitExpr<'a> {
404    pub expr: Expr<'a>,
405}
406
407/// Outputs raw template code.
408#[cfg_attr(feature = "internal_debug", derive(Debug))]
409#[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))]
410pub struct EmitRaw<'a> {
411    pub raw: &'a str,
412}
413
414/// Looks up a variable.
415#[cfg_attr(feature = "internal_debug", derive(Debug))]
416#[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))]
417pub struct Var<'a> {
418    pub id: &'a str,
419}
420
421/// Loads a constant
422#[cfg_attr(feature = "internal_debug", derive(Debug))]
423#[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))]
424pub struct Const {
425    pub value: Value,
426}
427
428/// Represents a slice.
429#[cfg_attr(feature = "internal_debug", derive(Debug))]
430#[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))]
431pub struct Slice<'a> {
432    pub expr: Expr<'a>,
433    pub start: Option<Expr<'a>>,
434    pub stop: Option<Expr<'a>>,
435    pub step: Option<Expr<'a>>,
436}
437
438/// A kind of unary operator.
439#[cfg_attr(feature = "internal_debug", derive(Debug))]
440#[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))]
441pub enum UnaryOpKind {
442    Not,
443    Neg,
444}
445
446/// An unary operator expression.
447#[cfg_attr(feature = "internal_debug", derive(Debug))]
448#[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))]
449pub struct UnaryOp<'a> {
450    pub op: UnaryOpKind,
451    pub expr: Expr<'a>,
452}
453
454/// A kind of binary operator.
455#[cfg_attr(feature = "internal_debug", derive(Debug))]
456#[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))]
457pub enum BinOpKind {
458    Eq,
459    Ne,
460    Lt,
461    Lte,
462    Gt,
463    Gte,
464    ScAnd,
465    ScOr,
466    Add,
467    Sub,
468    Mul,
469    Div,
470    FloorDiv,
471    Rem,
472    Pow,
473    Concat,
474    In,
475}
476
477/// A binary operator expression.
478#[cfg_attr(feature = "internal_debug", derive(Debug))]
479#[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))]
480pub struct BinOp<'a> {
481    pub op: BinOpKind,
482    pub left: Expr<'a>,
483    pub right: Expr<'a>,
484}
485
486/// An if expression.
487#[cfg_attr(feature = "internal_debug", derive(Debug))]
488#[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))]
489pub struct IfExpr<'a> {
490    pub test_expr: Expr<'a>,
491    pub true_expr: Expr<'a>,
492    pub false_expr: Option<Expr<'a>>,
493}
494
495/// A filter expression.
496#[cfg_attr(feature = "internal_debug", derive(Debug))]
497#[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))]
498pub struct Filter<'a> {
499    pub name: &'a str,
500    pub expr: Option<Expr<'a>>,
501    pub args: Vec<CallArg<'a>>,
502}
503
504/// A test expression.
505#[cfg_attr(feature = "internal_debug", derive(Debug))]
506#[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))]
507pub struct Test<'a> {
508    pub name: &'a str,
509    pub expr: Expr<'a>,
510    pub args: Vec<CallArg<'a>>,
511}
512
513/// An attribute lookup expression.
514#[cfg_attr(feature = "internal_debug", derive(Debug))]
515#[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))]
516pub struct GetAttr<'a> {
517    pub expr: Expr<'a>,
518    pub name: &'a str,
519}
520
521/// An item lookup expression.
522#[cfg_attr(feature = "internal_debug", derive(Debug))]
523#[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))]
524pub struct GetItem<'a> {
525    pub expr: Expr<'a>,
526    pub subscript_expr: Expr<'a>,
527}
528
529/// Calls something.
530#[cfg_attr(feature = "internal_debug", derive(Debug))]
531#[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))]
532pub struct Call<'a> {
533    pub expr: Expr<'a>,
534    pub args: Vec<CallArg<'a>>,
535}
536
537/// A call argument helper
538#[cfg_attr(feature = "internal_debug", derive(Debug))]
539#[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))]
540pub enum CallArg<'a> {
541    Pos(Expr<'a>),
542    Kwarg(&'a str, Expr<'a>),
543    PosSplat(Expr<'a>),
544    KwargSplat(Expr<'a>),
545}
546
547/// Creates a list of values.
548#[cfg_attr(feature = "internal_debug", derive(Debug))]
549#[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))]
550pub struct List<'a> {
551    pub items: Vec<Expr<'a>>,
552}
553
554impl List<'_> {
555    pub fn as_const(&self) -> Option<Value> {
556        if !self.items.iter().all(|x| matches!(x, Expr::Const(_))) {
557            return None;
558        }
559
560        let items = self.items.iter();
561        let sequence = items.filter_map(|expr| match expr {
562            Expr::Const(v) => Some(v.value.clone()),
563            _ => None,
564        });
565
566        Some(Value::from(sequence.collect::<Vec<_>>()))
567    }
568}
569
570/// Creates a map of values.
571#[cfg_attr(feature = "internal_debug", derive(Debug))]
572#[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))]
573pub struct Map<'a> {
574    pub keys: Vec<Expr<'a>>,
575    pub values: Vec<Expr<'a>>,
576}
577
578impl Map<'_> {
579    pub fn as_const(&self) -> Option<Value> {
580        if !self.keys.iter().all(|x| matches!(x, Expr::Const(_)))
581            || !self.values.iter().all(|x| matches!(x, Expr::Const(_)))
582        {
583            return None;
584        }
585
586        let mut rv = value_map_with_capacity(self.keys.len());
587        for (key, value) in self.keys.iter().zip(self.values.iter()) {
588            if let (Expr::Const(maybe_key), Expr::Const(value)) = (key, value) {
589                rv.insert(maybe_key.value.clone(), value.value.clone());
590            }
591        }
592
593        Some(Value::from_object(rv))
594    }
595}
596
597/// Defines the specific type of call.
598#[cfg_attr(feature = "internal_debug", derive(Debug))]
599#[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))]
600pub enum CallType<'ast, 'source> {
601    Function(&'source str),
602    Method(&'ast Expr<'source>, &'source str),
603    #[cfg(feature = "multi_template")]
604    Block(&'source str),
605    Object(&'ast Expr<'source>),
606}
607
608impl<'a> Call<'a> {
609    /// Try to isolate a method call.
610    ///
611    /// name + call and attribute lookup + call are really method
612    /// calls which are easier to handle for the compiler as a separate
613    /// thing.
614    pub fn identify_call(&self) -> CallType<'_, 'a> {
615        match self.expr {
616            Expr::Var(ref var) => CallType::Function(var.id),
617            Expr::GetAttr(ref attr) => {
618                #[cfg(feature = "multi_template")]
619                {
620                    if let Expr::Var(ref var) = attr.expr {
621                        if var.id == "self" {
622                            return CallType::Block(attr.name);
623                        }
624                    }
625                }
626                CallType::Method(&attr.expr, attr.name)
627            }
628            _ => CallType::Object(&self.expr),
629        }
630    }
631}