Repository: python/mypy

Issue:
Union of a class with Any base and None is sometimes None
Without `--strict-optional`, the second `reveal_type` produces an incorrect result:

```
from typing import Any, Union, TypeVar

class C(Any):
    pass

T = TypeVar('T')
S = TypeVar('S')

def f(x: T, y: S) -> Union[T, S]:
    pass

reveal_type(f(C(), None))  # t.C
reveal_type(f(None, C()))  # builtins.None   <--- wrong
```

With `--strict-optional`, both of them are wrong:

```
...   # as above

reveal_type(f(C(), None))  # builtins.None   <--- wrong
reveal_type(f(None, C()))  # builtins.None   <--- wrong
```

This seems to cause problems in real-world code together with python/typeshed@6008b9d, so this is high priority.


Discussion:
This looks very similar to the issue fixed by #2197. Maybe I'll just finish up that PR.

Resolving patch:
diff --git a/mypy/types.py b/mypy/types.py
--- a/mypy/types.py
+++ b/mypy/types.py
@@ -1034,13 +1034,18 @@ def make_simplified_union(items: List[Type], line: int = -1, column: int = -1) -
             return AnyType()
 
         from mypy.subtypes import is_subtype
+        from mypy.sametypes import is_same_type
+
         removed = set()  # type: Set[int]
         for i, ti in enumerate(items):
             if i in removed: continue
             # Keep track of the truishness info for deleted subtypes which can be relevant
             cbt = cbf = False
             for j, tj in enumerate(items):
-                if i != j and is_subtype(tj, ti):
+                if (i != j
+                    and is_subtype(tj, ti)
+                    and (not (isinstance(tj, Instance) and tj.type.fallback_to_any)
+                         or is_same_type(ti, tj))):
                     removed.add(j)
                     cbt = cbt or tj.can_be_true
                     cbf = cbf or tj.can_be_false


Test patch:
diff --git a/test-data/unit/check-optional.test b/test-data/unit/check-optional.test
--- a/test-data/unit/check-optional.test
+++ b/test-data/unit/check-optional.test
@@ -525,3 +525,22 @@ f = None # type: Optional[Callable[[int], None]]
 f = lambda x: None
 f(0)
 [builtins fixtures/function.pyi]
+
+[case testUnionSimplificationWithStrictOptional]
+from typing import Any, TypeVar, Union
+class C(Any): pass
+T = TypeVar('T')
+S = TypeVar('S')
+def u(x: T, y: S) -> Union[S, T]: pass
+a = None # type: Any
+
+# Test both orders
+reveal_type(u(C(), None))  # E: Revealed type is 'Union[builtins.None, __main__.C*]'
+reveal_type(u(None, C()))  # E: Revealed type is 'Union[__main__.C*, builtins.None]'
+
+# This will be fixed later
+reveal_type(u(a, None))  # E: Revealed type is 'Any'
+reveal_type(u(None, a))  # E: Revealed type is 'Any'
+
+reveal_type(u(1, None))  # E: Revealed type is 'Union[builtins.None, builtins.int*]'
+reveal_type(u(None, 1))  # E: Revealed type is 'Union[builtins.int*, builtins.None]'
diff --git a/test-data/unit/check-unions.test b/test-data/unit/check-unions.test
--- a/test-data/unit/check-unions.test
+++ b/test-data/unit/check-unions.test
@@ -168,3 +168,52 @@ if foo():
     def g(x: Union[int, str, bytes]) -> None: pass
 else:
     def g(x: Union[int, str]) -> None: pass  # E: All conditional function variants must have identical signatures
+
+[case testUnionSimplificationSpecialCases]
+from typing import Any, TypeVar, Union
+
+class C(Any): pass
+
+T = TypeVar('T')
+S = TypeVar('S')
+def u(x: T, y: S) -> Union[S, T]: pass
+
+a = None # type: Any
+
+reveal_type(u(C(), None))  # E: Revealed type is '__main__.C*'
+reveal_type(u(None, C()))  # E: Revealed type is '__main__.C*'
+
+# This will be fixed later
+reveal_type(u(C(), a))  # E: Revealed type is 'Any'
+reveal_type(u(a, C()))  # E: Revealed type is 'Any'
+
+reveal_type(u(C(), C()))  # E: Revealed type is '__main__.C*'
+reveal_type(u(a, a))  # E: Revealed type is 'Any'
+
+[case testUnionSimplificationSpecialCase2]
+from typing import Any, TypeVar, Union
+
+class C(Any): pass
+
+T = TypeVar('T')
+S = TypeVar('S')
+def u(x: T, y: S) -> Union[S, T]: pass
+
+def f(x: T) -> None:
+    reveal_type(u(C(), x)) # E: Revealed type is 'Union[T`-1, __main__.C*]'
+    reveal_type(u(x, C())) # E: Revealed type is 'Union[__main__.C*, T`-1]'
+
+[case testUnionSimplificationSpecialCase3]
+from typing import Any, TypeVar, Generic, Union
+
+class C(Any): pass
+
+V = TypeVar('V')
+T = TypeVar('T')
+
+class M(Generic[V]):
+    def get(self, default: T) -> Union[V, T]: ...
+
+def f(x: M[C]) -> None:
+    y = x.get(None)
+    reveal_type(y) # E: Revealed type is '__main__.C'