Skip to content

Commit d094c38

Browse files
authored
[use before def] handle class and function definitions (#14203)
Previously, we would ignore any class definitions and would fail to detect undefined classes and functions. This updates the logic to handle them. Closes #686
1 parent 8ab0ef1 commit d094c38

File tree

2 files changed

+50
-6
lines changed

2 files changed

+50
-6
lines changed

mypy/partially_defined.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
AssignmentExpr,
88
AssignmentStmt,
99
BreakStmt,
10+
ClassDef,
1011
Context,
1112
ContinueStmt,
1213
DictionaryComprehension,
@@ -271,13 +272,16 @@ def variable_may_be_undefined(self, name: str, context: Context) -> None:
271272
if self.msg.errors.is_error_code_enabled(errorcodes.PARTIALLY_DEFINED):
272273
self.msg.variable_may_be_undefined(name, context)
273274

275+
def process_definition(self, name: str) -> None:
276+
# Was this name previously used? If yes, it's a use-before-definition error.
277+
refs = self.tracker.pop_undefined_ref(name)
278+
for ref in refs:
279+
self.var_used_before_def(name, ref)
280+
self.tracker.record_definition(name)
281+
274282
def process_lvalue(self, lvalue: Lvalue | None) -> None:
275283
if isinstance(lvalue, NameExpr):
276-
# Was this name previously used? If yes, it's a use-before-definition error.
277-
refs = self.tracker.pop_undefined_ref(lvalue.name)
278-
for ref in refs:
279-
self.var_used_before_def(lvalue.name, ref)
280-
self.tracker.record_definition(lvalue.name)
284+
self.process_definition(lvalue.name)
281285
elif isinstance(lvalue, StarExpr):
282286
self.process_lvalue(lvalue.expr)
283287
elif isinstance(lvalue, (ListExpr, TupleExpr)):
@@ -327,7 +331,7 @@ def visit_match_stmt(self, o: MatchStmt) -> None:
327331
self.tracker.end_branch_statement()
328332

329333
def visit_func_def(self, o: FuncDef) -> None:
330-
self.tracker.record_definition(o.name)
334+
self.process_definition(o.name)
331335
self.tracker.enter_scope()
332336
super().visit_func_def(o)
333337
self.tracker.exit_scope()
@@ -476,6 +480,12 @@ def visit_with_stmt(self, o: WithStmt) -> None:
476480
self.process_lvalue(idx)
477481
o.body.accept(self)
478482

483+
def visit_class_def(self, o: ClassDef) -> None:
484+
self.process_definition(o.name)
485+
self.tracker.enter_scope()
486+
super().visit_class_def(o)
487+
self.tracker.exit_scope()
488+
479489
def visit_import(self, o: Import) -> None:
480490
for mod, alias in o.ids:
481491
if alias is not None:

test-data/unit/check-partially-defined.test

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,40 @@ def f0(b: bool) -> None:
140140
fn = lambda: 2
141141
y = fn # E: Name "fn" may be undefined
142142

143+
[case testUseBeforeDefClass]
144+
# flags: --enable-error-code partially-defined --enable-error-code use-before-def
145+
def f(x: A): # No error here.
146+
pass
147+
y = A() # E: Name "A" is used before definition
148+
class A: pass
149+
150+
[case testClassScope]
151+
# flags: --enable-error-code partially-defined --enable-error-code use-before-def
152+
class C:
153+
x = 0
154+
def f0(self) -> None: pass
155+
156+
def f2(self) -> None:
157+
f0() # No error.
158+
self.f0() # No error.
159+
160+
f0() # E: Name "f0" is used before definition
161+
def f0() -> None: pass
162+
y = x # E: Name "x" is used before definition
163+
x = 1
164+
165+
[case testClassInsideFunction]
166+
# flags: --enable-error-code partially-defined --enable-error-code use-before-def
167+
def f() -> None:
168+
class C: pass
169+
170+
c = C() # E: Name "C" is used before definition
171+
class C: pass
172+
173+
[case testUseBeforeDefFunc]
174+
# flags: --enable-error-code partially-defined --enable-error-code use-before-def
175+
foo() # E: Name "foo" is used before definition
176+
def foo(): pass
143177
[case testGenerator]
144178
# flags: --enable-error-code partially-defined
145179
if int():

0 commit comments

Comments
 (0)