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

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

1192 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: t.Optional[str], 

105 filename: t.Optional[str], 

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

107 defer_init: bool = False, 

108 optimized: bool = True, 

109) -> t.Optional[str]: 

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

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

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

113 

114 generator = environment.code_generator_class( 

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

116 ) 

117 generator.visit(node) 

118 

119 if stream is None: 

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

121 

122 return None 

123 

124 

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

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

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

128 return True 

129 

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

131 return True 

132 

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

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

135 

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

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

138 

139 return False 

140 

141 

142def find_undeclared( 

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

144) -> t.Set[str]: 

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

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

147 """ 

148 visitor = UndeclaredNameVisitor(names) 

149 try: 

150 for node in nodes: 

151 visitor.visit(node) 

152 except VisitorExit: 

153 pass 

154 return visitor.undeclared 

155 

156 

157class MacroRef: 

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

159 self.node = node 

160 self.accesses_caller = False 

161 self.accesses_kwargs = False 

162 self.accesses_varargs = False 

163 

164 

165class Frame: 

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

167 

168 def __init__( 

169 self, 

170 eval_ctx: EvalContext, 

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

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

173 ) -> None: 

174 self.eval_ctx = eval_ctx 

175 

176 # the parent of this frame 

177 self.parent = parent 

178 

179 if parent is None: 

180 self.symbols = Symbols(level=level) 

181 

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

183 # write tests around output statements. 

184 self.require_output_check = False 

185 

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

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

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

189 # buffer. 

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

191 

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

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

194 

195 else: 

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

197 self.require_output_check = parent.require_output_check 

198 self.buffer = parent.buffer 

199 self.block = parent.block 

200 

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

202 self.toplevel = False 

203 

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

205 # conditions. This information is used to optimize inheritance 

206 # situations. 

207 self.rootlevel = False 

208 

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

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

211 self.loop_frame = False 

212 self.block_frame = False 

213 

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

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

216 # or compile time. 

217 self.soft_frame = False 

218 

219 def copy(self) -> "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 ( 

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

556 ( 

557 self.tests, 

558 visitor.tests, 

559 "tests", 

560 ), 

561 ): 

562 for name in sorted(names): 

563 if name not in id_map: 

564 id_map[name] = self.temporary_identifier() 

565 

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

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

568 self.writeline("try:") 

569 self.indent() 

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

571 self.outdent() 

572 self.writeline("except KeyError:") 

573 self.indent() 

574 self.writeline("@internalcode") 

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

576 self.indent() 

577 self.writeline( 

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

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

580 ) 

581 self.outdent() 

582 self.outdent() 

583 

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

585 undefs = [] 

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

587 if action == VAR_LOAD_PARAMETER: 

588 pass 

589 elif action == VAR_LOAD_RESOLVE: 

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

591 elif action == VAR_LOAD_ALIAS: 

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

593 elif action == VAR_LOAD_UNDEFINED: 

594 undefs.append(target) 

595 else: 

596 raise NotImplementedError("unknown load instruction") 

597 if undefs: 

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

599 

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

601 if not with_python_scope: 

602 undefs = [] 

603 for target in frame.symbols.loads: 

604 undefs.append(target) 

605 if undefs: 

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

607 

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

609 return async_value if self.environment.is_async else sync_value 

610 

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

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

613 

614 def macro_body( 

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

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

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

618 frame = frame.inner() 

619 frame.symbols.analyze_node(node) 

620 macro_ref = MacroRef(node) 

621 

622 explicit_caller = None 

623 skip_special_params = set() 

624 args = [] 

625 

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

627 if arg.name == "caller": 

628 explicit_caller = idx 

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

630 skip_special_params.add(arg.name) 

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

632 

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

634 

635 if "caller" in undeclared: 

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

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

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

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

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

641 # the argument list. 

642 if explicit_caller is not None: 

643 try: 

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

645 except IndexError: 

646 self.fail( 

647 "When defining macros or call blocks the " 

648 'special "caller" argument must be omitted ' 

649 "or be given a default.", 

650 node.lineno, 

651 ) 

652 else: 

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

654 macro_ref.accesses_caller = True 

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

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

657 macro_ref.accesses_kwargs = True 

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

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

660 macro_ref.accesses_varargs = True 

661 

662 # macros are delayed, they never require output checks 

663 frame.require_output_check = False 

664 frame.symbols.analyze_node(node) 

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

666 self.indent() 

667 

668 self.buffer(frame) 

669 self.enter_frame(frame) 

670 

671 self.push_parameter_definitions(frame) 

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

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

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

675 self.indent() 

676 try: 

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

678 except IndexError: 

679 self.writeline( 

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

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

682 ) 

683 else: 

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

685 self.visit(default, frame) 

686 self.mark_parameter_stored(ref) 

687 self.outdent() 

688 self.pop_parameter_definitions() 

689 

690 self.blockvisit(node.body, frame) 

691 self.return_buffer_contents(frame, force_unescaped=True) 

692 self.leave_frame(frame, with_python_scope=True) 

693 self.outdent() 

694 

695 return frame, macro_ref 

696 

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

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

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

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

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

702 arg_tuple += "," 

703 self.write( 

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

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

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

707 ) 

708 

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

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

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

712 if self.name is not None: 

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

714 return rv 

715 

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

717 items_kv = ", ".join( 

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

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

720 ) 

721 return f"{{{items_kv}}}" 

722 

723 def write_commons(self) -> None: 

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

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

726 through a dead branch. 

727 """ 

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

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

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

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

732 # conditional expressions 

733 self.writeline("cond_expr_undefined = Undefined") 

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

735 

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

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

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

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

740 undefined expressions for parameters in macros as macros can reference 

741 otherwise unbound parameters. 

742 """ 

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

744 

745 def pop_parameter_definitions(self) -> None: 

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

747 self._param_def_block.pop() 

748 

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

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

751 This will skip the enforced undefined checks. 

752 """ 

753 if self._param_def_block: 

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

755 

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

757 self._context_reference_stack.append(target) 

758 

759 def pop_context_reference(self) -> None: 

760 self._context_reference_stack.pop() 

761 

762 def get_context_ref(self) -> str: 

763 return self._context_reference_stack[-1] 

764 

765 def get_resolve_func(self) -> str: 

766 target = self._context_reference_stack[-1] 

767 if target == "context": 

768 return "resolve" 

769 return f"{target}.resolve" 

770 

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

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

773 

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

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

776 if not self._param_def_block: 

777 return False 

778 return target in self._param_def_block[-1] 

779 

780 def push_assign_tracking(self) -> None: 

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

782 self._assign_stack.append(set()) 

783 

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

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

786 context variables if necessary. 

787 """ 

788 vars = self._assign_stack.pop() 

789 if ( 

790 not frame.block_frame 

791 and not frame.loop_frame 

792 and not frame.toplevel 

793 or not vars 

794 ): 

795 return 

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

797 if len(vars) == 1: 

798 name = next(iter(vars)) 

799 ref = frame.symbols.ref(name) 

800 if frame.loop_frame: 

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

802 return 

803 if frame.block_frame: 

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

805 return 

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

807 else: 

808 if frame.loop_frame: 

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

810 elif frame.block_frame: 

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

812 else: 

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

814 for idx, name in enumerate(vars): 

815 if idx: 

816 self.write(", ") 

817 ref = frame.symbols.ref(name) 

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

819 self.write("})") 

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

821 if len(public_names) == 1: 

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

823 else: 

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

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

826 

827 # -- Statement Visitors 

828 

829 def visit_Template( 

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

831 ) -> None: 

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

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

834 

835 from .runtime import async_exported 

836 from .runtime import exported 

837 

838 if self.environment.is_async: 

839 exported_names = sorted(exported + async_exported) 

840 else: 

841 exported_names = sorted(exported) 

842 

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

844 

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

846 # environment into a local name 

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

848 

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

850 # overhead by just not processing any inheritance code. 

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

852 

853 # find all blocks 

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

855 if block.name in self.blocks: 

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

857 self.blocks[block.name] = block 

858 

859 # find all imports and import them 

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

861 if import_.importname not in self.import_aliases: 

862 imp = import_.importname 

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

864 if "." in imp: 

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

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

867 else: 

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

869 

870 # add the load name 

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

872 

873 # generate the root render function. 

874 self.writeline( 

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

876 ) 

877 self.indent() 

878 self.write_commons() 

879 

880 # process the root 

881 frame = Frame(eval_ctx) 

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

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

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

885 frame.symbols.analyze_node(node) 

886 frame.toplevel = frame.rootlevel = True 

887 frame.require_output_check = have_extends and not self.has_known_extends 

888 if have_extends: 

889 self.writeline("parent_template = None") 

890 self.enter_frame(frame) 

891 self.pull_dependencies(node.body) 

892 self.blockvisit(node.body, frame) 

893 self.leave_frame(frame, with_python_scope=True) 

894 self.outdent() 

895 

896 # make sure that the parent root is called. 

897 if have_extends: 

898 if not self.has_known_extends: 

899 self.indent() 

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

901 self.indent() 

902 if not self.environment.is_async: 

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

904 else: 

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

906 self.writeline("try:") 

907 self.indent() 

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

909 self.indent() 

910 self.writeline("yield event") 

911 self.outdent() 

912 self.outdent() 

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

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

915 

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

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

918 self.writeline( 

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

920 block, 

921 1, 

922 ) 

923 self.indent() 

924 self.write_commons() 

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

926 # toplevel template. This would cause a variety of 

927 # interesting issues with identifier tracking. 

928 block_frame = Frame(eval_ctx) 

929 block_frame.block_frame = True 

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

931 if "self" in undeclared: 

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

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

934 if "super" in undeclared: 

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

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

937 block_frame.symbols.analyze_node(block) 

938 block_frame.block = name 

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

940 self.enter_frame(block_frame) 

941 self.pull_dependencies(block.body) 

942 self.blockvisit(block.body, block_frame) 

943 self.leave_frame(block_frame, with_python_scope=True) 

944 self.outdent() 

945 

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

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

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

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

950 

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

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

953 level = 0 

954 if frame.toplevel: 

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

956 # check if we are one 

957 if self.has_known_extends: 

958 return 

959 if self.extends_so_far > 0: 

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

961 self.indent() 

962 level += 1 

963 

964 if node.scoped: 

965 context = self.derive_context(frame) 

966 else: 

967 context = self.get_context_ref() 

968 

969 if node.required: 

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

971 self.indent() 

972 self.writeline( 

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

974 node, 

975 ) 

976 self.outdent() 

977 

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

979 self.writeline( 

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

981 ) 

982 else: 

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

984 self.writeline("try:") 

985 self.indent() 

986 self.writeline( 

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

988 node, 

989 ) 

990 self.indent() 

991 self.simple_write("event", frame) 

992 self.outdent() 

993 self.outdent() 

994 self.writeline( 

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

996 ) 

997 

998 self.outdent(level) 

999 

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

1001 """Calls the extender.""" 

1002 if not frame.toplevel: 

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

1004 

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

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

1007 # the template before this one. 

1008 if self.extends_so_far > 0: 

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

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

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

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

1013 if not self.has_known_extends: 

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

1015 self.indent() 

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

1017 

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

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

1020 if self.has_known_extends: 

1021 raise CompilerExit() 

1022 else: 

1023 self.outdent() 

1024 

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

1026 self.visit(node.template, frame) 

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

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

1029 self.indent() 

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

1031 self.outdent() 

1032 

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

1034 # advantage of that information and simplify the generated code 

1035 # in the top level from this point onwards 

1036 if frame.rootlevel: 

1037 self.has_known_extends = True 

1038 

1039 # and now we have one more 

1040 self.extends_so_far += 1 

1041 

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

1043 """Handles includes.""" 

1044 if node.ignore_missing: 

1045 self.writeline("try:") 

1046 self.indent() 

1047 

1048 func_name = "get_or_select_template" 

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

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

1051 func_name = "get_template" 

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

1053 func_name = "select_template" 

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

1055 func_name = "select_template" 

1056 

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

1058 self.visit(node.template, frame) 

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

1060 if node.ignore_missing: 

1061 self.outdent() 

1062 self.writeline("except TemplateNotFound:") 

1063 self.indent() 

1064 self.writeline("pass") 

1065 self.outdent() 

1066 self.writeline("else:") 

1067 self.indent() 

1068 

1069 def loop_body() -> None: 

1070 self.indent() 

1071 self.simple_write("event", frame) 

1072 self.outdent() 

1073 

1074 if node.with_context: 

1075 self.writeline( 

1076 f"gen = template.root_render_func(" 

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

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

1079 ) 

1080 self.writeline("try:") 

1081 self.indent() 

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

1083 loop_body() 

1084 self.outdent() 

1085 self.writeline( 

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

1087 ) 

1088 elif self.environment.is_async: 

1089 self.writeline( 

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

1091 "._body_stream:" 

1092 ) 

1093 loop_body() 

1094 else: 

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

1096 

1097 if node.ignore_missing: 

1098 self.outdent() 

1099 

1100 def _import_common( 

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

1102 ) -> None: 

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

1104 self.visit(node.template, frame) 

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

1106 

1107 if node.with_context: 

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

1109 self.write( 

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

1111 ) 

1112 else: 

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

1114 

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

1116 """Visit regular imports.""" 

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

1118 if frame.toplevel: 

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

1120 

1121 self._import_common(node, frame) 

1122 

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

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

1125 

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

1127 """Visit named imports.""" 

1128 self.newline(node) 

1129 self.write("included_template = ") 

1130 self._import_common(node, frame) 

1131 var_names = [] 

1132 discarded_names = [] 

1133 for name in node.names: 

1134 if isinstance(name, tuple): 

1135 name, alias = name 

1136 else: 

1137 alias = name 

1138 self.writeline( 

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

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

1141 ) 

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

1143 self.indent() 

1144 message = ( 

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

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

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

1148 ) 

1149 self.writeline( 

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

1151 ) 

1152 self.outdent() 

1153 if frame.toplevel: 

1154 var_names.append(alias) 

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

1156 discarded_names.append(alias) 

1157 

1158 if var_names: 

1159 if len(var_names) == 1: 

1160 name = var_names[0] 

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

1162 else: 

1163 names_kv = ", ".join( 

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

1165 ) 

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

1167 if discarded_names: 

1168 if len(discarded_names) == 1: 

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

1170 else: 

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

1172 self.writeline( 

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

1174 ) 

1175 

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

1177 loop_frame = frame.inner() 

1178 loop_frame.loop_frame = True 

1179 test_frame = frame.inner() 

1180 else_frame = frame.inner() 

1181 

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

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

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

1185 extended_loop = ( 

1186 node.recursive 

1187 or "loop" 

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

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

1190 ) 

1191 

1192 loop_ref = None 

1193 if extended_loop: 

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

1195 

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

1197 if node.else_: 

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

1199 

1200 if node.test: 

1201 loop_filter_func = self.temporary_identifier() 

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

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

1204 self.indent() 

1205 self.enter_frame(test_frame) 

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

1207 self.visit(node.target, loop_frame) 

1208 self.write(" in ") 

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

1210 self.write(":") 

1211 self.indent() 

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

1213 self.visit(node.test, test_frame) 

1214 self.write(":") 

1215 self.indent() 

1216 self.writeline("yield ") 

1217 self.visit(node.target, loop_frame) 

1218 self.outdent(3) 

1219 self.leave_frame(test_frame, with_python_scope=True) 

1220 

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

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

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

1224 if node.recursive: 

1225 self.writeline( 

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

1227 ) 

1228 self.indent() 

1229 self.buffer(loop_frame) 

1230 

1231 # Use the same buffer for the else frame 

1232 else_frame.buffer = loop_frame.buffer 

1233 

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

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

1236 if extended_loop: 

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

1238 

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

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

1241 self.fail( 

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

1243 name.lineno, 

1244 ) 

1245 

1246 if node.else_: 

1247 iteration_indicator = self.temporary_identifier() 

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

1249 

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

1251 self.visit(node.target, loop_frame) 

1252 if extended_loop: 

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

1254 else: 

1255 self.write(" in ") 

1256 

1257 if node.test: 

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

1259 if node.recursive: 

1260 self.write("reciter") 

1261 else: 

1262 if self.environment.is_async and not extended_loop: 

1263 self.write("auto_aiter(") 

1264 self.visit(node.iter, frame) 

1265 if self.environment.is_async and not extended_loop: 

1266 self.write(")") 

1267 if node.test: 

1268 self.write(")") 

1269 

1270 if node.recursive: 

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

1272 else: 

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

1274 

1275 self.indent() 

1276 self.enter_frame(loop_frame) 

1277 

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

1279 self.blockvisit(node.body, loop_frame) 

1280 if node.else_: 

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

1282 self.outdent() 

1283 self.leave_frame( 

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

1285 ) 

1286 

1287 if node.else_: 

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

1289 self.indent() 

1290 self.enter_frame(else_frame) 

1291 self.blockvisit(node.else_, else_frame) 

1292 self.leave_frame(else_frame) 

1293 self.outdent() 

1294 

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

1296 # and start the iteration code 

1297 if node.recursive: 

1298 self.return_buffer_contents(loop_frame) 

1299 self.outdent() 

1300 self.start_write(frame, node) 

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

1302 if self.environment.is_async: 

1303 self.write("auto_aiter(") 

1304 self.visit(node.iter, frame) 

1305 if self.environment.is_async: 

1306 self.write(")") 

1307 self.write(", loop)") 

1308 self.end_write(frame) 

1309 

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

1311 # loop from the top level 

1312 if self._assign_stack: 

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

1314 

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

1316 if_frame = frame.soft() 

1317 self.writeline("if ", node) 

1318 self.visit(node.test, if_frame) 

1319 self.write(":") 

1320 self.indent() 

1321 self.blockvisit(node.body, if_frame) 

1322 self.outdent() 

1323 for elif_ in node.elif_: 

1324 self.writeline("elif ", elif_) 

1325 self.visit(elif_.test, if_frame) 

1326 self.write(":") 

1327 self.indent() 

1328 self.blockvisit(elif_.body, if_frame) 

1329 self.outdent() 

1330 if node.else_: 

1331 self.writeline("else:") 

1332 self.indent() 

1333 self.blockvisit(node.else_, if_frame) 

1334 self.outdent() 

1335 

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

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

1338 self.newline() 

1339 if frame.toplevel: 

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

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

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

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

1344 self.macro_def(macro_ref, macro_frame) 

1345 

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

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

1348 self.writeline("caller = ") 

1349 self.macro_def(macro_ref, call_frame) 

1350 self.start_write(frame, node) 

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

1352 self.end_write(frame) 

1353 

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

1355 filter_frame = frame.inner() 

1356 filter_frame.symbols.analyze_node(node) 

1357 self.enter_frame(filter_frame) 

1358 self.buffer(filter_frame) 

1359 self.blockvisit(node.body, filter_frame) 

1360 self.start_write(frame, node) 

1361 self.visit_Filter(node.filter, filter_frame) 

1362 self.end_write(frame) 

1363 self.leave_frame(filter_frame) 

1364 

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

1366 with_frame = frame.inner() 

1367 with_frame.symbols.analyze_node(node) 

1368 self.enter_frame(with_frame) 

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

1370 self.newline() 

1371 self.visit(target, with_frame) 

1372 self.write(" = ") 

1373 self.visit(expr, frame) 

1374 self.blockvisit(node.body, with_frame) 

1375 self.leave_frame(with_frame) 

1376 

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

1378 self.newline(node) 

1379 self.visit(node.node, frame) 

1380 

1381 class _FinalizeInfo(t.NamedTuple): 

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

1383 src: t.Optional[str] 

1384 

1385 @staticmethod 

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

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

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

1389 called on that function's output for constants. 

1390 """ 

1391 return str(value) 

1392 

1393 _finalize: t.Optional[_FinalizeInfo] = None 

1394 

1395 def _make_finalize(self) -> _FinalizeInfo: 

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

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

1398 

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

1400 

1401 ``const`` 

1402 A function to finalize constant data at compile time. 

1403 

1404 ``src`` 

1405 Source code to output around nodes to be evaluated at 

1406 runtime. 

1407 """ 

1408 if self._finalize is not None: 

1409 return self._finalize 

1410 

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

1412 finalize = default = self._default_finalize 

1413 src = None 

1414 

1415 if self.environment.finalize: 

1416 src = "environment.finalize(" 

1417 env_finalize = self.environment.finalize 

1418 pass_arg = { 

1419 _PassArg.context: "context", 

1420 _PassArg.eval_context: "context.eval_ctx", 

1421 _PassArg.environment: "environment", 

1422 }.get( 

1423 _PassArg.from_obj(env_finalize) # type: ignore 

1424 ) 

1425 finalize = None 

1426 

1427 if pass_arg is None: 

1428 

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

1430 return default(env_finalize(value)) 

1431 

1432 else: 

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

1434 

1435 if pass_arg == "environment": 

1436 

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

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

1439 

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

1441 return self._finalize 

1442 

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

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

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

1446 source. 

1447 """ 

1448 return repr(concat(group)) 

1449 

1450 def _output_child_to_const( 

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

1452 ) -> str: 

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

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

1455 

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

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

1458 evaluated at runtime for easier debugging. 

1459 """ 

1460 const = node.as_const(frame.eval_ctx) 

1461 

1462 if frame.eval_ctx.autoescape: 

1463 const = escape(const) 

1464 

1465 # Template data doesn't go through finalize. 

1466 if isinstance(node, nodes.TemplateData): 

1467 return str(const) 

1468 

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

1470 

1471 def _output_child_pre( 

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

1473 ) -> None: 

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

1475 ``Output`` node. 

1476 """ 

1477 if frame.eval_ctx.volatile: 

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

1479 elif frame.eval_ctx.autoescape: 

1480 self.write("escape(") 

1481 else: 

1482 self.write("str(") 

1483 

1484 if finalize.src is not None: 

1485 self.write(finalize.src) 

1486 

1487 def _output_child_post( 

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

1489 ) -> None: 

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

1491 ``Output`` node. 

1492 """ 

1493 self.write(")") 

1494 

1495 if finalize.src is not None: 

1496 self.write(")") 

1497 

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

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

1500 if frame.require_output_check: 

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

1502 if self.has_known_extends: 

1503 return 

1504 

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

1506 self.indent() 

1507 

1508 finalize = self._make_finalize() 

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

1510 

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

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

1513 # evaluated at runtime. 

1514 for child in node.nodes: 

1515 try: 

1516 if not ( 

1517 # If the finalize function requires runtime context, 

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

1519 finalize.const 

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

1521 # finalized anyway. 

1522 or isinstance(child, nodes.TemplateData) 

1523 ): 

1524 raise nodes.Impossible() 

1525 

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

1527 except (nodes.Impossible, Exception): 

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

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

1530 # to debug at runtime. 

1531 body.append(child) 

1532 continue 

1533 

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

1535 body[-1].append(const) 

1536 else: 

1537 body.append([const]) 

1538 

1539 if frame.buffer is not None: 

1540 if len(body) == 1: 

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

1542 else: 

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

1544 

1545 self.indent() 

1546 

1547 for item in body: 

1548 if isinstance(item, list): 

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

1550 val = self._output_const_repr(item) 

1551 

1552 if frame.buffer is None: 

1553 self.writeline("yield " + val) 

1554 else: 

1555 self.writeline(val + ",") 

1556 else: 

1557 if frame.buffer is None: 

1558 self.writeline("yield ", item) 

1559 else: 

1560 self.newline(item) 

1561 

1562 # A node to be evaluated at runtime. 

1563 self._output_child_pre(item, frame, finalize) 

1564 self.visit(item, frame) 

1565 self._output_child_post(item, frame, finalize) 

1566 

1567 if frame.buffer is not None: 

1568 self.write(",") 

1569 

1570 if frame.buffer is not None: 

1571 self.outdent() 

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

1573 

1574 if frame.require_output_check: 

1575 self.outdent() 

1576 

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

1578 self.push_assign_tracking() 

1579 self.newline(node) 

1580 self.visit(node.target, frame) 

1581 self.write(" = ") 

1582 self.visit(node.node, frame) 

1583 self.pop_assign_tracking(frame) 

1584 

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

1586 self.push_assign_tracking() 

1587 block_frame = frame.inner() 

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

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

1590 # toplevel even in extended templates. 

1591 block_frame.require_output_check = False 

1592 block_frame.symbols.analyze_node(node) 

1593 self.enter_frame(block_frame) 

1594 self.buffer(block_frame) 

1595 self.blockvisit(node.body, block_frame) 

1596 self.newline(node) 

1597 self.visit(node.target, frame) 

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

1599 if node.filter is not None: 

1600 self.visit_Filter(node.filter, block_frame) 

1601 else: 

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

1603 self.write(")") 

1604 self.pop_assign_tracking(frame) 

1605 self.leave_frame(block_frame) 

1606 

1607 # -- Expression Visitors 

1608 

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

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

1611 frame.toplevel or frame.loop_frame or frame.block_frame 

1612 ): 

1613 if self._assign_stack: 

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

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

1616 

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

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

1619 # instruction indicates a parameter which are always defined. 

1620 if node.ctx == "load": 

1621 load = frame.symbols.find_load(ref) 

1622 if not ( 

1623 load is not None 

1624 and load[0] == VAR_LOAD_PARAMETER 

1625 and not self.parameter_is_undeclared(ref) 

1626 ): 

1627 self.write( 

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

1629 ) 

1630 return 

1631 

1632 self.write(ref) 

1633 

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

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

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

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

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

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

1640 self.indent() 

1641 self.writeline( 

1642 "raise TemplateRuntimeError" 

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

1644 ) 

1645 self.outdent() 

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

1647 

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

1649 val = node.as_const(frame.eval_ctx) 

1650 if isinstance(val, float): 

1651 self.write(str(val)) 

1652 else: 

1653 self.write(repr(val)) 

1654 

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

1656 try: 

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

1658 except nodes.Impossible: 

1659 self.write( 

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

1661 ) 

1662 

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

1664 self.write("(") 

1665 idx = -1 

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

1667 if idx: 

1668 self.write(", ") 

1669 self.visit(item, frame) 

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

1671 

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

1673 self.write("[") 

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

1675 if idx: 

1676 self.write(", ") 

1677 self.visit(item, frame) 

1678 self.write("]") 

1679 

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

1681 self.write("{") 

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

1683 if idx: 

1684 self.write(", ") 

1685 self.visit(item.key, frame) 

1686 self.write(": ") 

1687 self.visit(item.value, frame) 

1688 self.write("}") 

1689 

1690 visit_Add = _make_binop("+") 

1691 visit_Sub = _make_binop("-") 

1692 visit_Mul = _make_binop("*") 

1693 visit_Div = _make_binop("/") 

1694 visit_FloorDiv = _make_binop("//") 

1695 visit_Pow = _make_binop("**") 

1696 visit_Mod = _make_binop("%") 

1697 visit_And = _make_binop("and") 

1698 visit_Or = _make_binop("or") 

1699 visit_Pos = _make_unop("+") 

1700 visit_Neg = _make_unop("-") 

1701 visit_Not = _make_unop("not ") 

1702 

1703 @optimizeconst 

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

1705 if frame.eval_ctx.volatile: 

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

1707 elif frame.eval_ctx.autoescape: 

1708 func_name = "markup_join" 

1709 else: 

1710 func_name = "str_join" 

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

1712 for arg in node.nodes: 

1713 self.visit(arg, frame) 

1714 self.write(", ") 

1715 self.write("))") 

1716 

1717 @optimizeconst 

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

1719 self.write("(") 

1720 self.visit(node.expr, frame) 

1721 for op in node.ops: 

1722 self.visit(op, frame) 

1723 self.write(")") 

1724 

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

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

1727 self.visit(node.expr, frame) 

1728 

1729 @optimizeconst 

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

1731 if self.environment.is_async: 

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

1733 

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

1735 self.visit(node.node, frame) 

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

1737 

1738 if self.environment.is_async: 

1739 self.write("))") 

1740 

1741 @optimizeconst 

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

1743 # slices bypass the environment getitem method. 

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

1745 self.visit(node.node, frame) 

1746 self.write("[") 

1747 self.visit(node.arg, frame) 

1748 self.write("]") 

1749 else: 

1750 if self.environment.is_async: 

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

1752 

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

1754 self.visit(node.node, frame) 

1755 self.write(", ") 

1756 self.visit(node.arg, frame) 

1757 self.write(")") 

1758 

1759 if self.environment.is_async: 

1760 self.write("))") 

1761 

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

1763 if node.start is not None: 

1764 self.visit(node.start, frame) 

1765 self.write(":") 

1766 if node.stop is not None: 

1767 self.visit(node.stop, frame) 

1768 if node.step is not None: 

1769 self.write(":") 

1770 self.visit(node.step, frame) 

1771 

1772 @contextmanager 

1773 def _filter_test_common( 

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

1775 ) -> t.Iterator[None]: 

1776 if self.environment.is_async: 

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

1778 

1779 if is_filter: 

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

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

1782 else: 

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

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

1785 

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

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

1788 # actually called at runtime. See pull_dependencies. 

1789 if func is None and not frame.soft_frame: 

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

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

1792 

1793 pass_arg = { 

1794 _PassArg.context: "context", 

1795 _PassArg.eval_context: "context.eval_ctx", 

1796 _PassArg.environment: "environment", 

1797 }.get( 

1798 _PassArg.from_obj(func) # type: ignore 

1799 ) 

1800 

1801 if pass_arg is not None: 

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

1803 

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

1805 # the filter or test. 

1806 yield 

1807 

1808 self.signature(node, frame) 

1809 self.write(")") 

1810 

1811 if self.environment.is_async: 

1812 self.write("))") 

1813 

1814 @optimizeconst 

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

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

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

1818 # and want to write to the current buffer 

1819 if node.node is not None: 

1820 self.visit(node.node, frame) 

1821 elif frame.eval_ctx.volatile: 

1822 self.write( 

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

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

1825 ) 

1826 elif frame.eval_ctx.autoescape: 

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

1828 else: 

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

1830 

1831 @optimizeconst 

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

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

1834 self.visit(node.node, frame) 

1835 

1836 @optimizeconst 

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

1838 frame = frame.soft() 

1839 

1840 def write_expr2() -> None: 

1841 if node.expr2 is not None: 

1842 self.visit(node.expr2, frame) 

1843 return 

1844 

1845 self.write( 

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

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

1848 f' section was defined.")' 

1849 ) 

1850 

1851 self.write("(") 

1852 self.visit(node.expr1, frame) 

1853 self.write(" if ") 

1854 self.visit(node.test, frame) 

1855 self.write(" else ") 

1856 write_expr2() 

1857 self.write(")") 

1858 

1859 @optimizeconst 

1860 def visit_Call( 

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

1862 ) -> None: 

1863 if self.environment.is_async: 

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

1865 if self.environment.sandboxed: 

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

1867 else: 

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

1869 self.visit(node.node, frame) 

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

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

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

1873 if extra_kwargs: 

1874 extra_kwargs.update(loop_kwargs, **block_kwargs) 

1875 elif loop_kwargs or block_kwargs: 

1876 extra_kwargs = dict(loop_kwargs, **block_kwargs) 

1877 self.signature(node, frame, extra_kwargs) 

1878 self.write(")") 

1879 if self.environment.is_async: 

1880 self.write("))") 

1881 

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

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

1884 self.visit(node.value, frame) 

1885 

1886 # -- Unused nodes for extensions 

1887 

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

1889 self.write("Markup(") 

1890 self.visit(node.expr, frame) 

1891 self.write(")") 

1892 

1893 def visit_MarkSafeIfAutoescape( 

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

1895 ) -> None: 

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

1897 self.visit(node.expr, frame) 

1898 self.write(")") 

1899 

1900 def visit_EnvironmentAttribute( 

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

1902 ) -> None: 

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

1904 

1905 def visit_ExtensionAttribute( 

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

1907 ) -> None: 

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

1909 

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

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

1912 

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

1914 self.write(node.name) 

1915 

1916 def visit_ContextReference( 

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

1918 ) -> None: 

1919 self.write("context") 

1920 

1921 def visit_DerivedContextReference( 

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

1923 ) -> None: 

1924 self.write(self.derive_context(frame)) 

1925 

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

1927 self.writeline("continue", node) 

1928 

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

1930 self.writeline("break", node) 

1931 

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

1933 scope_frame = frame.inner() 

1934 scope_frame.symbols.analyze_node(node) 

1935 self.enter_frame(scope_frame) 

1936 self.blockvisit(node.body, scope_frame) 

1937 self.leave_frame(scope_frame) 

1938 

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

1940 ctx = self.temporary_identifier() 

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

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

1943 self.visit(node.context, frame) 

1944 self.push_context_reference(ctx) 

1945 

1946 scope_frame = frame.inner(isolated=True) 

1947 scope_frame.symbols.analyze_node(node) 

1948 self.enter_frame(scope_frame) 

1949 self.blockvisit(node.body, scope_frame) 

1950 self.leave_frame(scope_frame) 

1951 self.pop_context_reference() 

1952 

1953 def visit_EvalContextModifier( 

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

1955 ) -> None: 

1956 for keyword in node.options: 

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

1958 self.visit(keyword.value, frame) 

1959 try: 

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

1961 except nodes.Impossible: 

1962 frame.eval_ctx.volatile = True 

1963 else: 

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

1965 

1966 def visit_ScopedEvalContextModifier( 

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

1968 ) -> None: 

1969 old_ctx_name = self.temporary_identifier() 

1970 saved_ctx = frame.eval_ctx.save() 

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

1972 self.visit_EvalContextModifier(node, frame) 

1973 for child in node.body: 

1974 self.visit(child, frame) 

1975 frame.eval_ctx.revert(saved_ctx) 

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