Repository: python/mypy

Issue:
Generic typeddict may corrupt the cache
<!--
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.
-->

Using a generic `TypedDict` in another module may corrupt the cache. The cache doesn't recover and must be deleted.

**To Reproduce**

In module `a`, define a generic `TypedDict`,

```python
# a.py
from typing import TypedDict, Generic, TypeVar

TValue = TypeVar("TValue")


class Dict(TypedDict, Generic[TValue]):
    value: TValue

```

in another module (say `b`) use this definition.  The easiest way to trigger the bug is to have at least one typing error in the file (here `g()`).

```python
# b.py
from a import Dict, TValue

def f(d: Dict[TValue]) -> TValue:
    return d["value"]


def g(d: Dict[TValue]) -> TValue:
    return d["x"]  # error
```

Now, type check `b`

```
$ mypy b.py
b.py:8: error: TypedDict "a.Dict[TValue]" has no key "x"  [typeddict-item]
Found 1 error in 1 file (checked 1 source file)
```

as expected.

However, running `mypy b.py` **a second time** reports an extra `[type-arg]` and a few more related errors as well,

```
$ mypy b.py
b.py:3: error: "Dict" expects no type arguments, but 1 given  [type-arg]
b.py:3: error: A function returning TypeVar should receive at least one argument containing the same TypeVar  [type-var]
b.py:4: error: Incompatible return value type (got "TValue", expected "TValue")  [return-value]
b.py:7: error: "Dict" expects no type arguments, but 1 given  [type-arg]
b.py:7: error: A function returning TypeVar should receive at least one argument containing the same TypeVar  [type-var]
b.py:8: error: TypedDict "a.Dict[TValue]" has no key "x"  [typeddict-item]
Found 6 errors in 1 file (checked 1 source file)
```

from then on, the only way to get rid of the `[type-arg]` error is to run with `--no-increment` or delete the cache. Here, I removed `g()`, leaving

```python
# b.py [edited: deleted definition of g()]
from a import Dict, TValue

def f(d: Dict[TValue]) -> TValue:
    return d["value"]
```

and
```
$ mypy b.py
b.py:3: error: "Dict" expects no type arguments, but 1 given  [type-arg]
b.py:3: error: A function returning TypeVar should receive at least one argument containing the same TypeVar  [type-var]
b.py:4: error: Incompatible return value type (got "TValue", expected "TValue")  [return-value]
Found 3 errors in 1 file (checked 1 source file)
```

but

```
$ mypy --no-increment b.py
Success: no issues found in 1 source file
```

**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 difference between `mypy`, `mypy --no-increment` and the generic `TypedDict` do not corrupt the cache.

**Actual Behavior**

<!-- What went wrong? Paste mypy's output. -->

mypy reports false positives
```
b.py:3: error: "Dict" expects no type arguments, but 1 given  [type-arg]
b.py:3: error: A function returning TypeVar should receive at least one argument containing the same TypeVar  [type-var]
b.py:4: error: Incompatible return value type (got "TValue", expected "TValue")  [return-value]
```

**Your Environment**

<!-- Include as many relevant details about the environment you experienced the bug in -->

- Mypy version used: 0.991, 1.0.0, master
- Mypy command-line flags: `mypy` vs. `mypy --no-increment`
- Mypy configuration options from `mypy.ini` (and other config files): none
- Python version used: 3.11 or importing `TypedDict` from `mypy_extensions` on Python 3.10.

I could also reproduce that under Linux (Ubuntu 22.04) and MacOS (M1---Python installed via MacPorts).


Discussion:

Resolving patch:
diff --git a/mypy/fixup.py b/mypy/fixup.py
--- a/mypy/fixup.py
+++ b/mypy/fixup.py
@@ -80,9 +80,13 @@ def visit_type_info(self, info: TypeInfo) -> None:
             if info.tuple_type:
                 info.tuple_type.accept(self.type_fixer)
                 info.update_tuple_type(info.tuple_type)
+                if info.special_alias:
+                    info.special_alias.alias_tvars = list(info.defn.type_vars)
             if info.typeddict_type:
                 info.typeddict_type.accept(self.type_fixer)
                 info.update_typeddict_type(info.typeddict_type)
+                if info.special_alias:
+                    info.special_alias.alias_tvars = list(info.defn.type_vars)
             if info.declared_metaclass:
                 info.declared_metaclass.accept(self.type_fixer)
             if info.metaclass_type:
diff --git a/mypy/nodes.py b/mypy/nodes.py
--- a/mypy/nodes.py
+++ b/mypy/nodes.py
@@ -3473,8 +3473,13 @@ def __init__(
 
     @classmethod
     def from_tuple_type(cls, info: TypeInfo) -> TypeAlias:
-        """Generate an alias to the tuple type described by a given TypeInfo."""
+        """Generate an alias to the tuple type described by a given TypeInfo.
+
+        NOTE: this doesn't set type alias type variables (for generic tuple types),
+        they must be set by the caller (when fully analyzed).
+        """
         assert info.tuple_type
+        # TODO: is it possible to refactor this to set the correct type vars here?
         return TypeAlias(
             info.tuple_type.copy_modified(fallback=mypy.types.Instance(info, info.defn.type_vars)),
             info.fullname,
@@ -3484,8 +3489,13 @@ def from_tuple_type(cls, info: TypeInfo) -> TypeAlias:
 
     @classmethod
     def from_typeddict_type(cls, info: TypeInfo) -> TypeAlias:
-        """Generate an alias to the TypedDict type described by a given TypeInfo."""
+        """Generate an alias to the TypedDict type described by a given TypeInfo.
+
+        NOTE: this doesn't set type alias type variables (for generic TypedDicts),
+        they must be set by the caller (when fully analyzed).
+        """
         assert info.typeddict_type
+        # TODO: is it possible to refactor this to set the correct type vars here?
         return TypeAlias(
             info.typeddict_type.copy_modified(
                 fallback=mypy.types.Instance(info, info.defn.type_vars)


Test patch:
diff --git a/test-data/unit/check-incremental.test b/test-data/unit/check-incremental.test
--- a/test-data/unit/check-incremental.test
+++ b/test-data/unit/check-incremental.test
@@ -6359,3 +6359,34 @@ from m import Foo
 [file m.py]
 from missing_module import Meta  # type: ignore[import]
 class Foo(metaclass=Meta): ...
+
+[case testGenericTypedDictWithError]
+import b
+[file a.py]
+from typing import Generic, TypeVar
+from typing_extensions import TypedDict
+
+TValue = TypeVar("TValue")
+class Dict(TypedDict, Generic[TValue]):
+    value: TValue
+
+[file b.py]
+from a import Dict, TValue
+
+def f(d: Dict[TValue]) -> TValue:
+    return d["value"]
+def g(d: Dict[TValue]) -> TValue:
+    return d["x"]
+
+[file b.py.2]
+from a import Dict, TValue
+
+def f(d: Dict[TValue]) -> TValue:
+    return d["value"]
+def g(d: Dict[TValue]) -> TValue:
+    return d["y"]
+[builtins fixtures/dict.pyi]
+[out]
+tmp/b.py:6: error: TypedDict "a.Dict[TValue]" has no key "x"
+[out2]
+tmp/b.py:6: error: TypedDict "a.Dict[TValue]" has no key "y"