Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/jinja2/compiler.py: 12%

1176 statements  

« prev     ^ index     » next       coverage.py v6.4.4, created at 2023-07-17 14:22 -0600

1"""Compiles nodes from the parser into Python code.""" 

2import typing as t 

3from contextlib import contextmanager 

4from functools import update_wrapper 

5from io import StringIO 

6from itertools import chain 

7from keyword import iskeyword as is_python_keyword 

8 

9from markupsafe import escape 

10from markupsafe import Markup 

11 

12from . import nodes 

13from .exceptions import TemplateAssertionError 

14from .idtracking import Symbols 

15from .idtracking import VAR_LOAD_ALIAS 

16from .idtracking import VAR_LOAD_PARAMETER 

17from .idtracking import VAR_LOAD_RESOLVE 

18from .idtracking import VAR_LOAD_UNDEFINED 

19from .nodes import EvalContext 

20from .optimizer import Optimizer 

21from .utils import _PassArg 

22from .utils import concat 

23from .visitor import NodeVisitor 

24 

25if t.TYPE_CHECKING: 25 ↛ 26line 25 didn't jump to line 26, because the condition on line 25 was never true

26 import typing_extensions as te 

27 from .environment import Environment 

28 

29F = t.TypeVar("F", bound=t.Callable[..., t.Any]) 

30 

31operators = { 

32 "eq": "==", 

33 "ne": "!=", 

34 "gt": ">", 

35 "gteq": ">=", 

36 "lt": "<", 

37 "lteq": "<=", 

38 "in": "in", 

39 "notin": "not in", 

40} 

41 

42 

43def optimizeconst(f: F) -> F: 

44 def new_func( 

45 self: "CodeGenerator", node: nodes.Expr, frame: "Frame", **kwargs: t.Any 

46 ) -> t.Any: 

47 # Only optimize if the frame is not volatile 

48 if self.optimizer is not None and not frame.eval_ctx.volatile: 

49 new_node = self.optimizer.visit(node, frame.eval_ctx) 

50 

51 if new_node != node: 

52 return self.visit(new_node, frame) 

53 

54 return f(self, node, frame, **kwargs) 

55 

56 return update_wrapper(t.cast(F, new_func), f) 

57 

58 

59def _make_binop(op: str) -> t.Callable[["CodeGenerator", nodes.BinExpr, "Frame"], None]: 

60 @optimizeconst 

61 def visitor(self: "CodeGenerator", node: nodes.BinExpr, frame: Frame) -> None: 

62 if ( 

63 self.environment.sandboxed 

64 and op in self.environment.intercepted_binops # type: ignore 

65 ): 

66 self.write(f"environment.call_binop(context, {op!r}, ") 

67 self.visit(node.left, frame) 

68 self.write(", ") 

69 self.visit(node.right, frame) 

70 else: 

71 self.write("(") 

72 self.visit(node.left, frame) 

73 self.write(f" {op} ") 

74 self.visit(node.right, frame) 

75 

76 self.write(")") 

77 

78 return visitor 

79 

80 

81def _make_unop( 

82 op: str, 

83) -> t.Callable[["CodeGenerator", nodes.UnaryExpr, "Frame"], None]: 

84 @optimizeconst 

85 def visitor(self: "CodeGenerator", node: nodes.UnaryExpr, frame: Frame) -> None: 

86 if ( 

87 self.environment.sandboxed 

88 and op in self.environment.intercepted_unops # type: ignore 

89 ): 

90 self.write(f"environment.call_unop(context, {op!r}, ") 

91 self.visit(node.node, frame) 

92 else: 

93 self.write("(" + op) 

94 self.visit(node.node, frame) 

95 

96 self.write(")") 

97 

98 return visitor 

99 

100 

101def generate( 

102 node: nodes.Template, 

103 environment: "Environment", 

104 name: t.Optional[str], 

105 filename: t.Optional[str], 

106 stream: t.Optional[t.TextIO] = None, 

107 defer_init: bool = False, 

108 optimized: bool = True, 

109) -> t.Optional[str]: 

110 """Generate the python source for a node tree.""" 

111 if not isinstance(node, nodes.Template): 

112 raise TypeError("Can't compile non template nodes") 

113 

114 generator = environment.code_generator_class( 

115 environment, name, filename, stream, defer_init, optimized 

116 ) 

117 generator.visit(node) 

118 

119 if stream is None: 

120 return generator.stream.getvalue() # type: ignore 

121 

122 return None 

123 

124 

125def has_safe_repr(value: t.Any) -> bool: 

126 """Does the node have a safe representation?""" 

127 if value is None or value is NotImplemented or value is Ellipsis: 

128 return True 

129 

130 if type(value) in {bool, int, float, complex, range, str, Markup}: 

131 return True 

132 

133 if type(value) in {tuple, list, set, frozenset}: 

134 return all(has_safe_repr(v) for v in value) 

135 

136 if type(value) is dict: 

137 return all(has_safe_repr(k) and has_safe_repr(v) for k, v in value.items()) 

138 

139 return False 

140 

141 

142def find_undeclared( 

143 nodes: t.Iterable[nodes.Node], names: t.Iterable[str] 

144) -> t.Set[str]: 

145 """Check if the names passed are accessed undeclared. The return value 

146 is a set of all the undeclared names from the sequence of names found. 

147 """ 

148 visitor = UndeclaredNameVisitor(names) 

149 try: 

150 for node in nodes: 

151 visitor.visit(node) 

152 except VisitorExit: 

153 pass 

154 return visitor.undeclared 

155 

156 

157class MacroRef: 

158 def __init__(self, node: t.Union[nodes.Macro, nodes.CallBlock]) -> None: 

159 self.node = node 

160 self.accesses_caller = False 

161 self.accesses_kwargs = False 

162 self.accesses_varargs = False 

163 

164 

165class Frame: 

166 """Holds compile time information for us.""" 

167 

168 def __init__( 

169 self, 

170 eval_ctx: EvalContext, 

171 parent: t.Optional["Frame"] = None, 

172 level: t.Optional[int] = None, 

173 ) -> None: 

174 self.eval_ctx = eval_ctx 

175 

176 # the parent of this frame 

177 self.parent = parent 

178 

179 if parent is None: 

180 self.symbols = Symbols(level=level) 

181 

182 # in some dynamic inheritance situations the compiler needs to add 

183 # write tests around output statements. 

184 self.require_output_check = False 

185 

186 # inside some tags we are using a buffer rather than yield statements. 

187 # this for example affects {% filter %} or {% macro %}. If a frame 

188 # is buffered this variable points to the name of the list used as 

189 # buffer. 

190 self.buffer: t.Optional[str] = None 

191 

192 # the name of the block we're in, otherwise None. 

193 self.block: t.Optional[str] = None 

194 

195 else: 

196 self.symbols = Symbols(parent.symbols, level=level) 

197 self.require_output_check = parent.require_output_check 

198 self.buffer = parent.buffer 

199 self.block = parent.block 

200 

201 # a toplevel frame is the root + soft frames such as if conditions. 

202 self.toplevel = False 

203 

204 # the root frame is basically just the outermost frame, so no if 

205 # conditions. This information is used to optimize inheritance 

206 # situations. 

207 self.rootlevel = False 

208 

209 # variables set inside of loops and blocks should not affect outer frames, 

210 # but they still needs to be kept track of as part of the active context. 

211 self.loop_frame = False 

212 self.block_frame = False 

213 

214 # track whether the frame is being used in an if-statement or conditional 

215 # expression as it determines which errors should be raised during runtime 

216 # or compile time. 

217 self.soft_frame = False 

218 

219 def copy(self) -> "Frame": 

220 """Create a copy of the current one.""" 

221 rv = object.__new__(self.__class__) 

222 rv.__dict__.update(self.__dict__) 

223 rv.symbols = self.symbols.copy() 

224 return rv 

225 

226 def inner(self, isolated: bool = False) -> "Frame": 

227 """Return an inner frame.""" 

228 if isolated: 

229 return Frame(self.eval_ctx, level=self.symbols.level + 1) 

230 return Frame(self.eval_ctx, self) 

231 

232 def soft(self) -> "Frame": 

233 """Return a soft frame. A soft frame may not be modified as 

234 standalone thing as it shares the resources with the frame it 

235 was created of, but it's not a rootlevel frame any longer. 

236 

237 This is only used to implement if-statements and conditional 

238 expressions. 

239 """ 

240 rv = self.copy() 

241 rv.rootlevel = False 

242 rv.soft_frame = True 

243 return rv 

244 

245 __copy__ = copy 

246 

247 

248class VisitorExit(RuntimeError): 

249 """Exception used by the `UndeclaredNameVisitor` to signal a stop.""" 

250 

251 

252class DependencyFinderVisitor(NodeVisitor): 

253 """A visitor that collects filter and test calls.""" 

254 

255 def __init__(self) -> None: 

256 self.filters: t.Set[str] = set() 

257 self.tests: t.Set[str] = set() 

258 

259 def visit_Filter(self, node: nodes.Filter) -> None: 

260 self.generic_visit(node) 

261 self.filters.add(node.name) 

262 

263 def visit_Test(self, node: nodes.Test) -> None: 

264 self.generic_visit(node) 

265 self.tests.add(node.name) 

266 

267 def visit_Block(self, node: nodes.Block) -> None: 

268 """Stop visiting at blocks.""" 

269 

270 

271class UndeclaredNameVisitor(NodeVisitor): 

272 """A visitor that checks if a name is accessed without being 

273 declared. This is different from the frame visitor as it will 

274 not stop at closure frames. 

275 """ 

276 

277 def __init__(self, names: t.Iterable[str]) -> None: 

278 self.names = set(names) 

279 self.undeclared: t.Set[str] = set() 

280 

281 def visit_Name(self, node: nodes.Name) -> None: 

282 if node.ctx == "load" and node.name in self.names: 

283 self.undeclared.add(node.name) 

284 if self.undeclared == self.names: 

285 raise VisitorExit() 

286 else: 

287 self.names.discard(node.name) 

288 

289 def visit_Block(self, node: nodes.Block) -> None: 

290 """Stop visiting a blocks.""" 

291 

292 

293class CompilerExit(Exception): 

294 """Raised if the compiler encountered a situation where it just 

295 doesn't make sense to further process the code. Any block that 

296 raises such an exception is not further processed. 

297 """ 

298 

299 

300class CodeGenerator(NodeVisitor): 

301 def __init__( 

302 self, 

303 environment: "Environment", 

304 name: t.Optional[str], 

305 filename: t.Optional[str], 

306 stream: t.Optional[t.TextIO] = None, 

307 defer_init: bool = False, 

308 optimized: bool = True, 

309 ) -> None: 

310 if stream is None: 

311 stream = StringIO() 

312 self.environment = environment 

313 self.name = name 

314 self.filename = filename 

315 self.stream = stream 

316 self.created_block_context = False 

317 self.defer_init = defer_init 

318 self.optimizer: t.Optional[Optimizer] = None 

319 

320 if optimized: 

321 self.optimizer = Optimizer(environment) 

322 

323 # aliases for imports 

324 self.import_aliases: t.Dict[str, str] = {} 

325 

326 # a registry for all blocks. Because blocks are moved out 

327 # into the global python scope they are registered here 

328 self.blocks: t.Dict[str, nodes.Block] = {} 

329 

330 # the number of extends statements so far 

331 self.extends_so_far = 0 

332 

333 # some templates have a rootlevel extends. In this case we 

334 # can safely assume that we're a child template and do some 

335 # more optimizations. 

336 self.has_known_extends = False 

337 

338 # the current line number 

339 self.code_lineno = 1 

340 

341 # registry of all filters and tests (global, not block local) 

342 self.tests: t.Dict[str, str] = {} 

343 self.filters: t.Dict[str, str] = {} 

344 

345 # the debug information 

346 self.debug_info: t.List[t.Tuple[int, int]] = [] 

347 self._write_debug_info: t.Optional[int] = None 

348 

349 # the number of new lines before the next write() 

350 self._new_lines = 0 

351 

352 # the line number of the last written statement 

353 self._last_line = 0 

354 

355 # true if nothing was written so far. 

356 self._first_write = True 

357 

358 # used by the `temporary_identifier` method to get new 

359 # unique, temporary identifier 

360 self._last_identifier = 0 

361 

362 # the current indentation 

363 self._indentation = 0 

364 

365 # Tracks toplevel assignments 

366 self._assign_stack: t.List[t.Set[str]] = [] 

367 

368 # Tracks parameter definition blocks 

369 self._param_def_block: t.List[t.Set[str]] = [] 

370 

371 # Tracks the current context. 

372 self._context_reference_stack = ["context"] 

373 

374 @property 

375 def optimized(self) -> bool: 

376 return self.optimizer is not None 

377 

378 # -- Various compilation helpers 

379 

380 def fail(self, msg: str, lineno: int) -> "te.NoReturn": 

381 """Fail with a :exc:`TemplateAssertionError`.""" 

382 raise TemplateAssertionError(msg, lineno, self.name, self.filename) 

383 

384 def temporary_identifier(self) -> str: 

385 """Get a new unique identifier.""" 

386 self._last_identifier += 1 

387 return f"t_{self._last_identifier}" 

388 

389 def buffer(self, frame: Frame) -> None: 

390 """Enable buffering for the frame from that point onwards.""" 

391 frame.buffer = self.temporary_identifier() 

392 self.writeline(f"{frame.buffer} = []") 

393 

394 def return_buffer_contents( 

395 self, frame: Frame, force_unescaped: bool = False 

396 ) -> None: 

397 """Return the buffer contents of the frame.""" 

398 if not force_unescaped: 

399 if frame.eval_ctx.volatile: 

400 self.writeline("if context.eval_ctx.autoescape:") 

401 self.indent() 

402 self.writeline(f"return Markup(concat({frame.buffer}))") 

403 self.outdent() 

404 self.writeline("else:") 

405 self.indent() 

406 self.writeline(f"return concat({frame.buffer})") 

407 self.outdent() 

408 return 

409 elif frame.eval_ctx.autoescape: 

410 self.writeline(f"return Markup(concat({frame.buffer}))") 

411 return 

412 self.writeline(f"return concat({frame.buffer})") 

413 

414 def indent(self) -> None: 

415 """Indent by one.""" 

416 self._indentation += 1 

417 

418 def outdent(self, step: int = 1) -> None: 

419 """Outdent by step.""" 

420 self._indentation -= step 

421 

422 def start_write(self, frame: Frame, node: t.Optional[nodes.Node] = None) -> None: 

423 """Yield or write into the frame buffer.""" 

424 if frame.buffer is None: 

425 self.writeline("yield ", node) 

426 else: 

427 self.writeline(f"{frame.buffer}.append(", node) 

428 

429 def end_write(self, frame: Frame) -> None: 

430 """End the writing process started by `start_write`.""" 

431 if frame.buffer is not None: 

432 self.write(")") 

433 

434 def simple_write( 

435 self, s: str, frame: Frame, node: t.Optional[nodes.Node] = None 

436 ) -> None: 

437 """Simple shortcut for start_write + write + end_write.""" 

438 self.start_write(frame, node) 

439 self.write(s) 

440 self.end_write(frame) 

441 

442 def blockvisit(self, nodes: t.Iterable[nodes.Node], frame: Frame) -> None: 

443 """Visit a list of nodes as block in a frame. If the current frame 

444 is no buffer a dummy ``if 0: yield None`` is written automatically. 

445 """ 

446 try: 

447 self.writeline("pass") 

448 for node in nodes: 

449 self.visit(node, frame) 

450 except CompilerExit: 

451 pass 

452 

453 def write(self, x: str) -> None: 

454 """Write a string into the output stream.""" 

455 if self._new_lines: 

456 if not self._first_write: 

457 self.stream.write("\n" * self._new_lines) 

458 self.code_lineno += self._new_lines 

459 if self._write_debug_info is not None: 

460 self.debug_info.append((self._write_debug_info, self.code_lineno)) 

461 self._write_debug_info = None 

462 self._first_write = False 

463 self.stream.write(" " * self._indentation) 

464 self._new_lines = 0 

465 self.stream.write(x) 

466 

467 def writeline( 

468 self, x: str, node: t.Optional[nodes.Node] = None, extra: int = 0 

469 ) -> None: 

470 """Combination of newline and write.""" 

471 self.newline(node, extra) 

472 self.write(x) 

473 

474 def newline(self, node: t.Optional[nodes.Node] = None, extra: int = 0) -> None: 

475 """Add one or more newlines before the next write.""" 

476 self._new_lines = max(self._new_lines, 1 + extra) 

477 if node is not None and node.lineno != self._last_line: 

478 self._write_debug_info = node.lineno 

479 self._last_line = node.lineno 

480 

481 def signature( 

482 self, 

483 node: t.Union[nodes.Call, nodes.Filter, nodes.Test], 

484 frame: Frame, 

485 extra_kwargs: t.Optional[t.Mapping[str, t.Any]] = None, 

486 ) -> None: 

487 """Writes a function call to the stream for the current node. 

488 A leading comma is added automatically. The extra keyword 

489 arguments may not include python keywords otherwise a syntax 

490 error could occur. The extra keyword arguments should be given 

491 as python dict. 

492 """ 

493 # if any of the given keyword arguments is a python keyword 

494 # we have to make sure that no invalid call is created. 

495 kwarg_workaround = any( 

496 is_python_keyword(t.cast(str, k)) 

497 for k in chain((x.key for x in node.kwargs), extra_kwargs or ()) 

498 ) 

499 

500 for arg in node.args: 

501 self.write(", ") 

502 self.visit(arg, frame) 

503 

504 if not kwarg_workaround: 

505 for kwarg in node.kwargs: 

506 self.write(", ") 

507 self.visit(kwarg, frame) 

508 if extra_kwargs is not None: 

509 for key, value in extra_kwargs.items(): 

510 self.write(f", {key}={value}") 

511 if node.dyn_args: 

512 self.write(", *") 

513 self.visit(node.dyn_args, frame) 

514 

515 if kwarg_workaround: 

516 if node.dyn_kwargs is not None: 

517 self.write(", **dict({") 

518 else: 

519 self.write(", **{") 

520 for kwarg in node.kwargs: 

521 self.write(f"{kwarg.key!r}: ") 

522 self.visit(kwarg.value, frame) 

523 self.write(", ") 

524 if extra_kwargs is not None: 

525 for key, value in extra_kwargs.items(): 

526 self.write(f"{key!r}: {value}, ") 

527 if node.dyn_kwargs is not None: 

528 self.write("}, **") 

529 self.visit(node.dyn_kwargs, frame) 

530 self.write(")") 

531 else: 

532 self.write("}") 

533 

534 elif node.dyn_kwargs is not None: 

535 self.write(", **") 

536 self.visit(node.dyn_kwargs, frame) 

537 

538 def pull_dependencies(self, nodes: t.Iterable[nodes.Node]) -> None: 

539 """Find all filter and test names used in the template and 

540 assign them to variables in the compiled namespace. Checking 

541 that the names are registered with the environment is done when 

542 compiling the Filter and Test nodes. If the node is in an If or 

543 CondExpr node, the check is done at runtime instead. 

544 

545 .. versionchanged:: 3.0 

546 Filters and tests in If and CondExpr nodes are checked at 

547 runtime instead of compile time. 

548 """ 

549 visitor = DependencyFinderVisitor() 

550 

551 for node in nodes: 

552 visitor.visit(node) 

553 

554 for id_map, names, dependency in (self.filters, visitor.filters, "filters"), ( 

555 self.tests, 

556 visitor.tests, 

557 "tests", 

558 ): 

559 for name in sorted(names): 

560 if name not in id_map: 

561 id_map[name] = self.temporary_identifier() 

562 

563 # add check during runtime that dependencies used inside of executed 

564 # blocks are defined, as this step may be skipped during compile time 

565 self.writeline("try:") 

566 self.indent() 

567 self.writeline(f"{id_map[name]} = environment.{dependency}[{name!r}]") 

568 self.outdent() 

569 self.writeline("except KeyError:") 

570 self.indent() 

571 self.writeline("@internalcode") 

572 self.writeline(f"def {id_map[name]}(*unused):") 

573 self.indent() 

574 self.writeline( 

575 f'raise TemplateRuntimeError("No {dependency[:-1]}' 

576 f' named {name!r} found.")' 

577 ) 

578 self.outdent() 

579 self.outdent() 

580 

581 def enter_frame(self, frame: Frame) -> None: 

582 undefs = [] 

583 for target, (action, param) in frame.symbols.loads.items(): 

584 if action == VAR_LOAD_PARAMETER: 

585 pass 

586 elif action == VAR_LOAD_RESOLVE: 

587 self.writeline(f"{target} = {self.get_resolve_func()}({param!r})") 

588 elif action == VAR_LOAD_ALIAS: 

589 self.writeline(f"{target} = {param}") 

590 elif action == VAR_LOAD_UNDEFINED: 

591 undefs.append(target) 

592 else: 

593 raise NotImplementedError("unknown load instruction") 

594 if undefs: 

595 self.writeline(f"{' = '.join(undefs)} = missing") 

596 

597 def leave_frame(self, frame: Frame, with_python_scope: bool = False) -> None: 

598 if not with_python_scope: 

599 undefs = [] 

600 for target in frame.symbols.loads: 

601 undefs.append(target) 

602 if undefs: 

603 self.writeline(f"{' = '.join(undefs)} = missing") 

604 

605 def choose_async(self, async_value: str = "async ", sync_value: str = "") -> str: 

606 return async_value if self.environment.is_async else sync_value 

607 

608 def func(self, name: str) -> str: 

609 return f"{self.choose_async()}def {name}" 

610 

611 def macro_body( 

612 self, node: t.Union[nodes.Macro, nodes.CallBlock], frame: Frame 

613 ) -> t.Tuple[Frame, MacroRef]: 

614 """Dump the function def of a macro or call block.""" 

615 frame = frame.inner() 

616 frame.symbols.analyze_node(node) 

617 macro_ref = MacroRef(node) 

618 

619 explicit_caller = None 

620 skip_special_params = set() 

621 args = [] 

622 

623 for idx, arg in enumerate(node.args): 

624 if arg.name == "caller": 

625 explicit_caller = idx 

626 if arg.name in ("kwargs", "varargs"): 

627 skip_special_params.add(arg.name) 

628 args.append(frame.symbols.ref(arg.name)) 

629 

630 undeclared = find_undeclared(node.body, ("caller", "kwargs", "varargs")) 

631 

632 if "caller" in undeclared: 

633 # In older Jinja versions there was a bug that allowed caller 

634 # to retain the special behavior even if it was mentioned in 

635 # the argument list. However thankfully this was only really 

636 # working if it was the last argument. So we are explicitly 

637 # checking this now and error out if it is anywhere else in 

638 # the argument list. 

639 if explicit_caller is not None: 

640 try: 

641 node.defaults[explicit_caller - len(node.args)] 

642 except IndexError: 

643 self.fail( 

644 "When defining macros or call blocks the " 

645 'special "caller" argument must be omitted ' 

646 "or be given a default.", 

647 node.lineno, 

648 ) 

649 else: 

650 args.append(frame.symbols.declare_parameter("caller")) 

651 macro_ref.accesses_caller = True 

652 if "kwargs" in undeclared and "kwargs" not in skip_special_params: 

653 args.append(frame.symbols.declare_parameter("kwargs")) 

654 macro_ref.accesses_kwargs = True 

655 if "varargs" in undeclared and "varargs" not in skip_special_params: 

656 args.append(frame.symbols.declare_parameter("varargs")) 

657 macro_ref.accesses_varargs = True 

658 

659 # macros are delayed, they never require output checks 

660 frame.require_output_check = False 

661 frame.symbols.analyze_node(node) 

662 self.writeline(f"{self.func('macro')}({', '.join(args)}):", node) 

663 self.indent() 

664 

665 self.buffer(frame) 

666 self.enter_frame(frame) 

667 

668 self.push_parameter_definitions(frame) 

669 for idx, arg in enumerate(node.args): 

670 ref = frame.symbols.ref(arg.name) 

671 self.writeline(f"if {ref} is missing:") 

672 self.indent() 

673 try: 

674 default = node.defaults[idx - len(node.args)] 

675 except IndexError: 

676 self.writeline( 

677 f'{ref} = undefined("parameter {arg.name!r} was not provided",' 

678 f" name={arg.name!r})" 

679 ) 

680 else: 

681 self.writeline(f"{ref} = ") 

682 self.visit(default, frame) 

683 self.mark_parameter_stored(ref) 

684 self.outdent() 

685 self.pop_parameter_definitions() 

686 

687 self.blockvisit(node.body, frame) 

688 self.return_buffer_contents(frame, force_unescaped=True) 

689 self.leave_frame(frame, with_python_scope=True) 

690 self.outdent() 

691 

692 return frame, macro_ref 

693 

694 def macro_def(self, macro_ref: MacroRef, frame: Frame) -> None: 

695 """Dump the macro definition for the def created by macro_body.""" 

696 arg_tuple = ", ".join(repr(x.name) for x in macro_ref.node.args) 

697 name = getattr(macro_ref.node, "name", None) 

698 if len(macro_ref.node.args) == 1: 

699 arg_tuple += "," 

700 self.write( 

701 f"Macro(environment, macro, {name!r}, ({arg_tuple})," 

702 f" {macro_ref.accesses_kwargs!r}, {macro_ref.accesses_varargs!r}," 

703 f" {macro_ref.accesses_caller!r}, context.eval_ctx.autoescape)" 

704 ) 

705 

706 def position(self, node: nodes.Node) -> str: 

707 """Return a human readable position for the node.""" 

708 rv = f"line {node.lineno}" 

709 if self.name is not None: 

710 rv = f"{rv} in {self.name!r}" 

711 return rv 

712 

713 def dump_local_context(self, frame: Frame) -> str: 

714 items_kv = ", ".join( 

715 f"{name!r}: {target}" 

716 for name, target in frame.symbols.dump_stores().items() 

717 ) 

718 return f"{{{items_kv}}}" 

719 

720 def write_commons(self) -> None: 

721 """Writes a common preamble that is used by root and block functions. 

722 Primarily this sets up common local helpers and enforces a generator 

723 through a dead branch. 

724 """ 

725 self.writeline("resolve = context.resolve_or_missing") 

726 self.writeline("undefined = environment.undefined") 

727 self.writeline("concat = environment.concat") 

728 # always use the standard Undefined class for the implicit else of 

729 # conditional expressions 

730 self.writeline("cond_expr_undefined = Undefined") 

731 self.writeline("if 0: yield None") 

732 

733 def push_parameter_definitions(self, frame: Frame) -> None: 

734 """Pushes all parameter targets from the given frame into a local 

735 stack that permits tracking of yet to be assigned parameters. In 

736 particular this enables the optimization from `visit_Name` to skip 

737 undefined expressions for parameters in macros as macros can reference 

738 otherwise unbound parameters. 

739 """ 

740 self._param_def_block.append(frame.symbols.dump_param_targets()) 

741 

742 def pop_parameter_definitions(self) -> None: 

743 """Pops the current parameter definitions set.""" 

744 self._param_def_block.pop() 

745 

746 def mark_parameter_stored(self, target: str) -> None: 

747 """Marks a parameter in the current parameter definitions as stored. 

748 This will skip the enforced undefined checks. 

749 """ 

750 if self._param_def_block: 

751 self._param_def_block[-1].discard(target) 

752 

753 def push_context_reference(self, target: str) -> None: 

754 self._context_reference_stack.append(target) 

755 

756 def pop_context_reference(self) -> None: 

757 self._context_reference_stack.pop() 

758 

759 def get_context_ref(self) -> str: 

760 return self._context_reference_stack[-1] 

761 

762 def get_resolve_func(self) -> str: 

763 target = self._context_reference_stack[-1] 

764 if target == "context": 

765 return "resolve" 

766 return f"{target}.resolve" 

767 

768 def derive_context(self, frame: Frame) -> str: 

769 return f"{self.get_context_ref()}.derived({self.dump_local_context(frame)})" 

770 

771 def parameter_is_undeclared(self, target: str) -> bool: 

772 """Checks if a given target is an undeclared parameter.""" 

773 if not self._param_def_block: 

774 return False 

775 return target in self._param_def_block[-1] 

776 

777 def push_assign_tracking(self) -> None: 

778 """Pushes a new layer for assignment tracking.""" 

779 self._assign_stack.append(set()) 

780 

781 def pop_assign_tracking(self, frame: Frame) -> None: 

782 """Pops the topmost level for assignment tracking and updates the 

783 context variables if necessary. 

784 """ 

785 vars = self._assign_stack.pop() 

786 if ( 

787 not frame.block_frame 

788 and not frame.loop_frame 

789 and not frame.toplevel 

790 or not vars 

791 ): 

792 return 

793 public_names = [x for x in vars if x[:1] != "_"] 

794 if len(vars) == 1: 

795 name = next(iter(vars)) 

796 ref = frame.symbols.ref(name) 

797 if frame.loop_frame: 

798 self.writeline(f"_loop_vars[{name!r}] = {ref}") 

799 return 

800 if frame.block_frame: 

801 self.writeline(f"_block_vars[{name!r}] = {ref}") 

802 return 

803 self.writeline(f"context.vars[{name!r}] = {ref}") 

804 else: 

805 if frame.loop_frame: 

806 self.writeline("_loop_vars.update({") 

807 elif frame.block_frame: 

808 self.writeline("_block_vars.update({") 

809 else: 

810 self.writeline("context.vars.update({") 

811 for idx, name in enumerate(vars): 

812 if idx: 

813 self.write(", ") 

814 ref = frame.symbols.ref(name) 

815 self.write(f"{name!r}: {ref}") 

816 self.write("})") 

817 if not frame.block_frame and not frame.loop_frame and public_names: 

818 if len(public_names) == 1: 

819 self.writeline(f"context.exported_vars.add({public_names[0]!r})") 

820 else: 

821 names_str = ", ".join(map(repr, public_names)) 

822 self.writeline(f"context.exported_vars.update(({names_str}))") 

823 

824 # -- Statement Visitors 

825 

826 def visit_Template( 

827 self, node: nodes.Template, frame: t.Optional[Frame] = None 

828 ) -> None: 

829 assert frame is None, "no root frame allowed" 

830 eval_ctx = EvalContext(self.environment, self.name) 

831 

832 from .runtime import exported, async_exported 

833 

834 if self.environment.is_async: 

835 exported_names = sorted(exported + async_exported) 

836 else: 

837 exported_names = sorted(exported) 

838 

839 self.writeline("from jinja2.runtime import " + ", ".join(exported_names)) 

840 

841 # if we want a deferred initialization we cannot move the 

842 # environment into a local name 

843 envenv = "" if self.defer_init else ", environment=environment" 

844 

845 # do we have an extends tag at all? If not, we can save some 

846 # overhead by just not processing any inheritance code. 

847 have_extends = node.find(nodes.Extends) is not None 

848 

849 # find all blocks 

850 for block in node.find_all(nodes.Block): 

851 if block.name in self.blocks: 

852 self.fail(f"block {block.name!r} defined twice", block.lineno) 

853 self.blocks[block.name] = block 

854 

855 # find all imports and import them 

856 for import_ in node.find_all(nodes.ImportedName): 

857 if import_.importname not in self.import_aliases: 

858 imp = import_.importname 

859 self.import_aliases[imp] = alias = self.temporary_identifier() 

860 if "." in imp: 

861 module, obj = imp.rsplit(".", 1) 

862 self.writeline(f"from {module} import {obj} as {alias}") 

863 else: 

864 self.writeline(f"import {imp} as {alias}") 

865 

866 # add the load name 

867 self.writeline(f"name = {self.name!r}") 

868 

869 # generate the root render function. 

870 self.writeline( 

871 f"{self.func('root')}(context, missing=missing{envenv}):", extra=1 

872 ) 

873 self.indent() 

874 self.write_commons() 

875 

876 # process the root 

877 frame = Frame(eval_ctx) 

878 if "self" in find_undeclared(node.body, ("self",)): 

879 ref = frame.symbols.declare_parameter("self") 

880 self.writeline(f"{ref} = TemplateReference(context)") 

881 frame.symbols.analyze_node(node) 

882 frame.toplevel = frame.rootlevel = True 

883 frame.require_output_check = have_extends and not self.has_known_extends 

884 if have_extends: 

885 self.writeline("parent_template = None") 

886 self.enter_frame(frame) 

887 self.pull_dependencies(node.body) 

888 self.blockvisit(node.body, frame) 

889 self.leave_frame(frame, with_python_scope=True) 

890 self.outdent() 

891 

892 # make sure that the parent root is called. 

893 if have_extends: 

894 if not self.has_known_extends: 

895 self.indent() 

896 self.writeline("if parent_template is not None:") 

897 self.indent() 

898 if not self.environment.is_async: 

899 self.writeline("yield from parent_template.root_render_func(context)") 

900 else: 

901 self.writeline( 

902 "async for event in parent_template.root_render_func(context):" 

903 ) 

904 self.indent() 

905 self.writeline("yield event") 

906 self.outdent() 

907 self.outdent(1 + (not self.has_known_extends)) 

908 

909 # at this point we now have the blocks collected and can visit them too. 

910 for name, block in self.blocks.items(): 

911 self.writeline( 

912 f"{self.func('block_' + name)}(context, missing=missing{envenv}):", 

913 block, 

914 1, 

915 ) 

916 self.indent() 

917 self.write_commons() 

918 # It's important that we do not make this frame a child of the 

919 # toplevel template. This would cause a variety of 

920 # interesting issues with identifier tracking. 

921 block_frame = Frame(eval_ctx) 

922 block_frame.block_frame = True 

923 undeclared = find_undeclared(block.body, ("self", "super")) 

924 if "self" in undeclared: 

925 ref = block_frame.symbols.declare_parameter("self") 

926 self.writeline(f"{ref} = TemplateReference(context)") 

927 if "super" in undeclared: 

928 ref = block_frame.symbols.declare_parameter("super") 

929 self.writeline(f"{ref} = context.super({name!r}, block_{name})") 

930 block_frame.symbols.analyze_node(block) 

931 block_frame.block = name 

932 self.writeline("_block_vars = {}") 

933 self.enter_frame(block_frame) 

934 self.pull_dependencies(block.body) 

935 self.blockvisit(block.body, block_frame) 

936 self.leave_frame(block_frame, with_python_scope=True) 

937 self.outdent() 

938 

939 blocks_kv_str = ", ".join(f"{x!r}: block_{x}" for x in self.blocks) 

940 self.writeline(f"blocks = {{{blocks_kv_str}}}", extra=1) 

941 debug_kv_str = "&".join(f"{k}={v}" for k, v in self.debug_info) 

942 self.writeline(f"debug_info = {debug_kv_str!r}") 

943 

944 def visit_Block(self, node: nodes.Block, frame: Frame) -> None: 

945 """Call a block and register it for the template.""" 

946 level = 0 

947 if frame.toplevel: 

948 # if we know that we are a child template, there is no need to 

949 # check if we are one 

950 if self.has_known_extends: 

951 return 

952 if self.extends_so_far > 0: 

953 self.writeline("if parent_template is None:") 

954 self.indent() 

955 level += 1 

956 

957 if node.scoped: 

958 context = self.derive_context(frame) 

959 else: 

960 context = self.get_context_ref() 

961 

962 if node.required: 

963 self.writeline(f"if len(context.blocks[{node.name!r}]) <= 1:", node) 

964 self.indent() 

965 self.writeline( 

966 f'raise TemplateRuntimeError("Required block {node.name!r} not found")', 

967 node, 

968 ) 

969 self.outdent() 

970 

971 if not self.environment.is_async and frame.buffer is None: 

972 self.writeline( 

973 f"yield from context.blocks[{node.name!r}][0]({context})", node 

974 ) 

975 else: 

976 self.writeline( 

977 f"{self.choose_async()}for event in" 

978 f" context.blocks[{node.name!r}][0]({context}):", 

979 node, 

980 ) 

981 self.indent() 

982 self.simple_write("event", frame) 

983 self.outdent() 

984 

985 self.outdent(level) 

986 

987 def visit_Extends(self, node: nodes.Extends, frame: Frame) -> None: 

988 """Calls the extender.""" 

989 if not frame.toplevel: 

990 self.fail("cannot use extend from a non top-level scope", node.lineno) 

991 

992 # if the number of extends statements in general is zero so 

993 # far, we don't have to add a check if something extended 

994 # the template before this one. 

995 if self.extends_so_far > 0: 

996 

997 # if we have a known extends we just add a template runtime 

998 # error into the generated code. We could catch that at compile 

999 # time too, but i welcome it not to confuse users by throwing the 

1000 # same error at different times just "because we can". 

1001 if not self.has_known_extends: 

1002 self.writeline("if parent_template is not None:") 

1003 self.indent() 

1004 self.writeline('raise TemplateRuntimeError("extended multiple times")') 

1005 

1006 # if we have a known extends already we don't need that code here 

1007 # as we know that the template execution will end here. 

1008 if self.has_known_extends: 

1009 raise CompilerExit() 

1010 else: 

1011 self.outdent() 

1012 

1013 self.writeline("parent_template = environment.get_template(", node) 

1014 self.visit(node.template, frame) 

1015 self.write(f", {self.name!r})") 

1016 self.writeline("for name, parent_block in parent_template.blocks.items():") 

1017 self.indent() 

1018 self.writeline("context.blocks.setdefault(name, []).append(parent_block)") 

1019 self.outdent() 

1020 

1021 # if this extends statement was in the root level we can take 

1022 # advantage of that information and simplify the generated code 

1023 # in the top level from this point onwards 

1024 if frame.rootlevel: 

1025 self.has_known_extends = True 

1026 

1027 # and now we have one more 

1028 self.extends_so_far += 1 

1029 

1030 def visit_Include(self, node: nodes.Include, frame: Frame) -> None: 

1031 """Handles includes.""" 

1032 if node.ignore_missing: 

1033 self.writeline("try:") 

1034 self.indent() 

1035 

1036 func_name = "get_or_select_template" 

1037 if isinstance(node.template, nodes.Const): 

1038 if isinstance(node.template.value, str): 

1039 func_name = "get_template" 

1040 elif isinstance(node.template.value, (tuple, list)): 

1041 func_name = "select_template" 

1042 elif isinstance(node.template, (nodes.Tuple, nodes.List)): 

1043 func_name = "select_template" 

1044 

1045 self.writeline(f"template = environment.{func_name}(", node) 

1046 self.visit(node.template, frame) 

1047 self.write(f", {self.name!r})") 

1048 if node.ignore_missing: 

1049 self.outdent() 

1050 self.writeline("except TemplateNotFound:") 

1051 self.indent() 

1052 self.writeline("pass") 

1053 self.outdent() 

1054 self.writeline("else:") 

1055 self.indent() 

1056 

1057 skip_event_yield = False 

1058 if node.with_context: 

1059 self.writeline( 

1060 f"{self.choose_async()}for event in template.root_render_func(" 

1061 "template.new_context(context.get_all(), True," 

1062 f" {self.dump_local_context(frame)})):" 

1063 ) 

1064 elif self.environment.is_async: 

1065 self.writeline( 

1066 "for event in (await template._get_default_module_async())" 

1067 "._body_stream:" 

1068 ) 

1069 else: 

1070 self.writeline("yield from template._get_default_module()._body_stream") 

1071 skip_event_yield = True 

1072 

1073 if not skip_event_yield: 

1074 self.indent() 

1075 self.simple_write("event", frame) 

1076 self.outdent() 

1077 

1078 if node.ignore_missing: 

1079 self.outdent() 

1080 

1081 def _import_common( 

1082 self, node: t.Union[nodes.Import, nodes.FromImport], frame: Frame 

1083 ) -> None: 

1084 self.write(f"{self.choose_async('await ')}environment.get_template(") 

1085 self.visit(node.template, frame) 

1086 self.write(f", {self.name!r}).") 

1087 

1088 if node.with_context: 

1089 f_name = f"make_module{self.choose_async('_async')}" 

1090 self.write( 

1091 f"{f_name}(context.get_all(), True, {self.dump_local_context(frame)})" 

1092 ) 

1093 else: 

1094 self.write(f"_get_default_module{self.choose_async('_async')}(context)") 

1095 

1096 def visit_Import(self, node: nodes.Import, frame: Frame) -> None: 

1097 """Visit regular imports.""" 

1098 self.writeline(f"{frame.symbols.ref(node.target)} = ", node) 

1099 if frame.toplevel: 

1100 self.write(f"context.vars[{node.target!r}] = ") 

1101 

1102 self._import_common(node, frame) 

1103 

1104 if frame.toplevel and not node.target.startswith("_"): 

1105 self.writeline(f"context.exported_vars.discard({node.target!r})") 

1106 

1107 def visit_FromImport(self, node: nodes.FromImport, frame: Frame) -> None: 

1108 """Visit named imports.""" 

1109 self.newline(node) 

1110 self.write("included_template = ") 

1111 self._import_common(node, frame) 

1112 var_names = [] 

1113 discarded_names = [] 

1114 for name in node.names: 

1115 if isinstance(name, tuple): 

1116 name, alias = name 

1117 else: 

1118 alias = name 

1119 self.writeline( 

1120 f"{frame.symbols.ref(alias)} =" 

1121 f" getattr(included_template, {name!r}, missing)" 

1122 ) 

1123 self.writeline(f"if {frame.symbols.ref(alias)} is missing:") 

1124 self.indent() 

1125 message = ( 

1126 "the template {included_template.__name__!r}" 

1127 f" (imported on {self.position(node)})" 

1128 f" does not export the requested name {name!r}" 

1129 ) 

1130 self.writeline( 

1131 f"{frame.symbols.ref(alias)} = undefined(f{message!r}, name={name!r})" 

1132 ) 

1133 self.outdent() 

1134 if frame.toplevel: 

1135 var_names.append(alias) 

1136 if not alias.startswith("_"): 

1137 discarded_names.append(alias) 

1138 

1139 if var_names: 

1140 if len(var_names) == 1: 

1141 name = var_names[0] 

1142 self.writeline(f"context.vars[{name!r}] = {frame.symbols.ref(name)}") 

1143 else: 

1144 names_kv = ", ".join( 

1145 f"{name!r}: {frame.symbols.ref(name)}" for name in var_names 

1146 ) 

1147 self.writeline(f"context.vars.update({{{names_kv}}})") 

1148 if discarded_names: 

1149 if len(discarded_names) == 1: 

1150 self.writeline(f"context.exported_vars.discard({discarded_names[0]!r})") 

1151 else: 

1152 names_str = ", ".join(map(repr, discarded_names)) 

1153 self.writeline( 

1154 f"context.exported_vars.difference_update(({names_str}))" 

1155 ) 

1156 

1157 def visit_For(self, node: nodes.For, frame: Frame) -> None: 

1158 loop_frame = frame.inner() 

1159 loop_frame.loop_frame = True 

1160 test_frame = frame.inner() 

1161 else_frame = frame.inner() 

1162 

1163 # try to figure out if we have an extended loop. An extended loop 

1164 # is necessary if the loop is in recursive mode if the special loop 

1165 # variable is accessed in the body if the body is a scoped block. 

1166 extended_loop = ( 

1167 node.recursive 

1168 or "loop" 

1169 in find_undeclared(node.iter_child_nodes(only=("body",)), ("loop",)) 

1170 or any(block.scoped for block in node.find_all(nodes.Block)) 

1171 ) 

1172 

1173 loop_ref = None 

1174 if extended_loop: 

1175 loop_ref = loop_frame.symbols.declare_parameter("loop") 

1176 

1177 loop_frame.symbols.analyze_node(node, for_branch="body") 

1178 if node.else_: 

1179 else_frame.symbols.analyze_node(node, for_branch="else") 

1180 

1181 if node.test: 

1182 loop_filter_func = self.temporary_identifier() 

1183 test_frame.symbols.analyze_node(node, for_branch="test") 

1184 self.writeline(f"{self.func(loop_filter_func)}(fiter):", node.test) 

1185 self.indent() 

1186 self.enter_frame(test_frame) 

1187 self.writeline(self.choose_async("async for ", "for ")) 

1188 self.visit(node.target, loop_frame) 

1189 self.write(" in ") 

1190 self.write(self.choose_async("auto_aiter(fiter)", "fiter")) 

1191 self.write(":") 

1192 self.indent() 

1193 self.writeline("if ", node.test) 

1194 self.visit(node.test, test_frame) 

1195 self.write(":") 

1196 self.indent() 

1197 self.writeline("yield ") 

1198 self.visit(node.target, loop_frame) 

1199 self.outdent(3) 

1200 self.leave_frame(test_frame, with_python_scope=True) 

1201 

1202 # if we don't have an recursive loop we have to find the shadowed 

1203 # variables at that point. Because loops can be nested but the loop 

1204 # variable is a special one we have to enforce aliasing for it. 

1205 if node.recursive: 

1206 self.writeline( 

1207 f"{self.func('loop')}(reciter, loop_render_func, depth=0):", node 

1208 ) 

1209 self.indent() 

1210 self.buffer(loop_frame) 

1211 

1212 # Use the same buffer for the else frame 

1213 else_frame.buffer = loop_frame.buffer 

1214 

1215 # make sure the loop variable is a special one and raise a template 

1216 # assertion error if a loop tries to write to loop 

1217 if extended_loop: 

1218 self.writeline(f"{loop_ref} = missing") 

1219 

1220 for name in node.find_all(nodes.Name): 

1221 if name.ctx == "store" and name.name == "loop": 

1222 self.fail( 

1223 "Can't assign to special loop variable in for-loop target", 

1224 name.lineno, 

1225 ) 

1226 

1227 if node.else_: 

1228 iteration_indicator = self.temporary_identifier() 

1229 self.writeline(f"{iteration_indicator} = 1") 

1230 

1231 self.writeline(self.choose_async("async for ", "for "), node) 

1232 self.visit(node.target, loop_frame) 

1233 if extended_loop: 

1234 self.write(f", {loop_ref} in {self.choose_async('Async')}LoopContext(") 

1235 else: 

1236 self.write(" in ") 

1237 

1238 if node.test: 

1239 self.write(f"{loop_filter_func}(") 

1240 if node.recursive: 

1241 self.write("reciter") 

1242 else: 

1243 if self.environment.is_async and not extended_loop: 

1244 self.write("auto_aiter(") 

1245 self.visit(node.iter, frame) 

1246 if self.environment.is_async and not extended_loop: 

1247 self.write(")") 

1248 if node.test: 

1249 self.write(")") 

1250 

1251 if node.recursive: 

1252 self.write(", undefined, loop_render_func, depth):") 

1253 else: 

1254 self.write(", undefined):" if extended_loop else ":") 

1255 

1256 self.indent() 

1257 self.enter_frame(loop_frame) 

1258 

1259 self.writeline("_loop_vars = {}") 

1260 self.blockvisit(node.body, loop_frame) 

1261 if node.else_: 

1262 self.writeline(f"{iteration_indicator} = 0") 

1263 self.outdent() 

1264 self.leave_frame( 

1265 loop_frame, with_python_scope=node.recursive and not node.else_ 

1266 ) 

1267 

1268 if node.else_: 

1269 self.writeline(f"if {iteration_indicator}:") 

1270 self.indent() 

1271 self.enter_frame(else_frame) 

1272 self.blockvisit(node.else_, else_frame) 

1273 self.leave_frame(else_frame) 

1274 self.outdent() 

1275 

1276 # if the node was recursive we have to return the buffer contents 

1277 # and start the iteration code 

1278 if node.recursive: 

1279 self.return_buffer_contents(loop_frame) 

1280 self.outdent() 

1281 self.start_write(frame, node) 

1282 self.write(f"{self.choose_async('await ')}loop(") 

1283 if self.environment.is_async: 

1284 self.write("auto_aiter(") 

1285 self.visit(node.iter, frame) 

1286 if self.environment.is_async: 

1287 self.write(")") 

1288 self.write(", loop)") 

1289 self.end_write(frame) 

1290 

1291 # at the end of the iteration, clear any assignments made in the 

1292 # loop from the top level 

1293 if self._assign_stack: 

1294 self._assign_stack[-1].difference_update(loop_frame.symbols.stores) 

1295 

1296 def visit_If(self, node: nodes.If, frame: Frame) -> None: 

1297 if_frame = frame.soft() 

1298 self.writeline("if ", node) 

1299 self.visit(node.test, if_frame) 

1300 self.write(":") 

1301 self.indent() 

1302 self.blockvisit(node.body, if_frame) 

1303 self.outdent() 

1304 for elif_ in node.elif_: 

1305 self.writeline("elif ", elif_) 

1306 self.visit(elif_.test, if_frame) 

1307 self.write(":") 

1308 self.indent() 

1309 self.blockvisit(elif_.body, if_frame) 

1310 self.outdent() 

1311 if node.else_: 

1312 self.writeline("else:") 

1313 self.indent() 

1314 self.blockvisit(node.else_, if_frame) 

1315 self.outdent() 

1316 

1317 def visit_Macro(self, node: nodes.Macro, frame: Frame) -> None: 

1318 macro_frame, macro_ref = self.macro_body(node, frame) 

1319 self.newline() 

1320 if frame.toplevel: 

1321 if not node.name.startswith("_"): 

1322 self.write(f"context.exported_vars.add({node.name!r})") 

1323 self.writeline(f"context.vars[{node.name!r}] = ") 

1324 self.write(f"{frame.symbols.ref(node.name)} = ") 

1325 self.macro_def(macro_ref, macro_frame) 

1326 

1327 def visit_CallBlock(self, node: nodes.CallBlock, frame: Frame) -> None: 

1328 call_frame, macro_ref = self.macro_body(node, frame) 

1329 self.writeline("caller = ") 

1330 self.macro_def(macro_ref, call_frame) 

1331 self.start_write(frame, node) 

1332 self.visit_Call(node.call, frame, forward_caller=True) 

1333 self.end_write(frame) 

1334 

1335 def visit_FilterBlock(self, node: nodes.FilterBlock, frame: Frame) -> None: 

1336 filter_frame = frame.inner() 

1337 filter_frame.symbols.analyze_node(node) 

1338 self.enter_frame(filter_frame) 

1339 self.buffer(filter_frame) 

1340 self.blockvisit(node.body, filter_frame) 

1341 self.start_write(frame, node) 

1342 self.visit_Filter(node.filter, filter_frame) 

1343 self.end_write(frame) 

1344 self.leave_frame(filter_frame) 

1345 

1346 def visit_With(self, node: nodes.With, frame: Frame) -> None: 

1347 with_frame = frame.inner() 

1348 with_frame.symbols.analyze_node(node) 

1349 self.enter_frame(with_frame) 

1350 for target, expr in zip(node.targets, node.values): 

1351 self.newline() 

1352 self.visit(target, with_frame) 

1353 self.write(" = ") 

1354 self.visit(expr, frame) 

1355 self.blockvisit(node.body, with_frame) 

1356 self.leave_frame(with_frame) 

1357 

1358 def visit_ExprStmt(self, node: nodes.ExprStmt, frame: Frame) -> None: 

1359 self.newline(node) 

1360 self.visit(node.node, frame) 

1361 

1362 class _FinalizeInfo(t.NamedTuple): 

1363 const: t.Optional[t.Callable[..., str]] 

1364 src: t.Optional[str] 

1365 

1366 @staticmethod 

1367 def _default_finalize(value: t.Any) -> t.Any: 

1368 """The default finalize function if the environment isn't 

1369 configured with one. Or, if the environment has one, this is 

1370 called on that function's output for constants. 

1371 """ 

1372 return str(value) 

1373 

1374 _finalize: t.Optional[_FinalizeInfo] = None 

1375 

1376 def _make_finalize(self) -> _FinalizeInfo: 

1377 """Build the finalize function to be used on constants and at 

1378 runtime. Cached so it's only created once for all output nodes. 

1379 

1380 Returns a ``namedtuple`` with the following attributes: 

1381 

1382 ``const`` 

1383 A function to finalize constant data at compile time. 

1384 

1385 ``src`` 

1386 Source code to output around nodes to be evaluated at 

1387 runtime. 

1388 """ 

1389 if self._finalize is not None: 

1390 return self._finalize 

1391 

1392 finalize: t.Optional[t.Callable[..., t.Any]] 

1393 finalize = default = self._default_finalize 

1394 src = None 

1395 

1396 if self.environment.finalize: 

1397 src = "environment.finalize(" 

1398 env_finalize = self.environment.finalize 

1399 pass_arg = { 

1400 _PassArg.context: "context", 

1401 _PassArg.eval_context: "context.eval_ctx", 

1402 _PassArg.environment: "environment", 

1403 }.get( 

1404 _PassArg.from_obj(env_finalize) # type: ignore 

1405 ) 

1406 finalize = None 

1407 

1408 if pass_arg is None: 

1409 

1410 def finalize(value: t.Any) -> t.Any: 

1411 return default(env_finalize(value)) 

1412 

1413 else: 

1414 src = f"{src}{pass_arg}, " 

1415 

1416 if pass_arg == "environment": 

1417 

1418 def finalize(value: t.Any) -> t.Any: 

1419 return default(env_finalize(self.environment, value)) 

1420 

1421 self._finalize = self._FinalizeInfo(finalize, src) 

1422 return self._finalize 

1423 

1424 def _output_const_repr(self, group: t.Iterable[t.Any]) -> str: 

1425 """Given a group of constant values converted from ``Output`` 

1426 child nodes, produce a string to write to the template module 

1427 source. 

1428 """ 

1429 return repr(concat(group)) 

1430 

1431 def _output_child_to_const( 

1432 self, node: nodes.Expr, frame: Frame, finalize: _FinalizeInfo 

1433 ) -> str: 

1434 """Try to optimize a child of an ``Output`` node by trying to 

1435 convert it to constant, finalized data at compile time. 

1436 

1437 If :exc:`Impossible` is raised, the node is not constant and 

1438 will be evaluated at runtime. Any other exception will also be 

1439 evaluated at runtime for easier debugging. 

1440 """ 

1441 const = node.as_const(frame.eval_ctx) 

1442 

1443 if frame.eval_ctx.autoescape: 

1444 const = escape(const) 

1445 

1446 # Template data doesn't go through finalize. 

1447 if isinstance(node, nodes.TemplateData): 

1448 return str(const) 

1449 

1450 return finalize.const(const) # type: ignore 

1451 

1452 def _output_child_pre( 

1453 self, node: nodes.Expr, frame: Frame, finalize: _FinalizeInfo 

1454 ) -> None: 

1455 """Output extra source code before visiting a child of an 

1456 ``Output`` node. 

1457 """ 

1458 if frame.eval_ctx.volatile: 

1459 self.write("(escape if context.eval_ctx.autoescape else str)(") 

1460 elif frame.eval_ctx.autoescape: 

1461 self.write("escape(") 

1462 else: 

1463 self.write("str(") 

1464 

1465 if finalize.src is not None: 

1466 self.write(finalize.src) 

1467 

1468 def _output_child_post( 

1469 self, node: nodes.Expr, frame: Frame, finalize: _FinalizeInfo 

1470 ) -> None: 

1471 """Output extra source code after visiting a child of an 

1472 ``Output`` node. 

1473 """ 

1474 self.write(")") 

1475 

1476 if finalize.src is not None: 

1477 self.write(")") 

1478 

1479 def visit_Output(self, node: nodes.Output, frame: Frame) -> None: 

1480 # If an extends is active, don't render outside a block. 

1481 if frame.require_output_check: 

1482 # A top-level extends is known to exist at compile time. 

1483 if self.has_known_extends: 

1484 return 

1485 

1486 self.writeline("if parent_template is None:") 

1487 self.indent() 

1488 

1489 finalize = self._make_finalize() 

1490 body: t.List[t.Union[t.List[t.Any], nodes.Expr]] = [] 

1491 

1492 # Evaluate constants at compile time if possible. Each item in 

1493 # body will be either a list of static data or a node to be 

1494 # evaluated at runtime. 

1495 for child in node.nodes: 

1496 try: 

1497 if not ( 

1498 # If the finalize function requires runtime context, 

1499 # constants can't be evaluated at compile time. 

1500 finalize.const 

1501 # Unless it's basic template data that won't be 

1502 # finalized anyway. 

1503 or isinstance(child, nodes.TemplateData) 

1504 ): 

1505 raise nodes.Impossible() 

1506 

1507 const = self._output_child_to_const(child, frame, finalize) 

1508 except (nodes.Impossible, Exception): 

1509 # The node was not constant and needs to be evaluated at 

1510 # runtime. Or another error was raised, which is easier 

1511 # to debug at runtime. 

1512 body.append(child) 

1513 continue 

1514 

1515 if body and isinstance(body[-1], list): 

1516 body[-1].append(const) 

1517 else: 

1518 body.append([const]) 

1519 

1520 if frame.buffer is not None: 

1521 if len(body) == 1: 

1522 self.writeline(f"{frame.buffer}.append(") 

1523 else: 

1524 self.writeline(f"{frame.buffer}.extend((") 

1525 

1526 self.indent() 

1527 

1528 for item in body: 

1529 if isinstance(item, list): 

1530 # A group of constant data to join and output. 

1531 val = self._output_const_repr(item) 

1532 

1533 if frame.buffer is None: 

1534 self.writeline("yield " + val) 

1535 else: 

1536 self.writeline(val + ",") 

1537 else: 

1538 if frame.buffer is None: 

1539 self.writeline("yield ", item) 

1540 else: 

1541 self.newline(item) 

1542 

1543 # A node to be evaluated at runtime. 

1544 self._output_child_pre(item, frame, finalize) 

1545 self.visit(item, frame) 

1546 self._output_child_post(item, frame, finalize) 

1547 

1548 if frame.buffer is not None: 

1549 self.write(",") 

1550 

1551 if frame.buffer is not None: 

1552 self.outdent() 

1553 self.writeline(")" if len(body) == 1 else "))") 

1554 

1555 if frame.require_output_check: 

1556 self.outdent() 

1557 

1558 def visit_Assign(self, node: nodes.Assign, frame: Frame) -> None: 

1559 self.push_assign_tracking() 

1560 self.newline(node) 

1561 self.visit(node.target, frame) 

1562 self.write(" = ") 

1563 self.visit(node.node, frame) 

1564 self.pop_assign_tracking(frame) 

1565 

1566 def visit_AssignBlock(self, node: nodes.AssignBlock, frame: Frame) -> None: 

1567 self.push_assign_tracking() 

1568 block_frame = frame.inner() 

1569 # This is a special case. Since a set block always captures we 

1570 # will disable output checks. This way one can use set blocks 

1571 # toplevel even in extended templates. 

1572 block_frame.require_output_check = False 

1573 block_frame.symbols.analyze_node(node) 

1574 self.enter_frame(block_frame) 

1575 self.buffer(block_frame) 

1576 self.blockvisit(node.body, block_frame) 

1577 self.newline(node) 

1578 self.visit(node.target, frame) 

1579 self.write(" = (Markup if context.eval_ctx.autoescape else identity)(") 

1580 if node.filter is not None: 

1581 self.visit_Filter(node.filter, block_frame) 

1582 else: 

1583 self.write(f"concat({block_frame.buffer})") 

1584 self.write(")") 

1585 self.pop_assign_tracking(frame) 

1586 self.leave_frame(block_frame) 

1587 

1588 # -- Expression Visitors 

1589 

1590 def visit_Name(self, node: nodes.Name, frame: Frame) -> None: 

1591 if node.ctx == "store" and ( 

1592 frame.toplevel or frame.loop_frame or frame.block_frame 

1593 ): 

1594 if self._assign_stack: 

1595 self._assign_stack[-1].add(node.name) 

1596 ref = frame.symbols.ref(node.name) 

1597 

1598 # If we are looking up a variable we might have to deal with the 

1599 # case where it's undefined. We can skip that case if the load 

1600 # instruction indicates a parameter which are always defined. 

1601 if node.ctx == "load": 

1602 load = frame.symbols.find_load(ref) 

1603 if not ( 

1604 load is not None 

1605 and load[0] == VAR_LOAD_PARAMETER 

1606 and not self.parameter_is_undeclared(ref) 

1607 ): 

1608 self.write( 

1609 f"(undefined(name={node.name!r}) if {ref} is missing else {ref})" 

1610 ) 

1611 return 

1612 

1613 self.write(ref) 

1614 

1615 def visit_NSRef(self, node: nodes.NSRef, frame: Frame) -> None: 

1616 # NSRefs can only be used to store values; since they use the normal 

1617 # `foo.bar` notation they will be parsed as a normal attribute access 

1618 # when used anywhere but in a `set` context 

1619 ref = frame.symbols.ref(node.name) 

1620 self.writeline(f"if not isinstance({ref}, Namespace):") 

1621 self.indent() 

1622 self.writeline( 

1623 "raise TemplateRuntimeError" 

1624 '("cannot assign attribute on non-namespace object")' 

1625 ) 

1626 self.outdent() 

1627 self.writeline(f"{ref}[{node.attr!r}]") 

1628 

1629 def visit_Const(self, node: nodes.Const, frame: Frame) -> None: 

1630 val = node.as_const(frame.eval_ctx) 

1631 if isinstance(val, float): 

1632 self.write(str(val)) 

1633 else: 

1634 self.write(repr(val)) 

1635 

1636 def visit_TemplateData(self, node: nodes.TemplateData, frame: Frame) -> None: 

1637 try: 

1638 self.write(repr(node.as_const(frame.eval_ctx))) 

1639 except nodes.Impossible: 

1640 self.write( 

1641 f"(Markup if context.eval_ctx.autoescape else identity)({node.data!r})" 

1642 ) 

1643 

1644 def visit_Tuple(self, node: nodes.Tuple, frame: Frame) -> None: 

1645 self.write("(") 

1646 idx = -1 

1647 for idx, item in enumerate(node.items): 

1648 if idx: 

1649 self.write(", ") 

1650 self.visit(item, frame) 

1651 self.write(",)" if idx == 0 else ")") 

1652 

1653 def visit_List(self, node: nodes.List, frame: Frame) -> None: 

1654 self.write("[") 

1655 for idx, item in enumerate(node.items): 

1656 if idx: 

1657 self.write(", ") 

1658 self.visit(item, frame) 

1659 self.write("]") 

1660 

1661 def visit_Dict(self, node: nodes.Dict, frame: Frame) -> None: 

1662 self.write("{") 

1663 for idx, item in enumerate(node.items): 

1664 if idx: 

1665 self.write(", ") 

1666 self.visit(item.key, frame) 

1667 self.write(": ") 

1668 self.visit(item.value, frame) 

1669 self.write("}") 

1670 

1671 visit_Add = _make_binop("+") 

1672 visit_Sub = _make_binop("-") 

1673 visit_Mul = _make_binop("*") 

1674 visit_Div = _make_binop("/") 

1675 visit_FloorDiv = _make_binop("//") 

1676 visit_Pow = _make_binop("**") 

1677 visit_Mod = _make_binop("%") 

1678 visit_And = _make_binop("and") 

1679 visit_Or = _make_binop("or") 

1680 visit_Pos = _make_unop("+") 

1681 visit_Neg = _make_unop("-") 

1682 visit_Not = _make_unop("not ") 

1683 

1684 @optimizeconst 

1685 def visit_Concat(self, node: nodes.Concat, frame: Frame) -> None: 

1686 if frame.eval_ctx.volatile: 

1687 func_name = "(markup_join if context.eval_ctx.volatile else str_join)" 

1688 elif frame.eval_ctx.autoescape: 

1689 func_name = "markup_join" 

1690 else: 

1691 func_name = "str_join" 

1692 self.write(f"{func_name}((") 

1693 for arg in node.nodes: 

1694 self.visit(arg, frame) 

1695 self.write(", ") 

1696 self.write("))") 

1697 

1698 @optimizeconst 

1699 def visit_Compare(self, node: nodes.Compare, frame: Frame) -> None: 

1700 self.write("(") 

1701 self.visit(node.expr, frame) 

1702 for op in node.ops: 

1703 self.visit(op, frame) 

1704 self.write(")") 

1705 

1706 def visit_Operand(self, node: nodes.Operand, frame: Frame) -> None: 

1707 self.write(f" {operators[node.op]} ") 

1708 self.visit(node.expr, frame) 

1709 

1710 @optimizeconst 

1711 def visit_Getattr(self, node: nodes.Getattr, frame: Frame) -> None: 

1712 if self.environment.is_async: 

1713 self.write("(await auto_await(") 

1714 

1715 self.write("environment.getattr(") 

1716 self.visit(node.node, frame) 

1717 self.write(f", {node.attr!r})") 

1718 

1719 if self.environment.is_async: 

1720 self.write("))") 

1721 

1722 @optimizeconst 

1723 def visit_Getitem(self, node: nodes.Getitem, frame: Frame) -> None: 

1724 # slices bypass the environment getitem method. 

1725 if isinstance(node.arg, nodes.Slice): 

1726 self.visit(node.node, frame) 

1727 self.write("[") 

1728 self.visit(node.arg, frame) 

1729 self.write("]") 

1730 else: 

1731 if self.environment.is_async: 

1732 self.write("(await auto_await(") 

1733 

1734 self.write("environment.getitem(") 

1735 self.visit(node.node, frame) 

1736 self.write(", ") 

1737 self.visit(node.arg, frame) 

1738 self.write(")") 

1739 

1740 if self.environment.is_async: 

1741 self.write("))") 

1742 

1743 def visit_Slice(self, node: nodes.Slice, frame: Frame) -> None: 

1744 if node.start is not None: 

1745 self.visit(node.start, frame) 

1746 self.write(":") 

1747 if node.stop is not None: 

1748 self.visit(node.stop, frame) 

1749 if node.step is not None: 

1750 self.write(":") 

1751 self.visit(node.step, frame) 

1752 

1753 @contextmanager 

1754 def _filter_test_common( 

1755 self, node: t.Union[nodes.Filter, nodes.Test], frame: Frame, is_filter: bool 

1756 ) -> t.Iterator[None]: 

1757 if self.environment.is_async: 

1758 self.write("(await auto_await(") 

1759 

1760 if is_filter: 

1761 self.write(f"{self.filters[node.name]}(") 

1762 func = self.environment.filters.get(node.name) 

1763 else: 

1764 self.write(f"{self.tests[node.name]}(") 

1765 func = self.environment.tests.get(node.name) 

1766 

1767 # When inside an If or CondExpr frame, allow the filter to be 

1768 # undefined at compile time and only raise an error if it's 

1769 # actually called at runtime. See pull_dependencies. 

1770 if func is None and not frame.soft_frame: 

1771 type_name = "filter" if is_filter else "test" 

1772 self.fail(f"No {type_name} named {node.name!r}.", node.lineno) 

1773 

1774 pass_arg = { 

1775 _PassArg.context: "context", 

1776 _PassArg.eval_context: "context.eval_ctx", 

1777 _PassArg.environment: "environment", 

1778 }.get( 

1779 _PassArg.from_obj(func) # type: ignore 

1780 ) 

1781 

1782 if pass_arg is not None: 

1783 self.write(f"{pass_arg}, ") 

1784 

1785 # Back to the visitor function to handle visiting the target of 

1786 # the filter or test. 

1787 yield 

1788 

1789 self.signature(node, frame) 

1790 self.write(")") 

1791 

1792 if self.environment.is_async: 

1793 self.write("))") 

1794 

1795 @optimizeconst 

1796 def visit_Filter(self, node: nodes.Filter, frame: Frame) -> None: 

1797 with self._filter_test_common(node, frame, True): 

1798 # if the filter node is None we are inside a filter block 

1799 # and want to write to the current buffer 

1800 if node.node is not None: 

1801 self.visit(node.node, frame) 

1802 elif frame.eval_ctx.volatile: 

1803 self.write( 

1804 f"(Markup(concat({frame.buffer}))" 

1805 f" if context.eval_ctx.autoescape else concat({frame.buffer}))" 

1806 ) 

1807 elif frame.eval_ctx.autoescape: 

1808 self.write(f"Markup(concat({frame.buffer}))") 

1809 else: 

1810 self.write(f"concat({frame.buffer})") 

1811 

1812 @optimizeconst 

1813 def visit_Test(self, node: nodes.Test, frame: Frame) -> None: 

1814 with self._filter_test_common(node, frame, False): 

1815 self.visit(node.node, frame) 

1816 

1817 @optimizeconst 

1818 def visit_CondExpr(self, node: nodes.CondExpr, frame: Frame) -> None: 

1819 frame = frame.soft() 

1820 

1821 def write_expr2() -> None: 

1822 if node.expr2 is not None: 

1823 self.visit(node.expr2, frame) 

1824 return 

1825 

1826 self.write( 

1827 f'cond_expr_undefined("the inline if-expression on' 

1828 f" {self.position(node)} evaluated to false and no else" 

1829 f' section was defined.")' 

1830 ) 

1831 

1832 self.write("(") 

1833 self.visit(node.expr1, frame) 

1834 self.write(" if ") 

1835 self.visit(node.test, frame) 

1836 self.write(" else ") 

1837 write_expr2() 

1838 self.write(")") 

1839 

1840 @optimizeconst 

1841 def visit_Call( 

1842 self, node: nodes.Call, frame: Frame, forward_caller: bool = False 

1843 ) -> None: 

1844 if self.environment.is_async: 

1845 self.write("(await auto_await(") 

1846 if self.environment.sandboxed: 

1847 self.write("environment.call(context, ") 

1848 else: 

1849 self.write("context.call(") 

1850 self.visit(node.node, frame) 

1851 extra_kwargs = {"caller": "caller"} if forward_caller else None 

1852 loop_kwargs = {"_loop_vars": "_loop_vars"} if frame.loop_frame else {} 

1853 block_kwargs = {"_block_vars": "_block_vars"} if frame.block_frame else {} 

1854 if extra_kwargs: 

1855 extra_kwargs.update(loop_kwargs, **block_kwargs) 

1856 elif loop_kwargs or block_kwargs: 

1857 extra_kwargs = dict(loop_kwargs, **block_kwargs) 

1858 self.signature(node, frame, extra_kwargs) 

1859 self.write(")") 

1860 if self.environment.is_async: 

1861 self.write("))") 

1862 

1863 def visit_Keyword(self, node: nodes.Keyword, frame: Frame) -> None: 

1864 self.write(node.key + "=") 

1865 self.visit(node.value, frame) 

1866 

1867 # -- Unused nodes for extensions 

1868 

1869 def visit_MarkSafe(self, node: nodes.MarkSafe, frame: Frame) -> None: 

1870 self.write("Markup(") 

1871 self.visit(node.expr, frame) 

1872 self.write(")") 

1873 

1874 def visit_MarkSafeIfAutoescape( 

1875 self, node: nodes.MarkSafeIfAutoescape, frame: Frame 

1876 ) -> None: 

1877 self.write("(Markup if context.eval_ctx.autoescape else identity)(") 

1878 self.visit(node.expr, frame) 

1879 self.write(")") 

1880 

1881 def visit_EnvironmentAttribute( 

1882 self, node: nodes.EnvironmentAttribute, frame: Frame 

1883 ) -> None: 

1884 self.write("environment." + node.name) 

1885 

1886 def visit_ExtensionAttribute( 

1887 self, node: nodes.ExtensionAttribute, frame: Frame 

1888 ) -> None: 

1889 self.write(f"environment.extensions[{node.identifier!r}].{node.name}") 

1890 

1891 def visit_ImportedName(self, node: nodes.ImportedName, frame: Frame) -> None: 

1892 self.write(self.import_aliases[node.importname]) 

1893 

1894 def visit_InternalName(self, node: nodes.InternalName, frame: Frame) -> None: 

1895 self.write(node.name) 

1896 

1897 def visit_ContextReference( 

1898 self, node: nodes.ContextReference, frame: Frame 

1899 ) -> None: 

1900 self.write("context") 

1901 

1902 def visit_DerivedContextReference( 

1903 self, node: nodes.DerivedContextReference, frame: Frame 

1904 ) -> None: 

1905 self.write(self.derive_context(frame)) 

1906 

1907 def visit_Continue(self, node: nodes.Continue, frame: Frame) -> None: 

1908 self.writeline("continue", node) 

1909 

1910 def visit_Break(self, node: nodes.Break, frame: Frame) -> None: 

1911 self.writeline("break", node) 

1912 

1913 def visit_Scope(self, node: nodes.Scope, frame: Frame) -> None: 

1914 scope_frame = frame.inner() 

1915 scope_frame.symbols.analyze_node(node) 

1916 self.enter_frame(scope_frame) 

1917 self.blockvisit(node.body, scope_frame) 

1918 self.leave_frame(scope_frame) 

1919 

1920 def visit_OverlayScope(self, node: nodes.OverlayScope, frame: Frame) -> None: 

1921 ctx = self.temporary_identifier() 

1922 self.writeline(f"{ctx} = {self.derive_context(frame)}") 

1923 self.writeline(f"{ctx}.vars = ") 

1924 self.visit(node.context, frame) 

1925 self.push_context_reference(ctx) 

1926 

1927 scope_frame = frame.inner(isolated=True) 

1928 scope_frame.symbols.analyze_node(node) 

1929 self.enter_frame(scope_frame) 

1930 self.blockvisit(node.body, scope_frame) 

1931 self.leave_frame(scope_frame) 

1932 self.pop_context_reference() 

1933 

1934 def visit_EvalContextModifier( 

1935 self, node: nodes.EvalContextModifier, frame: Frame 

1936 ) -> None: 

1937 for keyword in node.options: 

1938 self.writeline(f"context.eval_ctx.{keyword.key} = ") 

1939 self.visit(keyword.value, frame) 

1940 try: 

1941 val = keyword.value.as_const(frame.eval_ctx) 

1942 except nodes.Impossible: 

1943 frame.eval_ctx.volatile = True 

1944 else: 

1945 setattr(frame.eval_ctx, keyword.key, val) 

1946 

1947 def visit_ScopedEvalContextModifier( 

1948 self, node: nodes.ScopedEvalContextModifier, frame: Frame 

1949 ) -> None: 

1950 old_ctx_name = self.temporary_identifier() 

1951 saved_ctx = frame.eval_ctx.save() 

1952 self.writeline(f"{old_ctx_name} = context.eval_ctx.save()") 

1953 self.visit_EvalContextModifier(node, frame) 

1954 for child in node.body: 

1955 self.visit(child, frame) 

1956 frame.eval_ctx.revert(saved_ctx) 

1957 self.writeline(f"context.eval_ctx.revert({old_ctx_name})")