Propagate module references across assignments. Recursively handles the simple form of iterable unpacking; doesn't handle advanced unpacking with *rest, dictionary unpacking, etc. In an expression like x = y = z, z is the rval and lvals will be [x, y].
(
self, lvals: list[Lvalue], rval: Expression, ctx: AssignmentStmt
)
| 5259 | self.fail(message_registry.CLASS_VAR_OUTSIDE_OF_CLASS, context) |
| 5260 | |
| 5261 | def process_module_assignment( |
| 5262 | self, lvals: list[Lvalue], rval: Expression, ctx: AssignmentStmt |
| 5263 | ) -> None: |
| 5264 | """Propagate module references across assignments. |
| 5265 | |
| 5266 | Recursively handles the simple form of iterable unpacking; doesn't |
| 5267 | handle advanced unpacking with *rest, dictionary unpacking, etc. |
| 5268 | |
| 5269 | In an expression like x = y = z, z is the rval and lvals will be [x, |
| 5270 | y]. |
| 5271 | |
| 5272 | """ |
| 5273 | if isinstance(rval, (TupleExpr, ListExpr)) and all( |
| 5274 | isinstance(v, TupleExpr) for v in lvals |
| 5275 | ): |
| 5276 | # rval and all lvals are either list or tuple, so we are dealing |
| 5277 | # with unpacking assignment like `x, y = a, b`. Mypy didn't |
| 5278 | # understand our all(isinstance(...)), so cast them as TupleExpr |
| 5279 | # so mypy knows it is safe to access their .items attribute. |
| 5280 | seq_lvals = cast(list[TupleExpr], lvals) |
| 5281 | # given an assignment like: |
| 5282 | # (x, y) = (m, n) = (a, b) |
| 5283 | # we now have: |
| 5284 | # seq_lvals = [(x, y), (m, n)] |
| 5285 | # seq_rval = (a, b) |
| 5286 | # We now zip this into: |
| 5287 | # elementwise_assignments = [(a, x, m), (b, y, n)] |
| 5288 | # where each elementwise assignment includes one element of rval and the |
| 5289 | # corresponding element of each lval. Basically we unpack |
| 5290 | # (x, y) = (m, n) = (a, b) |
| 5291 | # into elementwise assignments |
| 5292 | # x = m = a |
| 5293 | # y = n = b |
| 5294 | # and then we recursively call this method for each of those assignments. |
| 5295 | # If the rval and all lvals are not all of the same length, zip will just ignore |
| 5296 | # extra elements, so no error will be raised here; mypy will later complain |
| 5297 | # about the length mismatch in type-checking. |
| 5298 | elementwise_assignments = zip(rval.items, *[v.items for v in seq_lvals]) |
| 5299 | for rv, *lvs in elementwise_assignments: |
| 5300 | self.process_module_assignment(lvs, rv, ctx) |
| 5301 | elif isinstance(rval, RefExpr): |
| 5302 | rnode = self.lookup_type_node(rval) |
| 5303 | if rnode and isinstance(rnode.node, MypyFile): |
| 5304 | for lval in lvals: |
| 5305 | if not isinstance(lval, RefExpr): |
| 5306 | continue |
| 5307 | # respect explicitly annotated type |
| 5308 | if isinstance(lval.node, Var) and lval.node.type is not None: |
| 5309 | continue |
| 5310 | |
| 5311 | # We can handle these assignments to locals and to self |
| 5312 | if isinstance(lval, NameExpr): |
| 5313 | lnode = self.current_symbol_table().get(lval.name) |
| 5314 | elif isinstance(lval, MemberExpr) and self.is_self_member_ref(lval): |
| 5315 | assert self.type is not None |
| 5316 | lnode = self.type.names.get(lval.name) |
| 5317 | else: |
| 5318 | continue |
no test coverage detected