Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/jinja2/compiler.py: 85%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1200 statements  

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

2 

3import typing as t 

4from contextlib import contextmanager 

5from functools import update_wrapper 

6from io import StringIO 

7from itertools import chain 

8from keyword import iskeyword as is_python_keyword 

9 

10from markupsafe import escape 

11from markupsafe import Markup 

12 

13from . import nodes 

14from .exceptions import TemplateAssertionError 

15from .idtracking import Symbols 

16from .idtracking import VAR_LOAD_ALIAS 

17from .idtracking import VAR_LOAD_PARAMETER 

18from .idtracking import VAR_LOAD_RESOLVE 

19from .idtracking import VAR_LOAD_UNDEFINED 

20from .nodes import EvalContext 

21from .optimizer import Optimizer 

22from .utils import _PassArg 

23from .utils import concat 

24from .visitor import NodeVisitor 

25 

26if t.TYPE_CHECKING: 

27 import typing_extensions as te 

28 

29 from .environment import Environment 

30 

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

32 

33operators = { 

34 "eq": "==", 

35 "ne": "!=", 

36 "gt": ">", 

37 "gteq": ">=", 

38 "lt": "<", 

39 "lteq": "<=", 

40 "in": "in", 

41 "notin": "not in", 

42} 

43 

44 

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

46 def new_func( 

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

48 ) -> t.Any: 

49 # Only optimize if the frame is not volatile 

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

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

52 

53 if new_node != node: 

54 return self.visit(new_node, frame) 

55 

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

57 

58 return update_wrapper(new_func, f) # type: ignore[return-value] 

59 

60 

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

62 @optimizeconst 

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

64 if ( 

65 self.environment.sandboxed and op in self.environment.intercepted_binops # type: ignore 

66 ): 

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

68 self.visit(node.left, frame) 

69 self.write(", ") 

70 self.visit(node.right, frame) 

71 else: 

72 self.write("(") 

73 self.visit(node.left, frame) 

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

75 self.visit(node.right, frame) 

76 

77 self.write(")") 

78 

79 return visitor 

80 

81 

82def _make_unop( 

83 op: str, 

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

85 @optimizeconst 

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

87 if ( 

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

89 ): 

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

91 self.visit(node.node, frame) 

92 else: 

93 self.write("(" + op) 

94 self.visit(node.node, frame) 

95 

96 self.write(")") 

97 

98 return visitor 

99 

100 

101def generate( 

102 node: nodes.Template, 

103 environment: "Environment", 

104 name: str | None, 

105 filename: str | None, 

106 stream: t.TextIO | None = None, 

107 defer_init: bool = False, 

108 optimized: bool = True, 

109) -> str | None: 

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

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

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

113 

114 generator = environment.code_generator_class( 

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

116 ) 

117 generator.visit(node) 

118 

119 if stream is None: 

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

121 

122 return None 

123 

124 

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

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

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

128 return True 

129 

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

131 return True 

132 

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

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

135 

136 if type(value) is dict: # noqa E721 

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

138 

139 return False 

140 

141 

142def find_undeclared(nodes: t.Iterable[nodes.Node], names: t.Iterable[str]) -> set[str]: 

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

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

145 """ 

146 visitor = UndeclaredNameVisitor(names) 

147 try: 

148 for node in nodes: 

149 visitor.visit(node) 

150 except VisitorExit: 

151 pass 

152 return visitor.undeclared 

153 

154 

155class MacroRef: 

156 def __init__(self, node: nodes.Macro | nodes.CallBlock) -> None: 

157 self.node = node 

158 self.accesses_caller = False 

159 self.accesses_kwargs = False 

160 self.accesses_varargs = False 

161 

162 

163class Frame: 

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

165 

166 def __init__( 

167 self, 

168 eval_ctx: EvalContext, 

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

170 level: int | None = None, 

171 ) -> None: 

172 self.eval_ctx = eval_ctx 

173 

174 # the parent of this frame 

175 self.parent = parent 

176 

177 if parent is None: 

178 self.symbols = Symbols(level=level) 

179 

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

181 # write tests around output statements. 

182 self.require_output_check = False 

183 

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

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

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

187 # buffer. 

188 self.buffer: str | None = None 

189 

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

191 self.block: str | None = None 

192 

193 else: 

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

195 self.require_output_check = parent.require_output_check 

196 self.buffer = parent.buffer 

197 self.block = parent.block 

198 

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

200 self.toplevel = False 

201 

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

203 # conditions. This information is used to optimize inheritance 

204 # situations. 

205 self.rootlevel = False 

206 

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

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

209 self.loop_frame = False 

210 self.block_frame = False 

211 

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

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

214 # or compile time. 

215 self.soft_frame = False 

216 

217 def copy(self) -> "te.Self": 

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

219 rv = object.__new__(self.__class__) 

220 rv.__dict__.update(self.__dict__) 

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

222 return rv 

223 

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

225 """Return an inner frame.""" 

226 if isolated: 

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

228 return Frame(self.eval_ctx, self) 

229 

230 def soft(self) -> "te.Self": 

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

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

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

234 

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

236 expressions. 

237 """ 

238 rv = self.copy() 

239 rv.rootlevel = False 

240 rv.soft_frame = True 

241 return rv 

242 

243 __copy__ = copy 

244 

245 

246class VisitorExit(RuntimeError): 

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

248 

249 

250class DependencyFinderVisitor(NodeVisitor): 

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

252 

253 def __init__(self) -> None: 

254 self.filters: set[str] = set() 

255 self.tests: set[str] = set() 

256 

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

258 self.generic_visit(node) 

259 self.filters.add(node.name) 

260 

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

262 self.generic_visit(node) 

263 self.tests.add(node.name) 

264 

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

266 """Stop visiting at blocks.""" 

267 

268 

269class UndeclaredNameVisitor(NodeVisitor): 

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

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

272 not stop at closure frames. 

273 """ 

274 

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

276 self.names = set(names) 

277 self.undeclared: set[str] = set() 

278 

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

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

281 self.undeclared.add(node.name) 

282 if self.undeclared == self.names: 

283 raise VisitorExit() 

284 else: 

285 self.names.discard(node.name) 

286 

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

288 """Stop visiting a blocks.""" 

289 

290 

291class CompilerExit(Exception): 

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

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

294 raises such an exception is not further processed. 

295 """ 

296 

297 

298class CodeGenerator(NodeVisitor): 

299 def __init__( 

300 self, 

301 environment: "Environment", 

302 name: str | None, 

303 filename: str | None, 

304 stream: t.TextIO | None = None, 

305 defer_init: bool = False, 

306 optimized: bool = True, 

307 ) -> None: 

308 if stream is None: 

309 stream = StringIO() 

310 self.environment = environment 

311 self.name = name 

312 self.filename = filename 

313 self.stream = stream 

314 self.created_block_context = False 

315 self.defer_init = defer_init 

316 self.optimizer: Optimizer | None = None 

317 

318 if optimized: 

319 self.optimizer = Optimizer(environment) 

320 

321 # aliases for imports 

322 self.import_aliases: dict[str, str] = {} 

323 

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

325 # into the global python scope they are registered here 

326 self.blocks: dict[str, nodes.Block] = {} 

327 

328 # the number of extends statements so far 

329 self.extends_so_far = 0 

330 

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

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

333 # more optimizations. 

334 self.has_known_extends = False 

335 

336 # the current line number 

337 self.code_lineno = 1 

338 

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

340 self.tests: dict[str, str] = {} 

341 self.filters: dict[str, str] = {} 

342 

343 # the debug information 

344 self.debug_info: list[tuple[int, int]] = [] 

345 self._write_debug_info: int | None = None 

346 

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

348 self._new_lines = 0 

349 

350 # the line number of the last written statement 

351 self._last_line = 0 

352 

353 # true if nothing was written so far. 

354 self._first_write = True 

355 

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

357 # unique, temporary identifier 

358 self._last_identifier = 0 

359 

360 # the current indentation 

361 self._indentation = 0 

362 

363 # Tracks toplevel assignments 

364 self._assign_stack: list[set[str]] = [] 

365 

366 # Tracks parameter definition blocks 

367 self._param_def_block: list[set[str]] = [] 

368 

369 # Tracks the current context. 

370 self._context_reference_stack = ["context"] 

371 

372 @property 

373 def optimized(self) -> bool: 

374 return self.optimizer is not None 

375 

376 # -- Various compilation helpers 

377 

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

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

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

381 

382 def temporary_identifier(self) -> str: 

383 """Get a new unique identifier.""" 

384 self._last_identifier += 1 

385 return f"t_{self._last_identifier}" 

386 

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

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

389 frame.buffer = self.temporary_identifier() 

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

391 

392 def return_buffer_contents( 

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

394 ) -> None: 

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

396 if not force_unescaped: 

397 if frame.eval_ctx.volatile: 

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

399 self.indent() 

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

401 self.outdent() 

402 self.writeline("else:") 

403 self.indent() 

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

405 self.outdent() 

406 return 

407 elif frame.eval_ctx.autoescape: 

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

409 return 

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

411 

412 def indent(self) -> None: 

413 """Indent by one.""" 

414 self._indentation += 1 

415 

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

417 """Outdent by step.""" 

418 self._indentation -= step 

419 

420 def start_write(self, frame: Frame, node: nodes.Node | None = None) -> None: 

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

422 if frame.buffer is None: 

423 self.writeline("yield ", node) 

424 else: 

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

426 

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

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

429 if frame.buffer is not None: 

430 self.write(")") 

431 

432 def simple_write( 

433 self, s: str, frame: Frame, node: nodes.Node | None = None 

434 ) -> None: 

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

436 self.start_write(frame, node) 

437 self.write(s) 

438 self.end_write(frame) 

439 

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

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

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

443 """ 

444 try: 

445 self.writeline("pass") 

446 for node in nodes: 

447 self.visit(node, frame) 

448 except CompilerExit: 

449 pass 

450 

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

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

453 if self._new_lines: 

454 if not self._first_write: 

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

456 self.code_lineno += self._new_lines 

457 if self._write_debug_info is not None: 

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

459 self._write_debug_info = None 

460 self._first_write = False 

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

462 self._new_lines = 0 

463 self.stream.write(x) 

464 

465 def writeline(self, x: str, node: nodes.Node | None = None, extra: int = 0) -> None: 

466 """Combination of newline and write.""" 

467 self.newline(node, extra) 

468 self.write(x) 

469 

470 def newline(self, node: nodes.Node | None = None, extra: int = 0) -> None: 

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

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

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

474 self._write_debug_info = node.lineno 

475 self._last_line = node.lineno 

476 

477 def signature( 

478 self, 

479 node: nodes.Call | nodes.Filter | nodes.Test, 

480 frame: Frame, 

481 extra_kwargs: t.Mapping[str, t.Any] | None = None, 

482 ) -> None: 

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

484 A leading comma is added automatically. The extra keyword 

485 arguments may not include python keywords otherwise a syntax 

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

487 as python dict. 

488 """ 

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

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

491 kwarg_workaround = any( 

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

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

494 ) 

495 

496 for arg in node.args: 

497 self.write(", ") 

498 self.visit(arg, frame) 

499 

500 if not kwarg_workaround: 

501 for kwarg in node.kwargs: 

502 self.write(", ") 

503 self.visit(kwarg, frame) 

504 if extra_kwargs is not None: 

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

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

507 if node.dyn_args: 

508 self.write(", *") 

509 self.visit(node.dyn_args, frame) 

510 

511 if kwarg_workaround: 

512 if node.dyn_kwargs is not None: 

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

514 else: 

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

516 for kwarg in node.kwargs: 

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

518 self.visit(kwarg.value, frame) 

519 self.write(", ") 

520 if extra_kwargs is not None: 

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

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

523 if node.dyn_kwargs is not None: 

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

525 self.visit(node.dyn_kwargs, frame) 

526 self.write(")") 

527 else: 

528 self.write("}") 

529 

530 elif node.dyn_kwargs is not None: 

531 self.write(", **") 

532 self.visit(node.dyn_kwargs, frame) 

533 

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

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

536 assign them to variables in the compiled namespace. Checking 

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

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

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

540 

541 .. versionchanged:: 3.0 

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

543 runtime instead of compile time. 

544 """ 

545 visitor = DependencyFinderVisitor() 

546 

547 for node in nodes: 

548 visitor.visit(node) 

549 

550 for id_map, names, dependency in ( 

551 (self.filters, visitor.filters, "filters"), 

552 ( 

553 self.tests, 

554 visitor.tests, 

555 "tests", 

556 ), 

557 ): 

558 for name in sorted(names): 

559 if name not in id_map: 

560 id_map[name] = self.temporary_identifier() 

561 

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

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

564 self.writeline("try:") 

565 self.indent() 

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

567 self.outdent() 

568 self.writeline("except KeyError:") 

569 self.indent() 

570 self.writeline("@internalcode") 

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

572 self.indent() 

573 self.writeline( 

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

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

576 ) 

577 self.outdent() 

578 self.outdent() 

579 

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

581 undefs = [] 

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

583 if action == VAR_LOAD_PARAMETER: 

584 pass 

585 elif action == VAR_LOAD_RESOLVE: 

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

587 elif action == VAR_LOAD_ALIAS: 

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

589 elif action == VAR_LOAD_UNDEFINED: 

590 undefs.append(target) 

591 else: 

592 raise NotImplementedError("unknown load instruction") 

593 if undefs: 

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

595 

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

597 if not with_python_scope: 

598 undefs = [] 

599 for target in frame.symbols.loads: 

600 undefs.append(target) 

601 if undefs: 

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

603 

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

605 return async_value if self.environment.is_async else sync_value 

606 

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

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

609 

610 def macro_body( 

611 self, node: nodes.Macro | nodes.CallBlock, frame: Frame 

612 ) -> tuple[Frame, MacroRef]: 

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

614 frame = frame.inner() 

615 frame.symbols.analyze_node(node) 

616 macro_ref = MacroRef(node) 

617 

618 explicit_caller = None 

619 skip_special_params = set() 

620 args = [] 

621 

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

623 if arg.name == "caller": 

624 explicit_caller = idx 

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

626 skip_special_params.add(arg.name) 

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

628 

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

630 

631 if "caller" in undeclared: 

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

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

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

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

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

637 # the argument list. 

638 if explicit_caller is not None: 

639 try: 

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

641 except IndexError: 

642 self.fail( 

643 "When defining macros or call blocks the " 

644 'special "caller" argument must be omitted ' 

645 "or be given a default.", 

646 node.lineno, 

647 ) 

648 else: 

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

650 macro_ref.accesses_caller = True 

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

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

653 macro_ref.accesses_kwargs = True 

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

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

656 macro_ref.accesses_varargs = True 

657 

658 # macros are delayed, they never require output checks 

659 frame.require_output_check = False 

660 frame.symbols.analyze_node(node) 

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

662 self.indent() 

663 

664 self.buffer(frame) 

665 self.enter_frame(frame) 

666 

667 self.push_parameter_definitions(frame) 

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

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

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

671 self.indent() 

672 try: 

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

674 except IndexError: 

675 self.writeline( 

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

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

678 ) 

679 else: 

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

681 self.visit(default, frame) 

682 self.mark_parameter_stored(ref) 

683 self.outdent() 

684 self.pop_parameter_definitions() 

685 

686 self.blockvisit(node.body, frame) 

687 self.return_buffer_contents(frame, force_unescaped=True) 

688 self.leave_frame(frame, with_python_scope=True) 

689 self.outdent() 

690 

691 return frame, macro_ref 

692 

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

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

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

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

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

698 arg_tuple += "," 

699 self.write( 

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

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

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

703 ) 

704 

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

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

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

708 if self.name is not None: 

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

710 return rv 

711 

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

713 items_kv = ", ".join( 

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

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

716 ) 

717 return f"{{{items_kv}}}" 

718 

719 def write_commons(self) -> None: 

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

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

722 through a dead branch. 

723 """ 

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

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

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

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

728 # conditional expressions 

729 self.writeline("cond_expr_undefined = Undefined") 

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

731 

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

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

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

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

736 undefined expressions for parameters in macros as macros can reference 

737 otherwise unbound parameters. 

738 """ 

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

740 

741 def pop_parameter_definitions(self) -> None: 

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

743 self._param_def_block.pop() 

744 

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

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

747 This will skip the enforced undefined checks. 

748 """ 

749 if self._param_def_block: 

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

751 

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

753 self._context_reference_stack.append(target) 

754 

755 def pop_context_reference(self) -> None: 

756 self._context_reference_stack.pop() 

757 

758 def get_context_ref(self) -> str: 

759 return self._context_reference_stack[-1] 

760 

761 def get_resolve_func(self) -> str: 

762 target = self._context_reference_stack[-1] 

763 if target == "context": 

764 return "resolve" 

765 return f"{target}.resolve" 

766 

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

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

769 

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

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

772 if not self._param_def_block: 

773 return False 

774 return target in self._param_def_block[-1] 

775 

776 def push_assign_tracking(self) -> None: 

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

778 self._assign_stack.append(set()) 

779 

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

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

782 context variables if necessary. 

783 """ 

784 vars = self._assign_stack.pop() 

785 if ( 

786 not frame.block_frame 

787 and not frame.loop_frame 

788 and not frame.toplevel 

789 or not vars 

790 ): 

791 return 

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

793 if len(vars) == 1: 

794 name = next(iter(vars)) 

795 ref = frame.symbols.ref(name) 

796 if frame.loop_frame: 

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

798 return 

799 if frame.block_frame: 

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

801 return 

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

803 else: 

804 if frame.loop_frame: 

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

806 elif frame.block_frame: 

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

808 else: 

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

810 for idx, name in enumerate(sorted(vars)): 

811 if idx: 

812 self.write(", ") 

813 ref = frame.symbols.ref(name) 

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

815 self.write("})") 

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

817 if len(public_names) == 1: 

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

819 else: 

820 names_str = ", ".join(map(repr, sorted(public_names))) 

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

822 

823 # -- Statement Visitors 

824 

825 def visit_Template(self, node: nodes.Template, frame: Frame | None = None) -> None: 

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

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

828 

829 from .runtime import async_exported 

830 from .runtime import exported 

831 

832 if self.environment.is_async: 

833 exported_names = sorted(exported + async_exported) 

834 else: 

835 exported_names = sorted(exported) 

836 

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

838 

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

840 # environment into a local name 

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

842 

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

844 # overhead by just not processing any inheritance code. 

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

846 

847 # find all blocks 

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

849 if block.name in self.blocks: 

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

851 self.blocks[block.name] = block 

852 

853 # find all imports and import them 

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

855 if import_.importname not in self.import_aliases: 

856 imp = import_.importname 

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

858 if "." in imp: 

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

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

861 else: 

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

863 

864 # add the load name 

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

866 

867 # generate the root render function. 

868 self.writeline( 

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

870 ) 

871 self.indent() 

872 self.write_commons() 

873 

874 # process the root 

875 frame = Frame(eval_ctx) 

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

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

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

879 frame.symbols.analyze_node(node) 

880 frame.toplevel = frame.rootlevel = True 

881 frame.require_output_check = have_extends and not self.has_known_extends 

882 if have_extends: 

883 self.writeline("parent_template = None") 

884 self.enter_frame(frame) 

885 self.pull_dependencies(node.body) 

886 self.blockvisit(node.body, frame) 

887 self.leave_frame(frame, with_python_scope=True) 

888 self.outdent() 

889 

890 # make sure that the parent root is called. 

891 if have_extends: 

892 if not self.has_known_extends: 

893 self.indent() 

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

895 self.indent() 

896 if not self.environment.is_async: 

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

898 else: 

899 self.writeline("agen = parent_template.root_render_func(context)") 

900 self.writeline("try:") 

901 self.indent() 

902 self.writeline("async for event in agen:") 

903 self.indent() 

904 self.writeline("yield event") 

905 self.outdent() 

906 self.outdent() 

907 self.writeline("finally: await agen.aclose()") 

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

909 

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

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

912 self.writeline( 

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

914 block, 

915 1, 

916 ) 

917 self.indent() 

918 self.write_commons() 

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

920 # toplevel template. This would cause a variety of 

921 # interesting issues with identifier tracking. 

922 block_frame = Frame(eval_ctx) 

923 block_frame.block_frame = True 

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

925 if "self" in undeclared: 

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

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

928 if "super" in undeclared: 

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

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

931 block_frame.symbols.analyze_node(block) 

932 block_frame.block = name 

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

934 self.enter_frame(block_frame) 

935 self.pull_dependencies(block.body) 

936 self.blockvisit(block.body, block_frame) 

937 self.leave_frame(block_frame, with_python_scope=True) 

938 self.outdent() 

939 

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

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

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

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

944 

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

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

947 level = 0 

948 if frame.toplevel: 

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

950 # check if we are one 

951 if self.has_known_extends: 

952 return 

953 if self.extends_so_far > 0: 

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

955 self.indent() 

956 level += 1 

957 

958 if node.scoped: 

959 context = self.derive_context(frame) 

960 else: 

961 context = self.get_context_ref() 

962 

963 if node.required: 

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

965 self.indent() 

966 self.writeline( 

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

968 node, 

969 ) 

970 self.outdent() 

971 

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

973 self.writeline( 

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

975 ) 

976 else: 

977 self.writeline(f"gen = context.blocks[{node.name!r}][0]({context})") 

978 self.writeline("try:") 

979 self.indent() 

980 self.writeline( 

981 f"{self.choose_async()}for event in gen:", 

982 node, 

983 ) 

984 self.indent() 

985 self.simple_write("event", frame) 

986 self.outdent() 

987 self.outdent() 

988 self.writeline( 

989 f"finally: {self.choose_async('await gen.aclose()', 'gen.close()')}" 

990 ) 

991 

992 self.outdent(level) 

993 

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

995 """Calls the extender.""" 

996 if not frame.toplevel: 

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

998 

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

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

1001 # the template before this one. 

1002 if self.extends_so_far > 0: 

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

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

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

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

1007 if not self.has_known_extends: 

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

1009 self.indent() 

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

1011 

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

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

1014 if self.has_known_extends: 

1015 raise CompilerExit() 

1016 else: 

1017 self.outdent() 

1018 

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

1020 self.visit(node.template, frame) 

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

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

1023 self.indent() 

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

1025 self.outdent() 

1026 

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

1028 # advantage of that information and simplify the generated code 

1029 # in the top level from this point onwards 

1030 if frame.rootlevel: 

1031 self.has_known_extends = True 

1032 

1033 # and now we have one more 

1034 self.extends_so_far += 1 

1035 

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

1037 """Handles includes.""" 

1038 if node.ignore_missing: 

1039 self.writeline("try:") 

1040 self.indent() 

1041 

1042 func_name = "get_or_select_template" 

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

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

1045 func_name = "get_template" 

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

1047 func_name = "select_template" 

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

1049 func_name = "select_template" 

1050 

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

1052 self.visit(node.template, frame) 

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

1054 if node.ignore_missing: 

1055 self.outdent() 

1056 self.writeline("except TemplateNotFound:") 

1057 self.indent() 

1058 self.writeline("pass") 

1059 self.outdent() 

1060 self.writeline("else:") 

1061 self.indent() 

1062 

1063 def loop_body() -> None: 

1064 self.indent() 

1065 self.simple_write("event", frame) 

1066 self.outdent() 

1067 

1068 if node.with_context: 

1069 self.writeline( 

1070 f"gen = template.root_render_func(" 

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

1072 f" {self.dump_local_context(frame)}))" 

1073 ) 

1074 self.writeline("try:") 

1075 self.indent() 

1076 self.writeline(f"{self.choose_async()}for event in gen:") 

1077 loop_body() 

1078 self.outdent() 

1079 self.writeline( 

1080 f"finally: {self.choose_async('await gen.aclose()', 'gen.close()')}" 

1081 ) 

1082 elif self.environment.is_async: 

1083 self.writeline( 

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

1085 "._body_stream:" 

1086 ) 

1087 loop_body() 

1088 else: 

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

1090 

1091 if node.ignore_missing: 

1092 self.outdent() 

1093 

1094 def _import_common( 

1095 self, node: nodes.Import | nodes.FromImport, frame: Frame 

1096 ) -> None: 

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

1098 self.visit(node.template, frame) 

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

1100 

1101 if node.with_context: 

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

1103 self.write( 

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

1105 ) 

1106 else: 

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

1108 

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

1110 """Visit regular imports.""" 

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

1112 if frame.toplevel: 

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

1114 

1115 self._import_common(node, frame) 

1116 

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

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

1119 

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

1121 """Visit named imports.""" 

1122 self.newline(node) 

1123 self.write("included_template = ") 

1124 self._import_common(node, frame) 

1125 var_names = [] 

1126 discarded_names = [] 

1127 for name in node.names: 

1128 if isinstance(name, tuple): 

1129 name, alias = name 

1130 else: 

1131 alias = name 

1132 self.writeline( 

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

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

1135 ) 

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

1137 self.indent() 

1138 # The position will contain the template name, and will be formatted 

1139 # into a string that will be compiled into an f-string. Curly braces 

1140 # in the name must be replaced with escapes so that they will not be 

1141 # executed as part of the f-string. 

1142 position = self.position(node).replace("{", "{{").replace("}", "}}") 

1143 message = ( 

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

1145 f" (imported on {position})" 

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

1147 ) 

1148 self.writeline( 

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

1150 ) 

1151 self.outdent() 

1152 if frame.toplevel: 

1153 var_names.append(alias) 

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

1155 discarded_names.append(alias) 

1156 

1157 if var_names: 

1158 if len(var_names) == 1: 

1159 name = var_names[0] 

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

1161 else: 

1162 names_kv = ", ".join( 

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

1164 ) 

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

1166 if discarded_names: 

1167 if len(discarded_names) == 1: 

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

1169 else: 

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

1171 self.writeline( 

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

1173 ) 

1174 

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

1176 loop_frame = frame.inner() 

1177 loop_frame.loop_frame = True 

1178 test_frame = frame.inner() 

1179 else_frame = frame.inner() 

1180 

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

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

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

1184 extended_loop = ( 

1185 node.recursive 

1186 or "loop" 

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

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

1189 ) 

1190 

1191 loop_ref = None 

1192 if extended_loop: 

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

1194 

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

1196 if node.else_: 

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

1198 

1199 if node.test: 

1200 loop_filter_func = self.temporary_identifier() 

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

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

1203 self.indent() 

1204 self.enter_frame(test_frame) 

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

1206 self.visit(node.target, loop_frame) 

1207 self.write(" in ") 

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

1209 self.write(":") 

1210 self.indent() 

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

1212 self.visit(node.test, test_frame) 

1213 self.write(":") 

1214 self.indent() 

1215 self.writeline("yield ") 

1216 self.visit(node.target, loop_frame) 

1217 self.outdent(3) 

1218 self.leave_frame(test_frame, with_python_scope=True) 

1219 

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

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

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

1223 if node.recursive: 

1224 self.writeline( 

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

1226 ) 

1227 self.indent() 

1228 self.buffer(loop_frame) 

1229 

1230 # Use the same buffer for the else frame 

1231 else_frame.buffer = loop_frame.buffer 

1232 

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

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

1235 if extended_loop: 

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

1237 

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

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

1240 self.fail( 

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

1242 name.lineno, 

1243 ) 

1244 

1245 if node.else_: 

1246 iteration_indicator = self.temporary_identifier() 

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

1248 

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

1250 self.visit(node.target, loop_frame) 

1251 if extended_loop: 

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

1253 else: 

1254 self.write(" in ") 

1255 

1256 if node.test: 

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

1258 if node.recursive: 

1259 self.write("reciter") 

1260 else: 

1261 if self.environment.is_async and not extended_loop: 

1262 self.write("auto_aiter(") 

1263 self.visit(node.iter, frame) 

1264 if self.environment.is_async and not extended_loop: 

1265 self.write(")") 

1266 if node.test: 

1267 self.write(")") 

1268 

1269 if node.recursive: 

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

1271 else: 

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

1273 

1274 self.indent() 

1275 self.enter_frame(loop_frame) 

1276 

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

1278 self.blockvisit(node.body, loop_frame) 

1279 if node.else_: 

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

1281 self.outdent() 

1282 self.leave_frame( 

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

1284 ) 

1285 

1286 if node.else_: 

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

1288 self.indent() 

1289 self.enter_frame(else_frame) 

1290 self.blockvisit(node.else_, else_frame) 

1291 self.leave_frame(else_frame) 

1292 self.outdent() 

1293 

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

1295 # and start the iteration code 

1296 if node.recursive: 

1297 self.return_buffer_contents(loop_frame) 

1298 self.outdent() 

1299 self.start_write(frame, node) 

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

1301 if self.environment.is_async: 

1302 self.write("auto_aiter(") 

1303 self.visit(node.iter, frame) 

1304 if self.environment.is_async: 

1305 self.write(")") 

1306 self.write(", loop)") 

1307 self.end_write(frame) 

1308 

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

1310 # loop from the top level 

1311 if self._assign_stack: 

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

1313 

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

1315 if_frame = frame.soft() 

1316 self.writeline("if ", node) 

1317 self.visit(node.test, if_frame) 

1318 self.write(":") 

1319 self.indent() 

1320 self.blockvisit(node.body, if_frame) 

1321 self.outdent() 

1322 for elif_ in node.elif_: 

1323 self.writeline("elif ", elif_) 

1324 self.visit(elif_.test, if_frame) 

1325 self.write(":") 

1326 self.indent() 

1327 self.blockvisit(elif_.body, if_frame) 

1328 self.outdent() 

1329 if node.else_: 

1330 self.writeline("else:") 

1331 self.indent() 

1332 self.blockvisit(node.else_, if_frame) 

1333 self.outdent() 

1334 

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

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

1337 self.newline() 

1338 if frame.toplevel: 

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

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

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

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

1343 self.macro_def(macro_ref, macro_frame) 

1344 

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

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

1347 self.writeline("caller = ") 

1348 self.macro_def(macro_ref, call_frame) 

1349 self.start_write(frame, node) 

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

1351 self.end_write(frame) 

1352 

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

1354 filter_frame = frame.inner() 

1355 filter_frame.symbols.analyze_node(node) 

1356 self.enter_frame(filter_frame) 

1357 self.buffer(filter_frame) 

1358 self.blockvisit(node.body, filter_frame) 

1359 self.start_write(frame, node) 

1360 self.visit_Filter(node.filter, filter_frame) 

1361 self.end_write(frame) 

1362 self.leave_frame(filter_frame) 

1363 

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

1365 with_frame = frame.inner() 

1366 with_frame.symbols.analyze_node(node) 

1367 self.enter_frame(with_frame) 

1368 for target, expr in zip(node.targets, node.values, strict=False): 

1369 self.newline() 

1370 self.visit(target, with_frame) 

1371 self.write(" = ") 

1372 self.visit(expr, frame) 

1373 self.blockvisit(node.body, with_frame) 

1374 self.leave_frame(with_frame) 

1375 

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

1377 self.newline(node) 

1378 self.visit(node.node, frame) 

1379 

1380 class _FinalizeInfo(t.NamedTuple): 

1381 const: t.Callable[..., str] | None 

1382 src: str | None 

1383 

1384 @staticmethod 

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

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

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

1388 called on that function's output for constants. 

1389 """ 

1390 return str(value) 

1391 

1392 _finalize: _FinalizeInfo | None = None 

1393 

1394 def _make_finalize(self) -> _FinalizeInfo: 

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

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

1397 

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

1399 

1400 ``const`` 

1401 A function to finalize constant data at compile time. 

1402 

1403 ``src`` 

1404 Source code to output around nodes to be evaluated at 

1405 runtime. 

1406 """ 

1407 if self._finalize is not None: 

1408 return self._finalize 

1409 

1410 finalize: t.Callable[..., t.Any] | None 

1411 finalize = default = self._default_finalize 

1412 src = None 

1413 

1414 if self.environment.finalize: 

1415 src = "environment.finalize(" 

1416 env_finalize = self.environment.finalize 

1417 pass_arg = { 

1418 _PassArg.context: "context", 

1419 _PassArg.eval_context: "context.eval_ctx", 

1420 _PassArg.environment: "environment", 

1421 }.get( 

1422 _PassArg.from_obj(env_finalize) # type: ignore 

1423 ) 

1424 finalize = None 

1425 

1426 if pass_arg is None: 

1427 

1428 def finalize(value: t.Any) -> t.Any: # noqa: F811 

1429 return default(env_finalize(value)) 

1430 

1431 else: 

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

1433 

1434 if pass_arg == "environment": 

1435 

1436 def finalize(value: t.Any) -> t.Any: # noqa: F811 

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

1438 

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

1440 return self._finalize 

1441 

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

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

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

1445 source. 

1446 """ 

1447 return repr(concat(group)) 

1448 

1449 def _output_child_to_const( 

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

1451 ) -> str: 

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

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

1454 

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

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

1457 evaluated at runtime for easier debugging. 

1458 """ 

1459 const = node.as_const(frame.eval_ctx) 

1460 

1461 if frame.eval_ctx.autoescape: 

1462 const = escape(const) 

1463 

1464 # Template data doesn't go through finalize. 

1465 if isinstance(node, nodes.TemplateData): 

1466 return str(const) 

1467 

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

1469 

1470 def _output_child_pre( 

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

1472 ) -> None: 

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

1474 ``Output`` node. 

1475 """ 

1476 if frame.eval_ctx.volatile: 

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

1478 elif frame.eval_ctx.autoescape: 

1479 self.write("escape(") 

1480 else: 

1481 self.write("str(") 

1482 

1483 if finalize.src is not None: 

1484 self.write(finalize.src) 

1485 

1486 def _output_child_post( 

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

1488 ) -> None: 

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

1490 ``Output`` node. 

1491 """ 

1492 self.write(")") 

1493 

1494 if finalize.src is not None: 

1495 self.write(")") 

1496 

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

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

1499 if frame.require_output_check: 

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

1501 if self.has_known_extends: 

1502 return 

1503 

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

1505 self.indent() 

1506 

1507 finalize = self._make_finalize() 

1508 body: list[list[t.Any] | nodes.Expr] = [] 

1509 

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

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

1512 # evaluated at runtime. 

1513 for child in node.nodes: 

1514 try: 

1515 if not ( 

1516 # If the finalize function requires runtime context, 

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

1518 finalize.const 

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

1520 # finalized anyway. 

1521 or isinstance(child, nodes.TemplateData) 

1522 ): 

1523 raise nodes.Impossible() 

1524 

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

1526 except (nodes.Impossible, Exception): 

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

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

1529 # to debug at runtime. 

1530 body.append(child) 

1531 continue 

1532 

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

1534 body[-1].append(const) 

1535 else: 

1536 body.append([const]) 

1537 

1538 if frame.buffer is not None: 

1539 if len(body) == 1: 

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

1541 else: 

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

1543 

1544 self.indent() 

1545 

1546 for item in body: 

1547 if isinstance(item, list): 

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

1549 val = self._output_const_repr(item) 

1550 

1551 if frame.buffer is None: 

1552 self.writeline("yield " + val) 

1553 else: 

1554 self.writeline(val + ",") 

1555 else: 

1556 if frame.buffer is None: 

1557 self.writeline("yield ", item) 

1558 else: 

1559 self.newline(item) 

1560 

1561 # A node to be evaluated at runtime. 

1562 self._output_child_pre(item, frame, finalize) 

1563 self.visit(item, frame) 

1564 self._output_child_post(item, frame, finalize) 

1565 

1566 if frame.buffer is not None: 

1567 self.write(",") 

1568 

1569 if frame.buffer is not None: 

1570 self.outdent() 

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

1572 

1573 if frame.require_output_check: 

1574 self.outdent() 

1575 

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

1577 self.push_assign_tracking() 

1578 

1579 # ``a.b`` is allowed for assignment, and is parsed as an NSRef. However, 

1580 # it is only valid if it references a Namespace object. Emit a check for 

1581 # that for each ref here, before assignment code is emitted. This can't 

1582 # be done in visit_NSRef as the ref could be in the middle of a tuple. 

1583 seen_refs: set[str] = set() 

1584 

1585 for nsref in node.find_all(nodes.NSRef): 

1586 if nsref.name in seen_refs: 

1587 # Only emit the check for each reference once, in case the same 

1588 # ref is used multiple times in a tuple, `ns.a, ns.b = c, d`. 

1589 continue 

1590 

1591 seen_refs.add(nsref.name) 

1592 ref = frame.symbols.ref(nsref.name) 

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

1594 self.indent() 

1595 self.writeline( 

1596 "raise TemplateRuntimeError" 

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

1598 ) 

1599 self.outdent() 

1600 

1601 self.newline(node) 

1602 self.visit(node.target, frame) 

1603 self.write(" = ") 

1604 self.visit(node.node, frame) 

1605 self.pop_assign_tracking(frame) 

1606 

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

1608 self.push_assign_tracking() 

1609 block_frame = frame.inner() 

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

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

1612 # toplevel even in extended templates. 

1613 block_frame.require_output_check = False 

1614 block_frame.symbols.analyze_node(node) 

1615 self.enter_frame(block_frame) 

1616 self.buffer(block_frame) 

1617 self.blockvisit(node.body, block_frame) 

1618 self.newline(node) 

1619 self.visit(node.target, frame) 

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

1621 if node.filter is not None: 

1622 self.visit_Filter(node.filter, block_frame) 

1623 else: 

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

1625 self.write(")") 

1626 self.pop_assign_tracking(frame) 

1627 self.leave_frame(block_frame) 

1628 

1629 # -- Expression Visitors 

1630 

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

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

1633 frame.toplevel or frame.loop_frame or frame.block_frame 

1634 ): 

1635 if self._assign_stack: 

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

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

1638 

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

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

1641 # instruction indicates a parameter which are always defined. 

1642 if node.ctx == "load": 

1643 load = frame.symbols.find_load(ref) 

1644 if not ( 

1645 load is not None 

1646 and load[0] == VAR_LOAD_PARAMETER 

1647 and not self.parameter_is_undeclared(ref) 

1648 ): 

1649 self.write( 

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

1651 ) 

1652 return 

1653 

1654 self.write(ref) 

1655 

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

1657 # NSRef is a dotted assignment target a.b=c, but uses a[b]=c internally. 

1658 # visit_Assign emits code to validate that each ref is to a Namespace 

1659 # object only. That can't be emitted here as the ref could be in the 

1660 # middle of a tuple assignment. 

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

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

1663 

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

1665 val = node.as_const(frame.eval_ctx) 

1666 if isinstance(val, float): 

1667 self.write(str(val)) 

1668 else: 

1669 self.write(repr(val)) 

1670 

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

1672 try: 

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

1674 except nodes.Impossible: 

1675 self.write( 

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

1677 ) 

1678 

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

1680 self.write("(") 

1681 idx = -1 

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

1683 if idx: 

1684 self.write(", ") 

1685 self.visit(item, frame) 

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

1687 

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

1689 self.write("[") 

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

1691 if idx: 

1692 self.write(", ") 

1693 self.visit(item, frame) 

1694 self.write("]") 

1695 

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

1697 self.write("{") 

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

1699 if idx: 

1700 self.write(", ") 

1701 self.visit(item.key, frame) 

1702 self.write(": ") 

1703 self.visit(item.value, frame) 

1704 self.write("}") 

1705 

1706 visit_Add = _make_binop("+") 

1707 visit_Sub = _make_binop("-") 

1708 visit_Mul = _make_binop("*") 

1709 visit_Div = _make_binop("/") 

1710 visit_FloorDiv = _make_binop("//") 

1711 visit_Pow = _make_binop("**") 

1712 visit_Mod = _make_binop("%") 

1713 visit_And = _make_binop("and") 

1714 visit_Or = _make_binop("or") 

1715 visit_Pos = _make_unop("+") 

1716 visit_Neg = _make_unop("-") 

1717 visit_Not = _make_unop("not ") 

1718 

1719 @optimizeconst 

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

1721 if frame.eval_ctx.volatile: 

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

1723 elif frame.eval_ctx.autoescape: 

1724 func_name = "markup_join" 

1725 else: 

1726 func_name = "str_join" 

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

1728 for arg in node.nodes: 

1729 self.visit(arg, frame) 

1730 self.write(", ") 

1731 self.write("))") 

1732 

1733 @optimizeconst 

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

1735 self.write("(") 

1736 self.visit(node.expr, frame) 

1737 for op in node.ops: 

1738 self.visit(op, frame) 

1739 self.write(")") 

1740 

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

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

1743 self.visit(node.expr, frame) 

1744 

1745 @optimizeconst 

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

1747 if self.environment.is_async: 

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

1749 

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

1751 self.visit(node.node, frame) 

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

1753 

1754 if self.environment.is_async: 

1755 self.write("))") 

1756 

1757 @optimizeconst 

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

1759 # slices bypass the environment getitem method. 

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

1761 self.visit(node.node, frame) 

1762 self.write("[") 

1763 self.visit(node.arg, frame) 

1764 self.write("]") 

1765 else: 

1766 if self.environment.is_async: 

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

1768 

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

1770 self.visit(node.node, frame) 

1771 self.write(", ") 

1772 self.visit(node.arg, frame) 

1773 self.write(")") 

1774 

1775 if self.environment.is_async: 

1776 self.write("))") 

1777 

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

1779 if node.start is not None: 

1780 self.visit(node.start, frame) 

1781 self.write(":") 

1782 if node.stop is not None: 

1783 self.visit(node.stop, frame) 

1784 if node.step is not None: 

1785 self.write(":") 

1786 self.visit(node.step, frame) 

1787 

1788 @contextmanager 

1789 def _filter_test_common( 

1790 self, node: nodes.Filter | nodes.Test, frame: Frame, is_filter: bool 

1791 ) -> t.Iterator[None]: 

1792 if self.environment.is_async: 

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

1794 

1795 if is_filter: 

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

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

1798 else: 

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

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

1801 

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

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

1804 # actually called at runtime. See pull_dependencies. 

1805 if func is None and not frame.soft_frame: 

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

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

1808 

1809 pass_arg = { 

1810 _PassArg.context: "context", 

1811 _PassArg.eval_context: "context.eval_ctx", 

1812 _PassArg.environment: "environment", 

1813 }.get( 

1814 _PassArg.from_obj(func) # type: ignore 

1815 ) 

1816 

1817 if pass_arg is not None: 

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

1819 

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

1821 # the filter or test. 

1822 yield 

1823 

1824 self.signature(node, frame) 

1825 self.write(")") 

1826 

1827 if self.environment.is_async: 

1828 self.write("))") 

1829 

1830 @optimizeconst 

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

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

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

1834 # and want to write to the current buffer 

1835 if node.node is not None: 

1836 self.visit(node.node, frame) 

1837 elif frame.eval_ctx.volatile: 

1838 self.write( 

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

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

1841 ) 

1842 elif frame.eval_ctx.autoescape: 

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

1844 else: 

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

1846 

1847 @optimizeconst 

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

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

1850 self.visit(node.node, frame) 

1851 

1852 @optimizeconst 

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

1854 frame = frame.soft() 

1855 

1856 def write_expr2() -> None: 

1857 if node.expr2 is not None: 

1858 self.visit(node.expr2, frame) 

1859 return 

1860 

1861 self.write( 

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

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

1864 f' section was defined.")' 

1865 ) 

1866 

1867 self.write("(") 

1868 self.visit(node.expr1, frame) 

1869 self.write(" if ") 

1870 self.visit(node.test, frame) 

1871 self.write(" else ") 

1872 write_expr2() 

1873 self.write(")") 

1874 

1875 @optimizeconst 

1876 def visit_Call( 

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

1878 ) -> None: 

1879 if self.environment.is_async: 

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

1881 if self.environment.sandboxed: 

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

1883 else: 

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

1885 self.visit(node.node, frame) 

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

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

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

1889 if extra_kwargs: 

1890 extra_kwargs.update(loop_kwargs, **block_kwargs) 

1891 elif loop_kwargs or block_kwargs: 

1892 extra_kwargs = dict(loop_kwargs, **block_kwargs) 

1893 self.signature(node, frame, extra_kwargs) 

1894 self.write(")") 

1895 if self.environment.is_async: 

1896 self.write("))") 

1897 

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

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

1900 self.visit(node.value, frame) 

1901 

1902 # -- Unused nodes for extensions 

1903 

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

1905 self.write("Markup(") 

1906 self.visit(node.expr, frame) 

1907 self.write(")") 

1908 

1909 def visit_MarkSafeIfAutoescape( 

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

1911 ) -> None: 

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

1913 self.visit(node.expr, frame) 

1914 self.write(")") 

1915 

1916 def visit_EnvironmentAttribute( 

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

1918 ) -> None: 

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

1920 

1921 def visit_ExtensionAttribute( 

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

1923 ) -> None: 

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

1925 

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

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

1928 

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

1930 self.write(node.name) 

1931 

1932 def visit_ContextReference( 

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

1934 ) -> None: 

1935 self.write("context") 

1936 

1937 def visit_DerivedContextReference( 

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

1939 ) -> None: 

1940 self.write(self.derive_context(frame)) 

1941 

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

1943 self.writeline("continue", node) 

1944 

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

1946 self.writeline("break", node) 

1947 

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

1949 scope_frame = frame.inner() 

1950 scope_frame.symbols.analyze_node(node) 

1951 self.enter_frame(scope_frame) 

1952 self.blockvisit(node.body, scope_frame) 

1953 self.leave_frame(scope_frame) 

1954 

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

1956 ctx = self.temporary_identifier() 

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

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

1959 self.visit(node.context, frame) 

1960 self.push_context_reference(ctx) 

1961 

1962 scope_frame = frame.inner(isolated=True) 

1963 scope_frame.symbols.analyze_node(node) 

1964 self.enter_frame(scope_frame) 

1965 self.blockvisit(node.body, scope_frame) 

1966 self.leave_frame(scope_frame) 

1967 self.pop_context_reference() 

1968 

1969 def visit_EvalContextModifier( 

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

1971 ) -> None: 

1972 for keyword in node.options: 

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

1974 self.visit(keyword.value, frame) 

1975 try: 

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

1977 except nodes.Impossible: 

1978 frame.eval_ctx.volatile = True 

1979 else: 

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

1981 

1982 def visit_ScopedEvalContextModifier( 

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

1984 ) -> None: 

1985 old_ctx_name = self.temporary_identifier() 

1986 saved_ctx = frame.eval_ctx.save() 

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

1988 self.visit_EvalContextModifier(node, frame) 

1989 for child in node.body: 

1990 self.visit(child, frame) 

1991 frame.eval_ctx.revert(saved_ctx) 

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