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