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

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

211 statements  

1import typing as t 

2 

3from . import nodes 

4from .visitor import NodeVisitor 

5 

6if t.TYPE_CHECKING: 

7 import typing_extensions as te 

8 

9VAR_LOAD_PARAMETER = "param" 

10VAR_LOAD_RESOLVE = "resolve" 

11VAR_LOAD_ALIAS = "alias" 

12VAR_LOAD_UNDEFINED = "undefined" 

13 

14 

15def find_symbols( 

16 nodes: t.Iterable[nodes.Node], parent_symbols: t.Optional["Symbols"] = None 

17) -> "Symbols": 

18 sym = Symbols(parent=parent_symbols) 

19 visitor = FrameSymbolVisitor(sym) 

20 for node in nodes: 

21 visitor.visit(node) 

22 return sym 

23 

24 

25def symbols_for_node( 

26 node: nodes.Node, parent_symbols: t.Optional["Symbols"] = None 

27) -> "Symbols": 

28 sym = Symbols(parent=parent_symbols) 

29 sym.analyze_node(node) 

30 return sym 

31 

32 

33class Symbols: 

34 def __init__( 

35 self, parent: t.Optional["Symbols"] = None, level: int | None = None 

36 ) -> None: 

37 if level is None: 

38 if parent is None: 

39 level = 0 

40 else: 

41 level = parent.level + 1 

42 

43 self.level: int = level 

44 self.parent = parent 

45 self.refs: dict[str, str] = {} 

46 self.loads: dict[str, t.Any] = {} 

47 self.stores: set[str] = set() 

48 

49 def analyze_node(self, node: nodes.Node, **kwargs: t.Any) -> None: 

50 visitor = RootVisitor(self) 

51 visitor.visit(node, **kwargs) 

52 

53 def _define_ref(self, name: str, load: tuple[str, str | None] | None = None) -> str: 

54 ident = f"l_{self.level}_{name}" 

55 self.refs[name] = ident 

56 if load is not None: 

57 self.loads[ident] = load 

58 return ident 

59 

60 def find_load(self, target: str) -> t.Any | None: 

61 if target in self.loads: 

62 return self.loads[target] 

63 

64 if self.parent is not None: 

65 return self.parent.find_load(target) 

66 

67 return None 

68 

69 def find_ref(self, name: str) -> str | None: 

70 if name in self.refs: 

71 return self.refs[name] 

72 

73 if self.parent is not None: 

74 return self.parent.find_ref(name) 

75 

76 return None 

77 

78 def ref(self, name: str) -> str: 

79 rv = self.find_ref(name) 

80 if rv is None: 

81 raise AssertionError( 

82 "Tried to resolve a name to a reference that was" 

83 f" unknown to the frame ({name!r})" 

84 ) 

85 return rv 

86 

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

88 rv = object.__new__(self.__class__) 

89 rv.__dict__.update(self.__dict__) 

90 rv.refs = self.refs.copy() 

91 rv.loads = self.loads.copy() 

92 rv.stores = self.stores.copy() 

93 return rv 

94 

95 def store(self, name: str) -> None: 

96 self.stores.add(name) 

97 

98 # If we have not see the name referenced yet, we need to figure 

99 # out what to set it to. 

100 if name not in self.refs: 

101 # If there is a parent scope we check if the name has a 

102 # reference there. If it does it means we might have to alias 

103 # to a variable there. 

104 if self.parent is not None: 

105 outer_ref = self.parent.find_ref(name) 

106 if outer_ref is not None: 

107 self._define_ref(name, load=(VAR_LOAD_ALIAS, outer_ref)) 

108 return 

109 

110 # Otherwise we can just set it to undefined. 

111 self._define_ref(name, load=(VAR_LOAD_UNDEFINED, None)) 

112 

113 def declare_parameter(self, name: str) -> str: 

114 self.stores.add(name) 

115 return self._define_ref(name, load=(VAR_LOAD_PARAMETER, None)) 

116 

117 def load(self, name: str) -> None: 

118 if self.find_ref(name) is None: 

119 self._define_ref(name, load=(VAR_LOAD_RESOLVE, name)) 

120 

121 def branch_update(self, branch_symbols: t.Sequence["Symbols"]) -> None: 

122 stores: set[str] = set() 

123 

124 for branch in branch_symbols: 

125 stores.update(branch.stores) 

126 

127 stores.difference_update(self.stores) 

128 

129 for sym in branch_symbols: 

130 self.refs.update(sym.refs) 

131 self.loads.update(sym.loads) 

132 self.stores.update(sym.stores) 

133 

134 for name in stores: 

135 target = self.find_ref(name) 

136 assert target is not None, "should not happen" 

137 

138 if self.parent is not None: 

139 outer_target = self.parent.find_ref(name) 

140 if outer_target is not None: 

141 self.loads[target] = (VAR_LOAD_ALIAS, outer_target) 

142 continue 

143 self.loads[target] = (VAR_LOAD_RESOLVE, name) 

144 

145 def dump_stores(self) -> dict[str, str]: 

146 rv: dict[str, str] = {} 

147 node: Symbols | None = self 

148 

149 while node is not None: 

150 for name in sorted(node.stores): 

151 if name not in rv: 

152 rv[name] = self.find_ref(name) # type: ignore 

153 

154 node = node.parent 

155 

156 return rv 

157 

158 def dump_param_targets(self) -> set[str]: 

159 rv = set() 

160 node: Symbols | None = self 

161 

162 while node is not None: 

163 for target, (instr, _) in self.loads.items(): 

164 if instr == VAR_LOAD_PARAMETER: 

165 rv.add(target) 

166 

167 node = node.parent 

168 

169 return rv 

170 

171 

172class RootVisitor(NodeVisitor): 

173 def __init__(self, symbols: "Symbols") -> None: 

174 self.sym_visitor = FrameSymbolVisitor(symbols) 

175 

176 def _simple_visit(self, node: nodes.Node, **kwargs: t.Any) -> None: 

177 for child in node.iter_child_nodes(): 

178 self.sym_visitor.visit(child) 

179 

180 visit_Template = _simple_visit 

181 visit_Block = _simple_visit 

182 visit_Macro = _simple_visit 

183 visit_FilterBlock = _simple_visit 

184 visit_Scope = _simple_visit 

185 visit_If = _simple_visit 

186 visit_ScopedEvalContextModifier = _simple_visit 

187 

188 def visit_AssignBlock(self, node: nodes.AssignBlock, **kwargs: t.Any) -> None: 

189 for child in node.body: 

190 self.sym_visitor.visit(child) 

191 

192 def visit_CallBlock(self, node: nodes.CallBlock, **kwargs: t.Any) -> None: 

193 for child in node.iter_child_nodes(exclude=("call",)): 

194 self.sym_visitor.visit(child) 

195 

196 def visit_OverlayScope(self, node: nodes.OverlayScope, **kwargs: t.Any) -> None: 

197 for child in node.body: 

198 self.sym_visitor.visit(child) 

199 

200 def visit_For( 

201 self, node: nodes.For, for_branch: str = "body", **kwargs: t.Any 

202 ) -> None: 

203 if for_branch == "body": 

204 self.sym_visitor.visit(node.target, store_as_param=True) 

205 branch = node.body 

206 elif for_branch == "else": 

207 branch = node.else_ 

208 elif for_branch == "test": 

209 self.sym_visitor.visit(node.target, store_as_param=True) 

210 if node.test is not None: 

211 self.sym_visitor.visit(node.test) 

212 return 

213 else: 

214 raise RuntimeError("Unknown for branch") 

215 

216 if branch: 

217 for item in branch: 

218 self.sym_visitor.visit(item) 

219 

220 def visit_With(self, node: nodes.With, **kwargs: t.Any) -> None: 

221 for target in node.targets: 

222 self.sym_visitor.visit(target) 

223 for child in node.body: 

224 self.sym_visitor.visit(child) 

225 

226 def generic_visit(self, node: nodes.Node, *args: t.Any, **kwargs: t.Any) -> None: 

227 raise NotImplementedError(f"Cannot find symbols for {type(node).__name__!r}") 

228 

229 

230class FrameSymbolVisitor(NodeVisitor): 

231 """A visitor for `Frame.inspect`.""" 

232 

233 def __init__(self, symbols: "Symbols") -> None: 

234 self.symbols = symbols 

235 

236 def visit_Name( 

237 self, node: nodes.Name, store_as_param: bool = False, **kwargs: t.Any 

238 ) -> None: 

239 """All assignments to names go through this function.""" 

240 if store_as_param or node.ctx == "param": 

241 self.symbols.declare_parameter(node.name) 

242 elif node.ctx == "store": 

243 self.symbols.store(node.name) 

244 elif node.ctx == "load": 

245 self.symbols.load(node.name) 

246 

247 def visit_NSRef(self, node: nodes.NSRef, **kwargs: t.Any) -> None: 

248 self.symbols.load(node.name) 

249 

250 def visit_If(self, node: nodes.If, **kwargs: t.Any) -> None: 

251 self.visit(node.test, **kwargs) 

252 original_symbols = self.symbols 

253 

254 def inner_visit(nodes: t.Iterable[nodes.Node]) -> "Symbols": 

255 self.symbols = rv = original_symbols.copy() 

256 

257 for subnode in nodes: 

258 self.visit(subnode, **kwargs) 

259 

260 self.symbols = original_symbols 

261 return rv 

262 

263 body_symbols = inner_visit(node.body) 

264 elif_symbols = inner_visit(node.elif_) 

265 else_symbols = inner_visit(node.else_ or ()) 

266 self.symbols.branch_update([body_symbols, elif_symbols, else_symbols]) 

267 

268 def visit_Macro(self, node: nodes.Macro, **kwargs: t.Any) -> None: 

269 self.symbols.store(node.name) 

270 

271 def visit_Import(self, node: nodes.Import, **kwargs: t.Any) -> None: 

272 self.generic_visit(node, **kwargs) 

273 self.symbols.store(node.target) 

274 

275 def visit_FromImport(self, node: nodes.FromImport, **kwargs: t.Any) -> None: 

276 self.generic_visit(node, **kwargs) 

277 

278 for name in node.names: 

279 if isinstance(name, tuple): 

280 self.symbols.store(name[1]) 

281 else: 

282 self.symbols.store(name) 

283 

284 def visit_Assign(self, node: nodes.Assign, **kwargs: t.Any) -> None: 

285 """Visit assignments in the correct order.""" 

286 self.visit(node.node, **kwargs) 

287 self.visit(node.target, **kwargs) 

288 

289 def visit_For(self, node: nodes.For, **kwargs: t.Any) -> None: 

290 """Visiting stops at for blocks. However the block sequence 

291 is visited as part of the outer scope. 

292 """ 

293 self.visit(node.iter, **kwargs) 

294 

295 def visit_CallBlock(self, node: nodes.CallBlock, **kwargs: t.Any) -> None: 

296 self.visit(node.call, **kwargs) 

297 

298 def visit_FilterBlock(self, node: nodes.FilterBlock, **kwargs: t.Any) -> None: 

299 self.visit(node.filter, **kwargs) 

300 

301 def visit_With(self, node: nodes.With, **kwargs: t.Any) -> None: 

302 for target in node.values: 

303 self.visit(target) 

304 

305 def visit_AssignBlock(self, node: nodes.AssignBlock, **kwargs: t.Any) -> None: 

306 """Stop visiting at block assigns.""" 

307 self.visit(node.target, **kwargs) 

308 

309 def visit_Scope(self, node: nodes.Scope, **kwargs: t.Any) -> None: 

310 """Stop visiting at scopes.""" 

311 

312 def visit_Block(self, node: nodes.Block, **kwargs: t.Any) -> None: 

313 """Stop visiting at blocks.""" 

314 

315 def visit_OverlayScope(self, node: nodes.OverlayScope, **kwargs: t.Any) -> None: 

316 """Do not visit into overlay scopes."""