cinderx 2026.1.16.2__cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. __static__/__init__.py +641 -0
  2. __static__/compiler_flags.py +8 -0
  3. __static__/enum.py +160 -0
  4. __static__/native_utils.py +77 -0
  5. __static__/type_code.py +48 -0
  6. __strict__/__init__.py +39 -0
  7. _cinderx.so +0 -0
  8. cinderx/__init__.py +577 -0
  9. cinderx/__pycache__/__init__.cpython-314.pyc +0 -0
  10. cinderx/_asyncio.py +156 -0
  11. cinderx/compileall.py +710 -0
  12. cinderx/compiler/__init__.py +40 -0
  13. cinderx/compiler/__main__.py +137 -0
  14. cinderx/compiler/config.py +7 -0
  15. cinderx/compiler/consts.py +72 -0
  16. cinderx/compiler/debug.py +70 -0
  17. cinderx/compiler/dis_stable.py +283 -0
  18. cinderx/compiler/errors.py +151 -0
  19. cinderx/compiler/flow_graph_optimizer.py +1287 -0
  20. cinderx/compiler/future.py +91 -0
  21. cinderx/compiler/misc.py +32 -0
  22. cinderx/compiler/opcode_cinder.py +18 -0
  23. cinderx/compiler/opcode_static.py +100 -0
  24. cinderx/compiler/opcodebase.py +158 -0
  25. cinderx/compiler/opcodes.py +991 -0
  26. cinderx/compiler/optimizer.py +547 -0
  27. cinderx/compiler/pyassem.py +3711 -0
  28. cinderx/compiler/pycodegen.py +7660 -0
  29. cinderx/compiler/pysourceloader.py +62 -0
  30. cinderx/compiler/static/__init__.py +1404 -0
  31. cinderx/compiler/static/compiler.py +629 -0
  32. cinderx/compiler/static/declaration_visitor.py +335 -0
  33. cinderx/compiler/static/definite_assignment_checker.py +280 -0
  34. cinderx/compiler/static/effects.py +160 -0
  35. cinderx/compiler/static/module_table.py +666 -0
  36. cinderx/compiler/static/type_binder.py +2176 -0
  37. cinderx/compiler/static/types.py +10580 -0
  38. cinderx/compiler/static/util.py +81 -0
  39. cinderx/compiler/static/visitor.py +91 -0
  40. cinderx/compiler/strict/__init__.py +69 -0
  41. cinderx/compiler/strict/class_conflict_checker.py +249 -0
  42. cinderx/compiler/strict/code_gen_base.py +409 -0
  43. cinderx/compiler/strict/common.py +507 -0
  44. cinderx/compiler/strict/compiler.py +352 -0
  45. cinderx/compiler/strict/feature_extractor.py +130 -0
  46. cinderx/compiler/strict/flag_extractor.py +97 -0
  47. cinderx/compiler/strict/loader.py +827 -0
  48. cinderx/compiler/strict/preprocessor.py +11 -0
  49. cinderx/compiler/strict/rewriter/__init__.py +5 -0
  50. cinderx/compiler/strict/rewriter/remove_annotations.py +84 -0
  51. cinderx/compiler/strict/rewriter/rewriter.py +975 -0
  52. cinderx/compiler/strict/runtime.py +77 -0
  53. cinderx/compiler/symbols.py +1754 -0
  54. cinderx/compiler/unparse.py +414 -0
  55. cinderx/compiler/visitor.py +194 -0
  56. cinderx/jit.py +230 -0
  57. cinderx/opcode.py +202 -0
  58. cinderx/static.py +113 -0
  59. cinderx/strictmodule.py +6 -0
  60. cinderx/test_support.py +341 -0
  61. cinderx-2026.1.16.2.dist-info/METADATA +15 -0
  62. cinderx-2026.1.16.2.dist-info/RECORD +68 -0
  63. cinderx-2026.1.16.2.dist-info/WHEEL +6 -0
  64. cinderx-2026.1.16.2.dist-info/licenses/LICENSE +21 -0
  65. cinderx-2026.1.16.2.dist-info/top_level.txt +5 -0
  66. opcodes/__init__.py +0 -0
  67. opcodes/assign_opcode_numbers.py +272 -0
  68. opcodes/cinderx_opcodes.py +121 -0
@@ -0,0 +1,335 @@
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ # pyre-strict
4
+
5
+ from __future__ import annotations
6
+
7
+ import ast
8
+ from ast import (
9
+ AnnAssign,
10
+ Assign,
11
+ AST,
12
+ AsyncFor,
13
+ AsyncFunctionDef,
14
+ AsyncWith,
15
+ ClassDef,
16
+ For,
17
+ FunctionDef,
18
+ If,
19
+ Import,
20
+ ImportFrom,
21
+ Name,
22
+ Try,
23
+ While,
24
+ With,
25
+ )
26
+ from typing import TYPE_CHECKING, Union
27
+
28
+ from .module_table import DeferredImport, ModuleTable
29
+ from .types import (
30
+ AwaitableTypeRef,
31
+ Class,
32
+ DecoratedMethod,
33
+ Function,
34
+ InitSubclassFunction,
35
+ ResolvedTypeRef,
36
+ TypeEnvironment,
37
+ TypeName,
38
+ TypeRef,
39
+ UnknownDecoratedMethod,
40
+ )
41
+ from .util import make_qualname, sys_hexversion_check
42
+ from .visitor import GenericVisitor
43
+
44
+ if TYPE_CHECKING:
45
+ from .compiler import Compiler
46
+
47
+
48
+ class NestedScope:
49
+ def __init__(self, parent_qualname: str | None) -> None:
50
+ self.qualname: str = make_qualname(parent_qualname, "<nested>")
51
+
52
+ def declare_class(self, node: AST, klass: Class) -> None:
53
+ pass
54
+
55
+ def declare_function(self, func: Function | DecoratedMethod) -> None:
56
+ pass
57
+
58
+ def declare_variable(self, node: AnnAssign, module: ModuleTable) -> None:
59
+ pass
60
+
61
+ def declare_variables(self, node: Assign, module: ModuleTable) -> None:
62
+ pass
63
+
64
+ # pyre-ignore[11]: Annotation `ast.TypeAlias` is not defined as a type
65
+ def declare_type_alias(self, node: ast.TypeAlias) -> None:
66
+ pass
67
+
68
+
69
+ TScopeTypes = Union[ModuleTable, Class, Function, NestedScope]
70
+
71
+
72
+ class DeclarationVisitor(GenericVisitor[None]):
73
+ def __init__(
74
+ self, mod_name: str, filename: str, symbols: Compiler, optimize: int
75
+ ) -> None:
76
+ module = symbols[mod_name] = ModuleTable(
77
+ mod_name, filename, symbols, first_pass_done=False
78
+ )
79
+ super().__init__(module)
80
+ self.scopes: list[TScopeTypes] = [self.module]
81
+ self.optimize = optimize
82
+ self.compiler = symbols
83
+ self.type_env: TypeEnvironment = symbols.type_env
84
+
85
+ def finish_bind(self) -> None:
86
+ self.module.finish_bind()
87
+
88
+ def parent_scope(self) -> TScopeTypes:
89
+ return self.scopes[-1]
90
+
91
+ def enter_scope(self, scope: TScopeTypes) -> None:
92
+ self.scopes.append(scope)
93
+
94
+ def exit_scope(self) -> None:
95
+ self.scopes.pop()
96
+
97
+ def qualify_name(self, name: str) -> str:
98
+ return make_qualname(self.parent_scope().qualname, name)
99
+
100
+ def make_nested_scope(self) -> NestedScope:
101
+ return NestedScope(self.parent_scope().qualname)
102
+
103
+ def enter_nested_scope(self) -> None:
104
+ self.enter_scope(self.make_nested_scope())
105
+
106
+ def visitAnnAssign(self, node: AnnAssign) -> None:
107
+ self.parent_scope().declare_variable(node, self.module)
108
+
109
+ def visitAssign(self, node: Assign) -> None:
110
+ self.parent_scope().declare_variables(node, self.module)
111
+
112
+ # pyre-ignore[11]: Annotation `ast.TypeAlias` is not defined as a type
113
+ def visitTypeAlias(self, node: ast.TypeAlias) -> None:
114
+ self.parent_scope().declare_type_alias(node)
115
+
116
+ def visitClassDef(self, node: ClassDef) -> None:
117
+ parent_scope = self.parent_scope()
118
+ qualname = make_qualname(parent_scope.qualname, node.name)
119
+
120
+ bases = [
121
+ self.module.resolve_type(base, qualname) or self.type_env.dynamic
122
+ for base in node.bases
123
+ ]
124
+ if not bases:
125
+ bases.append(self.type_env.object)
126
+
127
+ with self.compiler.error_sink.error_context(self.filename, node):
128
+ klasses = []
129
+ for base in bases:
130
+ klasses.append(
131
+ base.make_subclass(
132
+ TypeName(self.module_name, qualname),
133
+ bases,
134
+ )
135
+ )
136
+ for cur_type in klasses:
137
+ if type(cur_type) is not type(klasses[0]):
138
+ self.syntax_error("Incompatible subtypes", node)
139
+ klass = klasses[0]
140
+
141
+ for base in bases:
142
+ if base is self.type_env.named_tuple:
143
+ # In named tuples, the fields are actually elements
144
+ # of the tuple, so we can't do any advanced binding against it.
145
+ klass = self.type_env.dynamic
146
+ break
147
+
148
+ if base is self.type_env.protocol:
149
+ # Protocols aren't guaranteed to exist in the actual MRO, so let's treat
150
+ # them as dynamic to force dynamic dispatch.
151
+ klass = self.type_env.dynamic
152
+ break
153
+
154
+ if base is self.type_env.typed_dict:
155
+ # TASK(T121706684) Supporting typed dicts is tricky, similar
156
+ # to protocols and named tuples
157
+ klass = self.type_env.dynamic
158
+ break
159
+
160
+ if base.is_final:
161
+ self.syntax_error(
162
+ f"Class `{klass.instance.name}` cannot subclass a Final class: `{base.instance.name}`",
163
+ node,
164
+ )
165
+
166
+ # we can't statically load classes nested inside functions
167
+ if not isinstance(parent_scope, (ModuleTable, Class)):
168
+ klass = self.type_env.dynamic
169
+
170
+ self.enter_scope(
171
+ self.make_nested_scope() if klass is self.type_env.dynamic else klass
172
+ )
173
+ for item in node.body:
174
+ with self.compiler.error_sink.error_context(self.filename, item):
175
+ self.visit(item)
176
+ self.exit_scope()
177
+
178
+ for d in reversed(node.decorator_list):
179
+ if klass is self.type_env.dynamic:
180
+ break
181
+ with self.compiler.error_sink.error_context(self.filename, d):
182
+ decorator = self.module.resolve_decorator(d) or self.type_env.dynamic
183
+ klass = decorator.resolve_decorate_class(klass, d, self)
184
+
185
+ parent_scope.declare_class(node, klass.exact_type())
186
+ # We want the name corresponding to `C` to be the exact type when imported.
187
+ self.module.types[node] = klass.exact_type()
188
+
189
+ def _visitFunc(self, node: FunctionDef | AsyncFunctionDef) -> None:
190
+ function = self._make_function(node)
191
+ self.parent_scope().declare_function(function)
192
+
193
+ def _make_function(self, node: FunctionDef | AsyncFunctionDef) -> Function:
194
+ qualname = self.qualify_name(node.name)
195
+ if node.name == "__init_subclass__":
196
+ func = InitSubclassFunction(
197
+ node, self.module, self.return_type_ref(node, qualname)
198
+ )
199
+ parent_scope = self.parent_scope()
200
+ if isinstance(parent_scope, Class):
201
+ parent_scope.has_init_subclass = True
202
+ else:
203
+ func = Function(node, self.module, self.return_type_ref(node, qualname))
204
+ self.enter_scope(func)
205
+ for item in node.body:
206
+ self.visit(item)
207
+ self.exit_scope()
208
+
209
+ func_type = func
210
+ if node.decorator_list:
211
+ # Since we haven't resolved decorators yet (until finish_bind), we
212
+ # don't know what type we should ultimately set for this node;
213
+ # Function.finish_bind() will likely override this.
214
+ func_type = UnknownDecoratedMethod(func, node.decorator_list[0])
215
+
216
+ self.module.types[node] = func_type
217
+ return func
218
+
219
+ def visitFunctionDef(self, node: FunctionDef) -> None:
220
+ self._visitFunc(node)
221
+
222
+ def visitAsyncFunctionDef(self, node: AsyncFunctionDef) -> None:
223
+ self._visitFunc(node)
224
+
225
+ def return_type_ref(
226
+ self, node: FunctionDef | AsyncFunctionDef, requester: str
227
+ ) -> TypeRef:
228
+ ann = node.returns
229
+ if not ann:
230
+ res = ResolvedTypeRef(self.type_env.dynamic)
231
+ else:
232
+ res = TypeRef(self.module, requester, ann)
233
+ if isinstance(node, AsyncFunctionDef):
234
+ res = AwaitableTypeRef(res, self.module.compiler)
235
+ return res
236
+
237
+ def visitImport(self, node: Import) -> None:
238
+ for name in node.names:
239
+ asname = name.asname
240
+ if asname is None:
241
+ top_level_module = name.name.split(".")[0]
242
+ self.module.declare_import(
243
+ top_level_module,
244
+ None,
245
+ DeferredImport(
246
+ self.module,
247
+ name.name,
248
+ top_level_module,
249
+ self.optimize,
250
+ self.compiler,
251
+ mod_to_return=top_level_module,
252
+ ),
253
+ )
254
+ else:
255
+ self.module.declare_import(
256
+ asname,
257
+ None,
258
+ DeferredImport(
259
+ self.module, name.name, asname, self.optimize, self.compiler
260
+ ),
261
+ )
262
+
263
+ def visitImportFrom(self, node: ImportFrom) -> None:
264
+ mod_name = node.module
265
+ if not mod_name or node.level:
266
+ raise NotImplementedError("relative imports aren't supported")
267
+ for name in node.names:
268
+ child_name = name.asname or name.name
269
+ self.module.declare_import(
270
+ child_name,
271
+ (mod_name, name.name),
272
+ DeferredImport(
273
+ self.module,
274
+ mod_name,
275
+ name.name,
276
+ self.optimize,
277
+ self.compiler,
278
+ name.name,
279
+ ),
280
+ )
281
+
282
+ # We don't pick up declarations in nested statements
283
+ def visitFor(self, node: For) -> None:
284
+ self.enter_nested_scope()
285
+ self.generic_visit(node)
286
+ self.exit_scope()
287
+
288
+ def visitAsyncFor(self, node: AsyncFor) -> None:
289
+ self.enter_nested_scope()
290
+ self.generic_visit(node)
291
+ self.exit_scope()
292
+
293
+ def visitWhile(self, node: While) -> None:
294
+ self.enter_nested_scope()
295
+ self.generic_visit(node)
296
+ self.exit_scope()
297
+
298
+ def visitIf(self, node: If) -> None:
299
+ test = node.test
300
+ if isinstance(test, Name) and test.id == "TYPE_CHECKING":
301
+ self.visit_list(node.body)
302
+ else:
303
+ result = sys_hexversion_check(node)
304
+ # We should check the version if provided
305
+ if result is not None:
306
+ self.module.mark_known_boolean_test(test, value=bool(result))
307
+ if result:
308
+ self.visit_list(node.body)
309
+ else:
310
+ self.visit_list(node.orelse)
311
+ return
312
+ else:
313
+ self.enter_nested_scope()
314
+ self.visit_list(node.body)
315
+ self.exit_scope()
316
+
317
+ if node.orelse:
318
+ self.enter_nested_scope()
319
+ self.visit_list(node.orelse)
320
+ self.exit_scope()
321
+
322
+ def visitWith(self, node: With) -> None:
323
+ self.enter_nested_scope()
324
+ self.generic_visit(node)
325
+ self.exit_scope()
326
+
327
+ def visitAsyncWith(self, node: AsyncWith) -> None:
328
+ self.enter_nested_scope()
329
+ self.generic_visit(node)
330
+ self.exit_scope()
331
+
332
+ def visitTry(self, node: Try) -> None:
333
+ self.enter_nested_scope()
334
+ self.generic_visit(node)
335
+ self.exit_scope()
@@ -0,0 +1,280 @@
1
+ # @oncall strictmod
2
+
3
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
4
+ # pyre-strict
5
+
6
+ from __future__ import annotations
7
+
8
+ import ast
9
+ from ast import (
10
+ AnnAssign,
11
+ Assign,
12
+ AsyncFunctionDef,
13
+ AugAssign,
14
+ ClassDef,
15
+ For,
16
+ FunctionDef,
17
+ If,
18
+ Import,
19
+ ImportFrom,
20
+ Lambda,
21
+ Name,
22
+ NodeVisitor,
23
+ Raise,
24
+ stmt,
25
+ Try,
26
+ While,
27
+ With,
28
+ )
29
+ from typing import Iterable
30
+
31
+ from ..consts import SC_CELL, SC_LOCAL
32
+ from ..symbols import Scope
33
+
34
+
35
+ class DefiniteAssignmentVisitor(NodeVisitor):
36
+ def __init__(self, scope: Scope) -> None:
37
+ self.scope = scope
38
+ self.assigned: set[str] = set()
39
+ self.unassigned: set[Name] = set()
40
+
41
+ def analyzeFunction(self, node: FunctionDef | AsyncFunctionDef) -> None:
42
+ for arg in node.args.args:
43
+ self.assigned.add(arg.arg)
44
+ for arg in node.args.kwonlyargs:
45
+ self.assigned.add(arg.arg)
46
+ for arg in node.args.posonlyargs:
47
+ self.assigned.add(arg.arg)
48
+
49
+ vararg = node.args.vararg
50
+ if vararg:
51
+ self.assigned.add(vararg.arg)
52
+ kwarg = node.args.kwarg
53
+ if kwarg:
54
+ self.assigned.add(kwarg.arg)
55
+ for body_stmt in node.body:
56
+ self.visit(body_stmt)
57
+
58
+ def set_assigned(self, name: str) -> None:
59
+ if self.is_local(name):
60
+ self.assigned.add(name)
61
+
62
+ def is_local(self, name: str) -> bool:
63
+ scope = self.scope.check_name(name)
64
+ return scope == SC_LOCAL or scope == SC_CELL
65
+
66
+ def visit_Name(self, node: Name) -> None:
67
+ # we only walk the module and class defs and nested class defs
68
+ # can't access parent class def variables, so the variable is
69
+ # definitely assigned in the inner most class def
70
+ # TODO: T52624111 needs special handling for __class__
71
+ if not self.is_local(node.id):
72
+ return
73
+
74
+ if isinstance(node.ctx, ast.Load):
75
+ if node.id not in self.assigned:
76
+ self.unassigned.add(node)
77
+ elif isinstance(node.ctx, ast.Del):
78
+ if node.id not in self.assigned:
79
+ self.unassigned.add(node)
80
+ else:
81
+ self.assigned.remove(node.id)
82
+ else:
83
+ self.assigned.add(node.id)
84
+
85
+ def visit_Assign(self, node: Assign) -> None:
86
+ # generic_visit will evaluate these in a different order due to _fields
87
+ # being targets, value
88
+ self.visit(node.value)
89
+ for target in node.targets:
90
+ self.visit(target)
91
+
92
+ def visit_AugAssign(self, node: AugAssign) -> None:
93
+ target = node.target
94
+ if isinstance(target, ast.Name):
95
+ if target.id not in self.assigned:
96
+ self.unassigned.add(target)
97
+ self.generic_visit(node.value)
98
+ return
99
+
100
+ self.generic_visit(node)
101
+
102
+ def visit_Try(self, node: Try) -> None:
103
+ if not node.handlers:
104
+ # Try/finally, all assignments are guaranteed
105
+ entry = set(self.assigned)
106
+ self.walk_stmts(node.body)
107
+
108
+ post_try = set(self.assigned)
109
+
110
+ # finally is not guaranteed to have any try statements executed,
111
+ # but any deletes should be assumed
112
+ self.assigned = entry.intersection(post_try)
113
+
114
+ self.walk_stmts(node.finalbody)
115
+
116
+ # Anything the finally deletes is removed (the finally cannot delete
117
+ # things which weren't defined on entry, because they aren't definitely
118
+ # assigned)
119
+ for value in entry:
120
+ if value not in self.assigned and value in post_try:
121
+ post_try.remove(value)
122
+ #
123
+ # Anything that was assigned in the try is assigned after
124
+ post_try.update(self.assigned)
125
+ self.assigned = post_try
126
+ return
127
+
128
+ # try/except/maybe finally. Only the finally assignments are guaranteed
129
+ entry = set(self.assigned)
130
+ self.walk_stmts(node.body)
131
+ # Remove anything that got deleted...
132
+
133
+ elseentry = set(self.assigned)
134
+ entry.intersection_update(self.assigned)
135
+
136
+ finalentry = set(entry)
137
+ for handler in node.handlers:
138
+ self.assigned = set(entry)
139
+ handler_name = handler.name
140
+ if handler_name is not None:
141
+ self.set_assigned(handler_name)
142
+
143
+ self.walk_stmts(handler.body)
144
+ # All deletes for any entry apply to the finally
145
+ finalentry.intersection_update(self.assigned)
146
+
147
+ if node.orelse:
148
+ self.assigned = elseentry
149
+ self.walk_stmts(node.orelse)
150
+ finalentry.intersection_update(self.assigned)
151
+
152
+ self.assigned = finalentry
153
+ if node.finalbody:
154
+ self.walk_stmts(node.finalbody)
155
+
156
+ def visit_ClassDef(self, node: ClassDef) -> None:
157
+ for base in node.bases:
158
+ self.visit(base)
159
+
160
+ for kw in node.keywords:
161
+ self.visit(kw)
162
+
163
+ for dec in node.decorator_list:
164
+ self.visit(dec)
165
+
166
+ self.set_assigned(node.name)
167
+
168
+ def visit_FunctionDef(self, node: FunctionDef) -> None:
169
+ self._visit_func_like(node)
170
+
171
+ def visit_AsyncFunctionDef(self, node: AsyncFunctionDef) -> None:
172
+ self._visit_func_like(node)
173
+
174
+ def visit_Lambda(self, node: Lambda) -> None:
175
+ self.visit(node.args)
176
+
177
+ def _visit_func_like(self, node: FunctionDef | AsyncFunctionDef) -> None:
178
+ self.visit(node.args)
179
+
180
+ returns = node.returns
181
+ if returns:
182
+ self.visit(returns)
183
+
184
+ for dec in node.decorator_list:
185
+ self.visit(dec)
186
+
187
+ # Body doesn't run so isn't processed
188
+ self.set_assigned(node.name)
189
+
190
+ def visit_With(self, node: With) -> None:
191
+ # All of the with items will be definitely assigned
192
+ for item in node.items:
193
+ self.visit(item)
194
+
195
+ entry = set(self.assigned)
196
+
197
+ self.walk_stmts(node.body)
198
+ # No assignments are guaranteed, the body can throw and the context
199
+ # manager can suppress it. But all deletes need to be assumed to have
200
+ # occurred
201
+ entry.intersection_update(self.assigned)
202
+ self.assigned = entry
203
+
204
+ def visit_Import(self, node: Import) -> None:
205
+ for name in node.names:
206
+ self.set_assigned(name.asname or name.name.partition(".")[0])
207
+
208
+ def visit_ImportFrom(self, node: ImportFrom) -> None:
209
+ for name in node.names:
210
+ self.set_assigned(name.asname or name.name)
211
+
212
+ def visit_AnnAssign(self, node: AnnAssign) -> None:
213
+ # We won't actually define anything w/o a value
214
+ if node.value:
215
+ self.generic_visit(node)
216
+
217
+ # Don't need AsyncFor, AsyncWith Return as they can't be present at
218
+ # module / class level
219
+
220
+ def visit_For(self, node: For) -> None:
221
+ self.visit(node.iter)
222
+
223
+ entry = set(self.assigned)
224
+ self.visit(node.target)
225
+ self.walk_stmts(node.body)
226
+
227
+ # no assigns are guaranteed, need to assume deletes occur, and
228
+ # they impact the else block too.
229
+ entry.intersection_update(self.assigned)
230
+ if node.orelse:
231
+ self.assigned = set(entry)
232
+ self.walk_stmts(node.orelse)
233
+
234
+ # else may not run, so again no signs guaranteed, deletes are assumed
235
+ entry.intersection_update(self.assigned)
236
+
237
+ self.assigned = entry
238
+
239
+ def visit_If(self, node: If) -> None:
240
+ self.visit(node.test)
241
+ entry = set(self.assigned)
242
+ self.walk_stmts(node.body)
243
+
244
+ post_if = self.assigned
245
+
246
+ if node.orelse:
247
+ self.assigned = set(entry)
248
+
249
+ self.walk_stmts(node.orelse)
250
+
251
+ self.assigned = self.assigned.intersection(post_if)
252
+ else:
253
+ # Remove anything which was deleted
254
+ entry.intersection_update(post_if)
255
+ self.assigned = entry
256
+
257
+ def visit_While(self, node: While) -> None:
258
+ self.visit(node.test)
259
+ entry = set(self.assigned)
260
+ self.walk_stmts(node.body)
261
+
262
+ # Any dels will remove assignment
263
+ entry.intersection_update(self.assigned)
264
+ if node.orelse:
265
+ self.assigned = set(entry)
266
+
267
+ self.walk_stmts(node.orelse)
268
+ # Any dels will remove definite assignment
269
+ entry.intersection_update(self.assigned)
270
+
271
+ # While loop may never be entered, else may never be entered, so
272
+ # it never definitely assigns anything
273
+ self.assigned = entry
274
+
275
+ def walk_stmts(self, nodes: Iterable[stmt]) -> None:
276
+ for node in nodes:
277
+ self.visit(node)
278
+ if isinstance(node, Raise):
279
+ # following statements are unreachable
280
+ return