From b37c47955588d46fe94f10c0bdc1801317656b30 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Thu, 29 Sep 2022 21:55:52 -0700 Subject: [PATCH 1/3] Fix module and protocol subtyping --- mypy/subtypes.py | 1 + test-data/unit/lib-stub/types.pyi | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/mypy/subtypes.py b/mypy/subtypes.py index bc35b1a4d683..7be877fd2cfc 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -1078,6 +1078,7 @@ def find_member( and name not in ["__getattr__", "__setattr__", "__getattribute__"] and not is_operator and not class_obj + and itype.extra_attrs is None # skip ModuleType.__getattr__ ): for method_name in ("__getattribute__", "__getattr__"): # Normally, mypy assumes that instances that define __getattr__ have all diff --git a/test-data/unit/lib-stub/types.pyi b/test-data/unit/lib-stub/types.pyi index 4a6093f701cc..012fd8503377 100644 --- a/test-data/unit/lib-stub/types.pyi +++ b/test-data/unit/lib-stub/types.pyi @@ -1,4 +1,4 @@ -from typing import TypeVar +from typing import Any, TypeVar import sys _T = TypeVar('_T') @@ -6,7 +6,8 @@ _T = TypeVar('_T') def coroutine(func: _T) -> _T: pass class ModuleType: - __file__ = ... # type: str + __file__: str + def __getattr__(self, name: str) -> Any: pass if sys.version_info >= (3, 10): class Union: From 4d38174371923de2a7c793ff114c1b53fe3d4d64 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Thu, 29 Sep 2022 22:46:23 -0700 Subject: [PATCH 2/3] fix module hasattr --- mypy/checker.py | 9 ++++++++- mypy/types.py | 3 +++ test-data/unit/check-isinstance.test | 7 +++++++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/mypy/checker.py b/mypy/checker.py index 8dac00bba23a..f4566ec6bb6f 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -6472,8 +6472,14 @@ def partition_union_by_attr( return with_attr, without_attr def has_valid_attribute(self, typ: Type, name: str) -> bool: - if isinstance(get_proper_type(typ), AnyType): + p_typ = get_proper_type(typ) + if isinstance(p_typ, AnyType): return False + if isinstance(p_typ, Instance) and p_typ.extra_attrs and p_typ.extra_attrs.mod_name: + # Presence of module_symbol_table means this check will skip ModuleType.__getattr__ + module_symbol_table = p_typ.type.names + else: + module_symbol_table = None with self.msg.filter_errors() as watcher: analyze_member_access( name, @@ -6487,6 +6493,7 @@ def has_valid_attribute(self, typ: Type, name: str) -> bool: chk=self, # This is not a real attribute lookup so don't mess with deferring nodes. no_deferral=True, + module_symbol_table=module_symbol_table, ) return not watcher.has_new_errors() diff --git a/mypy/types.py b/mypy/types.py index d82b511f7d5a..e322cf02505f 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -1192,6 +1192,9 @@ def __eq__(self, other: object) -> bool: def copy(self) -> ExtraAttrs: return ExtraAttrs(self.attrs.copy(), self.immutable.copy(), self.mod_name) + def __repr__(self) -> str: + return f"ExtraAttrs({self.attrs!r}, {self.immutable!r}, {self.mod_name!r})" + class Instance(ProperType): """An instance type of form C[T1, ..., Tn]. diff --git a/test-data/unit/check-isinstance.test b/test-data/unit/check-isinstance.test index c06802e69a69..40b335adac54 100644 --- a/test-data/unit/check-isinstance.test +++ b/test-data/unit/check-isinstance.test @@ -2895,6 +2895,13 @@ else: mod.y # E: Module has no attribute "y" reveal_type(mod.x) # N: Revealed type is "builtins.int" +if hasattr(mod, "x"): + mod.y # E: Module has no attribute "y" + reveal_type(mod.x) # N: Revealed type is "builtins.int" +else: + mod.y # E: Module has no attribute "y" + reveal_type(mod.x) # N: Revealed type is "builtins.int" + [file mod.py] x: int [builtins fixtures/module.pyi] From 1a1f3a13b4e77d7b42641c791ddcc54b39bbb1e5 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Thu, 29 Sep 2022 22:48:46 -0700 Subject: [PATCH 3/3] random tests --- test-data/unit/check-modules.test | 8 ++++---- test-data/unit/fine-grained-inspect.test | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/test-data/unit/check-modules.test b/test-data/unit/check-modules.test index 558a53973818..f90bd4a3c68d 100644 --- a/test-data/unit/check-modules.test +++ b/test-data/unit/check-modules.test @@ -1672,11 +1672,11 @@ mod_any: Any = m mod_int: int = m # E: Incompatible types in assignment (expression has type Module, variable has type "int") reveal_type(mod_mod) # N: Revealed type is "types.ModuleType" -mod_mod.a # E: Module has no attribute "a" +reveal_type(mod_mod.a) # N: Revealed type is "Any" reveal_type(mod_mod2) # N: Revealed type is "types.ModuleType" -mod_mod2.a # E: Module has no attribute "a" +reveal_type(mod_mod2.a) # N: Revealed type is "Any" reveal_type(mod_mod3) # N: Revealed type is "types.ModuleType" -mod_mod3.a # E: Module has no attribute "a" +reveal_type(mod_mod3.a) # N: Revealed type is "Any" reveal_type(mod_any) # N: Revealed type is "Any" [file m.py] @@ -1736,7 +1736,7 @@ if bool(): else: x = n -x.a # E: Module has no attribute "a" +reveal_type(x.nope) # N: Revealed type is "Any" reveal_type(x.__file__) # N: Revealed type is "builtins.str" [file m.py] diff --git a/test-data/unit/fine-grained-inspect.test b/test-data/unit/fine-grained-inspect.test index 5661c14bc093..a52db3959633 100644 --- a/test-data/unit/fine-grained-inspect.test +++ b/test-data/unit/fine-grained-inspect.test @@ -236,7 +236,7 @@ class C: ... [builtins fixtures/module.pyi] [out] == -{"": ["C", "__annotations__", "__doc__", "__file__", "__name__", "__package__", "bar", "x"], "ModuleType": ["__file__"]} +{"": ["C", "__annotations__", "__doc__", "__file__", "__name__", "__package__", "bar", "x"], "ModuleType": ["__file__", "__getattr__"]} [case testInspectModuleDef] # inspect2: --show=definition --include-kind foo.py:2:1