Repository: python/mypy

Issue:
Bounded TypeVar with TypedDict doesn't work correctly
TypedDict does not work correctly as `bound` argument for TypeVar:

```
from typing import TypeVar, Generic
from typing_extensions import TypedDict


class BaseDict(TypedDict, total=False):
    foo: int


_DICT_T = TypeVar('_DICT_T', bound=BaseDict)


class SomeGeneric(Generic[_DICT_T]):
    def __init__(self, data: _DICT_T) -> None:
        self._data: _DICT_T = data

    def set_state(self) -> None:
        self._data['foo'] = 1  # error: Unsupported target for indexed assignment

    def set_something_else(self) -> None:
        raise NotImplementedError


class UserData(BaseDict):
    uid: int


class Implementation(SomeGeneric[UserData]):
    def set_something_else(self) -> None:
        self._data['uid'] = 10  # It is ok
        self._data['undefined'] = 10  # TypedDict "UserData" has no key 'undefined'
```
I'm trying to use bound TypeVar as default value for Generic because I need to extend TypedDict with some fields for every subclass and use this fields at overridden methods.

```
mypy==0.770
typing-extensions==3.7.4.1
Python 3.8.0




Discussion:
IIUC the actual issue is the false positive `# error: Unsupported target for indexed assignment`.

I think this should be not hard to fix. Looks like a missing corner case in type checking indexed assignments.

Resolving patch:
diff --git a/mypy/checker.py b/mypy/checker.py
--- a/mypy/checker.py
+++ b/mypy/checker.py
@@ -3049,8 +3049,15 @@ def check_indexed_assignment(self, lvalue: IndexExpr,
         """
         self.try_infer_partial_type_from_indexed_assignment(lvalue, rvalue)
         basetype = get_proper_type(self.expr_checker.accept(lvalue.base))
-        if isinstance(basetype, TypedDictType):
-            item_type = self.expr_checker.visit_typeddict_index_expr(basetype, lvalue.index)
+        if (isinstance(basetype, TypedDictType) or (isinstance(basetype, TypeVarType)
+                and isinstance(get_proper_type(basetype.upper_bound), TypedDictType))):
+            if isinstance(basetype, TypedDictType):
+                typed_dict_type = basetype
+            else:
+                upper_bound_type = get_proper_type(basetype.upper_bound)
+                assert isinstance(upper_bound_type, TypedDictType)
+                typed_dict_type = upper_bound_type
+            item_type = self.expr_checker.visit_typeddict_index_expr(typed_dict_type, lvalue.index)
             method_type = CallableType(
                 arg_types=[self.named_type('builtins.str'), item_type],
                 arg_kinds=[ARG_POS, ARG_POS],


Test patch:
diff --git a/test-data/unit/check-typeddict.test b/test-data/unit/check-typeddict.test
--- a/test-data/unit/check-typeddict.test
+++ b/test-data/unit/check-typeddict.test
@@ -2022,3 +2022,23 @@ class A:
 reveal_type(A().b)  # N: Revealed type is 'Any'
 [builtins fixtures/dict.pyi]
 [typing fixtures/typing-typeddict.pyi]
+
+[case testTypedDictAsUpperBoundAndIndexedAssign]
+from typing import TypeVar, Generic, TypedDict
+
+
+class BaseDict(TypedDict, total=False):
+    foo: int
+
+
+_DICT_T = TypeVar('_DICT_T', bound=BaseDict)
+
+
+class SomeGeneric(Generic[_DICT_T]):
+    def __init__(self, data: _DICT_T) -> None:
+        self._data: _DICT_T = data
+
+    def set_state(self) -> None:
+        self._data['foo'] = 1
+[builtins fixtures/dict.pyi]
+[typing fixtures/typing-typeddict.pyi]