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