• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
997            # if we have a known extends we just add a template runtime
998            # error into the generated code.  We could catch that at compile
999            # time too, but i welcome it not to confuse users by throwing the
1000            # same error at different times just "because we can".
1001            if not self.has_known_extends:
1002                self.writeline("if parent_template is not None:")
1003                self.indent()
1004            self.writeline('raise TemplateRuntimeError("extended multiple times")')
1005
1006            # if we have a known extends already we don't need that code here
1007            # as we know that the template execution will end here.
1008            if self.has_known_extends:
1009                raise CompilerExit()
1010            else:
1011                self.outdent()
1012
1013        self.writeline("parent_template = environment.get_template(", node)
1014        self.visit(node.template, frame)
1015        self.write(f", {self.name!r})")
1016        self.writeline("for name, parent_block in parent_template.blocks.items():")
1017        self.indent()
1018        self.writeline("context.blocks.setdefault(name, []).append(parent_block)")
1019        self.outdent()
1020
1021        # if this extends statement was in the root level we can take
1022        # advantage of that information and simplify the generated code
1023        # in the top level from this point onwards
1024        if frame.rootlevel:
1025            self.has_known_extends = True
1026
1027        # and now we have one more
1028        self.extends_so_far += 1
1029
1030    def visit_Include(self, node: nodes.Include, frame: Frame) -> None:
1031        """Handles includes."""
1032        if node.ignore_missing:
1033            self.writeline("try:")
1034            self.indent()
1035
1036        func_name = "get_or_select_template"
1037        if isinstance(node.template, nodes.Const):
1038            if isinstance(node.template.value, str):
1039                func_name = "get_template"
1040            elif isinstance(node.template.value, (tuple, list)):
1041                func_name = "select_template"
1042        elif isinstance(node.template, (nodes.Tuple, nodes.List)):
1043            func_name = "select_template"
1044
1045        self.writeline(f"template = environment.{func_name}(", node)
1046        self.visit(node.template, frame)
1047        self.write(f", {self.name!r})")
1048        if node.ignore_missing:
1049            self.outdent()
1050            self.writeline("except TemplateNotFound:")
1051            self.indent()
1052            self.writeline("pass")
1053            self.outdent()
1054            self.writeline("else:")
1055            self.indent()
1056
1057        skip_event_yield = False
1058        if node.with_context:
1059            self.writeline(
1060                f"{self.choose_async()}for event in template.root_render_func("
1061                "template.new_context(context.get_all(), True,"
1062                f" {self.dump_local_context(frame)})):"
1063            )
1064        elif self.environment.is_async:
1065            self.writeline(
1066                "for event in (await template._get_default_module_async())"
1067                "._body_stream:"
1068            )
1069        else:
1070            self.writeline("yield from template._get_default_module()._body_stream")
1071            skip_event_yield = True
1072
1073        if not skip_event_yield:
1074            self.indent()
1075            self.simple_write("event", frame)
1076            self.outdent()
1077
1078        if node.ignore_missing:
1079            self.outdent()
1080
1081    def _import_common(
1082        self, node: t.Union[nodes.Import, nodes.FromImport], frame: Frame
1083    ) -> None:
1084        self.write(f"{self.choose_async('await ')}environment.get_template(")
1085        self.visit(node.template, frame)
1086        self.write(f", {self.name!r}).")
1087
1088        if node.with_context:
1089            f_name = f"make_module{self.choose_async('_async')}"
1090            self.write(
1091                f"{f_name}(context.get_all(), True, {self.dump_local_context(frame)})"
1092            )
1093        else:
1094            self.write(f"_get_default_module{self.choose_async('_async')}(context)")
1095
1096    def visit_Import(self, node: nodes.Import, frame: Frame) -> None:
1097        """Visit regular imports."""
1098        self.writeline(f"{frame.symbols.ref(node.target)} = ", node)
1099        if frame.toplevel:
1100            self.write(f"context.vars[{node.target!r}] = ")
1101
1102        self._import_common(node, frame)
1103
1104        if frame.toplevel and not node.target.startswith("_"):
1105            self.writeline(f"context.exported_vars.discard({node.target!r})")
1106
1107    def visit_FromImport(self, node: nodes.FromImport, frame: Frame) -> None:
1108        """Visit named imports."""
1109        self.newline(node)
1110        self.write("included_template = ")
1111        self._import_common(node, frame)
1112        var_names = []
1113        discarded_names = []
1114        for name in node.names:
1115            if isinstance(name, tuple):
1116                name, alias = name
1117            else:
1118                alias = name
1119            self.writeline(
1120                f"{frame.symbols.ref(alias)} ="
1121                f" getattr(included_template, {name!r}, missing)"
1122            )
1123            self.writeline(f"if {frame.symbols.ref(alias)} is missing:")
1124            self.indent()
1125            message = (
1126                "the template {included_template.__name__!r}"
1127                f" (imported on {self.position(node)})"
1128                f" does not export the requested name {name!r}"
1129            )
1130            self.writeline(
1131                f"{frame.symbols.ref(alias)} = undefined(f{message!r}, name={name!r})"
1132            )
1133            self.outdent()
1134            if frame.toplevel:
1135                var_names.append(alias)
1136                if not alias.startswith("_"):
1137                    discarded_names.append(alias)
1138
1139        if var_names:
1140            if len(var_names) == 1:
1141                name = var_names[0]
1142                self.writeline(f"context.vars[{name!r}] = {frame.symbols.ref(name)}")
1143            else:
1144                names_kv = ", ".join(
1145                    f"{name!r}: {frame.symbols.ref(name)}" for name in var_names
1146                )
1147                self.writeline(f"context.vars.update({{{names_kv}}})")
1148        if discarded_names:
1149            if len(discarded_names) == 1:
1150                self.writeline(f"context.exported_vars.discard({discarded_names[0]!r})")
1151            else:
1152                names_str = ", ".join(map(repr, discarded_names))
1153                self.writeline(
1154                    f"context.exported_vars.difference_update(({names_str}))"
1155                )
1156
1157    def visit_For(self, node: nodes.For, frame: Frame) -> None:
1158        loop_frame = frame.inner()
1159        loop_frame.loop_frame = True
1160        test_frame = frame.inner()
1161        else_frame = frame.inner()
1162
1163        # try to figure out if we have an extended loop.  An extended loop
1164        # is necessary if the loop is in recursive mode if the special loop
1165        # variable is accessed in the body if the body is a scoped block.
1166        extended_loop = (
1167            node.recursive
1168            or "loop"
1169            in find_undeclared(node.iter_child_nodes(only=("body",)), ("loop",))
1170            or any(block.scoped for block in node.find_all(nodes.Block))
1171        )
1172
1173        loop_ref = None
1174        if extended_loop:
1175            loop_ref = loop_frame.symbols.declare_parameter("loop")
1176
1177        loop_frame.symbols.analyze_node(node, for_branch="body")
1178        if node.else_:
1179            else_frame.symbols.analyze_node(node, for_branch="else")
1180
1181        if node.test:
1182            loop_filter_func = self.temporary_identifier()
1183            test_frame.symbols.analyze_node(node, for_branch="test")
1184            self.writeline(f"{self.func(loop_filter_func)}(fiter):", node.test)
1185            self.indent()
1186            self.enter_frame(test_frame)
1187            self.writeline(self.choose_async("async for ", "for "))
1188            self.visit(node.target, loop_frame)
1189            self.write(" in ")
1190            self.write(self.choose_async("auto_aiter(fiter)", "fiter"))
1191            self.write(":")
1192            self.indent()
1193            self.writeline("if ", node.test)
1194            self.visit(node.test, test_frame)
1195            self.write(":")
1196            self.indent()
1197            self.writeline("yield ")
1198            self.visit(node.target, loop_frame)
1199            self.outdent(3)
1200            self.leave_frame(test_frame, with_python_scope=True)
1201
1202        # if we don't have an recursive loop we have to find the shadowed
1203        # variables at that point.  Because loops can be nested but the loop
1204        # variable is a special one we have to enforce aliasing for it.
1205        if node.recursive:
1206            self.writeline(
1207                f"{self.func('loop')}(reciter, loop_render_func, depth=0):", node
1208            )
1209            self.indent()
1210            self.buffer(loop_frame)
1211
1212            # Use the same buffer for the else frame
1213            else_frame.buffer = loop_frame.buffer
1214
1215        # make sure the loop variable is a special one and raise a template
1216        # assertion error if a loop tries to write to loop
1217        if extended_loop:
1218            self.writeline(f"{loop_ref} = missing")
1219
1220        for name in node.find_all(nodes.Name):
1221            if name.ctx == "store" and name.name == "loop":
1222                self.fail(
1223                    "Can't assign to special loop variable in for-loop target",
1224                    name.lineno,
1225                )
1226
1227        if node.else_:
1228            iteration_indicator = self.temporary_identifier()
1229            self.writeline(f"{iteration_indicator} = 1")
1230
1231        self.writeline(self.choose_async("async for ", "for "), node)
1232        self.visit(node.target, loop_frame)
1233        if extended_loop:
1234            self.write(f", {loop_ref} in {self.choose_async('Async')}LoopContext(")
1235        else:
1236            self.write(" in ")
1237
1238        if node.test:
1239            self.write(f"{loop_filter_func}(")
1240        if node.recursive:
1241            self.write("reciter")
1242        else:
1243            if self.environment.is_async and not extended_loop:
1244                self.write("auto_aiter(")
1245            self.visit(node.iter, frame)
1246            if self.environment.is_async and not extended_loop:
1247                self.write(")")
1248        if node.test:
1249            self.write(")")
1250
1251        if node.recursive:
1252            self.write(", undefined, loop_render_func, depth):")
1253        else:
1254            self.write(", undefined):" if extended_loop else ":")
1255
1256        self.indent()
1257        self.enter_frame(loop_frame)
1258
1259        self.writeline("_loop_vars = {}")
1260        self.blockvisit(node.body, loop_frame)
1261        if node.else_:
1262            self.writeline(f"{iteration_indicator} = 0")
1263        self.outdent()
1264        self.leave_frame(
1265            loop_frame, with_python_scope=node.recursive and not node.else_
1266        )
1267
1268        if node.else_:
1269            self.writeline(f"if {iteration_indicator}:")
1270            self.indent()
1271            self.enter_frame(else_frame)
1272            self.blockvisit(node.else_, else_frame)
1273            self.leave_frame(else_frame)
1274            self.outdent()
1275
1276        # if the node was recursive we have to return the buffer contents
1277        # and start the iteration code
1278        if node.recursive:
1279            self.return_buffer_contents(loop_frame)
1280            self.outdent()
1281            self.start_write(frame, node)
1282            self.write(f"{self.choose_async('await ')}loop(")
1283            if self.environment.is_async:
1284                self.write("auto_aiter(")
1285            self.visit(node.iter, frame)
1286            if self.environment.is_async:
1287                self.write(")")
1288            self.write(", loop)")
1289            self.end_write(frame)
1290
1291        # at the end of the iteration, clear any assignments made in the
1292        # loop from the top level
1293        if self._assign_stack:
1294            self._assign_stack[-1].difference_update(loop_frame.symbols.stores)
1295
1296    def visit_If(self, node: nodes.If, frame: Frame) -> None:
1297        if_frame = frame.soft()
1298        self.writeline("if ", node)
1299        self.visit(node.test, if_frame)
1300        self.write(":")
1301        self.indent()
1302        self.blockvisit(node.body, if_frame)
1303        self.outdent()
1304        for elif_ in node.elif_:
1305            self.writeline("elif ", elif_)
1306            self.visit(elif_.test, if_frame)
1307            self.write(":")
1308            self.indent()
1309            self.blockvisit(elif_.body, if_frame)
1310            self.outdent()
1311        if node.else_:
1312            self.writeline("else:")
1313            self.indent()
1314            self.blockvisit(node.else_, if_frame)
1315            self.outdent()
1316
1317    def visit_Macro(self, node: nodes.Macro, frame: Frame) -> None:
1318        macro_frame, macro_ref = self.macro_body(node, frame)
1319        self.newline()
1320        if frame.toplevel:
1321            if not node.name.startswith("_"):
1322                self.write(f"context.exported_vars.add({node.name!r})")
1323            self.writeline(f"context.vars[{node.name!r}] = ")
1324        self.write(f"{frame.symbols.ref(node.name)} = ")
1325        self.macro_def(macro_ref, macro_frame)
1326
1327    def visit_CallBlock(self, node: nodes.CallBlock, frame: Frame) -> None:
1328        call_frame, macro_ref = self.macro_body(node, frame)
1329        self.writeline("caller = ")
1330        self.macro_def(macro_ref, call_frame)
1331        self.start_write(frame, node)
1332        self.visit_Call(node.call, frame, forward_caller=True)
1333        self.end_write(frame)
1334
1335    def visit_FilterBlock(self, node: nodes.FilterBlock, frame: Frame) -> None:
1336        filter_frame = frame.inner()
1337        filter_frame.symbols.analyze_node(node)
1338        self.enter_frame(filter_frame)
1339        self.buffer(filter_frame)
1340        self.blockvisit(node.body, filter_frame)
1341        self.start_write(frame, node)
1342        self.visit_Filter(node.filter, filter_frame)
1343        self.end_write(frame)
1344        self.leave_frame(filter_frame)
1345
1346    def visit_With(self, node: nodes.With, frame: Frame) -> None:
1347        with_frame = frame.inner()
1348        with_frame.symbols.analyze_node(node)
1349        self.enter_frame(with_frame)
1350        for target, expr in zip(node.targets, node.values):
1351            self.newline()
1352            self.visit(target, with_frame)
1353            self.write(" = ")
1354            self.visit(expr, frame)
1355        self.blockvisit(node.body, with_frame)
1356        self.leave_frame(with_frame)
1357
1358    def visit_ExprStmt(self, node: nodes.ExprStmt, frame: Frame) -> None:
1359        self.newline(node)
1360        self.visit(node.node, frame)
1361
1362    class _FinalizeInfo(t.NamedTuple):
1363        const: t.Optional[t.Callable[..., str]]
1364        src: t.Optional[str]
1365
1366    @staticmethod
1367    def _default_finalize(value: t.Any) -> t.Any:
1368        """The default finalize function if the environment isn't
1369        configured with one. Or, if the environment has one, this is
1370        called on that function's output for constants.
1371        """
1372        return str(value)
1373
1374    _finalize: t.Optional[_FinalizeInfo] = None
1375
1376    def _make_finalize(self) -> _FinalizeInfo:
1377        """Build the finalize function to be used on constants and at
1378        runtime. Cached so it's only created once for all output nodes.
1379
1380        Returns a ``namedtuple`` with the following attributes:
1381
1382        ``const``
1383            A function to finalize constant data at compile time.
1384
1385        ``src``
1386            Source code to output around nodes to be evaluated at
1387            runtime.
1388        """
1389        if self._finalize is not None:
1390            return self._finalize
1391
1392        finalize: t.Optional[t.Callable[..., t.Any]]
1393        finalize = default = self._default_finalize
1394        src = None
1395
1396        if self.environment.finalize:
1397            src = "environment.finalize("
1398            env_finalize = self.environment.finalize
1399            pass_arg = {
1400                _PassArg.context: "context",
1401                _PassArg.eval_context: "context.eval_ctx",
1402                _PassArg.environment: "environment",
1403            }.get(
1404                _PassArg.from_obj(env_finalize)  # type: ignore
1405            )
1406            finalize = None
1407
1408            if pass_arg is None:
1409
1410                def finalize(value: t.Any) -> t.Any:
1411                    return default(env_finalize(value))
1412
1413            else:
1414                src = f"{src}{pass_arg}, "
1415
1416                if pass_arg == "environment":
1417
1418                    def finalize(value: t.Any) -> t.Any:
1419                        return default(env_finalize(self.environment, value))
1420
1421        self._finalize = self._FinalizeInfo(finalize, src)
1422        return self._finalize
1423
1424    def _output_const_repr(self, group: t.Iterable[t.Any]) -> str:
1425        """Given a group of constant values converted from ``Output``
1426        child nodes, produce a string to write to the template module
1427        source.
1428        """
1429        return repr(concat(group))
1430
1431    def _output_child_to_const(
1432        self, node: nodes.Expr, frame: Frame, finalize: _FinalizeInfo
1433    ) -> str:
1434        """Try to optimize a child of an ``Output`` node by trying to
1435        convert it to constant, finalized data at compile time.
1436
1437        If :exc:`Impossible` is raised, the node is not constant and
1438        will be evaluated at runtime. Any other exception will also be
1439        evaluated at runtime for easier debugging.
1440        """
1441        const = node.as_const(frame.eval_ctx)
1442
1443        if frame.eval_ctx.autoescape:
1444            const = escape(const)
1445
1446        # Template data doesn't go through finalize.
1447        if isinstance(node, nodes.TemplateData):
1448            return str(const)
1449
1450        return finalize.const(const)  # type: ignore
1451
1452    def _output_child_pre(
1453        self, node: nodes.Expr, frame: Frame, finalize: _FinalizeInfo
1454    ) -> None:
1455        """Output extra source code before visiting a child of an
1456        ``Output`` node.
1457        """
1458        if frame.eval_ctx.volatile:
1459            self.write("(escape if context.eval_ctx.autoescape else str)(")
1460        elif frame.eval_ctx.autoescape:
1461            self.write("escape(")
1462        else:
1463            self.write("str(")
1464
1465        if finalize.src is not None:
1466            self.write(finalize.src)
1467
1468    def _output_child_post(
1469        self, node: nodes.Expr, frame: Frame, finalize: _FinalizeInfo
1470    ) -> None:
1471        """Output extra source code after visiting a child of an
1472        ``Output`` node.
1473        """
1474        self.write(")")
1475
1476        if finalize.src is not None:
1477            self.write(")")
1478
1479    def visit_Output(self, node: nodes.Output, frame: Frame) -> None:
1480        # If an extends is active, don't render outside a block.
1481        if frame.require_output_check:
1482            # A top-level extends is known to exist at compile time.
1483            if self.has_known_extends:
1484                return
1485
1486            self.writeline("if parent_template is None:")
1487            self.indent()
1488
1489        finalize = self._make_finalize()
1490        body: t.List[t.Union[t.List[t.Any], nodes.Expr]] = []
1491
1492        # Evaluate constants at compile time if possible. Each item in
1493        # body will be either a list of static data or a node to be
1494        # evaluated at runtime.
1495        for child in node.nodes:
1496            try:
1497                if not (
1498                    # If the finalize function requires runtime context,
1499                    # constants can't be evaluated at compile time.
1500                    finalize.const
1501                    # Unless it's basic template data that won't be
1502                    # finalized anyway.
1503                    or isinstance(child, nodes.TemplateData)
1504                ):
1505                    raise nodes.Impossible()
1506
1507                const = self._output_child_to_const(child, frame, finalize)
1508            except (nodes.Impossible, Exception):
1509                # The node was not constant and needs to be evaluated at
1510                # runtime. Or another error was raised, which is easier
1511                # to debug at runtime.
1512                body.append(child)
1513                continue
1514
1515            if body and isinstance(body[-1], list):
1516                body[-1].append(const)
1517            else:
1518                body.append([const])
1519
1520        if frame.buffer is not None:
1521            if len(body) == 1:
1522                self.writeline(f"{frame.buffer}.append(")
1523            else:
1524                self.writeline(f"{frame.buffer}.extend((")
1525
1526            self.indent()
1527
1528        for item in body:
1529            if isinstance(item, list):
1530                # A group of constant data to join and output.
1531                val = self._output_const_repr(item)
1532
1533                if frame.buffer is None:
1534                    self.writeline("yield " + val)
1535                else:
1536                    self.writeline(val + ",")
1537            else:
1538                if frame.buffer is None:
1539                    self.writeline("yield ", item)
1540                else:
1541                    self.newline(item)
1542
1543                # A node to be evaluated at runtime.
1544                self._output_child_pre(item, frame, finalize)
1545                self.visit(item, frame)
1546                self._output_child_post(item, frame, finalize)
1547
1548                if frame.buffer is not None:
1549                    self.write(",")
1550
1551        if frame.buffer is not None:
1552            self.outdent()
1553            self.writeline(")" if len(body) == 1 else "))")
1554
1555        if frame.require_output_check:
1556            self.outdent()
1557
1558    def visit_Assign(self, node: nodes.Assign, frame: Frame) -> None:
1559        self.push_assign_tracking()
1560        self.newline(node)
1561        self.visit(node.target, frame)
1562        self.write(" = ")
1563        self.visit(node.node, frame)
1564        self.pop_assign_tracking(frame)
1565
1566    def visit_AssignBlock(self, node: nodes.AssignBlock, frame: Frame) -> None:
1567        self.push_assign_tracking()
1568        block_frame = frame.inner()
1569        # This is a special case.  Since a set block always captures we
1570        # will disable output checks.  This way one can use set blocks
1571        # toplevel even in extended templates.
1572        block_frame.require_output_check = False
1573        block_frame.symbols.analyze_node(node)
1574        self.enter_frame(block_frame)
1575        self.buffer(block_frame)
1576        self.blockvisit(node.body, block_frame)
1577        self.newline(node)
1578        self.visit(node.target, frame)
1579        self.write(" = (Markup if context.eval_ctx.autoescape else identity)(")
1580        if node.filter is not None:
1581            self.visit_Filter(node.filter, block_frame)
1582        else:
1583            self.write(f"concat({block_frame.buffer})")
1584        self.write(")")
1585        self.pop_assign_tracking(frame)
1586        self.leave_frame(block_frame)
1587
1588    # -- Expression Visitors
1589
1590    def visit_Name(self, node: nodes.Name, frame: Frame) -> None:
1591        if node.ctx == "store" and (
1592            frame.toplevel or frame.loop_frame or frame.block_frame
1593        ):
1594            if self._assign_stack:
1595                self._assign_stack[-1].add(node.name)
1596        ref = frame.symbols.ref(node.name)
1597
1598        # If we are looking up a variable we might have to deal with the
1599        # case where it's undefined.  We can skip that case if the load
1600        # instruction indicates a parameter which are always defined.
1601        if node.ctx == "load":
1602            load = frame.symbols.find_load(ref)
1603            if not (
1604                load is not None
1605                and load[0] == VAR_LOAD_PARAMETER
1606                and not self.parameter_is_undeclared(ref)
1607            ):
1608                self.write(
1609                    f"(undefined(name={node.name!r}) if {ref} is missing else {ref})"
1610                )
1611                return
1612
1613        self.write(ref)
1614
1615    def visit_NSRef(self, node: nodes.NSRef, frame: Frame) -> None:
1616        # NSRefs can only be used to store values; since they use the normal
1617        # `foo.bar` notation they will be parsed as a normal attribute access
1618        # when used anywhere but in a `set` context
1619        ref = frame.symbols.ref(node.name)
1620        self.writeline(f"if not isinstance({ref}, Namespace):")
1621        self.indent()
1622        self.writeline(
1623            "raise TemplateRuntimeError"
1624            '("cannot assign attribute on non-namespace object")'
1625        )
1626        self.outdent()
1627        self.writeline(f"{ref}[{node.attr!r}]")
1628
1629    def visit_Const(self, node: nodes.Const, frame: Frame) -> None:
1630        val = node.as_const(frame.eval_ctx)
1631        if isinstance(val, float):
1632            self.write(str(val))
1633        else:
1634            self.write(repr(val))
1635
1636    def visit_TemplateData(self, node: nodes.TemplateData, frame: Frame) -> None:
1637        try:
1638            self.write(repr(node.as_const(frame.eval_ctx)))
1639        except nodes.Impossible:
1640            self.write(
1641                f"(Markup if context.eval_ctx.autoescape else identity)({node.data!r})"
1642            )
1643
1644    def visit_Tuple(self, node: nodes.Tuple, frame: Frame) -> None:
1645        self.write("(")
1646        idx = -1
1647        for idx, item in enumerate(node.items):
1648            if idx:
1649                self.write(", ")
1650            self.visit(item, frame)
1651        self.write(",)" if idx == 0 else ")")
1652
1653    def visit_List(self, node: nodes.List, frame: Frame) -> None:
1654        self.write("[")
1655        for idx, item in enumerate(node.items):
1656            if idx:
1657                self.write(", ")
1658            self.visit(item, frame)
1659        self.write("]")
1660
1661    def visit_Dict(self, node: nodes.Dict, frame: Frame) -> None:
1662        self.write("{")
1663        for idx, item in enumerate(node.items):
1664            if idx:
1665                self.write(", ")
1666            self.visit(item.key, frame)
1667            self.write(": ")
1668            self.visit(item.value, frame)
1669        self.write("}")
1670
1671    visit_Add = _make_binop("+")
1672    visit_Sub = _make_binop("-")
1673    visit_Mul = _make_binop("*")
1674    visit_Div = _make_binop("/")
1675    visit_FloorDiv = _make_binop("//")
1676    visit_Pow = _make_binop("**")
1677    visit_Mod = _make_binop("%")
1678    visit_And = _make_binop("and")
1679    visit_Or = _make_binop("or")
1680    visit_Pos = _make_unop("+")
1681    visit_Neg = _make_unop("-")
1682    visit_Not = _make_unop("not ")
1683
1684    @optimizeconst
1685    def visit_Concat(self, node: nodes.Concat, frame: Frame) -> None:
1686        if frame.eval_ctx.volatile:
1687            func_name = "(markup_join if context.eval_ctx.volatile else str_join)"
1688        elif frame.eval_ctx.autoescape:
1689            func_name = "markup_join"
1690        else:
1691            func_name = "str_join"
1692        self.write(f"{func_name}((")
1693        for arg in node.nodes:
1694            self.visit(arg, frame)
1695            self.write(", ")
1696        self.write("))")
1697
1698    @optimizeconst
1699    def visit_Compare(self, node: nodes.Compare, frame: Frame) -> None:
1700        self.write("(")
1701        self.visit(node.expr, frame)
1702        for op in node.ops:
1703            self.visit(op, frame)
1704        self.write(")")
1705
1706    def visit_Operand(self, node: nodes.Operand, frame: Frame) -> None:
1707        self.write(f" {operators[node.op]} ")
1708        self.visit(node.expr, frame)
1709
1710    @optimizeconst
1711    def visit_Getattr(self, node: nodes.Getattr, frame: Frame) -> None:
1712        if self.environment.is_async:
1713            self.write("(await auto_await(")
1714
1715        self.write("environment.getattr(")
1716        self.visit(node.node, frame)
1717        self.write(f", {node.attr!r})")
1718
1719        if self.environment.is_async:
1720            self.write("))")
1721
1722    @optimizeconst
1723    def visit_Getitem(self, node: nodes.Getitem, frame: Frame) -> None:
1724        # slices bypass the environment getitem method.
1725        if isinstance(node.arg, nodes.Slice):
1726            self.visit(node.node, frame)
1727            self.write("[")
1728            self.visit(node.arg, frame)
1729            self.write("]")
1730        else:
1731            if self.environment.is_async:
1732                self.write("(await auto_await(")
1733
1734            self.write("environment.getitem(")
1735            self.visit(node.node, frame)
1736            self.write(", ")
1737            self.visit(node.arg, frame)
1738            self.write(")")
1739
1740            if self.environment.is_async:
1741                self.write("))")
1742
1743    def visit_Slice(self, node: nodes.Slice, frame: Frame) -> None:
1744        if node.start is not None:
1745            self.visit(node.start, frame)
1746        self.write(":")
1747        if node.stop is not None:
1748            self.visit(node.stop, frame)
1749        if node.step is not None:
1750            self.write(":")
1751            self.visit(node.step, frame)
1752
1753    @contextmanager
1754    def _filter_test_common(
1755        self, node: t.Union[nodes.Filter, nodes.Test], frame: Frame, is_filter: bool
1756    ) -> t.Iterator[None]:
1757        if self.environment.is_async:
1758            self.write("(await auto_await(")
1759
1760        if is_filter:
1761            self.write(f"{self.filters[node.name]}(")
1762            func = self.environment.filters.get(node.name)
1763        else:
1764            self.write(f"{self.tests[node.name]}(")
1765            func = self.environment.tests.get(node.name)
1766
1767        # When inside an If or CondExpr frame, allow the filter to be
1768        # undefined at compile time and only raise an error if it's
1769        # actually called at runtime. See pull_dependencies.
1770        if func is None and not frame.soft_frame:
1771            type_name = "filter" if is_filter else "test"
1772            self.fail(f"No {type_name} named {node.name!r}.", node.lineno)
1773
1774        pass_arg = {
1775            _PassArg.context: "context",
1776            _PassArg.eval_context: "context.eval_ctx",
1777            _PassArg.environment: "environment",
1778        }.get(
1779            _PassArg.from_obj(func)  # type: ignore
1780        )
1781
1782        if pass_arg is not None:
1783            self.write(f"{pass_arg}, ")
1784
1785        # Back to the visitor function to handle visiting the target of
1786        # the filter or test.
1787        yield
1788
1789        self.signature(node, frame)
1790        self.write(")")
1791
1792        if self.environment.is_async:
1793            self.write("))")
1794
1795    @optimizeconst
1796    def visit_Filter(self, node: nodes.Filter, frame: Frame) -> None:
1797        with self._filter_test_common(node, frame, True):
1798            # if the filter node is None we are inside a filter block
1799            # and want to write to the current buffer
1800            if node.node is not None:
1801                self.visit(node.node, frame)
1802            elif frame.eval_ctx.volatile:
1803                self.write(
1804                    f"(Markup(concat({frame.buffer}))"
1805                    f" if context.eval_ctx.autoescape else concat({frame.buffer}))"
1806                )
1807            elif frame.eval_ctx.autoescape:
1808                self.write(f"Markup(concat({frame.buffer}))")
1809            else:
1810                self.write(f"concat({frame.buffer})")
1811
1812    @optimizeconst
1813    def visit_Test(self, node: nodes.Test, frame: Frame) -> None:
1814        with self._filter_test_common(node, frame, False):
1815            self.visit(node.node, frame)
1816
1817    @optimizeconst
1818    def visit_CondExpr(self, node: nodes.CondExpr, frame: Frame) -> None:
1819        frame = frame.soft()
1820
1821        def write_expr2() -> None:
1822            if node.expr2 is not None:
1823                self.visit(node.expr2, frame)
1824                return
1825
1826            self.write(
1827                f'cond_expr_undefined("the inline if-expression on'
1828                f" {self.position(node)} evaluated to false and no else"
1829                f' section was defined.")'
1830            )
1831
1832        self.write("(")
1833        self.visit(node.expr1, frame)
1834        self.write(" if ")
1835        self.visit(node.test, frame)
1836        self.write(" else ")
1837        write_expr2()
1838        self.write(")")
1839
1840    @optimizeconst
1841    def visit_Call(
1842        self, node: nodes.Call, frame: Frame, forward_caller: bool = False
1843    ) -> None:
1844        if self.environment.is_async:
1845            self.write("(await auto_await(")
1846        if self.environment.sandboxed:
1847            self.write("environment.call(context, ")
1848        else:
1849            self.write("context.call(")
1850        self.visit(node.node, frame)
1851        extra_kwargs = {"caller": "caller"} if forward_caller else None
1852        loop_kwargs = {"_loop_vars": "_loop_vars"} if frame.loop_frame else {}
1853        block_kwargs = {"_block_vars": "_block_vars"} if frame.block_frame else {}
1854        if extra_kwargs:
1855            extra_kwargs.update(loop_kwargs, **block_kwargs)
1856        elif loop_kwargs or block_kwargs:
1857            extra_kwargs = dict(loop_kwargs, **block_kwargs)
1858        self.signature(node, frame, extra_kwargs)
1859        self.write(")")
1860        if self.environment.is_async:
1861            self.write("))")
1862
1863    def visit_Keyword(self, node: nodes.Keyword, frame: Frame) -> None:
1864        self.write(node.key + "=")
1865        self.visit(node.value, frame)
1866
1867    # -- Unused nodes for extensions
1868
1869    def visit_MarkSafe(self, node: nodes.MarkSafe, frame: Frame) -> None:
1870        self.write("Markup(")
1871        self.visit(node.expr, frame)
1872        self.write(")")
1873
1874    def visit_MarkSafeIfAutoescape(
1875        self, node: nodes.MarkSafeIfAutoescape, frame: Frame
1876    ) -> None:
1877        self.write("(Markup if context.eval_ctx.autoescape else identity)(")
1878        self.visit(node.expr, frame)
1879        self.write(")")
1880
1881    def visit_EnvironmentAttribute(
1882        self, node: nodes.EnvironmentAttribute, frame: Frame
1883    ) -> None:
1884        self.write("environment." + node.name)
1885
1886    def visit_ExtensionAttribute(
1887        self, node: nodes.ExtensionAttribute, frame: Frame
1888    ) -> None:
1889        self.write(f"environment.extensions[{node.identifier!r}].{node.name}")
1890
1891    def visit_ImportedName(self, node: nodes.ImportedName, frame: Frame) -> None:
1892        self.write(self.import_aliases[node.importname])
1893
1894    def visit_InternalName(self, node: nodes.InternalName, frame: Frame) -> None:
1895        self.write(node.name)
1896
1897    def visit_ContextReference(
1898        self, node: nodes.ContextReference, frame: Frame
1899    ) -> None:
1900        self.write("context")
1901
1902    def visit_DerivedContextReference(
1903        self, node: nodes.DerivedContextReference, frame: Frame
1904    ) -> None:
1905        self.write(self.derive_context(frame))
1906
1907    def visit_Continue(self, node: nodes.Continue, frame: Frame) -> None:
1908        self.writeline("continue", node)
1909
1910    def visit_Break(self, node: nodes.Break, frame: Frame) -> None:
1911        self.writeline("break", node)
1912
1913    def visit_Scope(self, node: nodes.Scope, frame: Frame) -> None:
1914        scope_frame = frame.inner()
1915        scope_frame.symbols.analyze_node(node)
1916        self.enter_frame(scope_frame)
1917        self.blockvisit(node.body, scope_frame)
1918        self.leave_frame(scope_frame)
1919
1920    def visit_OverlayScope(self, node: nodes.OverlayScope, frame: Frame) -> None:
1921        ctx = self.temporary_identifier()
1922        self.writeline(f"{ctx} = {self.derive_context(frame)}")
1923        self.writeline(f"{ctx}.vars = ")
1924        self.visit(node.context, frame)
1925        self.push_context_reference(ctx)
1926
1927        scope_frame = frame.inner(isolated=True)
1928        scope_frame.symbols.analyze_node(node)
1929        self.enter_frame(scope_frame)
1930        self.blockvisit(node.body, scope_frame)
1931        self.leave_frame(scope_frame)
1932        self.pop_context_reference()
1933
1934    def visit_EvalContextModifier(
1935        self, node: nodes.EvalContextModifier, frame: Frame
1936    ) -> None:
1937        for keyword in node.options:
1938            self.writeline(f"context.eval_ctx.{keyword.key} = ")
1939            self.visit(keyword.value, frame)
1940            try:
1941                val = keyword.value.as_const(frame.eval_ctx)
1942            except nodes.Impossible:
1943                frame.eval_ctx.volatile = True
1944            else:
1945                setattr(frame.eval_ctx, keyword.key, val)
1946
1947    def visit_ScopedEvalContextModifier(
1948        self, node: nodes.ScopedEvalContextModifier, frame: Frame
1949    ) -> None:
1950        old_ctx_name = self.temporary_identifier()
1951        saved_ctx = frame.eval_ctx.save()
1952        self.writeline(f"{old_ctx_name} = context.eval_ctx.save()")
1953        self.visit_EvalContextModifier(node, frame)
1954        for child in node.body:
1955            self.visit(child, frame)
1956        frame.eval_ctx.revert(saved_ctx)
1957        self.writeline(f"context.eval_ctx.revert({old_ctx_name})")
1958