(&mut self, expr: &'ast ast::Expr)
| 4346 | } |
| 4347 | |
| 4348 | fn visit_expr(&mut self, expr: &'ast ast::Expr) { |
| 4349 | self.with_semantic_checker(|semantic, context| semantic.visit_expr(expr, context)); |
| 4350 | |
| 4351 | self.scopes_by_expression |
| 4352 | .record_expression(expr, self.current_scope()); |
| 4353 | |
| 4354 | match expr { |
| 4355 | ast::Expr::Name(ast::ExprName { ctx, .. }) |
| 4356 | | ast::Expr::Attribute(ast::ExprAttribute { ctx, .. }) |
| 4357 | | ast::Expr::Subscript(ast::ExprSubscript { ctx, .. }) => { |
| 4358 | // Record place effects after walking the expression. For names, this is |
| 4359 | // equivalent because `walk_expr` is a no-op; for attribute/subscript places, |
| 4360 | // child evaluation can introduce bindings (for example via walrus operators), |
| 4361 | // and those bindings need to exist before we register parent/member associations. |
| 4362 | let mut deferred_effects = None; |
| 4363 | if let Some(mut place_expr) = PlaceExpr::try_from_expr(expr) { |
| 4364 | if let Some(method_scope_id) = self.is_method_or_eagerly_executed_in_method() |
| 4365 | && let PlaceExpr::Member(member) = &mut place_expr |
| 4366 | && member.is_instance_attribute_candidate() |
| 4367 | && let Some(attribute) = expr.as_attribute_expr() |
| 4368 | { |
| 4369 | // We specifically mark direct attribute assignments to the first |
| 4370 | // parameter of a method, i.e. typically `self` or `cls`. |
| 4371 | // However, we must check that the symbol hasn't been shadowed by an |
| 4372 | // intermediate scope (e.g., a comprehension variable: `for self in [...]`) |
| 4373 | // and that the AST base is still the original name rather than a |
| 4374 | // rebinding expression such as `(self := other).x`. |
| 4375 | let accessed_object_refers_to_first_parameter = |
| 4376 | self.current_first_parameter_name.is_some_and(|first| { |
| 4377 | attribute |
| 4378 | .value |
| 4379 | .as_name_expr() |
| 4380 | .is_some_and(|name| name.id == first) |
| 4381 | && !self.is_symbol_bound_in_intermediate_eager_scopes( |
| 4382 | first, |
| 4383 | method_scope_id, |
| 4384 | ) |
| 4385 | }); |
| 4386 | |
| 4387 | if accessed_object_refers_to_first_parameter { |
| 4388 | member.mark_instance_attribute(); |
| 4389 | } |
| 4390 | } |
| 4391 | |
| 4392 | let (is_use, is_definition) = match (ctx, self.current_assignment()) { |
| 4393 | (ast::ExprContext::Store, Some(CurrentAssignment::AugAssign(_))) => { |
| 4394 | // For augmented assignment, the target expression is also used. |
| 4395 | (true, true) |
| 4396 | } |
| 4397 | (ast::ExprContext::Load, _) => (true, false), |
| 4398 | (ast::ExprContext::Store, _) => (false, true), |
| 4399 | (ast::ExprContext::Del, _) => (true, true), |
| 4400 | (ast::ExprContext::Invalid, _) => (false, false), |
| 4401 | }; |
| 4402 | deferred_effects = Some((place_expr, is_use, is_definition)); |
| 4403 | } |
| 4404 | |
| 4405 | walk_expr(self, expr); |
no test coverage detected