From 0b8ffe36756935344ca29cdeb747940da5d6641e Mon Sep 17 00:00:00 2001 From: Stas Ilinskiy Date: Sun, 27 Nov 2022 11:42:10 -0600 Subject: [PATCH 1/4] [partially defined] handle class definitions --- mypy/partially_defined.py | 18 +++++++++++++----- test-data/unit/check-partially-defined.test | 8 ++++++++ 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/mypy/partially_defined.py b/mypy/partially_defined.py index 9a8c397c0c28a..8a408948b38d4 100644 --- a/mypy/partially_defined.py +++ b/mypy/partially_defined.py @@ -7,6 +7,7 @@ AssignmentExpr, AssignmentStmt, BreakStmt, + ClassDef, Context, ContinueStmt, DictionaryComprehension, @@ -258,13 +259,16 @@ def variable_may_be_undefined(self, name: str, context: Context) -> None: if self.msg.errors.is_error_code_enabled(errorcodes.PARTIALLY_DEFINED): self.msg.variable_may_be_undefined(name, context) + def process_definition(self, name: str) -> None: + # Was this name previously used? If yes, it's a use-before-definition error. + refs = self.tracker.pop_undefined_ref(name) + for ref in refs: + self.var_used_before_def(name, ref) + self.tracker.record_definition(name) + def process_lvalue(self, lvalue: Lvalue | None) -> None: if isinstance(lvalue, NameExpr): - # Was this name previously used? If yes, it's a use-before-definition error. - refs = self.tracker.pop_undefined_ref(lvalue.name) - for ref in refs: - self.var_used_before_def(lvalue.name, ref) - self.tracker.record_definition(lvalue.name) + self.process_definition(lvalue.name) elif isinstance(lvalue, StarExpr): self.process_lvalue(lvalue.expr) elif isinstance(lvalue, (ListExpr, TupleExpr)): @@ -435,6 +439,10 @@ def visit_with_stmt(self, o: WithStmt) -> None: self.process_lvalue(idx) o.body.accept(self) + def visit_class_def(self, o: ClassDef) -> None: + self.process_definition(o.name) + super().visit_class_def(o) + def visit_import(self, o: Import) -> None: for mod, alias in o.ids: if alias is not None: diff --git a/test-data/unit/check-partially-defined.test b/test-data/unit/check-partially-defined.test index 85bf08079f793..d9f29ee12714d 100644 --- a/test-data/unit/check-partially-defined.test +++ b/test-data/unit/check-partially-defined.test @@ -140,6 +140,14 @@ def f0(b: bool) -> None: fn = lambda: 2 y = fn # E: Name "fn" may be undefined +[case testUseBeforeDefClass] +# flags: --enable-error-code partially-defined --enable-error-code use-before-def +def f(x: A): + pass + +y = A() # E: Name "A" is used before definition + +class A: pass [case testGenerator] # flags: --enable-error-code partially-defined if int(): From 82c8c9b0046d6dc5f99897ba62aa2e4f31e800b0 Mon Sep 17 00:00:00 2001 From: Stas Ilinskiy Date: Sun, 27 Nov 2022 11:56:56 -0600 Subject: [PATCH 2/4] handle functions --- mypy/partially_defined.py | 2 +- test-data/unit/check-partially-defined.test | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/mypy/partially_defined.py b/mypy/partially_defined.py index 8a408948b38d4..7c213254b4bf3 100644 --- a/mypy/partially_defined.py +++ b/mypy/partially_defined.py @@ -318,7 +318,7 @@ def visit_match_stmt(self, o: MatchStmt) -> None: self.tracker.end_branch_statement() def visit_func_def(self, o: FuncDef) -> None: - self.tracker.record_definition(o.name) + self.process_definition(o.name) self.tracker.enter_scope() super().visit_func_def(o) self.tracker.exit_scope() diff --git a/test-data/unit/check-partially-defined.test b/test-data/unit/check-partially-defined.test index d9f29ee12714d..3f6c88d6b036a 100644 --- a/test-data/unit/check-partially-defined.test +++ b/test-data/unit/check-partially-defined.test @@ -142,12 +142,15 @@ def f0(b: bool) -> None: [case testUseBeforeDefClass] # flags: --enable-error-code partially-defined --enable-error-code use-before-def -def f(x: A): +def f(x: A): # No error here. pass - y = A() # E: Name "A" is used before definition - class A: pass + +[case testUseBeforeDefFunc] +# flags: --enable-error-code partially-defined --enable-error-code use-before-def +foo() # E: Name "foo" is used before definition +def foo(): pass [case testGenerator] # flags: --enable-error-code partially-defined if int(): From cf64b649aa2ca2d184c1150994fa51cb04d1d07a Mon Sep 17 00:00:00 2001 From: Stas Ilinskiy Date: Mon, 28 Nov 2022 17:59:57 -0600 Subject: [PATCH 3/4] enter and exit on class scope --- mypy/partially_defined.py | 2 ++ test-data/unit/check-partially-defined.test | 12 ++++++++++++ 2 files changed, 14 insertions(+) diff --git a/mypy/partially_defined.py b/mypy/partially_defined.py index abef10ad7afb9..c2c925e0477c2 100644 --- a/mypy/partially_defined.py +++ b/mypy/partially_defined.py @@ -482,7 +482,9 @@ def visit_with_stmt(self, o: WithStmt) -> None: def visit_class_def(self, o: ClassDef) -> None: self.process_definition(o.name) + self.tracker.enter_scope() super().visit_class_def(o) + self.tracker.exit_scope() def visit_import(self, o: Import) -> None: for mod, alias in o.ids: diff --git a/test-data/unit/check-partially-defined.test b/test-data/unit/check-partially-defined.test index 68fec4f737d4d..20a4057be969e 100644 --- a/test-data/unit/check-partially-defined.test +++ b/test-data/unit/check-partially-defined.test @@ -147,6 +147,18 @@ def f(x: A): # No error here. y = A() # E: Name "A" is used before definition class A: pass +[case testClassScope] +# flags: --enable-error-code partially-defined --enable-error-code use-before-def +class C: + def f0(self) -> None: pass + + def f2(self) -> None: + f0() # No error. + self.f0() # No error. + +f0() # E: Name "f0" is used before definition +def f0() -> None: pass + [case testUseBeforeDefFunc] # flags: --enable-error-code partially-defined --enable-error-code use-before-def foo() # E: Name "foo" is used before definition From 67099bb8b2f89ccef2a326dd2a6761cea0b42874 Mon Sep 17 00:00:00 2001 From: Stas Ilinskiy Date: Mon, 28 Nov 2022 18:09:02 -0600 Subject: [PATCH 4/4] More tests --- test-data/unit/check-partially-defined.test | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test-data/unit/check-partially-defined.test b/test-data/unit/check-partially-defined.test index 20a4057be969e..7c10306684ca8 100644 --- a/test-data/unit/check-partially-defined.test +++ b/test-data/unit/check-partially-defined.test @@ -150,6 +150,7 @@ class A: pass [case testClassScope] # flags: --enable-error-code partially-defined --enable-error-code use-before-def class C: + x = 0 def f0(self) -> None: pass def f2(self) -> None: @@ -158,6 +159,16 @@ class C: f0() # E: Name "f0" is used before definition def f0() -> None: pass +y = x # E: Name "x" is used before definition +x = 1 + +[case testClassInsideFunction] +# flags: --enable-error-code partially-defined --enable-error-code use-before-def +def f() -> None: + class C: pass + +c = C() # E: Name "C" is used before definition +class C: pass [case testUseBeforeDefFunc] # flags: --enable-error-code partially-defined --enable-error-code use-before-def