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