From 63a20bc17a2d6a2d93358f1f4345d2d4142baa2f Mon Sep 17 00:00:00 2001 From: sobolevn Date: Sun, 14 Nov 2021 11:49:13 +0300 Subject: [PATCH 1/2] Now `Type[None].__bool__` is a thing, refs #11539 --- mypy/checkmember.py | 27 +++++++++++++++++++++------ test-data/unit/check-basic.test | 20 ++++++++++++++++++++ 2 files changed, 41 insertions(+), 6 deletions(-) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 3efc39753627..04869493244e 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -291,6 +291,8 @@ def analyze_type_type_member_access(name: str, # Access member on metaclass object via Type[Type[C]] if isinstance(typ.item.item, Instance): item = typ.item.item.type.metaclass_type + elif isinstance(typ.item, NoneType): + return analyze_none_member_access(name, typ.item, mx, from_none_type=True) if item and not mx.is_operator: # See comment above for why operators are skipped result = analyze_class_attribute_access(item, name, mx, override_info) @@ -316,17 +318,30 @@ def analyze_union_member_access(name: str, typ: UnionType, mx: MemberContext) -> return make_simplified_union(results) -def analyze_none_member_access(name: str, typ: NoneType, mx: MemberContext) -> Type: +def analyze_none_member_access( + name: str, typ: NoneType, mx: MemberContext, + *, + from_none_type: bool = False, +) -> Type: is_python_3 = mx.chk.options.python_version[0] >= 3 # In Python 2 "None" has exactly the same attributes as "object". Python 3 adds a single # extra attribute, "__bool__". if is_python_3 and name == '__bool__': literal_false = LiteralType(False, fallback=mx.named_type('builtins.bool')) - return CallableType(arg_types=[], - arg_kinds=[], - arg_names=[], - ret_type=literal_false, - fallback=mx.named_type('builtins.function')) + arg_types = [] + arg_kinds = [] + arg_names = [] + if from_none_type: # This means that `type(None).__bool__` is accessed. + arg_types.append(typ) + arg_kinds.append(ARG_POS) + arg_names.append('self') + return CallableType( + arg_types=arg_types, + arg_kinds=arg_kinds, + arg_names=arg_names, + ret_type=literal_false, + fallback=mx.named_type('builtins.function'), + ) elif mx.chk.should_suppress_optional_error([typ]): return AnyType(TypeOfAny.from_error) else: diff --git a/test-data/unit/check-basic.test b/test-data/unit/check-basic.test index 03dee485c848..a2becfcd3649 100644 --- a/test-data/unit/check-basic.test +++ b/test-data/unit/check-basic.test @@ -404,6 +404,13 @@ b = none.__bool__() reveal_type(b) # N: Revealed type is "Literal[False]" [builtins fixtures/bool.pyi] +[case testNoneHasBoolPython27] +# flags: --python-version 2.7 +none = None +b = none.__bool__() # E: "None" has no attribute "__bool__" +reveal_type(b) # N: Revealed type is "Any" +[builtins fixtures/bool.pyi] + [case testNoneHasBoolShowNoneErrorsFalse] none = None b = none.__bool__() @@ -413,6 +420,19 @@ reveal_type(b) # N: Revealed type is "Literal[False]" \[mypy] show_none_errors = False +[case testTypeNoneHasBool] +from typing import Type +none: Type[None] +reveal_type(none.__bool__) # N: Revealed type is "def (self: None) -> Literal[False]" +[builtins fixtures/bool.pyi] + +[case testTypeNoneHasBoolPython27] +# flags: --python-version 2.7 +from typing import Type +none = type(None) # type: Type[None] +none.__bool__ # E: "Type[None]" has no attribute "__bool__" +[builtins fixtures/bool.pyi] + [case testAssignmentInvariantNoteForList] from typing import List x: List[int] From 93a11ea7157094f76ff6edd60167551dcac9a804 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Sun, 14 Nov 2021 12:15:04 +0300 Subject: [PATCH 2/2] Fixes CI --- mypy/checkmember.py | 3 ++- test-data/unit/check-basic.test | 9 +++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 04869493244e..41ed646d4239 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -291,7 +291,8 @@ def analyze_type_type_member_access(name: str, # Access member on metaclass object via Type[Type[C]] if isinstance(typ.item.item, Instance): item = typ.item.item.type.metaclass_type - elif isinstance(typ.item, NoneType): + elif isinstance(typ.item, NoneType) and name == '__bool__': + # Special case, `type(None).__bool__` is defined on python3. return analyze_none_member_access(name, typ.item, mx, from_none_type=True) if item and not mx.is_operator: # See comment above for why operators are skipped diff --git a/test-data/unit/check-basic.test b/test-data/unit/check-basic.test index a2becfcd3649..d0ca7cf93727 100644 --- a/test-data/unit/check-basic.test +++ b/test-data/unit/check-basic.test @@ -433,6 +433,15 @@ none = type(None) # type: Type[None] none.__bool__ # E: "Type[None]" has no attribute "__bool__" [builtins fixtures/bool.pyi] +[case testTypeNoneHasBoolShowNoneErrorsFalse] +from typing import Type +none: Type[None] +reveal_type(none.__bool__) # N: Revealed type is "def (self: None) -> Literal[False]" +[builtins fixtures/bool.pyi] +[file mypy.ini] +\[mypy] +show_none_errors = False + [case testAssignmentInvariantNoteForList] from typing import List x: List[int]