Repository: python/mypy

Issue:
Strange case with deepcopy() and Literal[True]
The following code:
```python3
from copy import deepcopy
from typing import Literal, Union

def f(a: str, b: bool) -> Union[Literal[True], str]:
    return b or deepcopy(a)

print(f("abc", False))
print(f("abc", True))
```
produces the following output:
```bash
$ python3 test.py
abc
True

$ mypy test.py
test.py: note: In function "f":
test.py:5:12: error: Incompatible return value type (got "bool", expected "Union[Literal[True], str]")  [return-value]
        return b or deepcopy(a)
               ^
test.py:5:26: error: Argument 1 to "deepcopy" has incompatible type "str"; expected "bool"  [arg-type]
        return b or deepcopy(a)
                             ^
Found 2 errors in 1 file (checked 1 source file)

$ python3 --version
Python 3.8.10

$ mypy --version
mypy 0.931

$ cat /etc/issue
Ubuntu 20.04.3 LTS \n \l

$ uname -a
Linux ubuntu 5.4.0-94-generic #106-Ubuntu SMP Thu Jan 6 23:58:14 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux
```
If `deepcopy()` call is removed, warnings go away.

In some similar cases only the second warning appears, which looks even weirder.

Mypy incorrectly gives an error for a ternary return statement
<!--
If you're not sure whether what you're experiencing is a mypy bug, please see the "Question and Help" form instead.
Please consider:

- checking our common issues page: https://mypy.readthedocs.io/en/stable/common_issues.html
- searching our issue tracker: https://github.com/python/mypy/issues to see if it's already been reported
- asking on gitter chat: https://gitter.im/python/typing
-->

**Bug Report**

<!--
If you're reporting a problem with a specific library function, the typeshed tracker is better suited for this report: https://github.com/python/typeshed/issues

If the project you encountered the issue in is open source, please provide a link to the project.
-->

Hi, I was using mypy and I think I've noticed a bug. For a function that returns either List[str] or List[List[str]], mypy gives an error if the return type is decided as a ternary. However, if the return type is decided with an 'if-else' instead, there are no problems. I also tested a similar function, except where the return type is either a float or str. In that case, mypy had no problems with the ternary usage.

**To Reproduce**

```python
from typing import Union, List

def get_str_float(b: bool) -> Union[str, float]:
    return 8 if b else '8' # mypy correctly gives no error here

def get_list(b: bool) -> Union[List[str], List[List[str]]]:
    return ['a'] if b else [['a']] # mypy incorrectly gives an error
    """
    Mypy gives no error if I replace the above line with the following:
    if b:
        return ['a']
    else:
        return [['a']]
    """

def main():
    print(get_list(True))
    print(get_str_float(True))

if __name__ == "__main__":
    main()
```

**Expected Behavior**

<!--
How did you expect mypy to behave? It’s fine if you’re not sure your understanding is correct.
Write down what you thought would happen. If you expected no errors, delete this section.
-->
No errors

**Actual Behavior**

`main.py:7: error: List item 0 has incompatible type "List[str]"; expected "str"  [list-item]`
`Found 1 error in 1 file (checked 1 source file)`

**Your Environment**

<!-- Include as many relevant details about the environment you experienced the bug in -->

- Mypy version used: mypy 1.3.0 (compiled: yes)
- Mypy command-line flags: none - just ran `mypy main.py`
- Mypy configuration options from `mypy.ini` (and other config files): none
- Python version used: 3.11.0
- OS: Windows 11

<!-- You can freely edit this text, please remove all the lines you believe are unnecessary. -->



Discussion:
Related / duplicate of #10696, just using `or` instead of `and`. Both issues have to do with bidirectional inference going awry when analyzing the right operand using the type of the left operand as the type context.





Patch:
diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py
index edc3ac70fa54..4ca55e1679e4 100644
--- a/mypy/checkexpr.py
+++ b/mypy/checkexpr.py
@@ -4288,7 +4288,9 @@ def check_boolean_op(self, e: OpExpr, context: Context) -> Type:
         ):
             self.msg.unreachable_right_operand(e.op, e.right)
 
-        right_type = self.analyze_cond_branch(right_map, e.right, expanded_left_type)
+        right_type = self.analyze_cond_branch(
+            right_map, e.right, self._combined_context(expanded_left_type)
+        )
 
         if left_map is None and right_map is None:
             return UninhabitedType()
@@ -5919,6 +5921,16 @@ def analyze_cond_branch(
             self.chk.push_type_map(map)
             return self.accept(node, type_context=context, allow_none_return=allow_none_return)
 
+    def _combined_context(self, ty: Type | None) -> Type | None:
+        ctx_items = []
+        if ty is not None:
+            ctx_items.append(ty)
+        if self.type_context and self.type_context[-1] is not None:
+            ctx_items.append(self.type_context[-1])
+        if ctx_items:
+            return make_simplified_union(ctx_items)
+        return None
+
     #
     # Helpers
     #


Test patch:
diff --git a/test-data/unit/check-inference-context.test b/test-data/unit/check-inference-context.test
index 0aa67b2bf7f3..67ae22a369b1 100644
--- a/test-data/unit/check-inference-context.test
+++ b/test-data/unit/check-inference-context.test
@@ -1510,3 +1510,24 @@ def mymin(
 def check(paths: Iterable[str], key: Callable[[str], int]) -> Union[str, None]:
     return mymin(paths, key=key, default=None)
 [builtins fixtures/tuple.pyi]
+
+[case testBinaryOpInferenceContext]
+from typing import Literal, TypeVar
+
+T = TypeVar("T")
+
+def identity(x: T) -> T:
+    return x
+
+def check1(use: bool, val: str) -> "str | Literal[True]":
+    return use or identity(val)
+
+def check2(use: bool, val: str) -> "str | bool":
+    return use or identity(val)
+
+def check3(use: bool, val: str) -> "str | Literal[False]":
+    return use and identity(val)
+
+def check4(use: bool, val: str) -> "str | bool":
+    return use and identity(val)
+[builtins fixtures/tuple.pyi]