Repository: python/mypy

Issue:
Block incorrectly treated as unreachable when strict optional disabled
This program doesn't generate an error when using `--no-strict-optional`, even though every line is reachable:

```py
from typing import NamedTuple, Dict

class C(NamedTuple):
    a: int
    b: str

x: Dict[int, C]
y = x.get(int())
if not y:
    1 + ''  # No error (considered unreachable)
```

Apparently mypy thinks that the tuple object is always truthy, even though it could also be `None` when strict optional checking is disabled.


Discussion:

Resolving patch:
diff --git a/mypy/types.py b/mypy/types.py
--- a/mypy/types.py
+++ b/mypy/types.py
@@ -2127,8 +2127,13 @@ def false_only(t: Type) -> Type:
     Restricted version of t with only False-ish values
     """
     if not t.can_be_false:
-        # All values of t are True-ish, so there are no false values in it
-        return UninhabitedType(line=t.line)
+        if state.strict_optional:
+            # All values of t are True-ish, so there are no false values in it
+            return UninhabitedType(line=t.line)
+        else:
+            # When strict optional checking is disabled, everything can be
+            # False-ish since anything can be None
+            return NoneType(line=t.line)
     elif not t.can_be_true:
         # All values of t are already False-ish, so false_only is idempotent in this case
         return t


Test patch:
diff --git a/mypy/test/testtypes.py b/mypy/test/testtypes.py
--- a/mypy/test/testtypes.py
+++ b/mypy/test/testtypes.py
@@ -341,8 +341,17 @@ def test_true_only_of_union(self) -> None:
         assert_true(to.items[1] is tup_type)
 
     def test_false_only_of_true_type_is_uninhabited(self) -> None:
-        fo = false_only(self.tuple(AnyType(TypeOfAny.special_form)))
-        assert_type(UninhabitedType, fo)
+        with strict_optional_set(True):
+            fo = false_only(self.tuple(AnyType(TypeOfAny.special_form)))
+            assert_type(UninhabitedType, fo)
+
+    def test_false_only_tuple(self) -> None:
+        with strict_optional_set(False):
+            fo = false_only(self.tuple(self.fx.a))
+            assert_equal(fo, NoneType())
+        with strict_optional_set(True):
+            fo = false_only(self.tuple(self.fx.a))
+            assert_equal(fo, UninhabitedType())
 
     def test_false_only_of_false_type_is_idempotent(self) -> None:
         always_false = NoneType()
@@ -359,18 +368,19 @@ def test_false_only_of_instance(self) -> None:
         assert_true(self.fx.a.can_be_true)
 
     def test_false_only_of_union(self) -> None:
-        tup_type = self.tuple()
-        # Union of something that is unknown, something that is always true, something
-        # that is always false
-        union_type = UnionType([self.fx.a, self.tuple(AnyType(TypeOfAny.special_form)),
-                                tup_type])
-        assert_equal(len(union_type.items), 3)
-        fo = false_only(union_type)
-        assert isinstance(fo, UnionType)
-        assert_equal(len(fo.items), 2)
-        assert_false(fo.items[0].can_be_true)
-        assert_true(fo.items[0].can_be_false)
-        assert_true(fo.items[1] is tup_type)
+        with strict_optional_set(True):
+            tup_type = self.tuple()
+            # Union of something that is unknown, something that is always true, something
+            # that is always false
+            union_type = UnionType([self.fx.a, self.tuple(AnyType(TypeOfAny.special_form)),
+                                    tup_type])
+            assert_equal(len(union_type.items), 3)
+            fo = false_only(union_type)
+            assert isinstance(fo, UnionType)
+            assert_equal(len(fo.items), 2)
+            assert_false(fo.items[0].can_be_true)
+            assert_true(fo.items[0].can_be_false)
+            assert_true(fo.items[1] is tup_type)
 
     # Helpers
 
diff --git a/test-data/unit/check-namedtuple.test b/test-data/unit/check-namedtuple.test
--- a/test-data/unit/check-namedtuple.test
+++ b/test-data/unit/check-namedtuple.test
@@ -846,3 +846,19 @@ class Child(Parent):
 
 reveal_type(Child.class_method())  # E: Revealed type is 'Tuple[builtins.str, fallback=__main__.Child]'
 [builtins fixtures/classmethod.pyi]
+
+[case testNamedTupleAsConditionalStrictOptionalDisabled]
+# flags: --no-strict-optional
+from typing import NamedTuple
+
+class C(NamedTuple):
+    a: int
+    b: str
+
+a: C
+if not a:
+    1()  # E: "int" not callable
+b = (1, 2)
+if not b:
+    ''()  # E: "str" not callable
+[builtins fixtures/tuple.pyi]