From ada692733646af886cf09d8d07f04083f5ea9e4f Mon Sep 17 00:00:00 2001 From: sobolevn Date: Fri, 24 Sep 2021 21:29:20 +0300 Subject: [PATCH] Better narrows `bool` in `if` statements --- mypy/checker.py | 7 ++++--- mypy/subtypes.py | 7 +++++++ test-data/unit/check-narrowing.test | 20 ++++++++++++++++++++ 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 102108499d7b..2059abd1a1b3 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -5307,9 +5307,10 @@ def conditional_type_map(expr: Expression, return None, {} else: # we can only restrict when the type is precise, not bounded - proposed_precise_type = UnionType([type_range.item - for type_range in proposed_type_ranges - if not type_range.is_upper_bound]) + proposed_precise_type = UnionType.make_union([ + type_range.item + for type_range in proposed_type_ranges + if not type_range.is_upper_bound]) remaining_type = restrict_subtype_away(current_type, proposed_precise_type) return {expr: proposed_type}, {expr: remaining_type} else: diff --git a/mypy/subtypes.py b/mypy/subtypes.py index 63cebc8aa483..6fadbdcd219f 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -1129,6 +1129,13 @@ def restrict_subtype_away(t: Type, s: Type, *, ignore_promotions: bool = False) if (isinstance(get_proper_type(item), AnyType) or not covers_at_runtime(item, s, ignore_promotions))] return UnionType.make_union(new_items) + elif isinstance(t, Instance) and t.type.fullname == 'builtins.bool': + # Narrowing `bool` special case. + # When we got `bool` and `Literal[True]` / `Literal[False]` + # we return the inverse boolean value. + if isinstance(s, LiteralType): + return LiteralType(False, t) if s.value is True else LiteralType(True, t) + return t else: return t diff --git a/test-data/unit/check-narrowing.test b/test-data/unit/check-narrowing.test index 7c8415b75fe1..9b9542a31586 100644 --- a/test-data/unit/check-narrowing.test +++ b/test-data/unit/check-narrowing.test @@ -1030,6 +1030,26 @@ else: [builtins fixtures/primitives.pyi] +[case testNarrowingBoolLiteralIdentityCheck] +x: bool +if x is False: + reveal_type(x) # N: Revealed type is "Literal[False]" +else: + reveal_type(x) # N: Revealed type is "Literal[True]" + +y: bool +if y is True: + reveal_type(y) # N: Revealed type is "Literal[True]" +else: + reveal_type(y) # N: Revealed type is "Literal[False]" + +z: bool +if z: # TODO: this can be later emulated to be `z is True` in `mypy` + reveal_type(z) # N: Revealed type is "builtins.bool" +else: + reveal_type(z) # N: Revealed type is "builtins.bool" +[builtins fixtures/primitives.pyi] + [case testNarrowingTypedDictUsingEnumLiteral] # flags: --python-version 3.6 from typing import Union