Repository: python/mypy

Issue:
False positive when using coroutines and generics
### Bug report

Mypy is not consistent between sync and async variants of the same code.

### How to reproduce
Consider the following code:
```python
from typing import Generic, TypeVar

T = TypeVar("T")

class Cls(Generic[T]):
    pass

def create_cls(arg: T) -> Cls[tuple[T]]:
    return Cls()

def inner(var: Cls[tuple[T]]) -> T | None:
    return None

def outer(x: int) -> int | None:
    c = create_cls(x)
    return inner(c)
```

Running `mypy test.py`  yields no errors, as expected.

However, if we make the functions async:
```python
from typing import Generic, TypeVar

T = TypeVar("T")

class Cls(Generic[T]):
    pass

def create_cls(arg: T) -> Cls[tuple[T]]:
    return Cls()

async def inner(var: Cls[tuple[T]]) -> T | None:
    return None

async def outer(x: int) -> int | None:
    c = create_cls(x)
    return await inner(c)
```
Then running `mypy test.py` gives:
```shell
test.py:21: error: Argument 1 to "inner" has incompatible type "Cls[tuple[int]]"; expected "Cls[tuple[int | None]]"  [arg-type]
Found 1 error in 1 file (checked 1 source file)
```

### Expected Behavior

Mypy should give no errors in the async case, there's no difference in any of the function signatures.

### Your Environment

- Mypy version used: `mypy 1.5.0 (compiled: yes)`
- Mypy command-line flags: None
- Mypy configuration options from `mypy.ini` (and other config files): None
- Python version used: 3.11.0rc1


mypy incorrectly tags function argument TypeVar with return type when called from a caller with the same signature
**Bug Report**

mypy incorrectly tags a function argument of type `Mapping[K, V]` where `K` and `V` are `TypeVar` with the return value of the caller function.

**To Reproduce**

The below small program demonstrates the issue:

```python
from typing import Dict, Mapping, TypeVar, Union

K = TypeVar("K")
V = TypeVar("V")
K2 = TypeVar("K2")
V2 = TypeVar("V2")

def func(one: Dict[K, V], two: Mapping[K2, V2]) -> Dict[Union[K, K2], Union[V, V2]]:
    reveal_type(one)
    reveal_type(two)
    return {}

def caller(arg1: Mapping[K, V], arg2: Mapping[K2, V2]) -> Dict[Union[K, K2], Union[V, V2]]:
    reveal_type(arg1)
    reveal_type(arg2)
    _arg1 = arg1 if isinstance(arg1, dict) else dict(arg1)
    return func(_arg1, arg2)
```

**Expected Behavior**

mypy should succeed without an error.

```
$ mypy --strict break_mypy.py
break_mypy.py:10: note: Revealed type is "builtins.dict[K`-1, V`-2]"
break_mypy.py:11: note: Revealed type is "typing.Mapping[K2`-3, V2`-4]"
break_mypy.py:16: note: Revealed type is "typing.Mapping[K`-1, V`-2]"
break_mypy.py:17: note: Revealed type is "typing.Mapping[K2`-3, V2`-4]"
Success: no issues found in 1 source file
```

**Actual Behavior**

mypy complains with a very weird error, even though the revealed types are correct

```
$ mypy --strict break_mypy.py
break_mypy.py:10: note: Revealed type is "builtins.dict[K`-1, V`-2]"
break_mypy.py:11: note: Revealed type is "typing.Mapping[K2`-3, V2`-4]"
break_mypy.py:16: note: Revealed type is "typing.Mapping[K`-1, V`-2]"
break_mypy.py:17: note: Revealed type is "typing.Mapping[K2`-3, V2`-4]"
break_mypy.py:19: error: Argument 2 to "func" has incompatible type "Mapping[K2, V2]"; expected "Mapping[Union[K, K2], Union[V, V2]]"  [arg-type]
Found 1 error in 1 file (checked 1 source file)
```

**More Info**

Note, if I change the type signature of the caller, it ends up changing the type signature of the called function as well:

```python
from typing import Dict, Mapping, TypeVar, Union

K = TypeVar("K")
V = TypeVar("V")
K2 = TypeVar("K2")
V2 = TypeVar("V2")


def func(one: Dict[K, V], two: Mapping[K2, V2]) -> Dict[Union[K, K2], Union[V, V2]]:
    reveal_type(one)
    reveal_type(two)
    return {}


def caller(arg1: Mapping[K, V], arg2: Mapping[K2, V2]) -> None:
    reveal_type(arg1)
    reveal_type(arg2)
    _arg1 = arg1 if isinstance(arg1, dict) else dict(arg1)
    func(_arg1, arg2)
```

will return:

```
$ mypy --strict break_mypy.py
break_mypy.py:10: note: Revealed type is "builtins.dict[K`-1, V`-2]"
break_mypy.py:11: note: Revealed type is "typing.Mapping[K2`-3, V2`-4]"
break_mypy.py:16: note: Revealed type is "typing.Mapping[K`-1, V`-2]"
break_mypy.py:17: note: Revealed type is "typing.Mapping[K2`-3, V2`-4]"
Success: no issues found in 1 source file
```

**Your Environment**

- Mypy version used: `mypy 0.991 (compiled: yes)`
- Mypy command-line flags: `--strict`
- Mypy configuration options from `mypy.ini` (and other config files):
  ```
  ignore_missing_imports = true
  ```
- Mypy plugins: `sqlalchemy.ext.mypy.plugin`, `pydantic.mypy` and `numpy.typing.mypy_plugin`
- Python version used: `Python 3.9.17`



Discussion:
I created a new test file and ran pytest with your exact code on Python 3.11 and it seemed to pass. Let me know if i missed anything.
```
[case checkasync]
# flags: --python-version 3.11

from typing import Generic, TypeVar

T = TypeVar("T")

class Cls(Generic[T]):
    pass

def create_cls(arg: T) -> Cls[tuple[T]]:
    return Cls()

async def inner(var: Cls[tuple[T]]) -> T | None:
    return None

async def outer(x: int) -> int | None:
    c = create_cls(x)
    return await inner(c)
[builtins fixtures/tuple.pyi]
```
<img width="1102" alt="image" src="https://github.com/python/mypy/assets/91278747/9b49440d-4d08-4aef-bc6c-8431c7f7e21b">


I'm getting the same results as you, the unit test is passing.
However running the mypy executable directly on the code still yields the issue.
What can lead to a difference between the unit test and the regular execution?

Here's an even smaller snippet to reproduce the error:
```python
from typing import Generic, TypeVar

T = TypeVar("T")

class Cls(Generic[T]):
    pass

async def inner(c: Cls[T]) -> T | None:
    return None

async def outer(c: Cls[T]) -> T | None:
    return await inner(c)
```
I'll check it out tonight? My guess is the pytest does not run the same code on the file as the regular execution ? Maybe we can collaborate on this issue on discord if you have one. 
Made some progress, here's an even simpler snippet that also fails in the mypy unit tests:
```python
from typing import Generic, TypeVar

T = TypeVar("T")

class Cls(Generic[T]):
    pass

def inner(c: Cls[T]) -> T | int:
    return 1

def outer(c: Cls[T]) -> T | int:
    return inner(c)
```

There's no async/await even, so it seems to be a problem purely with generics.
mypy can sometimes over eagerly use type context in cases like this. Removing type context by putting it on its own line can often fix (as can supplying exactly the context you want via annotation). That is:
```
def outer(c: Cls[T]) -> T | int:
    ret = inner(c)
    return ret
```
Using a covariant type var would also "fix", if acceptable in your real world case.
Thanks for the reply @hauntsaninja !

In case it matters, my real world case involves the [Select](https://github.com/sqlalchemy/sqlalchemy/blob/b382bff6e3464f039db0fd1f2ce1b79038675e48/lib/sqlalchemy/sql/selectable.py#L5038) class from SQLAlchemy, and the `.one_or_none` function which has the same `T -> T | None` pattern.

I have hundreds of instances of this issue, so having to create a variable every time is not ideal (not to mention that linters like Ruff will try to optimize this statement and remove the variable).
Using `covariant` does work but requires some extra hacking because this happens in a 3rd party lib.

I've made a PR that fixes the issue based on your comment, which also fixes the issue in my real world case.

This is not specific to `Mapping` and is reproducible with `Dict` as well. Looks like `TypeVar` along with having two functions with the same return type will cause this issue.

Below program reproduces this issue with `Dict`, instead of `Mapping`

```python
from typing import Dict, TypeVar, Union

K = TypeVar("K")
V = TypeVar("V")
K2 = TypeVar("K2")
V2 = TypeVar("V2")

def func(one: Dict[K, V], two: Dict[K2, V2]) -> Dict[Union[K, K2], Union[V, V2]]:
    reveal_type(one)
    reveal_type(two)
    return {}

def caller(arg1: Dict[K, V], arg2: Dict[K2, V2]) -> Dict[Union[K, K2], Union[V, V2]]:
    reveal_type(arg1)
    reveal_type(arg2)
    _arg1 = arg1 if isinstance(arg1, dict) else dict(arg1)
    return func(_arg1, arg2)
```

returns


```
$ mypy --strict break_mypy.py
break_mypy.py:10: note: Revealed type is "builtins.dict[K`-1, V`-2]"
break_mypy.py:11: note: Revealed type is "builtins.dict[K2`-3, V2`-4]"
break_mypy.py:16: note: Revealed type is "builtins.dict[K`-1, V`-2]"
break_mypy.py:17: note: Revealed type is "builtins.dict[K2`-3, V2`-4]"
break_mypy.py:19: error: Argument 1 to "func" has incompatible type "Dict[K, V]"; expected "Dict[Union[K, K2], Union[V, V2]]"  [arg-type]
break_mypy.py:19: error: Argument 2 to "func" has incompatible type "Dict[K2, V2]"; expected "Dict[Union[K, K2], Union[V, V2]]"  [arg-type]
Found 2 errors in 1 file (checked 1 source file)
```
This is reproducible on newest `mypy` and python versions as well:

```
$ mypy --version
mypy 1.8.0 (compiled: yes)
```

```
$ python --version
Python 3.11.4
```

```
$ mypy --strict break_mypy.py
break_mypy.py:10: note: Revealed type is "builtins.dict[K`-1, V`-2]"
break_mypy.py:11: note: Revealed type is "builtins.dict[K2`-3, V2`-4]"
break_mypy.py:16: note: Revealed type is "builtins.dict[K`-1, V`-2]"
break_mypy.py:17: note: Revealed type is "builtins.dict[K2`-3, V2`-4]"
break_mypy.py:19: error: Argument 1 to "func" has incompatible type "dict[K, V]"; expected "dict[K | K2, V | V2]"  [arg-type]
break_mypy.py:19: error: Argument 2 to "func" has incompatible type "dict[K2, V2]"; expected "dict[K | K2, V | V2]"  [arg-type]
Found 2 errors in 1 file (checked 1 source file)
```



Patch:
diff --git a/mypy/checker.py b/mypy/checker.py
index ae6ae591ed8c..12a86fe6fba1 100644
--- a/mypy/checker.py
+++ b/mypy/checker.py
@@ -90,6 +90,7 @@
     ContinueStmt,
     Decorator,
     DelStmt,
+    DictExpr,
     EllipsisExpr,
     Expression,
     ExpressionStmt,
@@ -124,6 +125,7 @@
     RaiseStmt,
     RefExpr,
     ReturnStmt,
+    SetExpr,
     StarExpr,
     Statement,
     StrExpr,
@@ -4859,6 +4861,42 @@ def visit_return_stmt(self, s: ReturnStmt) -> None:
         self.check_return_stmt(s)
         self.binder.unreachable()
 
+    def infer_context_dependent(
+        self, expr: Expression, type_ctx: Type, allow_none_func_call: bool
+    ) -> ProperType:
+        """Infer type of an expression with fallback to empty type context."""
+        with self.msg.filter_errors(
+            filter_errors=True, filter_deprecated=True, save_filtered_errors=True
+        ) as msg:
+            with self.local_type_map as type_map:
+                typ = get_proper_type(
+                    self.expr_checker.accept(
+                        expr, type_ctx, allow_none_return=allow_none_func_call
+                    )
+                )
+        if not msg.has_new_errors():
+            self.store_types(type_map)
+            return typ
+
+        # If there are errors with the original type context, try re-inferring in empty context.
+        original_messages = msg.filtered_errors()
+        original_type_map = type_map
+        with self.msg.filter_errors(
+            filter_errors=True, filter_deprecated=True, save_filtered_errors=True
+        ) as msg:
+            with self.local_type_map as type_map:
+                alt_typ = get_proper_type(
+                    self.expr_checker.accept(expr, None, allow_none_return=allow_none_func_call)
+                )
+        if not msg.has_new_errors() and is_subtype(alt_typ, type_ctx):
+            self.store_types(type_map)
+            return alt_typ
+
+        # If empty fallback didn't work, use results from the original type context.
+        self.msg.add_errors(original_messages)
+        self.store_types(original_type_map)
+        return typ
+
     def check_return_stmt(self, s: ReturnStmt) -> None:
         defn = self.scope.current_function()
         if defn is not None:
@@ -4891,11 +4929,18 @@ def check_return_stmt(self, s: ReturnStmt) -> None:
                 allow_none_func_call = is_lambda or declared_none_return or declared_any_return
 
                 # Return with a value.
-                typ = get_proper_type(
-                    self.expr_checker.accept(
-                        s.expr, return_type, allow_none_return=allow_none_func_call
+                if isinstance(s.expr, (CallExpr, ListExpr, TupleExpr, DictExpr, SetExpr, OpExpr)):
+                    # For expressions that (strongly) depend on type context (i.e. those that
+                    # are handled like a function call), we allow fallback to empty type context
+                    # in case of errors, this improves user experience in some cases,
+                    # see e.g. testReturnFallbackInference.
+                    typ = self.infer_context_dependent(s.expr, return_type, allow_none_func_call)
+                else:
+                    typ = get_proper_type(
+                        self.expr_checker.accept(
+                            s.expr, return_type, allow_none_return=allow_none_func_call
+                        )
                     )
-                )
                 # Treat NotImplemented as having type Any, consistent with its
                 # definition in typeshed prior to python/typeshed#4222.
                 if (
diff --git a/mypy/errors.py b/mypy/errors.py
index d75c1c62a1ed..f1b2faf67401 100644
--- a/mypy/errors.py
+++ b/mypy/errors.py
@@ -206,7 +206,8 @@ def on_error(self, file: str, info: ErrorInfo) -> bool:
         """
         if info.code == codes.DEPRECATED:
             # Deprecated is not a type error, so it is handled on opt-in basis here.
-            return self._filter_deprecated
+            if not self._filter_deprecated:
+                return False
 
         self._has_new_errors = True
         if isinstance(self._filter, bool):


Test patch:
diff --git a/test-data/unit/check-inference-context.test b/test-data/unit/check-inference-context.test
index cd44fb5b85cd..7dbbd68c4215 100644
--- a/test-data/unit/check-inference-context.test
+++ b/test-data/unit/check-inference-context.test
@@ -1540,3 +1540,45 @@ def f(x: dict[str, Union[str, None, int]]) -> None:
 def g(x: Optional[dict[str, Any]], s: Optional[str]) -> None:
     f(x or {'x': s})
 [builtins fixtures/dict.pyi]
+
+[case testReturnFallbackInferenceTuple]
+from typing import TypeVar, Union
+
+T = TypeVar("T")
+def foo(x: list[T]) -> tuple[T, ...]: ...
+
+def bar(x: list[int]) -> tuple[Union[str, int], ...]:
+    return foo(x)
+
+def bar2(x: list[int]) -> tuple[Union[str, int], ...]:
+    y = foo(x)
+    return y
+[builtins fixtures/tuple.pyi]
+
+[case testReturnFallbackInferenceUnion]
+from typing import Generic, TypeVar, Union
+
+T = TypeVar("T")
+
+class Cls(Generic[T]):
+    pass
+
+def inner(c: Cls[T]) -> Union[T, int]:
+    return 1
+
+def outer(c: Cls[T]) -> Union[T, int]:
+    return inner(c)
+
+[case testReturnFallbackInferenceAsync]
+from typing import Generic, TypeVar, Optional
+
+T = TypeVar("T")
+
+class Cls(Generic[T]):
+    pass
+
+async def inner(c: Cls[T]) -> Optional[T]:
+    return None
+
+async def outer(c: Cls[T]) -> Optional[T]:
+    return await inner(c)
diff --git a/test-data/unit/pythoneval.test b/test-data/unit/pythoneval.test
index 9b5d8a1ac54c..2910e59b9173 100644
--- a/test-data/unit/pythoneval.test
+++ b/test-data/unit/pythoneval.test
@@ -2188,3 +2188,19 @@ reveal_type([*map(str, x)])
 [out]
 _testUnpackIteratorBuiltins.py:4: note: Revealed type is "builtins.list[builtins.int]"
 _testUnpackIteratorBuiltins.py:5: note: Revealed type is "builtins.list[builtins.str]"
+
+[case testReturnFallbackInferenceDict]
+# Requires full dict stubs.
+from typing import Dict, Mapping, TypeVar, Union
+
+K = TypeVar("K")
+V = TypeVar("V")
+K2 = TypeVar("K2")
+V2 = TypeVar("V2")
+
+def func(one: Dict[K, V], two: Mapping[K2, V2]) -> Dict[Union[K, K2], Union[V, V2]]:
+    ...
+
+def caller(arg1: Mapping[K, V], arg2: Mapping[K2, V2]) -> Dict[Union[K, K2], Union[V, V2]]:
+    _arg1 = arg1 if isinstance(arg1, dict) else dict(arg1)
+    return func(_arg1, arg2)