1"""Compiles nodes from the parser into Python code."""
2from collections import namedtuple
3from functools import update_wrapper
4from io import StringIO
5from itertools import chain
6from keyword import iskeyword as is_python_keyword
7
8from markupsafe import escape
9from markupsafe import Markup
10
11from . import nodes
12from .exceptions import TemplateAssertionError
13from .idtracking import Symbols
14from .idtracking import VAR_LOAD_ALIAS
15from .idtracking import VAR_LOAD_PARAMETER
16from .idtracking import VAR_LOAD_RESOLVE
17from .idtracking import VAR_LOAD_UNDEFINED
18from .nodes import EvalContext
19from .optimizer import Optimizer
20from .utils import concat
21from .visitor import NodeVisitor
22
23operators = {
24    "eq": "==",
25    "ne": "!=",
26    "gt": ">",
27    "gteq": ">=",
28    "lt": "<",
29    "lteq": "<=",
30    "in": "in",
31    "notin": "not in",
32}
33
34
35def optimizeconst(f):
36    def new_func(self, node, frame, **kwargs):
37        # Only optimize if the frame is not volatile
38        if self.optimized and not frame.eval_ctx.volatile:
39            new_node = self.optimizer.visit(node, frame.eval_ctx)
40            if new_node != node:
41                return self.visit(new_node, frame)
42        return f(self, node, frame, **kwargs)
43
44    return update_wrapper(new_func, f)
45
46
47def generate(
48    node, environment, name, filename, stream=None, defer_init=False, optimized=True
49):
50    """Generate the python source for a node tree."""
51    if not isinstance(node, nodes.Template):
52        raise TypeError("Can't compile non template nodes")
53    generator = environment.code_generator_class(
54        environment, name, filename, stream, defer_init, optimized
55    )
56    generator.visit(node)
57    if stream is None:
58        return generator.stream.getvalue()
59
60
61def has_safe_repr(value):
62    """Does the node have a safe representation?"""
63    if value is None or value is NotImplemented or value is Ellipsis:
64        return True
65
66    if type(value) in {bool, int, float, complex, range, str, Markup}:
67        return True
68
69    if type(value) in {tuple, list, set, frozenset}:
70        return all(has_safe_repr(v) for v in value)
71
72    if type(value) is dict:
73        return all(has_safe_repr(k) and has_safe_repr(v) for k, v in value.items())
74
75    return False
76
77
78def find_undeclared(nodes, names):
79    """Check if the names passed are accessed undeclared.  The return value
80    is a set of all the undeclared names from the sequence of names found.
81    """
82    visitor = UndeclaredNameVisitor(names)
83    try:
84        for node in nodes:
85            visitor.visit(node)
86    except VisitorExit:
87        pass
88    return visitor.undeclared
89
90
91class MacroRef:
92    def __init__(self, node):
93        self.node = node
94        self.accesses_caller = False
95        self.accesses_kwargs = False
96        self.accesses_varargs = False
97
98
99class Frame:
100    """Holds compile time information for us."""
101
102    def __init__(self, eval_ctx, parent=None, level=None):
103        self.eval_ctx = eval_ctx
104        self.symbols = Symbols(parent.symbols if parent else None, level=level)
105
106        # a toplevel frame is the root + soft frames such as if conditions.
107        self.toplevel = False
108
109        # the root frame is basically just the outermost frame, so no if
110        # conditions.  This information is used to optimize inheritance
111        # situations.
112        self.rootlevel = False
113
114        # in some dynamic inheritance situations the compiler needs to add
115        # write tests around output statements.
116        self.require_output_check = parent and parent.require_output_check
117
118        # inside some tags we are using a buffer rather than yield statements.
119        # this for example affects {% filter %} or {% macro %}.  If a frame
120        # is buffered this variable points to the name of the list used as
121        # buffer.
122        self.buffer = None
123
124        # the name of the block we're in, otherwise None.
125        self.block = parent.block if parent else None
126
127        # the parent of this frame
128        self.parent = parent
129
130        if parent is not None:
131            self.buffer = parent.buffer
132
133    def copy(self):
134        """Create a copy of the current one."""
135        rv = object.__new__(self.__class__)
136        rv.__dict__.update(self.__dict__)
137        rv.symbols = self.symbols.copy()
138        return rv
139
140    def inner(self, isolated=False):
141        """Return an inner frame."""
142        if isolated:
143            return Frame(self.eval_ctx, level=self.symbols.level + 1)
144        return Frame(self.eval_ctx, self)
145
146    def soft(self):
147        """Return a soft frame.  A soft frame may not be modified as
148        standalone thing as it shares the resources with the frame it
149        was created of, but it's not a rootlevel frame any longer.
150
151        This is only used to implement if-statements.
152        """
153        rv = self.copy()
154        rv.rootlevel = False
155        return rv
156
157    __copy__ = copy
158
159
160class VisitorExit(RuntimeError):
161    """Exception used by the `UndeclaredNameVisitor` to signal a stop."""
162
163
164class DependencyFinderVisitor(NodeVisitor):
165    """A visitor that collects filter and test calls."""
166
167    def __init__(self):
168        self.filters = set()
169        self.tests = set()
170
171    def visit_Filter(self, node):
172        self.generic_visit(node)
173        self.filters.add(node.name)
174
175    def visit_Test(self, node):
176        self.generic_visit(node)
177        self.tests.add(node.name)
178
179    def visit_Block(self, node):
180        """Stop visiting at blocks."""
181
182
183class UndeclaredNameVisitor(NodeVisitor):
184    """A visitor that checks if a name is accessed without being
185    declared.  This is different from the frame visitor as it will
186    not stop at closure frames.
187    """
188
189    def __init__(self, names):
190        self.names = set(names)
191        self.undeclared = set()
192
193    def visit_Name(self, node):
194        if node.ctx == "load" and node.name in self.names:
195            self.undeclared.add(node.name)
196            if self.undeclared == self.names:
197                raise VisitorExit()
198        else:
199            self.names.discard(node.name)
200
201    def visit_Block(self, node):
202        """Stop visiting a blocks."""
203
204
205class CompilerExit(Exception):
206    """Raised if the compiler encountered a situation where it just
207    doesn't make sense to further process the code.  Any block that
208    raises such an exception is not further processed.
209    """
210
211
212class CodeGenerator(NodeVisitor):
213    def __init__(
214        self, environment, name, filename, stream=None, defer_init=False, optimized=True
215    ):
216        if stream is None:
217            stream = StringIO()
218        self.environment = environment
219        self.name = name
220        self.filename = filename
221        self.stream = stream
222        self.created_block_context = False
223        self.defer_init = defer_init
224        self.optimized = optimized
225        if optimized:
226            self.optimizer = Optimizer(environment)
227
228        # aliases for imports
229        self.import_aliases = {}
230
231        # a registry for all blocks.  Because blocks are moved out
232        # into the global python scope they are registered here
233        self.blocks = {}
234
235        # the number of extends statements so far
236        self.extends_so_far = 0
237
238        # some templates have a rootlevel extends.  In this case we
239        # can safely assume that we're a child template and do some
240        # more optimizations.
241        self.has_known_extends = False
242
243        # the current line number
244        self.code_lineno = 1
245
246        # registry of all filters and tests (global, not block local)
247        self.tests = {}
248        self.filters = {}
249
250        # the debug information
251        self.debug_info = []
252        self._write_debug_info = None
253
254        # the number of new lines before the next write()
255        self._new_lines = 0
256
257        # the line number of the last written statement
258        self._last_line = 0
259
260        # true if nothing was written so far.
261        self._first_write = True
262
263        # used by the `temporary_identifier` method to get new
264        # unique, temporary identifier
265        self._last_identifier = 0
266
267        # the current indentation
268        self._indentation = 0
269
270        # Tracks toplevel assignments
271        self._assign_stack = []
272
273        # Tracks parameter definition blocks
274        self._param_def_block = []
275
276        # Tracks the current context.
277        self._context_reference_stack = ["context"]
278
279    # -- Various compilation helpers
280
281    def fail(self, msg, lineno):
282        """Fail with a :exc:`TemplateAssertionError`."""
283        raise TemplateAssertionError(msg, lineno, self.name, self.filename)
284
285    def temporary_identifier(self):
286        """Get a new unique identifier."""
287        self._last_identifier += 1
288        return f"t_{self._last_identifier}"
289
290    def buffer(self, frame):
291        """Enable buffering for the frame from that point onwards."""
292        frame.buffer = self.temporary_identifier()
293        self.writeline(f"{frame.buffer} = []")
294
295    def return_buffer_contents(self, frame, force_unescaped=False):
296        """Return the buffer contents of the frame."""
297        if not force_unescaped:
298            if frame.eval_ctx.volatile:
299                self.writeline("if context.eval_ctx.autoescape:")
300                self.indent()
301                self.writeline(f"return Markup(concat({frame.buffer}))")
302                self.outdent()
303                self.writeline("else:")
304                self.indent()
305                self.writeline(f"return concat({frame.buffer})")
306                self.outdent()
307                return
308            elif frame.eval_ctx.autoescape:
309                self.writeline(f"return Markup(concat({frame.buffer}))")
310                return
311        self.writeline(f"return concat({frame.buffer})")
312
313    def indent(self):
314        """Indent by one."""
315        self._indentation += 1
316
317    def outdent(self, step=1):
318        """Outdent by step."""
319        self._indentation -= step
320
321    def start_write(self, frame, node=None):
322        """Yield or write into the frame buffer."""
323        if frame.buffer is None:
324            self.writeline("yield ", node)
325        else:
326            self.writeline(f"{frame.buffer}.append(", node)
327
328    def end_write(self, frame):
329        """End the writing process started by `start_write`."""
330        if frame.buffer is not None:
331            self.write(")")
332
333    def simple_write(self, s, frame, node=None):
334        """Simple shortcut for start_write + write + end_write."""
335        self.start_write(frame, node)
336        self.write(s)
337        self.end_write(frame)
338
339    def blockvisit(self, nodes, frame):
340        """Visit a list of nodes as block in a frame.  If the current frame
341        is no buffer a dummy ``if 0: yield None`` is written automatically.
342        """
343        try:
344            self.writeline("pass")
345            for node in nodes:
346                self.visit(node, frame)
347        except CompilerExit:
348            pass
349
350    def write(self, x):
351        """Write a string into the output stream."""
352        if self._new_lines:
353            if not self._first_write:
354                self.stream.write("\n" * self._new_lines)
355                self.code_lineno += self._new_lines
356                if self._write_debug_info is not None:
357                    self.debug_info.append((self._write_debug_info, self.code_lineno))
358                    self._write_debug_info = None
359            self._first_write = False
360            self.stream.write("    " * self._indentation)
361            self._new_lines = 0
362        self.stream.write(x)
363
364    def writeline(self, x, node=None, extra=0):
365        """Combination of newline and write."""
366        self.newline(node, extra)
367        self.write(x)
368
369    def newline(self, node=None, extra=0):
370        """Add one or more newlines before the next write."""
371        self._new_lines = max(self._new_lines, 1 + extra)
372        if node is not None and node.lineno != self._last_line:
373            self._write_debug_info = node.lineno
374            self._last_line = node.lineno
375
376    def signature(self, node, frame, extra_kwargs=None):
377        """Writes a function call to the stream for the current node.
378        A leading comma is added automatically.  The extra keyword
379        arguments may not include python keywords otherwise a syntax
380        error could occur.  The extra keyword arguments should be given
381        as python dict.
382        """
383        # if any of the given keyword arguments is a python keyword
384        # we have to make sure that no invalid call is created.
385        kwarg_workaround = False
386        for kwarg in chain((x.key for x in node.kwargs), extra_kwargs or ()):
387            if is_python_keyword(kwarg):
388                kwarg_workaround = True
389                break
390
391        for arg in node.args:
392            self.write(", ")
393            self.visit(arg, frame)
394
395        if not kwarg_workaround:
396            for kwarg in node.kwargs:
397                self.write(", ")
398                self.visit(kwarg, frame)
399            if extra_kwargs is not None:
400                for key, value in extra_kwargs.items():
401                    self.write(f", {key}={value}")
402        if node.dyn_args:
403            self.write(", *")
404            self.visit(node.dyn_args, frame)
405
406        if kwarg_workaround:
407            if node.dyn_kwargs is not None:
408                self.write(", **dict({")
409            else:
410                self.write(", **{")
411            for kwarg in node.kwargs:
412                self.write(f"{kwarg.key!r}: ")
413                self.visit(kwarg.value, frame)
414                self.write(", ")
415            if extra_kwargs is not None:
416                for key, value in extra_kwargs.items():
417                    self.write(f"{key!r}: {value}, ")
418            if node.dyn_kwargs is not None:
419                self.write("}, **")
420                self.visit(node.dyn_kwargs, frame)
421                self.write(")")
422            else:
423                self.write("}")
424
425        elif node.dyn_kwargs is not None:
426            self.write(", **")
427            self.visit(node.dyn_kwargs, frame)
428
429    def pull_dependencies(self, nodes):
430        """Pull all the dependencies."""
431        visitor = DependencyFinderVisitor()
432        for node in nodes:
433            visitor.visit(node)
434        for dependency in "filters", "tests":
435            mapping = getattr(self, dependency)
436            for name in getattr(visitor, dependency):
437                if name not in mapping:
438                    mapping[name] = self.temporary_identifier()
439                self.writeline(f"{mapping[name]} = environment.{dependency}[{name!r}]")
440
441    def enter_frame(self, frame):
442        undefs = []
443        for target, (action, param) in frame.symbols.loads.items():
444            if action == VAR_LOAD_PARAMETER:
445                pass
446            elif action == VAR_LOAD_RESOLVE:
447                self.writeline(f"{target} = {self.get_resolve_func()}({param!r})")
448            elif action == VAR_LOAD_ALIAS:
449                self.writeline(f"{target} = {param}")
450            elif action == VAR_LOAD_UNDEFINED:
451                undefs.append(target)
452            else:
453                raise NotImplementedError("unknown load instruction")
454        if undefs:
455            self.writeline(f"{' = '.join(undefs)} = missing")
456
457    def leave_frame(self, frame, with_python_scope=False):
458        if not with_python_scope:
459            undefs = []
460            for target in frame.symbols.loads:
461                undefs.append(target)
462            if undefs:
463                self.writeline(f"{' = '.join(undefs)} = missing")
464
465    def func(self, name):
466        if self.environment.is_async:
467            return f"async def {name}"
468        return f"def {name}"
469
470    def macro_body(self, node, frame):
471        """Dump the function def of a macro or call block."""
472        frame = frame.inner()
473        frame.symbols.analyze_node(node)
474        macro_ref = MacroRef(node)
475
476        explicit_caller = None
477        skip_special_params = set()
478        args = []
479        for idx, arg in enumerate(node.args):
480            if arg.name == "caller":
481                explicit_caller = idx
482            if arg.name in ("kwargs", "varargs"):
483                skip_special_params.add(arg.name)
484            args.append(frame.symbols.ref(arg.name))
485
486        undeclared = find_undeclared(node.body, ("caller", "kwargs", "varargs"))
487
488        if "caller" in undeclared:
489            # In older Jinja versions there was a bug that allowed caller
490            # to retain the special behavior even if it was mentioned in
491            # the argument list.  However thankfully this was only really
492            # working if it was the last argument.  So we are explicitly
493            # checking this now and error out if it is anywhere else in
494            # the argument list.
495            if explicit_caller is not None:
496                try:
497                    node.defaults[explicit_caller - len(node.args)]
498                except IndexError:
499                    self.fail(
500                        "When defining macros or call blocks the "
501                        'special "caller" argument must be omitted '
502                        "or be given a default.",
503                        node.lineno,
504                    )
505            else:
506                args.append(frame.symbols.declare_parameter("caller"))
507            macro_ref.accesses_caller = True
508        if "kwargs" in undeclared and "kwargs" not in skip_special_params:
509            args.append(frame.symbols.declare_parameter("kwargs"))
510            macro_ref.accesses_kwargs = True
511        if "varargs" in undeclared and "varargs" not in skip_special_params:
512            args.append(frame.symbols.declare_parameter("varargs"))
513            macro_ref.accesses_varargs = True
514
515        # macros are delayed, they never require output checks
516        frame.require_output_check = False
517        frame.symbols.analyze_node(node)
518        self.writeline(f"{self.func('macro')}({', '.join(args)}):", node)
519        self.indent()
520
521        self.buffer(frame)
522        self.enter_frame(frame)
523
524        self.push_parameter_definitions(frame)
525        for idx, arg in enumerate(node.args):
526            ref = frame.symbols.ref(arg.name)
527            self.writeline(f"if {ref} is missing:")
528            self.indent()
529            try:
530                default = node.defaults[idx - len(node.args)]
531            except IndexError:
532                self.writeline(
533                    f'{ref} = undefined("parameter {arg.name!r} was not provided",'
534                    f" name={arg.name!r})"
535                )
536            else:
537                self.writeline(f"{ref} = ")
538                self.visit(default, frame)
539            self.mark_parameter_stored(ref)
540            self.outdent()
541        self.pop_parameter_definitions()
542
543        self.blockvisit(node.body, frame)
544        self.return_buffer_contents(frame, force_unescaped=True)
545        self.leave_frame(frame, with_python_scope=True)
546        self.outdent()
547
548        return frame, macro_ref
549
550    def macro_def(self, macro_ref, frame):
551        """Dump the macro definition for the def created by macro_body."""
552        arg_tuple = ", ".join(repr(x.name) for x in macro_ref.node.args)
553        name = getattr(macro_ref.node, "name", None)
554        if len(macro_ref.node.args) == 1:
555            arg_tuple += ","
556        self.write(
557            f"Macro(environment, macro, {name!r}, ({arg_tuple}),"
558            f" {macro_ref.accesses_kwargs!r}, {macro_ref.accesses_varargs!r},"
559            f" {macro_ref.accesses_caller!r}, context.eval_ctx.autoescape)"
560        )
561
562    def position(self, node):
563        """Return a human readable position for the node."""
564        rv = f"line {node.lineno}"
565        if self.name is not None:
566            rv = f"{rv} in {self.name!r}"
567        return rv
568
569    def dump_local_context(self, frame):
570        items_kv = ", ".join(
571            f"{name!r}: {target}"
572            for name, target in frame.symbols.dump_stores().items()
573        )
574        return f"{{{items_kv}}}"
575
576    def write_commons(self):
577        """Writes a common preamble that is used by root and block functions.
578        Primarily this sets up common local helpers and enforces a generator
579        through a dead branch.
580        """
581        self.writeline("resolve = context.resolve_or_missing")
582        self.writeline("undefined = environment.undefined")
583        # always use the standard Undefined class for the implicit else of
584        # conditional expressions
585        self.writeline("cond_expr_undefined = Undefined")
586        self.writeline("if 0: yield None")
587
588    def push_parameter_definitions(self, frame):
589        """Pushes all parameter targets from the given frame into a local
590        stack that permits tracking of yet to be assigned parameters.  In
591        particular this enables the optimization from `visit_Name` to skip
592        undefined expressions for parameters in macros as macros can reference
593        otherwise unbound parameters.
594        """
595        self._param_def_block.append(frame.symbols.dump_param_targets())
596
597    def pop_parameter_definitions(self):
598        """Pops the current parameter definitions set."""
599        self._param_def_block.pop()
600
601    def mark_parameter_stored(self, target):
602        """Marks a parameter in the current parameter definitions as stored.
603        This will skip the enforced undefined checks.
604        """
605        if self._param_def_block:
606            self._param_def_block[-1].discard(target)
607
608    def push_context_reference(self, target):
609        self._context_reference_stack.append(target)
610
611    def pop_context_reference(self):
612        self._context_reference_stack.pop()
613
614    def get_context_ref(self):
615        return self._context_reference_stack[-1]
616
617    def get_resolve_func(self):
618        target = self._context_reference_stack[-1]
619        if target == "context":
620            return "resolve"
621        return f"{target}.resolve"
622
623    def derive_context(self, frame):
624        return f"{self.get_context_ref()}.derived({self.dump_local_context(frame)})"
625
626    def parameter_is_undeclared(self, target):
627        """Checks if a given target is an undeclared parameter."""
628        if not self._param_def_block:
629            return False
630        return target in self._param_def_block[-1]
631
632    def push_assign_tracking(self):
633        """Pushes a new layer for assignment tracking."""
634        self._assign_stack.append(set())
635
636    def pop_assign_tracking(self, frame):
637        """Pops the topmost level for assignment tracking and updates the
638        context variables if necessary.
639        """
640        vars = self._assign_stack.pop()
641        if not frame.toplevel or not vars:
642            return
643        public_names = [x for x in vars if x[:1] != "_"]
644        if len(vars) == 1:
645            name = next(iter(vars))
646            ref = frame.symbols.ref(name)
647            self.writeline(f"context.vars[{name!r}] = {ref}")
648        else:
649            self.writeline("context.vars.update({")
650            for idx, name in enumerate(vars):
651                if idx:
652                    self.write(", ")
653                ref = frame.symbols.ref(name)
654                self.write(f"{name!r}: {ref}")
655            self.write("})")
656        if public_names:
657            if len(public_names) == 1:
658                self.writeline(f"context.exported_vars.add({public_names[0]!r})")
659            else:
660                names_str = ", ".join(map(repr, public_names))
661                self.writeline(f"context.exported_vars.update(({names_str}))")
662
663    # -- Statement Visitors
664
665    def visit_Template(self, node, frame=None):
666        assert frame is None, "no root frame allowed"
667        eval_ctx = EvalContext(self.environment, self.name)
668
669        from .runtime import exported
670
671        self.writeline("from __future__ import generator_stop")  # Python < 3.7
672        self.writeline("from jinja2.runtime import " + ", ".join(exported))
673
674        if self.environment.is_async:
675            self.writeline(
676                "from jinja2.asyncsupport import auto_await, "
677                "auto_aiter, AsyncLoopContext"
678            )
679
680        # if we want a deferred initialization we cannot move the
681        # environment into a local name
682        envenv = "" if self.defer_init else ", environment=environment"
683
684        # do we have an extends tag at all?  If not, we can save some
685        # overhead by just not processing any inheritance code.
686        have_extends = node.find(nodes.Extends) is not None
687
688        # find all blocks
689        for block in node.find_all(nodes.Block):
690            if block.name in self.blocks:
691                self.fail(f"block {block.name!r} defined twice", block.lineno)
692            self.blocks[block.name] = block
693
694        # find all imports and import them
695        for import_ in node.find_all(nodes.ImportedName):
696            if import_.importname not in self.import_aliases:
697                imp = import_.importname
698                self.import_aliases[imp] = alias = self.temporary_identifier()
699                if "." in imp:
700                    module, obj = imp.rsplit(".", 1)
701                    self.writeline(f"from {module} import {obj} as {alias}")
702                else:
703                    self.writeline(f"import {imp} as {alias}")
704
705        # add the load name
706        self.writeline(f"name = {self.name!r}")
707
708        # generate the root render function.
709        self.writeline(
710            f"{self.func('root')}(context, missing=missing{envenv}):", extra=1
711        )
712        self.indent()
713        self.write_commons()
714
715        # process the root
716        frame = Frame(eval_ctx)
717        if "self" in find_undeclared(node.body, ("self",)):
718            ref = frame.symbols.declare_parameter("self")
719            self.writeline(f"{ref} = TemplateReference(context)")
720        frame.symbols.analyze_node(node)
721        frame.toplevel = frame.rootlevel = True
722        frame.require_output_check = have_extends and not self.has_known_extends
723        if have_extends:
724            self.writeline("parent_template = None")
725        self.enter_frame(frame)
726        self.pull_dependencies(node.body)
727        self.blockvisit(node.body, frame)
728        self.leave_frame(frame, with_python_scope=True)
729        self.outdent()
730
731        # make sure that the parent root is called.
732        if have_extends:
733            if not self.has_known_extends:
734                self.indent()
735                self.writeline("if parent_template is not None:")
736            self.indent()
737            if not self.environment.is_async:
738                self.writeline("yield from parent_template.root_render_func(context)")
739            else:
740                loop = "async for" if self.environment.is_async else "for"
741                self.writeline(
742                    f"{loop} event in parent_template.root_render_func(context):"
743                )
744                self.indent()
745                self.writeline("yield event")
746                self.outdent()
747            self.outdent(1 + (not self.has_known_extends))
748
749        # at this point we now have the blocks collected and can visit them too.
750        for name, block in self.blocks.items():
751            self.writeline(
752                f"{self.func('block_' + name)}(context, missing=missing{envenv}):",
753                block,
754                1,
755            )
756            self.indent()
757            self.write_commons()
758            # It's important that we do not make this frame a child of the
759            # toplevel template.  This would cause a variety of
760            # interesting issues with identifier tracking.
761            block_frame = Frame(eval_ctx)
762            undeclared = find_undeclared(block.body, ("self", "super"))
763            if "self" in undeclared:
764                ref = block_frame.symbols.declare_parameter("self")
765                self.writeline(f"{ref} = TemplateReference(context)")
766            if "super" in undeclared:
767                ref = block_frame.symbols.declare_parameter("super")
768                self.writeline(f"{ref} = context.super({name!r}, block_{name})")
769            block_frame.symbols.analyze_node(block)
770            block_frame.block = name
771            self.enter_frame(block_frame)
772            self.pull_dependencies(block.body)
773            self.blockvisit(block.body, block_frame)
774            self.leave_frame(block_frame, with_python_scope=True)
775            self.outdent()
776
777        blocks_kv_str = ", ".join(f"{x!r}: block_{x}" for x in self.blocks)
778        self.writeline(f"blocks = {{{blocks_kv_str}}}", extra=1)
779        debug_kv_str = "&".join(f"{k}={v}" for k, v in self.debug_info)
780        self.writeline(f"debug_info = {debug_kv_str!r}")
781
782    def visit_Block(self, node, frame):
783        """Call a block and register it for the template."""
784        level = 0
785        if frame.toplevel:
786            # if we know that we are a child template, there is no need to
787            # check if we are one
788            if self.has_known_extends:
789                return
790            if self.extends_so_far > 0:
791                self.writeline("if parent_template is None:")
792                self.indent()
793                level += 1
794
795        if node.scoped:
796            context = self.derive_context(frame)
797        else:
798            context = self.get_context_ref()
799
800        if not self.environment.is_async and frame.buffer is None:
801            self.writeline(
802                f"yield from context.blocks[{node.name!r}][0]({context})", node
803            )
804        else:
805            loop = "async for" if self.environment.is_async else "for"
806            self.writeline(
807                f"{loop} event in context.blocks[{node.name!r}][0]({context}):", node
808            )
809            self.indent()
810            self.simple_write("event", frame)
811            self.outdent()
812
813        self.outdent(level)
814
815    def visit_Extends(self, node, frame):
816        """Calls the extender."""
817        if not frame.toplevel:
818            self.fail("cannot use extend from a non top-level scope", node.lineno)
819
820        # if the number of extends statements in general is zero so
821        # far, we don't have to add a check if something extended
822        # the template before this one.
823        if self.extends_so_far > 0:
824
825            # if we have a known extends we just add a template runtime
826            # error into the generated code.  We could catch that at compile
827            # time too, but i welcome it not to confuse users by throwing the
828            # same error at different times just "because we can".
829            if not self.has_known_extends:
830                self.writeline("if parent_template is not None:")
831                self.indent()
832            self.writeline('raise TemplateRuntimeError("extended multiple times")')
833
834            # if we have a known extends already we don't need that code here
835            # as we know that the template execution will end here.
836            if self.has_known_extends:
837                raise CompilerExit()
838            else:
839                self.outdent()
840
841        self.writeline("parent_template = environment.get_template(", node)
842        self.visit(node.template, frame)
843        self.write(f", {self.name!r})")
844        self.writeline("for name, parent_block in parent_template.blocks.items():")
845        self.indent()
846        self.writeline("context.blocks.setdefault(name, []).append(parent_block)")
847        self.outdent()
848
849        # if this extends statement was in the root level we can take
850        # advantage of that information and simplify the generated code
851        # in the top level from this point onwards
852        if frame.rootlevel:
853            self.has_known_extends = True
854
855        # and now we have one more
856        self.extends_so_far += 1
857
858    def visit_Include(self, node, frame):
859        """Handles includes."""
860        if node.ignore_missing:
861            self.writeline("try:")
862            self.indent()
863
864        func_name = "get_or_select_template"
865        if isinstance(node.template, nodes.Const):
866            if isinstance(node.template.value, str):
867                func_name = "get_template"
868            elif isinstance(node.template.value, (tuple, list)):
869                func_name = "select_template"
870        elif isinstance(node.template, (nodes.Tuple, nodes.List)):
871            func_name = "select_template"
872
873        self.writeline(f"template = environment.{func_name}(", node)
874        self.visit(node.template, frame)
875        self.write(f", {self.name!r})")
876        if node.ignore_missing:
877            self.outdent()
878            self.writeline("except TemplateNotFound:")
879            self.indent()
880            self.writeline("pass")
881            self.outdent()
882            self.writeline("else:")
883            self.indent()
884
885        skip_event_yield = False
886        if node.with_context:
887            loop = "async for" if self.environment.is_async else "for"
888            self.writeline(
889                f"{loop} event in template.root_render_func("
890                "template.new_context(context.get_all(), True,"
891                f" {self.dump_local_context(frame)})):"
892            )
893        elif self.environment.is_async:
894            self.writeline(
895                "for event in (await template._get_default_module_async())"
896                "._body_stream:"
897            )
898        else:
899            self.writeline("yield from template._get_default_module()._body_stream")
900            skip_event_yield = True
901
902        if not skip_event_yield:
903            self.indent()
904            self.simple_write("event", frame)
905            self.outdent()
906
907        if node.ignore_missing:
908            self.outdent()
909
910    def visit_Import(self, node, frame):
911        """Visit regular imports."""
912        self.writeline(f"{frame.symbols.ref(node.target)} = ", node)
913        if frame.toplevel:
914            self.write(f"context.vars[{node.target!r}] = ")
915        if self.environment.is_async:
916            self.write("await ")
917        self.write("environment.get_template(")
918        self.visit(node.template, frame)
919        self.write(f", {self.name!r}).")
920        if node.with_context:
921            func = "make_module" + ("_async" if self.environment.is_async else "")
922            self.write(
923                f"{func}(context.get_all(), True, {self.dump_local_context(frame)})"
924            )
925        elif self.environment.is_async:
926            self.write("_get_default_module_async()")
927        else:
928            self.write("_get_default_module(context)")
929        if frame.toplevel and not node.target.startswith("_"):
930            self.writeline(f"context.exported_vars.discard({node.target!r})")
931
932    def visit_FromImport(self, node, frame):
933        """Visit named imports."""
934        self.newline(node)
935        prefix = "await " if self.environment.is_async else ""
936        self.write(f"included_template = {prefix}environment.get_template(")
937        self.visit(node.template, frame)
938        self.write(f", {self.name!r}).")
939        if node.with_context:
940            func = "make_module" + ("_async" if self.environment.is_async else "")
941            self.write(
942                f"{func}(context.get_all(), True, {self.dump_local_context(frame)})"
943            )
944        elif self.environment.is_async:
945            self.write("_get_default_module_async()")
946        else:
947            self.write("_get_default_module(context)")
948
949        var_names = []
950        discarded_names = []
951        for name in node.names:
952            if isinstance(name, tuple):
953                name, alias = name
954            else:
955                alias = name
956            self.writeline(
957                f"{frame.symbols.ref(alias)} ="
958                f" getattr(included_template, {name!r}, missing)"
959            )
960            self.writeline(f"if {frame.symbols.ref(alias)} is missing:")
961            self.indent()
962            message = (
963                "the template {included_template.__name__!r}"
964                f" (imported on {self.position(node)})"
965                f" does not export the requested name {name!r}"
966            )
967            self.writeline(
968                f"{frame.symbols.ref(alias)} = undefined(f{message!r}, name={name!r})"
969            )
970            self.outdent()
971            if frame.toplevel:
972                var_names.append(alias)
973                if not alias.startswith("_"):
974                    discarded_names.append(alias)
975
976        if var_names:
977            if len(var_names) == 1:
978                name = var_names[0]
979                self.writeline(f"context.vars[{name!r}] = {frame.symbols.ref(name)}")
980            else:
981                names_kv = ", ".join(
982                    f"{name!r}: {frame.symbols.ref(name)}" for name in var_names
983                )
984                self.writeline(f"context.vars.update({{{names_kv}}})")
985        if discarded_names:
986            if len(discarded_names) == 1:
987                self.writeline(f"context.exported_vars.discard({discarded_names[0]!r})")
988            else:
989                names_str = ", ".join(map(repr, discarded_names))
990                self.writeline(
991                    f"context.exported_vars.difference_update(({names_str}))"
992                )
993
994    def visit_For(self, node, frame):
995        loop_frame = frame.inner()
996        test_frame = frame.inner()
997        else_frame = frame.inner()
998
999        # try to figure out if we have an extended loop.  An extended loop
1000        # is necessary if the loop is in recursive mode if the special loop
1001        # variable is accessed in the body.
1002        extended_loop = node.recursive or "loop" in find_undeclared(
1003            node.iter_child_nodes(only=("body",)), ("loop",)
1004        )
1005
1006        loop_ref = None
1007        if extended_loop:
1008            loop_ref = loop_frame.symbols.declare_parameter("loop")
1009
1010        loop_frame.symbols.analyze_node(node, for_branch="body")
1011        if node.else_:
1012            else_frame.symbols.analyze_node(node, for_branch="else")
1013
1014        if node.test:
1015            loop_filter_func = self.temporary_identifier()
1016            test_frame.symbols.analyze_node(node, for_branch="test")
1017            self.writeline(f"{self.func(loop_filter_func)}(fiter):", node.test)
1018            self.indent()
1019            self.enter_frame(test_frame)
1020            self.writeline("async for " if self.environment.is_async else "for ")
1021            self.visit(node.target, loop_frame)
1022            self.write(" in ")
1023            self.write("auto_aiter(fiter)" if self.environment.is_async else "fiter")
1024            self.write(":")
1025            self.indent()
1026            self.writeline("if ", node.test)
1027            self.visit(node.test, test_frame)
1028            self.write(":")
1029            self.indent()
1030            self.writeline("yield ")
1031            self.visit(node.target, loop_frame)
1032            self.outdent(3)
1033            self.leave_frame(test_frame, with_python_scope=True)
1034
1035        # if we don't have an recursive loop we have to find the shadowed
1036        # variables at that point.  Because loops can be nested but the loop
1037        # variable is a special one we have to enforce aliasing for it.
1038        if node.recursive:
1039            self.writeline(
1040                f"{self.func('loop')}(reciter, loop_render_func, depth=0):", node
1041            )
1042            self.indent()
1043            self.buffer(loop_frame)
1044
1045            # Use the same buffer for the else frame
1046            else_frame.buffer = loop_frame.buffer
1047
1048        # make sure the loop variable is a special one and raise a template
1049        # assertion error if a loop tries to write to loop
1050        if extended_loop:
1051            self.writeline(f"{loop_ref} = missing")
1052
1053        for name in node.find_all(nodes.Name):
1054            if name.ctx == "store" and name.name == "loop":
1055                self.fail(
1056                    "Can't assign to special loop variable in for-loop target",
1057                    name.lineno,
1058                )
1059
1060        if node.else_:
1061            iteration_indicator = self.temporary_identifier()
1062            self.writeline(f"{iteration_indicator} = 1")
1063
1064        self.writeline("async for " if self.environment.is_async else "for ", node)
1065        self.visit(node.target, loop_frame)
1066        if extended_loop:
1067            prefix = "Async" if self.environment.is_async else ""
1068            self.write(f", {loop_ref} in {prefix}LoopContext(")
1069        else:
1070            self.write(" in ")
1071
1072        if node.test:
1073            self.write(f"{loop_filter_func}(")
1074        if node.recursive:
1075            self.write("reciter")
1076        else:
1077            if self.environment.is_async and not extended_loop:
1078                self.write("auto_aiter(")
1079            self.visit(node.iter, frame)
1080            if self.environment.is_async and not extended_loop:
1081                self.write(")")
1082        if node.test:
1083            self.write(")")
1084
1085        if node.recursive:
1086            self.write(", undefined, loop_render_func, depth):")
1087        else:
1088            self.write(", undefined):" if extended_loop else ":")
1089
1090        self.indent()
1091        self.enter_frame(loop_frame)
1092
1093        self.blockvisit(node.body, loop_frame)
1094        if node.else_:
1095            self.writeline(f"{iteration_indicator} = 0")
1096        self.outdent()
1097        self.leave_frame(
1098            loop_frame, with_python_scope=node.recursive and not node.else_
1099        )
1100
1101        if node.else_:
1102            self.writeline(f"if {iteration_indicator}:")
1103            self.indent()
1104            self.enter_frame(else_frame)
1105            self.blockvisit(node.else_, else_frame)
1106            self.leave_frame(else_frame)
1107            self.outdent()
1108
1109        # if the node was recursive we have to return the buffer contents
1110        # and start the iteration code
1111        if node.recursive:
1112            self.return_buffer_contents(loop_frame)
1113            self.outdent()
1114            self.start_write(frame, node)
1115            if self.environment.is_async:
1116                self.write("await ")
1117            self.write("loop(")
1118            if self.environment.is_async:
1119                self.write("auto_aiter(")
1120            self.visit(node.iter, frame)
1121            if self.environment.is_async:
1122                self.write(")")
1123            self.write(", loop)")
1124            self.end_write(frame)
1125
1126    def visit_If(self, node, frame):
1127        if_frame = frame.soft()
1128        self.writeline("if ", node)
1129        self.visit(node.test, if_frame)
1130        self.write(":")
1131        self.indent()
1132        self.blockvisit(node.body, if_frame)
1133        self.outdent()
1134        for elif_ in node.elif_:
1135            self.writeline("elif ", elif_)
1136            self.visit(elif_.test, if_frame)
1137            self.write(":")
1138            self.indent()
1139            self.blockvisit(elif_.body, if_frame)
1140            self.outdent()
1141        if node.else_:
1142            self.writeline("else:")
1143            self.indent()
1144            self.blockvisit(node.else_, if_frame)
1145            self.outdent()
1146
1147    def visit_Macro(self, node, frame):
1148        macro_frame, macro_ref = self.macro_body(node, frame)
1149        self.newline()
1150        if frame.toplevel:
1151            if not node.name.startswith("_"):
1152                self.write(f"context.exported_vars.add({node.name!r})")
1153            self.writeline(f"context.vars[{node.name!r}] = ")
1154        self.write(f"{frame.symbols.ref(node.name)} = ")
1155        self.macro_def(macro_ref, macro_frame)
1156
1157    def visit_CallBlock(self, node, frame):
1158        call_frame, macro_ref = self.macro_body(node, frame)
1159        self.writeline("caller = ")
1160        self.macro_def(macro_ref, call_frame)
1161        self.start_write(frame, node)
1162        self.visit_Call(node.call, frame, forward_caller=True)
1163        self.end_write(frame)
1164
1165    def visit_FilterBlock(self, node, frame):
1166        filter_frame = frame.inner()
1167        filter_frame.symbols.analyze_node(node)
1168        self.enter_frame(filter_frame)
1169        self.buffer(filter_frame)
1170        self.blockvisit(node.body, filter_frame)
1171        self.start_write(frame, node)
1172        self.visit_Filter(node.filter, filter_frame)
1173        self.end_write(frame)
1174        self.leave_frame(filter_frame)
1175
1176    def visit_With(self, node, frame):
1177        with_frame = frame.inner()
1178        with_frame.symbols.analyze_node(node)
1179        self.enter_frame(with_frame)
1180        for target, expr in zip(node.targets, node.values):
1181            self.newline()
1182            self.visit(target, with_frame)
1183            self.write(" = ")
1184            self.visit(expr, frame)
1185        self.blockvisit(node.body, with_frame)
1186        self.leave_frame(with_frame)
1187
1188    def visit_ExprStmt(self, node, frame):
1189        self.newline(node)
1190        self.visit(node.node, frame)
1191
1192    _FinalizeInfo = namedtuple("_FinalizeInfo", ("const", "src"))
1193    #: The default finalize function if the environment isn't configured
1194    #: with one. Or if the environment has one, this is called on that
1195    #: function's output for constants.
1196    _default_finalize = str
1197    _finalize = None
1198
1199    def _make_finalize(self):
1200        """Build the finalize function to be used on constants and at
1201        runtime. Cached so it's only created once for all output nodes.
1202
1203        Returns a ``namedtuple`` with the following attributes:
1204
1205        ``const``
1206            A function to finalize constant data at compile time.
1207
1208        ``src``
1209            Source code to output around nodes to be evaluated at
1210            runtime.
1211        """
1212        if self._finalize is not None:
1213            return self._finalize
1214
1215        finalize = default = self._default_finalize
1216        src = None
1217
1218        if self.environment.finalize:
1219            src = "environment.finalize("
1220            env_finalize = self.environment.finalize
1221
1222            def finalize(value):
1223                return default(env_finalize(value))
1224
1225            if getattr(env_finalize, "contextfunction", False) is True:
1226                src += "context, "
1227                finalize = None  # noqa: F811
1228            elif getattr(env_finalize, "evalcontextfunction", False) is True:
1229                src += "context.eval_ctx, "
1230                finalize = None
1231            elif getattr(env_finalize, "environmentfunction", False) is True:
1232                src += "environment, "
1233
1234                def finalize(value):
1235                    return default(env_finalize(self.environment, value))
1236
1237        self._finalize = self._FinalizeInfo(finalize, src)
1238        return self._finalize
1239
1240    def _output_const_repr(self, group):
1241        """Given a group of constant values converted from ``Output``
1242        child nodes, produce a string to write to the template module
1243        source.
1244        """
1245        return repr(concat(group))
1246
1247    def _output_child_to_const(self, node, frame, finalize):
1248        """Try to optimize a child of an ``Output`` node by trying to
1249        convert it to constant, finalized data at compile time.
1250
1251        If :exc:`Impossible` is raised, the node is not constant and
1252        will be evaluated at runtime. Any other exception will also be
1253        evaluated at runtime for easier debugging.
1254        """
1255        const = node.as_const(frame.eval_ctx)
1256
1257        if frame.eval_ctx.autoescape:
1258            const = escape(const)
1259
1260        # Template data doesn't go through finalize.
1261        if isinstance(node, nodes.TemplateData):
1262            return str(const)
1263
1264        return finalize.const(const)
1265
1266    def _output_child_pre(self, node, frame, finalize):
1267        """Output extra source code before visiting a child of an
1268        ``Output`` node.
1269        """
1270        if frame.eval_ctx.volatile:
1271            self.write("(escape if context.eval_ctx.autoescape else str)(")
1272        elif frame.eval_ctx.autoescape:
1273            self.write("escape(")
1274        else:
1275            self.write("str(")
1276
1277        if finalize.src is not None:
1278            self.write(finalize.src)
1279
1280    def _output_child_post(self, node, frame, finalize):
1281        """Output extra source code after visiting a child of an
1282        ``Output`` node.
1283        """
1284        self.write(")")
1285
1286        if finalize.src is not None:
1287            self.write(")")
1288
1289    def visit_Output(self, node, frame):
1290        # If an extends is active, don't render outside a block.
1291        if frame.require_output_check:
1292            # A top-level extends is known to exist at compile time.
1293            if self.has_known_extends:
1294                return
1295
1296            self.writeline("if parent_template is None:")
1297            self.indent()
1298
1299        finalize = self._make_finalize()
1300        body = []
1301
1302        # Evaluate constants at compile time if possible. Each item in
1303        # body will be either a list of static data or a node to be
1304        # evaluated at runtime.
1305        for child in node.nodes:
1306            try:
1307                if not (
1308                    # If the finalize function requires runtime context,
1309                    # constants can't be evaluated at compile time.
1310                    finalize.const
1311                    # Unless it's basic template data that won't be
1312                    # finalized anyway.
1313                    or isinstance(child, nodes.TemplateData)
1314                ):
1315                    raise nodes.Impossible()
1316
1317                const = self._output_child_to_const(child, frame, finalize)
1318            except (nodes.Impossible, Exception):
1319                # The node was not constant and needs to be evaluated at
1320                # runtime. Or another error was raised, which is easier
1321                # to debug at runtime.
1322                body.append(child)
1323                continue
1324
1325            if body and isinstance(body[-1], list):
1326                body[-1].append(const)
1327            else:
1328                body.append([const])
1329
1330        if frame.buffer is not None:
1331            if len(body) == 1:
1332                self.writeline(f"{frame.buffer}.append(")
1333            else:
1334                self.writeline(f"{frame.buffer}.extend((")
1335
1336            self.indent()
1337
1338        for item in body:
1339            if isinstance(item, list):
1340                # A group of constant data to join and output.
1341                val = self._output_const_repr(item)
1342
1343                if frame.buffer is None:
1344                    self.writeline("yield " + val)
1345                else:
1346                    self.writeline(val + ",")
1347            else:
1348                if frame.buffer is None:
1349                    self.writeline("yield ", item)
1350                else:
1351                    self.newline(item)
1352
1353                # A node to be evaluated at runtime.
1354                self._output_child_pre(item, frame, finalize)
1355                self.visit(item, frame)
1356                self._output_child_post(item, frame, finalize)
1357
1358                if frame.buffer is not None:
1359                    self.write(",")
1360
1361        if frame.buffer is not None:
1362            self.outdent()
1363            self.writeline(")" if len(body) == 1 else "))")
1364
1365        if frame.require_output_check:
1366            self.outdent()
1367
1368    def visit_Assign(self, node, frame):
1369        self.push_assign_tracking()
1370        self.newline(node)
1371        self.visit(node.target, frame)
1372        self.write(" = ")
1373        self.visit(node.node, frame)
1374        self.pop_assign_tracking(frame)
1375
1376    def visit_AssignBlock(self, node, frame):
1377        self.push_assign_tracking()
1378        block_frame = frame.inner()
1379        # This is a special case.  Since a set block always captures we
1380        # will disable output checks.  This way one can use set blocks
1381        # toplevel even in extended templates.
1382        block_frame.require_output_check = False
1383        block_frame.symbols.analyze_node(node)
1384        self.enter_frame(block_frame)
1385        self.buffer(block_frame)
1386        self.blockvisit(node.body, block_frame)
1387        self.newline(node)
1388        self.visit(node.target, frame)
1389        self.write(" = (Markup if context.eval_ctx.autoescape else identity)(")
1390        if node.filter is not None:
1391            self.visit_Filter(node.filter, block_frame)
1392        else:
1393            self.write(f"concat({block_frame.buffer})")
1394        self.write(")")
1395        self.pop_assign_tracking(frame)
1396        self.leave_frame(block_frame)
1397
1398    # -- Expression Visitors
1399
1400    def visit_Name(self, node, frame):
1401        if node.ctx == "store" and frame.toplevel:
1402            if self._assign_stack:
1403                self._assign_stack[-1].add(node.name)
1404        ref = frame.symbols.ref(node.name)
1405
1406        # If we are looking up a variable we might have to deal with the
1407        # case where it's undefined.  We can skip that case if the load
1408        # instruction indicates a parameter which are always defined.
1409        if node.ctx == "load":
1410            load = frame.symbols.find_load(ref)
1411            if not (
1412                load is not None
1413                and load[0] == VAR_LOAD_PARAMETER
1414                and not self.parameter_is_undeclared(ref)
1415            ):
1416                self.write(
1417                    f"(undefined(name={node.name!r}) if {ref} is missing else {ref})"
1418                )
1419                return
1420
1421        self.write(ref)
1422
1423    def visit_NSRef(self, node, frame):
1424        # NSRefs can only be used to store values; since they use the normal
1425        # `foo.bar` notation they will be parsed as a normal attribute access
1426        # when used anywhere but in a `set` context
1427        ref = frame.symbols.ref(node.name)
1428        self.writeline(f"if not isinstance({ref}, Namespace):")
1429        self.indent()
1430        self.writeline(
1431            "raise TemplateRuntimeError"
1432            '("cannot assign attribute on non-namespace object")'
1433        )
1434        self.outdent()
1435        self.writeline(f"{ref}[{node.attr!r}]")
1436
1437    def visit_Const(self, node, frame):
1438        val = node.as_const(frame.eval_ctx)
1439        if isinstance(val, float):
1440            self.write(str(val))
1441        else:
1442            self.write(repr(val))
1443
1444    def visit_TemplateData(self, node, frame):
1445        try:
1446            self.write(repr(node.as_const(frame.eval_ctx)))
1447        except nodes.Impossible:
1448            self.write(
1449                f"(Markup if context.eval_ctx.autoescape else identity)({node.data!r})"
1450            )
1451
1452    def visit_Tuple(self, node, frame):
1453        self.write("(")
1454        idx = -1
1455        for idx, item in enumerate(node.items):
1456            if idx:
1457                self.write(", ")
1458            self.visit(item, frame)
1459        self.write(",)" if idx == 0 else ")")
1460
1461    def visit_List(self, node, frame):
1462        self.write("[")
1463        for idx, item in enumerate(node.items):
1464            if idx:
1465                self.write(", ")
1466            self.visit(item, frame)
1467        self.write("]")
1468
1469    def visit_Dict(self, node, frame):
1470        self.write("{")
1471        for idx, item in enumerate(node.items):
1472            if idx:
1473                self.write(", ")
1474            self.visit(item.key, frame)
1475            self.write(": ")
1476            self.visit(item.value, frame)
1477        self.write("}")
1478
1479    def binop(operator, interceptable=True):  # noqa: B902
1480        @optimizeconst
1481        def visitor(self, node, frame):
1482            if (
1483                self.environment.sandboxed
1484                and operator in self.environment.intercepted_binops
1485            ):
1486                self.write(f"environment.call_binop(context, {operator!r}, ")
1487                self.visit(node.left, frame)
1488                self.write(", ")
1489                self.visit(node.right, frame)
1490            else:
1491                self.write("(")
1492                self.visit(node.left, frame)
1493                self.write(f" {operator} ")
1494                self.visit(node.right, frame)
1495            self.write(")")
1496
1497        return visitor
1498
1499    def uaop(operator, interceptable=True):  # noqa: B902
1500        @optimizeconst
1501        def visitor(self, node, frame):
1502            if (
1503                self.environment.sandboxed
1504                and operator in self.environment.intercepted_unops
1505            ):
1506                self.write(f"environment.call_unop(context, {operator!r}, ")
1507                self.visit(node.node, frame)
1508            else:
1509                self.write("(" + operator)
1510                self.visit(node.node, frame)
1511            self.write(")")
1512
1513        return visitor
1514
1515    visit_Add = binop("+")
1516    visit_Sub = binop("-")
1517    visit_Mul = binop("*")
1518    visit_Div = binop("/")
1519    visit_FloorDiv = binop("//")
1520    visit_Pow = binop("**")
1521    visit_Mod = binop("%")
1522    visit_And = binop("and", interceptable=False)
1523    visit_Or = binop("or", interceptable=False)
1524    visit_Pos = uaop("+")
1525    visit_Neg = uaop("-")
1526    visit_Not = uaop("not ", interceptable=False)
1527    del binop, uaop
1528
1529    @optimizeconst
1530    def visit_Concat(self, node, frame):
1531        if frame.eval_ctx.volatile:
1532            func_name = "(markup_join if context.eval_ctx.volatile else str_join)"
1533        elif frame.eval_ctx.autoescape:
1534            func_name = "markup_join"
1535        else:
1536            func_name = "str_join"
1537        self.write(f"{func_name}((")
1538        for arg in node.nodes:
1539            self.visit(arg, frame)
1540            self.write(", ")
1541        self.write("))")
1542
1543    @optimizeconst
1544    def visit_Compare(self, node, frame):
1545        self.write("(")
1546        self.visit(node.expr, frame)
1547        for op in node.ops:
1548            self.visit(op, frame)
1549        self.write(")")
1550
1551    def visit_Operand(self, node, frame):
1552        self.write(f" {operators[node.op]} ")
1553        self.visit(node.expr, frame)
1554
1555    @optimizeconst
1556    def visit_Getattr(self, node, frame):
1557        if self.environment.is_async:
1558            self.write("(await auto_await(")
1559
1560        self.write("environment.getattr(")
1561        self.visit(node.node, frame)
1562        self.write(f", {node.attr!r})")
1563
1564        if self.environment.is_async:
1565            self.write("))")
1566
1567    @optimizeconst
1568    def visit_Getitem(self, node, frame):
1569        # slices bypass the environment getitem method.
1570        if isinstance(node.arg, nodes.Slice):
1571            self.visit(node.node, frame)
1572            self.write("[")
1573            self.visit(node.arg, frame)
1574            self.write("]")
1575        else:
1576            if self.environment.is_async:
1577                self.write("(await auto_await(")
1578
1579            self.write("environment.getitem(")
1580            self.visit(node.node, frame)
1581            self.write(", ")
1582            self.visit(node.arg, frame)
1583            self.write(")")
1584
1585            if self.environment.is_async:
1586                self.write("))")
1587
1588    def visit_Slice(self, node, frame):
1589        if node.start is not None:
1590            self.visit(node.start, frame)
1591        self.write(":")
1592        if node.stop is not None:
1593            self.visit(node.stop, frame)
1594        if node.step is not None:
1595            self.write(":")
1596            self.visit(node.step, frame)
1597
1598    @optimizeconst
1599    def visit_Filter(self, node, frame):
1600        if self.environment.is_async:
1601            self.write("await auto_await(")
1602        self.write(self.filters[node.name] + "(")
1603        func = self.environment.filters.get(node.name)
1604        if func is None:
1605            self.fail(f"no filter named {node.name!r}", node.lineno)
1606        if getattr(func, "contextfilter", False) is True:
1607            self.write("context, ")
1608        elif getattr(func, "evalcontextfilter", False) is True:
1609            self.write("context.eval_ctx, ")
1610        elif getattr(func, "environmentfilter", False) is True:
1611            self.write("environment, ")
1612
1613        # if the filter node is None we are inside a filter block
1614        # and want to write to the current buffer
1615        if node.node is not None:
1616            self.visit(node.node, frame)
1617        elif frame.eval_ctx.volatile:
1618            self.write(
1619                f"(Markup(concat({frame.buffer}))"
1620                f" if context.eval_ctx.autoescape else concat({frame.buffer}))"
1621            )
1622        elif frame.eval_ctx.autoescape:
1623            self.write(f"Markup(concat({frame.buffer}))")
1624        else:
1625            self.write(f"concat({frame.buffer})")
1626        self.signature(node, frame)
1627        self.write(")")
1628        if self.environment.is_async:
1629            self.write(")")
1630
1631    @optimizeconst
1632    def visit_Test(self, node, frame):
1633        self.write(self.tests[node.name] + "(")
1634        if node.name not in self.environment.tests:
1635            self.fail(f"no test named {node.name!r}", node.lineno)
1636        self.visit(node.node, frame)
1637        self.signature(node, frame)
1638        self.write(")")
1639
1640    @optimizeconst
1641    def visit_CondExpr(self, node, frame):
1642        def write_expr2():
1643            if node.expr2 is not None:
1644                return self.visit(node.expr2, frame)
1645            self.write(
1646                f'cond_expr_undefined("the inline if-expression on'
1647                f" {self.position(node)} evaluated to false and no else"
1648                f' section was defined.")'
1649            )
1650
1651        self.write("(")
1652        self.visit(node.expr1, frame)
1653        self.write(" if ")
1654        self.visit(node.test, frame)
1655        self.write(" else ")
1656        write_expr2()
1657        self.write(")")
1658
1659    @optimizeconst
1660    def visit_Call(self, node, frame, forward_caller=False):
1661        if self.environment.is_async:
1662            self.write("await auto_await(")
1663        if self.environment.sandboxed:
1664            self.write("environment.call(context, ")
1665        else:
1666            self.write("context.call(")
1667        self.visit(node.node, frame)
1668        extra_kwargs = {"caller": "caller"} if forward_caller else None
1669        self.signature(node, frame, extra_kwargs)
1670        self.write(")")
1671        if self.environment.is_async:
1672            self.write(")")
1673
1674    def visit_Keyword(self, node, frame):
1675        self.write(node.key + "=")
1676        self.visit(node.value, frame)
1677
1678    # -- Unused nodes for extensions
1679
1680    def visit_MarkSafe(self, node, frame):
1681        self.write("Markup(")
1682        self.visit(node.expr, frame)
1683        self.write(")")
1684
1685    def visit_MarkSafeIfAutoescape(self, node, frame):
1686        self.write("(Markup if context.eval_ctx.autoescape else identity)(")
1687        self.visit(node.expr, frame)
1688        self.write(")")
1689
1690    def visit_EnvironmentAttribute(self, node, frame):
1691        self.write("environment." + node.name)
1692
1693    def visit_ExtensionAttribute(self, node, frame):
1694        self.write(f"environment.extensions[{node.identifier!r}].{node.name}")
1695
1696    def visit_ImportedName(self, node, frame):
1697        self.write(self.import_aliases[node.importname])
1698
1699    def visit_InternalName(self, node, frame):
1700        self.write(node.name)
1701
1702    def visit_ContextReference(self, node, frame):
1703        self.write("context")
1704
1705    def visit_DerivedContextReference(self, node, frame):
1706        self.write(self.derive_context(frame))
1707
1708    def visit_Continue(self, node, frame):
1709        self.writeline("continue", node)
1710
1711    def visit_Break(self, node, frame):
1712        self.writeline("break", node)
1713
1714    def visit_Scope(self, node, frame):
1715        scope_frame = frame.inner()
1716        scope_frame.symbols.analyze_node(node)
1717        self.enter_frame(scope_frame)
1718        self.blockvisit(node.body, scope_frame)
1719        self.leave_frame(scope_frame)
1720
1721    def visit_OverlayScope(self, node, frame):
1722        ctx = self.temporary_identifier()
1723        self.writeline(f"{ctx} = {self.derive_context(frame)}")
1724        self.writeline(f"{ctx}.vars = ")
1725        self.visit(node.context, frame)
1726        self.push_context_reference(ctx)
1727
1728        scope_frame = frame.inner(isolated=True)
1729        scope_frame.symbols.analyze_node(node)
1730        self.enter_frame(scope_frame)
1731        self.blockvisit(node.body, scope_frame)
1732        self.leave_frame(scope_frame)
1733        self.pop_context_reference()
1734
1735    def visit_EvalContextModifier(self, node, frame):
1736        for keyword in node.options:
1737            self.writeline(f"context.eval_ctx.{keyword.key} = ")
1738            self.visit(keyword.value, frame)
1739            try:
1740                val = keyword.value.as_const(frame.eval_ctx)
1741            except nodes.Impossible:
1742                frame.eval_ctx.volatile = True
1743            else:
1744                setattr(frame.eval_ctx, keyword.key, val)
1745
1746    def visit_ScopedEvalContextModifier(self, node, frame):
1747        old_ctx_name = self.temporary_identifier()
1748        saved_ctx = frame.eval_ctx.save()
1749        self.writeline(f"{old_ctx_name} = context.eval_ctx.save()")
1750        self.visit_EvalContextModifier(node, frame)
1751        for child in node.body:
1752            self.visit(child, frame)
1753        frame.eval_ctx.revert(saved_ctx)
1754        self.writeline(f"context.eval_ctx.revert({old_ctx_name})")
1755