From 391a7ae3ecc2493cc43fa75940b817f8d521f0bd Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 8 Oct 2017 16:17:58 +0200 Subject: [PATCH 1/4] Push correct enclosing class while deferring a method --- mypy/checker.py | 14 +++++++++++--- test-data/unit/check-classes.test | 26 ++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 05a5e91552e0..366560404d90 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -259,15 +259,14 @@ def check_top_level(self, node: MypyFile) -> None: def handle_cannot_determine_type(self, name: str, context: Context) -> None: node = self.scope.top_function() - if (self.pass_num < LAST_PASS and node is not None - and isinstance(node, (FuncDef, LambdaExpr))): + if self.pass_num < LAST_PASS and isinstance(node, (FuncDef, LambdaExpr)): # Don't report an error yet. Just defer. if self.errors.type_name: type_name = self.errors.type_name[-1] else: type_name = None # Shouldn't we freeze the entire scope? - active_class = self.scope.active_class() + active_class = self.scope.enclosing_class() self.deferred_nodes.append(DeferredNode(node, type_name, active_class)) # Set a marker so that we won't infer additional types in this # function. Any inferred types could be bogus, because there's at @@ -3323,6 +3322,15 @@ def active_class(self) -> Optional[TypeInfo]: return self.stack[-1] return None + def enclosing_class(self) -> Optional[TypeInfo]: + top = self.top_function() + assert top, "This method must be called from inside a function" + index = self.stack.index(top) + for e in reversed(self.stack[:index]): + if isinstance(e, TypeInfo): + return e + return None + def active_self_type(self) -> Optional[Union[Instance, TupleType]]: info = self.active_class() if info: diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 7243de98420b..98a791aed69c 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -3995,3 +3995,29 @@ class E(metaclass=t.M): pass class F(six.with_metaclass(t.M)): pass @six.add_metaclass(t.M) class G: pass + +[case testCorrectEnclosingClassPushedInDeferred] +class C: + def __getattr__(self, attr: str) -> int: + x: F + return x.f + +class F: + def __init__(self, f: int) -> None: + self.f = f +[out] + +[case testCorrectEnclosingClassPushedInDeferred2] +from typing import TypeVar +T = TypeVar('T', bound=C) +class C: + def m(self: T) -> T: + class Inner: + x: F + f = x.f + return self + +class F: + def __init__(self, f: int) -> None: + self.f = f +[out] From bce70d54d7632e314522d2230b6f74b10858d5b9 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 8 Oct 2017 17:38:33 +0200 Subject: [PATCH 2/4] CR --- mypy/checker.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 366560404d90..5a8734134b28 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -266,8 +266,8 @@ def handle_cannot_determine_type(self, name: str, context: Context) -> None: else: type_name = None # Shouldn't we freeze the entire scope? - active_class = self.scope.enclosing_class() - self.deferred_nodes.append(DeferredNode(node, type_name, active_class)) + enclosing_class = self.scope.enclosing_class() + self.deferred_nodes.append(DeferredNode(node, type_name, enclosing_class)) # Set a marker so that we won't infer additional types in this # function. Any inferred types could be bogus, because there's at # least one type that we don't know. From 1e6302f95bd7171f4f3afb8578d6e32f58539dec Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Wed, 11 Oct 2017 18:14:25 +0200 Subject: [PATCH 3/4] Fix regressoin --- mypy/checker.py | 7 ++++--- test-data/unit/check-classes.test | 9 +++++++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 5a8734134b28..59102994b36e 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -3326,9 +3326,10 @@ def enclosing_class(self) -> Optional[TypeInfo]: top = self.top_function() assert top, "This method must be called from inside a function" index = self.stack.index(top) - for e in reversed(self.stack[:index]): - if isinstance(e, TypeInfo): - return e + assert index + enclosing = self.stack[index - 1] + if isinstance(enclosing, TypeInfo): + return enclosing return None def active_self_type(self) -> Optional[Union[Instance, TupleType]]: diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 7168180feab5..adc6051f6a51 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -4022,6 +4022,15 @@ class F: self.f = f [out] +[case testCorrectEnclosingClassPushedInDeferred3] +class A: + def f(self) -> None: + def g(x: int) -> int: + return y + +y = int() +[out] + [case testMetaclassMemberAccessViaType] from typing import Type class M(type): From 02e495c1f9648905864e6330362a387d368d7f24 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Wed, 11 Oct 2017 20:22:44 +0200 Subject: [PATCH 4/4] Add explanatory assert message --- mypy/checker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/checker.py b/mypy/checker.py index 59102994b36e..393c23e381b4 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -3326,7 +3326,7 @@ def enclosing_class(self) -> Optional[TypeInfo]: top = self.top_function() assert top, "This method must be called from inside a function" index = self.stack.index(top) - assert index + assert index, "Scope stack must always start with a module" enclosing = self.stack[index - 1] if isinstance(enclosing, TypeInfo): return enclosing