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