Skip to content

Commit 34f5cec

Browse files
authored
Fix module and protocol subtyping, module hasattr (#13778)
Fixes #13771 (comment)
1 parent f85dfa1 commit 34f5cec

File tree

7 files changed

+27
-8
lines changed

7 files changed

+27
-8
lines changed

mypy/checker.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6472,8 +6472,14 @@ def partition_union_by_attr(
64726472
return with_attr, without_attr
64736473

64746474
def has_valid_attribute(self, typ: Type, name: str) -> bool:
6475-
if isinstance(get_proper_type(typ), AnyType):
6475+
p_typ = get_proper_type(typ)
6476+
if isinstance(p_typ, AnyType):
64766477
return False
6478+
if isinstance(p_typ, Instance) and p_typ.extra_attrs and p_typ.extra_attrs.mod_name:
6479+
# Presence of module_symbol_table means this check will skip ModuleType.__getattr__
6480+
module_symbol_table = p_typ.type.names
6481+
else:
6482+
module_symbol_table = None
64776483
with self.msg.filter_errors() as watcher:
64786484
analyze_member_access(
64796485
name,
@@ -6487,6 +6493,7 @@ def has_valid_attribute(self, typ: Type, name: str) -> bool:
64876493
chk=self,
64886494
# This is not a real attribute lookup so don't mess with deferring nodes.
64896495
no_deferral=True,
6496+
module_symbol_table=module_symbol_table,
64906497
)
64916498
return not watcher.has_new_errors()
64926499

mypy/subtypes.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1082,6 +1082,7 @@ def find_member(
10821082
and name not in ["__getattr__", "__setattr__", "__getattribute__"]
10831083
and not is_operator
10841084
and not class_obj
1085+
and itype.extra_attrs is None # skip ModuleType.__getattr__
10851086
):
10861087
for method_name in ("__getattribute__", "__getattr__"):
10871088
# Normally, mypy assumes that instances that define __getattr__ have all

mypy/types.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1192,6 +1192,9 @@ def __eq__(self, other: object) -> bool:
11921192
def copy(self) -> ExtraAttrs:
11931193
return ExtraAttrs(self.attrs.copy(), self.immutable.copy(), self.mod_name)
11941194

1195+
def __repr__(self) -> str:
1196+
return f"ExtraAttrs({self.attrs!r}, {self.immutable!r}, {self.mod_name!r})"
1197+
11951198

11961199
class Instance(ProperType):
11971200
"""An instance type of form C[T1, ..., Tn].

test-data/unit/check-isinstance.test

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2895,6 +2895,13 @@ else:
28952895
mod.y # E: Module has no attribute "y"
28962896
reveal_type(mod.x) # N: Revealed type is "builtins.int"
28972897

2898+
if hasattr(mod, "x"):
2899+
mod.y # E: Module has no attribute "y"
2900+
reveal_type(mod.x) # N: Revealed type is "builtins.int"
2901+
else:
2902+
mod.y # E: Module has no attribute "y"
2903+
reveal_type(mod.x) # N: Revealed type is "builtins.int"
2904+
28982905
[file mod.py]
28992906
x: int
29002907
[builtins fixtures/module.pyi]

test-data/unit/check-modules.test

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1672,11 +1672,11 @@ mod_any: Any = m
16721672
mod_int: int = m # E: Incompatible types in assignment (expression has type Module, variable has type "int")
16731673

16741674
reveal_type(mod_mod) # N: Revealed type is "types.ModuleType"
1675-
mod_mod.a # E: Module has no attribute "a"
1675+
reveal_type(mod_mod.a) # N: Revealed type is "Any"
16761676
reveal_type(mod_mod2) # N: Revealed type is "types.ModuleType"
1677-
mod_mod2.a # E: Module has no attribute "a"
1677+
reveal_type(mod_mod2.a) # N: Revealed type is "Any"
16781678
reveal_type(mod_mod3) # N: Revealed type is "types.ModuleType"
1679-
mod_mod3.a # E: Module has no attribute "a"
1679+
reveal_type(mod_mod3.a) # N: Revealed type is "Any"
16801680
reveal_type(mod_any) # N: Revealed type is "Any"
16811681

16821682
[file m.py]
@@ -1736,7 +1736,7 @@ if bool():
17361736
else:
17371737
x = n
17381738

1739-
x.a # E: Module has no attribute "a"
1739+
reveal_type(x.nope) # N: Revealed type is "Any"
17401740
reveal_type(x.__file__) # N: Revealed type is "builtins.str"
17411741

17421742
[file m.py]

test-data/unit/fine-grained-inspect.test

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ class C: ...
236236
[builtins fixtures/module.pyi]
237237
[out]
238238
==
239-
{"<pack.bar>": ["C", "__annotations__", "__doc__", "__file__", "__name__", "__package__", "bar", "x"], "ModuleType": ["__file__"]}
239+
{"<pack.bar>": ["C", "__annotations__", "__doc__", "__file__", "__name__", "__package__", "bar", "x"], "ModuleType": ["__file__", "__getattr__"]}
240240

241241
[case testInspectModuleDef]
242242
# inspect2: --show=definition --include-kind foo.py:2:1

test-data/unit/lib-stub/types.pyi

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1-
from typing import TypeVar
1+
from typing import Any, TypeVar
22
import sys
33

44
_T = TypeVar('_T')
55

66
def coroutine(func: _T) -> _T: pass
77

88
class ModuleType:
9-
__file__ = ... # type: str
9+
__file__: str
10+
def __getattr__(self, name: str) -> Any: pass
1011

1112
if sys.version_info >= (3, 10):
1213
class Union:

0 commit comments

Comments
 (0)