Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/jinja2/compiler.py: 57%
1176 statements
« prev ^ index » next coverage.py v7.3.1, created at 2023-09-25 06:15 +0000
« prev ^ index » next coverage.py v7.3.1, created at 2023-09-25 06:15 +0000
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
9from markupsafe import escape
10from markupsafe import Markup
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
25if t.TYPE_CHECKING:
26 import typing_extensions as te
27 from .environment import Environment
29F = t.TypeVar("F", bound=t.Callable[..., t.Any])
31operators = {
32 "eq": "==",
33 "ne": "!=",
34 "gt": ">",
35 "gteq": ">=",
36 "lt": "<",
37 "lteq": "<=",
38 "in": "in",
39 "notin": "not in",
40}
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)
51 if new_node != node:
52 return self.visit(new_node, frame)
54 return f(self, node, frame, **kwargs)
56 return update_wrapper(t.cast(F, new_func), f)
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)
76 self.write(")")
78 return visitor
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)
96 self.write(")")
98 return visitor
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")
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:
137 return all(has_safe_repr(k) and has_safe_repr(v) for k, v in value.items())
139 return False
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
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
165class Frame:
166 """Holds compile time information for us."""
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
176 # the parent of this frame
177 self.parent = parent
179 if parent is None:
180 self.symbols = Symbols(level=level)
182 # in some dynamic inheritance situations the compiler needs to add
183 # write tests around output statements.
184 self.require_output_check = False
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
192 # the name of the block we're in, otherwise None.
193 self.block: t.Optional[str] = None
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
201 # a toplevel frame is the root + soft frames such as if conditions.
202 self.toplevel = False
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
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
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
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
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)
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.
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
245 __copy__ = copy
248class VisitorExit(RuntimeError):
249 """Exception used by the `UndeclaredNameVisitor` to signal a stop."""
252class DependencyFinderVisitor(NodeVisitor):
253 """A visitor that collects filter and test calls."""
255 def __init__(self) -> None:
256 self.filters: t.Set[str] = set()
257 self.tests: t.Set[str] = set()
259 def visit_Filter(self, node: nodes.Filter) -> None:
260 self.generic_visit(node)
261 self.filters.add(node.name)
263 def visit_Test(self, node: nodes.Test) -> None:
264 self.generic_visit(node)
265 self.tests.add(node.name)
267 def visit_Block(self, node: nodes.Block) -> None:
268 """Stop visiting at blocks."""
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 """
277 def __init__(self, names: t.Iterable[str]) -> None:
278 self.names = set(names)
279 self.undeclared: t.Set[str] = set()
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)
289 def visit_Block(self, node: nodes.Block) -> None:
290 """Stop visiting a blocks."""
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 """
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
320 if optimized:
321 self.optimizer = Optimizer(environment)
323 # aliases for imports
324 self.import_aliases: t.Dict[str, str] = {}
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] = {}
330 # the number of extends statements so far
331 self.extends_so_far = 0
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
338 # the current line number
339 self.code_lineno = 1
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] = {}
345 # the debug information
346 self.debug_info: t.List[t.Tuple[int, int]] = []
347 self._write_debug_info: t.Optional[int] = None
349 # the number of new lines before the next write()
350 self._new_lines = 0
352 # the line number of the last written statement
353 self._last_line = 0
355 # true if nothing was written so far.
356 self._first_write = True
358 # used by the `temporary_identifier` method to get new
359 # unique, temporary identifier
360 self._last_identifier = 0
362 # the current indentation
363 self._indentation = 0
365 # Tracks toplevel assignments
366 self._assign_stack: t.List[t.Set[str]] = []
368 # Tracks parameter definition blocks
369 self._param_def_block: t.List[t.Set[str]] = []
371 # Tracks the current context.
372 self._context_reference_stack = ["context"]
374 @property
375 def optimized(self) -> bool:
376 return self.optimizer is not None
378 # -- Various compilation helpers
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)
384 def temporary_identifier(self) -> str:
385 """Get a new unique identifier."""
386 self._last_identifier += 1
387 return f"t_{self._last_identifier}"
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} = []")
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})")
414 def indent(self) -> None:
415 """Indent by one."""
416 self._indentation += 1
418 def outdent(self, step: int = 1) -> None:
419 """Outdent by step."""
420 self._indentation -= step
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)
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(")")
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)
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
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)
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)
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
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 )
500 for arg in node.args:
501 self.write(", ")
502 self.visit(arg, frame)
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)
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("}")
534 elif node.dyn_kwargs is not None:
535 self.write(", **")
536 self.visit(node.dyn_kwargs, frame)
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.
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()
551 for node in nodes:
552 visitor.visit(node)
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()
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()
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")
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")
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
608 def func(self, name: str) -> str:
609 return f"{self.choose_async()}def {name}"
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)
619 explicit_caller = None
620 skip_special_params = set()
621 args = []
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))
630 undeclared = find_undeclared(node.body, ("caller", "kwargs", "varargs"))
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
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()
665 self.buffer(frame)
666 self.enter_frame(frame)
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()
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()
692 return frame, macro_ref
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 )
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
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}}}"
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")
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())
742 def pop_parameter_definitions(self) -> None:
743 """Pops the current parameter definitions set."""
744 self._param_def_block.pop()
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)
753 def push_context_reference(self, target: str) -> None:
754 self._context_reference_stack.append(target)
756 def pop_context_reference(self) -> None:
757 self._context_reference_stack.pop()
759 def get_context_ref(self) -> str:
760 return self._context_reference_stack[-1]
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"
768 def derive_context(self, frame: Frame) -> str:
769 return f"{self.get_context_ref()}.derived({self.dump_local_context(frame)})"
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]
777 def push_assign_tracking(self) -> None:
778 """Pushes a new layer for assignment tracking."""
779 self._assign_stack.append(set())
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}))")
824 # -- Statement Visitors
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)
832 from .runtime import exported, async_exported
834 if self.environment.is_async:
835 exported_names = sorted(exported + async_exported)
836 else:
837 exported_names = sorted(exported)
839 self.writeline("from jinja2.runtime import " + ", ".join(exported_names))
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"
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
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
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}")
866 # add the load name
867 self.writeline(f"name = {self.name!r}")
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()
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()
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))
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()
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}")
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
957 if node.scoped:
958 context = self.derive_context(frame)
959 else:
960 context = self.get_context_ref()
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()
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()
985 self.outdent(level)
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)
992 # if the number of extends statements in general is zero so
993 # far, we don't have to add a check if something extended
994 # the template before this one.
995 if self.extends_so_far > 0:
996 # if we have a known extends we just add a template runtime
997 # error into the generated code. We could catch that at compile
998 # time too, but i welcome it not to confuse users by throwing the
999 # same error at different times just "because we can".
1000 if not self.has_known_extends:
1001 self.writeline("if parent_template is not None:")
1002 self.indent()
1003 self.writeline('raise TemplateRuntimeError("extended multiple times")')
1005 # if we have a known extends already we don't need that code here
1006 # as we know that the template execution will end here.
1007 if self.has_known_extends:
1008 raise CompilerExit()
1009 else:
1010 self.outdent()
1012 self.writeline("parent_template = environment.get_template(", node)
1013 self.visit(node.template, frame)
1014 self.write(f", {self.name!r})")
1015 self.writeline("for name, parent_block in parent_template.blocks.items():")
1016 self.indent()
1017 self.writeline("context.blocks.setdefault(name, []).append(parent_block)")
1018 self.outdent()
1020 # if this extends statement was in the root level we can take
1021 # advantage of that information and simplify the generated code
1022 # in the top level from this point onwards
1023 if frame.rootlevel:
1024 self.has_known_extends = True
1026 # and now we have one more
1027 self.extends_so_far += 1
1029 def visit_Include(self, node: nodes.Include, frame: Frame) -> None:
1030 """Handles includes."""
1031 if node.ignore_missing:
1032 self.writeline("try:")
1033 self.indent()
1035 func_name = "get_or_select_template"
1036 if isinstance(node.template, nodes.Const):
1037 if isinstance(node.template.value, str):
1038 func_name = "get_template"
1039 elif isinstance(node.template.value, (tuple, list)):
1040 func_name = "select_template"
1041 elif isinstance(node.template, (nodes.Tuple, nodes.List)):
1042 func_name = "select_template"
1044 self.writeline(f"template = environment.{func_name}(", node)
1045 self.visit(node.template, frame)
1046 self.write(f", {self.name!r})")
1047 if node.ignore_missing:
1048 self.outdent()
1049 self.writeline("except TemplateNotFound:")
1050 self.indent()
1051 self.writeline("pass")
1052 self.outdent()
1053 self.writeline("else:")
1054 self.indent()
1056 skip_event_yield = False
1057 if node.with_context:
1058 self.writeline(
1059 f"{self.choose_async()}for event in template.root_render_func("
1060 "template.new_context(context.get_all(), True,"
1061 f" {self.dump_local_context(frame)})):"
1062 )
1063 elif self.environment.is_async:
1064 self.writeline(
1065 "for event in (await template._get_default_module_async())"
1066 "._body_stream:"
1067 )
1068 else:
1069 self.writeline("yield from template._get_default_module()._body_stream")
1070 skip_event_yield = True
1072 if not skip_event_yield:
1073 self.indent()
1074 self.simple_write("event", frame)
1075 self.outdent()
1077 if node.ignore_missing:
1078 self.outdent()
1080 def _import_common(
1081 self, node: t.Union[nodes.Import, nodes.FromImport], frame: Frame
1082 ) -> None:
1083 self.write(f"{self.choose_async('await ')}environment.get_template(")
1084 self.visit(node.template, frame)
1085 self.write(f", {self.name!r}).")
1087 if node.with_context:
1088 f_name = f"make_module{self.choose_async('_async')}"
1089 self.write(
1090 f"{f_name}(context.get_all(), True, {self.dump_local_context(frame)})"
1091 )
1092 else:
1093 self.write(f"_get_default_module{self.choose_async('_async')}(context)")
1095 def visit_Import(self, node: nodes.Import, frame: Frame) -> None:
1096 """Visit regular imports."""
1097 self.writeline(f"{frame.symbols.ref(node.target)} = ", node)
1098 if frame.toplevel:
1099 self.write(f"context.vars[{node.target!r}] = ")
1101 self._import_common(node, frame)
1103 if frame.toplevel and not node.target.startswith("_"):
1104 self.writeline(f"context.exported_vars.discard({node.target!r})")
1106 def visit_FromImport(self, node: nodes.FromImport, frame: Frame) -> None:
1107 """Visit named imports."""
1108 self.newline(node)
1109 self.write("included_template = ")
1110 self._import_common(node, frame)
1111 var_names = []
1112 discarded_names = []
1113 for name in node.names:
1114 if isinstance(name, tuple):
1115 name, alias = name
1116 else:
1117 alias = name
1118 self.writeline(
1119 f"{frame.symbols.ref(alias)} ="
1120 f" getattr(included_template, {name!r}, missing)"
1121 )
1122 self.writeline(f"if {frame.symbols.ref(alias)} is missing:")
1123 self.indent()
1124 message = (
1125 "the template {included_template.__name__!r}"
1126 f" (imported on {self.position(node)})"
1127 f" does not export the requested name {name!r}"
1128 )
1129 self.writeline(
1130 f"{frame.symbols.ref(alias)} = undefined(f{message!r}, name={name!r})"
1131 )
1132 self.outdent()
1133 if frame.toplevel:
1134 var_names.append(alias)
1135 if not alias.startswith("_"):
1136 discarded_names.append(alias)
1138 if var_names:
1139 if len(var_names) == 1:
1140 name = var_names[0]
1141 self.writeline(f"context.vars[{name!r}] = {frame.symbols.ref(name)}")
1142 else:
1143 names_kv = ", ".join(
1144 f"{name!r}: {frame.symbols.ref(name)}" for name in var_names
1145 )
1146 self.writeline(f"context.vars.update({{{names_kv}}})")
1147 if discarded_names:
1148 if len(discarded_names) == 1:
1149 self.writeline(f"context.exported_vars.discard({discarded_names[0]!r})")
1150 else:
1151 names_str = ", ".join(map(repr, discarded_names))
1152 self.writeline(
1153 f"context.exported_vars.difference_update(({names_str}))"
1154 )
1156 def visit_For(self, node: nodes.For, frame: Frame) -> None:
1157 loop_frame = frame.inner()
1158 loop_frame.loop_frame = True
1159 test_frame = frame.inner()
1160 else_frame = frame.inner()
1162 # try to figure out if we have an extended loop. An extended loop
1163 # is necessary if the loop is in recursive mode if the special loop
1164 # variable is accessed in the body if the body is a scoped block.
1165 extended_loop = (
1166 node.recursive
1167 or "loop"
1168 in find_undeclared(node.iter_child_nodes(only=("body",)), ("loop",))
1169 or any(block.scoped for block in node.find_all(nodes.Block))
1170 )
1172 loop_ref = None
1173 if extended_loop:
1174 loop_ref = loop_frame.symbols.declare_parameter("loop")
1176 loop_frame.symbols.analyze_node(node, for_branch="body")
1177 if node.else_:
1178 else_frame.symbols.analyze_node(node, for_branch="else")
1180 if node.test:
1181 loop_filter_func = self.temporary_identifier()
1182 test_frame.symbols.analyze_node(node, for_branch="test")
1183 self.writeline(f"{self.func(loop_filter_func)}(fiter):", node.test)
1184 self.indent()
1185 self.enter_frame(test_frame)
1186 self.writeline(self.choose_async("async for ", "for "))
1187 self.visit(node.target, loop_frame)
1188 self.write(" in ")
1189 self.write(self.choose_async("auto_aiter(fiter)", "fiter"))
1190 self.write(":")
1191 self.indent()
1192 self.writeline("if ", node.test)
1193 self.visit(node.test, test_frame)
1194 self.write(":")
1195 self.indent()
1196 self.writeline("yield ")
1197 self.visit(node.target, loop_frame)
1198 self.outdent(3)
1199 self.leave_frame(test_frame, with_python_scope=True)
1201 # if we don't have an recursive loop we have to find the shadowed
1202 # variables at that point. Because loops can be nested but the loop
1203 # variable is a special one we have to enforce aliasing for it.
1204 if node.recursive:
1205 self.writeline(
1206 f"{self.func('loop')}(reciter, loop_render_func, depth=0):", node
1207 )
1208 self.indent()
1209 self.buffer(loop_frame)
1211 # Use the same buffer for the else frame
1212 else_frame.buffer = loop_frame.buffer
1214 # make sure the loop variable is a special one and raise a template
1215 # assertion error if a loop tries to write to loop
1216 if extended_loop:
1217 self.writeline(f"{loop_ref} = missing")
1219 for name in node.find_all(nodes.Name):
1220 if name.ctx == "store" and name.name == "loop":
1221 self.fail(
1222 "Can't assign to special loop variable in for-loop target",
1223 name.lineno,
1224 )
1226 if node.else_:
1227 iteration_indicator = self.temporary_identifier()
1228 self.writeline(f"{iteration_indicator} = 1")
1230 self.writeline(self.choose_async("async for ", "for "), node)
1231 self.visit(node.target, loop_frame)
1232 if extended_loop:
1233 self.write(f", {loop_ref} in {self.choose_async('Async')}LoopContext(")
1234 else:
1235 self.write(" in ")
1237 if node.test:
1238 self.write(f"{loop_filter_func}(")
1239 if node.recursive:
1240 self.write("reciter")
1241 else:
1242 if self.environment.is_async and not extended_loop:
1243 self.write("auto_aiter(")
1244 self.visit(node.iter, frame)
1245 if self.environment.is_async and not extended_loop:
1246 self.write(")")
1247 if node.test:
1248 self.write(")")
1250 if node.recursive:
1251 self.write(", undefined, loop_render_func, depth):")
1252 else:
1253 self.write(", undefined):" if extended_loop else ":")
1255 self.indent()
1256 self.enter_frame(loop_frame)
1258 self.writeline("_loop_vars = {}")
1259 self.blockvisit(node.body, loop_frame)
1260 if node.else_:
1261 self.writeline(f"{iteration_indicator} = 0")
1262 self.outdent()
1263 self.leave_frame(
1264 loop_frame, with_python_scope=node.recursive and not node.else_
1265 )
1267 if node.else_:
1268 self.writeline(f"if {iteration_indicator}:")
1269 self.indent()
1270 self.enter_frame(else_frame)
1271 self.blockvisit(node.else_, else_frame)
1272 self.leave_frame(else_frame)
1273 self.outdent()
1275 # if the node was recursive we have to return the buffer contents
1276 # and start the iteration code
1277 if node.recursive:
1278 self.return_buffer_contents(loop_frame)
1279 self.outdent()
1280 self.start_write(frame, node)
1281 self.write(f"{self.choose_async('await ')}loop(")
1282 if self.environment.is_async:
1283 self.write("auto_aiter(")
1284 self.visit(node.iter, frame)
1285 if self.environment.is_async:
1286 self.write(")")
1287 self.write(", loop)")
1288 self.end_write(frame)
1290 # at the end of the iteration, clear any assignments made in the
1291 # loop from the top level
1292 if self._assign_stack:
1293 self._assign_stack[-1].difference_update(loop_frame.symbols.stores)
1295 def visit_If(self, node: nodes.If, frame: Frame) -> None:
1296 if_frame = frame.soft()
1297 self.writeline("if ", node)
1298 self.visit(node.test, if_frame)
1299 self.write(":")
1300 self.indent()
1301 self.blockvisit(node.body, if_frame)
1302 self.outdent()
1303 for elif_ in node.elif_:
1304 self.writeline("elif ", elif_)
1305 self.visit(elif_.test, if_frame)
1306 self.write(":")
1307 self.indent()
1308 self.blockvisit(elif_.body, if_frame)
1309 self.outdent()
1310 if node.else_:
1311 self.writeline("else:")
1312 self.indent()
1313 self.blockvisit(node.else_, if_frame)
1314 self.outdent()
1316 def visit_Macro(self, node: nodes.Macro, frame: Frame) -> None:
1317 macro_frame, macro_ref = self.macro_body(node, frame)
1318 self.newline()
1319 if frame.toplevel:
1320 if not node.name.startswith("_"):
1321 self.write(f"context.exported_vars.add({node.name!r})")
1322 self.writeline(f"context.vars[{node.name!r}] = ")
1323 self.write(f"{frame.symbols.ref(node.name)} = ")
1324 self.macro_def(macro_ref, macro_frame)
1326 def visit_CallBlock(self, node: nodes.CallBlock, frame: Frame) -> None:
1327 call_frame, macro_ref = self.macro_body(node, frame)
1328 self.writeline("caller = ")
1329 self.macro_def(macro_ref, call_frame)
1330 self.start_write(frame, node)
1331 self.visit_Call(node.call, frame, forward_caller=True)
1332 self.end_write(frame)
1334 def visit_FilterBlock(self, node: nodes.FilterBlock, frame: Frame) -> None:
1335 filter_frame = frame.inner()
1336 filter_frame.symbols.analyze_node(node)
1337 self.enter_frame(filter_frame)
1338 self.buffer(filter_frame)
1339 self.blockvisit(node.body, filter_frame)
1340 self.start_write(frame, node)
1341 self.visit_Filter(node.filter, filter_frame)
1342 self.end_write(frame)
1343 self.leave_frame(filter_frame)
1345 def visit_With(self, node: nodes.With, frame: Frame) -> None:
1346 with_frame = frame.inner()
1347 with_frame.symbols.analyze_node(node)
1348 self.enter_frame(with_frame)
1349 for target, expr in zip(node.targets, node.values):
1350 self.newline()
1351 self.visit(target, with_frame)
1352 self.write(" = ")
1353 self.visit(expr, frame)
1354 self.blockvisit(node.body, with_frame)
1355 self.leave_frame(with_frame)
1357 def visit_ExprStmt(self, node: nodes.ExprStmt, frame: Frame) -> None:
1358 self.newline(node)
1359 self.visit(node.node, frame)
1361 class _FinalizeInfo(t.NamedTuple):
1362 const: t.Optional[t.Callable[..., str]]
1363 src: t.Optional[str]
1365 @staticmethod
1366 def _default_finalize(value: t.Any) -> t.Any:
1367 """The default finalize function if the environment isn't
1368 configured with one. Or, if the environment has one, this is
1369 called on that function's output for constants.
1370 """
1371 return str(value)
1373 _finalize: t.Optional[_FinalizeInfo] = None
1375 def _make_finalize(self) -> _FinalizeInfo:
1376 """Build the finalize function to be used on constants and at
1377 runtime. Cached so it's only created once for all output nodes.
1379 Returns a ``namedtuple`` with the following attributes:
1381 ``const``
1382 A function to finalize constant data at compile time.
1384 ``src``
1385 Source code to output around nodes to be evaluated at
1386 runtime.
1387 """
1388 if self._finalize is not None:
1389 return self._finalize
1391 finalize: t.Optional[t.Callable[..., t.Any]]
1392 finalize = default = self._default_finalize
1393 src = None
1395 if self.environment.finalize:
1396 src = "environment.finalize("
1397 env_finalize = self.environment.finalize
1398 pass_arg = {
1399 _PassArg.context: "context",
1400 _PassArg.eval_context: "context.eval_ctx",
1401 _PassArg.environment: "environment",
1402 }.get(
1403 _PassArg.from_obj(env_finalize) # type: ignore
1404 )
1405 finalize = None
1407 if pass_arg is None:
1409 def finalize(value: t.Any) -> t.Any:
1410 return default(env_finalize(value))
1412 else:
1413 src = f"{src}{pass_arg}, "
1415 if pass_arg == "environment":
1417 def finalize(value: t.Any) -> t.Any:
1418 return default(env_finalize(self.environment, value))
1420 self._finalize = self._FinalizeInfo(finalize, src)
1421 return self._finalize
1423 def _output_const_repr(self, group: t.Iterable[t.Any]) -> str:
1424 """Given a group of constant values converted from ``Output``
1425 child nodes, produce a string to write to the template module
1426 source.
1427 """
1428 return repr(concat(group))
1430 def _output_child_to_const(
1431 self, node: nodes.Expr, frame: Frame, finalize: _FinalizeInfo
1432 ) -> str:
1433 """Try to optimize a child of an ``Output`` node by trying to
1434 convert it to constant, finalized data at compile time.
1436 If :exc:`Impossible` is raised, the node is not constant and
1437 will be evaluated at runtime. Any other exception will also be
1438 evaluated at runtime for easier debugging.
1439 """
1440 const = node.as_const(frame.eval_ctx)
1442 if frame.eval_ctx.autoescape:
1443 const = escape(const)
1445 # Template data doesn't go through finalize.
1446 if isinstance(node, nodes.TemplateData):
1447 return str(const)
1449 return finalize.const(const) # type: ignore
1451 def _output_child_pre(
1452 self, node: nodes.Expr, frame: Frame, finalize: _FinalizeInfo
1453 ) -> None:
1454 """Output extra source code before visiting a child of an
1455 ``Output`` node.
1456 """
1457 if frame.eval_ctx.volatile:
1458 self.write("(escape if context.eval_ctx.autoescape else str)(")
1459 elif frame.eval_ctx.autoescape:
1460 self.write("escape(")
1461 else:
1462 self.write("str(")
1464 if finalize.src is not None:
1465 self.write(finalize.src)
1467 def _output_child_post(
1468 self, node: nodes.Expr, frame: Frame, finalize: _FinalizeInfo
1469 ) -> None:
1470 """Output extra source code after visiting a child of an
1471 ``Output`` node.
1472 """
1473 self.write(")")
1475 if finalize.src is not None:
1476 self.write(")")
1478 def visit_Output(self, node: nodes.Output, frame: Frame) -> None:
1479 # If an extends is active, don't render outside a block.
1480 if frame.require_output_check:
1481 # A top-level extends is known to exist at compile time.
1482 if self.has_known_extends:
1483 return
1485 self.writeline("if parent_template is None:")
1486 self.indent()
1488 finalize = self._make_finalize()
1489 body: t.List[t.Union[t.List[t.Any], nodes.Expr]] = []
1491 # Evaluate constants at compile time if possible. Each item in
1492 # body will be either a list of static data or a node to be
1493 # evaluated at runtime.
1494 for child in node.nodes:
1495 try:
1496 if not (
1497 # If the finalize function requires runtime context,
1498 # constants can't be evaluated at compile time.
1499 finalize.const
1500 # Unless it's basic template data that won't be
1501 # finalized anyway.
1502 or isinstance(child, nodes.TemplateData)
1503 ):
1504 raise nodes.Impossible()
1506 const = self._output_child_to_const(child, frame, finalize)
1507 except (nodes.Impossible, Exception):
1508 # The node was not constant and needs to be evaluated at
1509 # runtime. Or another error was raised, which is easier
1510 # to debug at runtime.
1511 body.append(child)
1512 continue
1514 if body and isinstance(body[-1], list):
1515 body[-1].append(const)
1516 else:
1517 body.append([const])
1519 if frame.buffer is not None:
1520 if len(body) == 1:
1521 self.writeline(f"{frame.buffer}.append(")
1522 else:
1523 self.writeline(f"{frame.buffer}.extend((")
1525 self.indent()
1527 for item in body:
1528 if isinstance(item, list):
1529 # A group of constant data to join and output.
1530 val = self._output_const_repr(item)
1532 if frame.buffer is None:
1533 self.writeline("yield " + val)
1534 else:
1535 self.writeline(val + ",")
1536 else:
1537 if frame.buffer is None:
1538 self.writeline("yield ", item)
1539 else:
1540 self.newline(item)
1542 # A node to be evaluated at runtime.
1543 self._output_child_pre(item, frame, finalize)
1544 self.visit(item, frame)
1545 self._output_child_post(item, frame, finalize)
1547 if frame.buffer is not None:
1548 self.write(",")
1550 if frame.buffer is not None:
1551 self.outdent()
1552 self.writeline(")" if len(body) == 1 else "))")
1554 if frame.require_output_check:
1555 self.outdent()
1557 def visit_Assign(self, node: nodes.Assign, frame: Frame) -> None:
1558 self.push_assign_tracking()
1559 self.newline(node)
1560 self.visit(node.target, frame)
1561 self.write(" = ")
1562 self.visit(node.node, frame)
1563 self.pop_assign_tracking(frame)
1565 def visit_AssignBlock(self, node: nodes.AssignBlock, frame: Frame) -> None:
1566 self.push_assign_tracking()
1567 block_frame = frame.inner()
1568 # This is a special case. Since a set block always captures we
1569 # will disable output checks. This way one can use set blocks
1570 # toplevel even in extended templates.
1571 block_frame.require_output_check = False
1572 block_frame.symbols.analyze_node(node)
1573 self.enter_frame(block_frame)
1574 self.buffer(block_frame)
1575 self.blockvisit(node.body, block_frame)
1576 self.newline(node)
1577 self.visit(node.target, frame)
1578 self.write(" = (Markup if context.eval_ctx.autoescape else identity)(")
1579 if node.filter is not None:
1580 self.visit_Filter(node.filter, block_frame)
1581 else:
1582 self.write(f"concat({block_frame.buffer})")
1583 self.write(")")
1584 self.pop_assign_tracking(frame)
1585 self.leave_frame(block_frame)
1587 # -- Expression Visitors
1589 def visit_Name(self, node: nodes.Name, frame: Frame) -> None:
1590 if node.ctx == "store" and (
1591 frame.toplevel or frame.loop_frame or frame.block_frame
1592 ):
1593 if self._assign_stack:
1594 self._assign_stack[-1].add(node.name)
1595 ref = frame.symbols.ref(node.name)
1597 # If we are looking up a variable we might have to deal with the
1598 # case where it's undefined. We can skip that case if the load
1599 # instruction indicates a parameter which are always defined.
1600 if node.ctx == "load":
1601 load = frame.symbols.find_load(ref)
1602 if not (
1603 load is not None
1604 and load[0] == VAR_LOAD_PARAMETER
1605 and not self.parameter_is_undeclared(ref)
1606 ):
1607 self.write(
1608 f"(undefined(name={node.name!r}) if {ref} is missing else {ref})"
1609 )
1610 return
1612 self.write(ref)
1614 def visit_NSRef(self, node: nodes.NSRef, frame: Frame) -> None:
1615 # NSRefs can only be used to store values; since they use the normal
1616 # `foo.bar` notation they will be parsed as a normal attribute access
1617 # when used anywhere but in a `set` context
1618 ref = frame.symbols.ref(node.name)
1619 self.writeline(f"if not isinstance({ref}, Namespace):")
1620 self.indent()
1621 self.writeline(
1622 "raise TemplateRuntimeError"
1623 '("cannot assign attribute on non-namespace object")'
1624 )
1625 self.outdent()
1626 self.writeline(f"{ref}[{node.attr!r}]")
1628 def visit_Const(self, node: nodes.Const, frame: Frame) -> None:
1629 val = node.as_const(frame.eval_ctx)
1630 if isinstance(val, float):
1631 self.write(str(val))
1632 else:
1633 self.write(repr(val))
1635 def visit_TemplateData(self, node: nodes.TemplateData, frame: Frame) -> None:
1636 try:
1637 self.write(repr(node.as_const(frame.eval_ctx)))
1638 except nodes.Impossible:
1639 self.write(
1640 f"(Markup if context.eval_ctx.autoescape else identity)({node.data!r})"
1641 )
1643 def visit_Tuple(self, node: nodes.Tuple, frame: Frame) -> None:
1644 self.write("(")
1645 idx = -1
1646 for idx, item in enumerate(node.items):
1647 if idx:
1648 self.write(", ")
1649 self.visit(item, frame)
1650 self.write(",)" if idx == 0 else ")")
1652 def visit_List(self, node: nodes.List, frame: Frame) -> None:
1653 self.write("[")
1654 for idx, item in enumerate(node.items):
1655 if idx:
1656 self.write(", ")
1657 self.visit(item, frame)
1658 self.write("]")
1660 def visit_Dict(self, node: nodes.Dict, frame: Frame) -> None:
1661 self.write("{")
1662 for idx, item in enumerate(node.items):
1663 if idx:
1664 self.write(", ")
1665 self.visit(item.key, frame)
1666 self.write(": ")
1667 self.visit(item.value, frame)
1668 self.write("}")
1670 visit_Add = _make_binop("+")
1671 visit_Sub = _make_binop("-")
1672 visit_Mul = _make_binop("*")
1673 visit_Div = _make_binop("/")
1674 visit_FloorDiv = _make_binop("//")
1675 visit_Pow = _make_binop("**")
1676 visit_Mod = _make_binop("%")
1677 visit_And = _make_binop("and")
1678 visit_Or = _make_binop("or")
1679 visit_Pos = _make_unop("+")
1680 visit_Neg = _make_unop("-")
1681 visit_Not = _make_unop("not ")
1683 @optimizeconst
1684 def visit_Concat(self, node: nodes.Concat, frame: Frame) -> None:
1685 if frame.eval_ctx.volatile:
1686 func_name = "(markup_join if context.eval_ctx.volatile else str_join)"
1687 elif frame.eval_ctx.autoescape:
1688 func_name = "markup_join"
1689 else:
1690 func_name = "str_join"
1691 self.write(f"{func_name}((")
1692 for arg in node.nodes:
1693 self.visit(arg, frame)
1694 self.write(", ")
1695 self.write("))")
1697 @optimizeconst
1698 def visit_Compare(self, node: nodes.Compare, frame: Frame) -> None:
1699 self.write("(")
1700 self.visit(node.expr, frame)
1701 for op in node.ops:
1702 self.visit(op, frame)
1703 self.write(")")
1705 def visit_Operand(self, node: nodes.Operand, frame: Frame) -> None:
1706 self.write(f" {operators[node.op]} ")
1707 self.visit(node.expr, frame)
1709 @optimizeconst
1710 def visit_Getattr(self, node: nodes.Getattr, frame: Frame) -> None:
1711 if self.environment.is_async:
1712 self.write("(await auto_await(")
1714 self.write("environment.getattr(")
1715 self.visit(node.node, frame)
1716 self.write(f", {node.attr!r})")
1718 if self.environment.is_async:
1719 self.write("))")
1721 @optimizeconst
1722 def visit_Getitem(self, node: nodes.Getitem, frame: Frame) -> None:
1723 # slices bypass the environment getitem method.
1724 if isinstance(node.arg, nodes.Slice):
1725 self.visit(node.node, frame)
1726 self.write("[")
1727 self.visit(node.arg, frame)
1728 self.write("]")
1729 else:
1730 if self.environment.is_async:
1731 self.write("(await auto_await(")
1733 self.write("environment.getitem(")
1734 self.visit(node.node, frame)
1735 self.write(", ")
1736 self.visit(node.arg, frame)
1737 self.write(")")
1739 if self.environment.is_async:
1740 self.write("))")
1742 def visit_Slice(self, node: nodes.Slice, frame: Frame) -> None:
1743 if node.start is not None:
1744 self.visit(node.start, frame)
1745 self.write(":")
1746 if node.stop is not None:
1747 self.visit(node.stop, frame)
1748 if node.step is not None:
1749 self.write(":")
1750 self.visit(node.step, frame)
1752 @contextmanager
1753 def _filter_test_common(
1754 self, node: t.Union[nodes.Filter, nodes.Test], frame: Frame, is_filter: bool
1755 ) -> t.Iterator[None]:
1756 if self.environment.is_async:
1757 self.write("(await auto_await(")
1759 if is_filter:
1760 self.write(f"{self.filters[node.name]}(")
1761 func = self.environment.filters.get(node.name)
1762 else:
1763 self.write(f"{self.tests[node.name]}(")
1764 func = self.environment.tests.get(node.name)
1766 # When inside an If or CondExpr frame, allow the filter to be
1767 # undefined at compile time and only raise an error if it's
1768 # actually called at runtime. See pull_dependencies.
1769 if func is None and not frame.soft_frame:
1770 type_name = "filter" if is_filter else "test"
1771 self.fail(f"No {type_name} named {node.name!r}.", node.lineno)
1773 pass_arg = {
1774 _PassArg.context: "context",
1775 _PassArg.eval_context: "context.eval_ctx",
1776 _PassArg.environment: "environment",
1777 }.get(
1778 _PassArg.from_obj(func) # type: ignore
1779 )
1781 if pass_arg is not None:
1782 self.write(f"{pass_arg}, ")
1784 # Back to the visitor function to handle visiting the target of
1785 # the filter or test.
1786 yield
1788 self.signature(node, frame)
1789 self.write(")")
1791 if self.environment.is_async:
1792 self.write("))")
1794 @optimizeconst
1795 def visit_Filter(self, node: nodes.Filter, frame: Frame) -> None:
1796 with self._filter_test_common(node, frame, True):
1797 # if the filter node is None we are inside a filter block
1798 # and want to write to the current buffer
1799 if node.node is not None:
1800 self.visit(node.node, frame)
1801 elif frame.eval_ctx.volatile:
1802 self.write(
1803 f"(Markup(concat({frame.buffer}))"
1804 f" if context.eval_ctx.autoescape else concat({frame.buffer}))"
1805 )
1806 elif frame.eval_ctx.autoescape:
1807 self.write(f"Markup(concat({frame.buffer}))")
1808 else:
1809 self.write(f"concat({frame.buffer})")
1811 @optimizeconst
1812 def visit_Test(self, node: nodes.Test, frame: Frame) -> None:
1813 with self._filter_test_common(node, frame, False):
1814 self.visit(node.node, frame)
1816 @optimizeconst
1817 def visit_CondExpr(self, node: nodes.CondExpr, frame: Frame) -> None:
1818 frame = frame.soft()
1820 def write_expr2() -> None:
1821 if node.expr2 is not None:
1822 self.visit(node.expr2, frame)
1823 return
1825 self.write(
1826 f'cond_expr_undefined("the inline if-expression on'
1827 f" {self.position(node)} evaluated to false and no else"
1828 f' section was defined.")'
1829 )
1831 self.write("(")
1832 self.visit(node.expr1, frame)
1833 self.write(" if ")
1834 self.visit(node.test, frame)
1835 self.write(" else ")
1836 write_expr2()
1837 self.write(")")
1839 @optimizeconst
1840 def visit_Call(
1841 self, node: nodes.Call, frame: Frame, forward_caller: bool = False
1842 ) -> None:
1843 if self.environment.is_async:
1844 self.write("(await auto_await(")
1845 if self.environment.sandboxed:
1846 self.write("environment.call(context, ")
1847 else:
1848 self.write("context.call(")
1849 self.visit(node.node, frame)
1850 extra_kwargs = {"caller": "caller"} if forward_caller else None
1851 loop_kwargs = {"_loop_vars": "_loop_vars"} if frame.loop_frame else {}
1852 block_kwargs = {"_block_vars": "_block_vars"} if frame.block_frame else {}
1853 if extra_kwargs:
1854 extra_kwargs.update(loop_kwargs, **block_kwargs)
1855 elif loop_kwargs or block_kwargs:
1856 extra_kwargs = dict(loop_kwargs, **block_kwargs)
1857 self.signature(node, frame, extra_kwargs)
1858 self.write(")")
1859 if self.environment.is_async:
1860 self.write("))")
1862 def visit_Keyword(self, node: nodes.Keyword, frame: Frame) -> None:
1863 self.write(node.key + "=")
1864 self.visit(node.value, frame)
1866 # -- Unused nodes for extensions
1868 def visit_MarkSafe(self, node: nodes.MarkSafe, frame: Frame) -> None:
1869 self.write("Markup(")
1870 self.visit(node.expr, frame)
1871 self.write(")")
1873 def visit_MarkSafeIfAutoescape(
1874 self, node: nodes.MarkSafeIfAutoescape, frame: Frame
1875 ) -> None:
1876 self.write("(Markup if context.eval_ctx.autoescape else identity)(")
1877 self.visit(node.expr, frame)
1878 self.write(")")
1880 def visit_EnvironmentAttribute(
1881 self, node: nodes.EnvironmentAttribute, frame: Frame
1882 ) -> None:
1883 self.write("environment." + node.name)
1885 def visit_ExtensionAttribute(
1886 self, node: nodes.ExtensionAttribute, frame: Frame
1887 ) -> None:
1888 self.write(f"environment.extensions[{node.identifier!r}].{node.name}")
1890 def visit_ImportedName(self, node: nodes.ImportedName, frame: Frame) -> None:
1891 self.write(self.import_aliases[node.importname])
1893 def visit_InternalName(self, node: nodes.InternalName, frame: Frame) -> None:
1894 self.write(node.name)
1896 def visit_ContextReference(
1897 self, node: nodes.ContextReference, frame: Frame
1898 ) -> None:
1899 self.write("context")
1901 def visit_DerivedContextReference(
1902 self, node: nodes.DerivedContextReference, frame: Frame
1903 ) -> None:
1904 self.write(self.derive_context(frame))
1906 def visit_Continue(self, node: nodes.Continue, frame: Frame) -> None:
1907 self.writeline("continue", node)
1909 def visit_Break(self, node: nodes.Break, frame: Frame) -> None:
1910 self.writeline("break", node)
1912 def visit_Scope(self, node: nodes.Scope, frame: Frame) -> None:
1913 scope_frame = frame.inner()
1914 scope_frame.symbols.analyze_node(node)
1915 self.enter_frame(scope_frame)
1916 self.blockvisit(node.body, scope_frame)
1917 self.leave_frame(scope_frame)
1919 def visit_OverlayScope(self, node: nodes.OverlayScope, frame: Frame) -> None:
1920 ctx = self.temporary_identifier()
1921 self.writeline(f"{ctx} = {self.derive_context(frame)}")
1922 self.writeline(f"{ctx}.vars = ")
1923 self.visit(node.context, frame)
1924 self.push_context_reference(ctx)
1926 scope_frame = frame.inner(isolated=True)
1927 scope_frame.symbols.analyze_node(node)
1928 self.enter_frame(scope_frame)
1929 self.blockvisit(node.body, scope_frame)
1930 self.leave_frame(scope_frame)
1931 self.pop_context_reference()
1933 def visit_EvalContextModifier(
1934 self, node: nodes.EvalContextModifier, frame: Frame
1935 ) -> None:
1936 for keyword in node.options:
1937 self.writeline(f"context.eval_ctx.{keyword.key} = ")
1938 self.visit(keyword.value, frame)
1939 try:
1940 val = keyword.value.as_const(frame.eval_ctx)
1941 except nodes.Impossible:
1942 frame.eval_ctx.volatile = True
1943 else:
1944 setattr(frame.eval_ctx, keyword.key, val)
1946 def visit_ScopedEvalContextModifier(
1947 self, node: nodes.ScopedEvalContextModifier, frame: Frame
1948 ) -> None:
1949 old_ctx_name = self.temporary_identifier()
1950 saved_ctx = frame.eval_ctx.save()
1951 self.writeline(f"{old_ctx_name} = context.eval_ctx.save()")
1952 self.visit_EvalContextModifier(node, frame)
1953 for child in node.body:
1954 self.visit(child, frame)
1955 frame.eval_ctx.revert(saved_ctx)
1956 self.writeline(f"context.eval_ctx.revert({old_ctx_name})")