@@ -128,6 +128,56 @@ def _get_unpacking_extra_info(node: nodes.Assign, inferred: InferenceResult) ->
128128 return more
129129
130130
131+ def _is_bound_before_class (
132+ class_node : nodes .ClassDef ,
133+ lookup_scope : nodes .LocalsDictNodeNG ,
134+ bindings : list [nodes .NodeNG ],
135+ ) -> bool :
136+ """Whether a module-level or builtin binding exists when the class body runs."""
137+ if not bindings :
138+ return False
139+ if lookup_scope is not class_node .root ():
140+ return True
141+ if any (
142+ isinstance (ancestor , (nodes .FunctionDef , nodes .Lambda ))
143+ for ancestor in class_node .node_ancestors ()
144+ ):
145+ # The class body runs when the function is called, which can be after
146+ # the module-level binding.
147+ return True
148+ return any (
149+ binding .lineno is not None and binding .lineno < class_node .lineno
150+ for binding in bindings
151+ )
152+
153+
154+ def _is_def_in_class_body (
155+ defstmt : _base_nodes .Statement , frame : nodes .LocalsDictNodeNG
156+ ) -> bool :
157+ """Whether ``defstmt`` is a def or class statement directly in the class body frame.
158+
159+ Such a statement is its own frame, but it binds its name in the class body.
160+ """
161+ return (
162+ isinstance (defstmt , (nodes .FunctionDef , nodes .ClassDef ))
163+ and isinstance (frame , nodes .ClassDef )
164+ and defstmt .parent .frame () is frame
165+ )
166+
167+
168+ def _has_outer_binding (
169+ name : str , defframe : nodes .LocalsDictNodeNG , class_node : nodes .ClassDef | None
170+ ) -> bool :
171+ """Whether name has a module-level or builtin binding.
172+
173+ When class_node is given, the binding must also exist when its body runs.
174+ """
175+ lookup_scope , bindings = defframe .root ().lookup (name )
176+ if class_node is None :
177+ return bool (bindings )
178+ return _is_bound_before_class (class_node , lookup_scope , bindings )
179+
180+
131181def _detect_global_scope (
132182 node : nodes .Name ,
133183 frame : nodes .LocalsDictNodeNG ,
@@ -2374,6 +2424,9 @@ def _is_variable_violation(
23742424 maybe_before_assign = True
23752425 annotation_return = False
23762426 use_outer_definition = False
2427+ def_in_class_body = _is_def_in_class_body (defstmt , frame )
2428+ if def_in_class_body :
2429+ defframe = frame
23772430 if frame is not defframe :
23782431 maybe_before_assign = _detect_global_scope (node , frame , defframe )
23792432 elif defframe .parent is None :
@@ -2394,7 +2447,9 @@ def _is_variable_violation(
23942447 isinstance (frame , nodes .FunctionDef )
23952448 or isinstance (node .frame (), nodes .Lambda )
23962449 ) and _assigned_locally (node )
2397- if not forbid_lookup and defframe .root ().lookup (node .name )[1 ]:
2450+ if not forbid_lookup and _has_outer_binding (
2451+ node .name , defframe , frame if def_in_class_body else None
2452+ ):
23982453 maybe_before_assign = False
23992454 use_outer_definition = stmt == defstmt and not isinstance (
24002455 defnode , nodes .Comprehension
0 commit comments