Repository: python/mypy

Issue:
Variable `typing_extensions.OrderedDict` is not valid as a type
```py
from typing_extensions import OrderedDict

a: OrderedDict[str, str] = {}  # error: Variable "typing_extensions.OrderedDict" is not valid as a type

class A(OrderedDict[str, str]):  #  error: Invalid base class "OrderedDict"
    ...
```
mypy 0.920+dev.6b9cc4774f7dbd556a655071800a8e17e368282b



Discussion:
This line might be the root cause: 

https://github.com/python/typeshed/blob/59408d9c9cd1d25e4083707893a1b2f2965c2c0a/stdlib/typing_extensions.pyi#L72-L74

I will try to fix it.
Yes, this code fixes this issue:

```python
if sys.version_info >= (3, 8):
    from typing import OrderedDict as OrderedDict
else:
    import collections

    class OrderedDict(
        collections.OrderedDict,
        typing.MutableMapping[_T, _KV],
    ):
        ...
```

This is a `typeshed` issue. I will open a PR there, feel free to close this issue!

Thanks for the report 👍 
You have contributed a lot of great bug reports! 
Or maybe it is not a typeshed issue 🤔 

Because all other types are also `_Alias()`. But, they work just fine, because `mypy` has special treament for them.
I need some more research.
Yes, this is solvable on mypy's side 👍 

Resolving patch:
diff --git a/mypy/nodes.py b/mypy/nodes.py
--- a/mypy/nodes.py
+++ b/mypy/nodes.py
@@ -135,6 +135,12 @@ def get_column(self) -> int:
     'typing.OrderedDict': (3, 7),
 }
 
+# This keeps track of aliases in `typing_extensions`, which we treat specially.
+typing_extensions_aliases: Final = {
+    # See: https://github.com/python/mypy/issues/11528
+    'typing_extensions.OrderedDict': 'collections.OrderedDict',
+}
+
 reverse_builtin_aliases: Final = {
     'builtins.list': 'typing.List',
     'builtins.dict': 'typing.Dict',
diff --git a/mypy/semanal.py b/mypy/semanal.py
--- a/mypy/semanal.py
+++ b/mypy/semanal.py
@@ -75,6 +75,7 @@
     PlaceholderNode, COVARIANT, CONTRAVARIANT, INVARIANT,
     get_nongen_builtins, get_member_expr_fullname, REVEAL_TYPE,
     REVEAL_LOCALS, is_final_node, TypedDictExpr, type_aliases_source_versions,
+    typing_extensions_aliases,
     EnumCallExpr, RUNTIME_PROTOCOL_DECOS, FakeExpression, Statement, AssignmentExpr,
     ParamSpecExpr, EllipsisExpr,
     FuncBase, implicit_module_attrs,
@@ -312,9 +313,12 @@ def prepare_file(self, file_node: MypyFile) -> None:
         if file_node.fullname == 'builtins':
             self.prepare_builtins_namespace(file_node)
         if file_node.fullname == 'typing':
-            self.prepare_typing_namespace(file_node)
+            self.prepare_typing_namespace(file_node, type_aliases)
+        if file_node.fullname == 'typing_extensions':
+            self.prepare_typing_namespace(file_node, typing_extensions_aliases)
 
-    def prepare_typing_namespace(self, file_node: MypyFile) -> None:
+    def prepare_typing_namespace(self, file_node: MypyFile,
+                                 aliases: Dict[str, str]) -> None:
         """Remove dummy alias definitions such as List = TypeAlias(object) from typing.
 
         They will be replaced with real aliases when corresponding targets are ready.
@@ -336,7 +340,7 @@ def helper(defs: List[Statement]) -> None:
                 if (isinstance(stmt, AssignmentStmt) and len(stmt.lvalues) == 1 and
                         isinstance(stmt.lvalues[0], NameExpr)):
                     # Assignment to a simple name, remove it if it is a dummy alias.
-                    if 'typing.' + stmt.lvalues[0].name in type_aliases:
+                    if f'{file_node.fullname}.{stmt.lvalues[0].name}' in aliases:
                         defs.remove(stmt)
 
         helper(file_node.defs)
@@ -413,6 +417,8 @@ def refresh_top_level(self, file_node: MypyFile) -> None:
             self.accept(d)
         if file_node.fullname == 'typing':
             self.add_builtin_aliases(file_node)
+        if file_node.fullname == 'typing_extensions':
+            self.add_typing_extension_aliases(file_node)
         self.adjust_public_exports()
         self.export_map[self.cur_mod_id] = self.all_exports
         self.all_exports = []
@@ -474,30 +480,53 @@ def add_builtin_aliases(self, tree: MypyFile) -> None:
             name = alias.split('.')[-1]
             if name in tree.names and not isinstance(tree.names[name].node, PlaceholderNode):
                 continue
-            tag = self.track_incomplete_refs()
-            n = self.lookup_fully_qualified_or_none(target_name)
-            if n:
-                if isinstance(n.node, PlaceholderNode):
-                    self.mark_incomplete(name, tree)
-                else:
-                    # Found built-in class target. Create alias.
-                    target = self.named_type_or_none(target_name, [])
-                    assert target is not None
-                    # Transform List to List[Any], etc.
-                    fix_instance_types(target, self.fail, self.note, self.options.python_version)
-                    alias_node = TypeAlias(target, alias,
-                                           line=-1, column=-1,  # there is no context
-                                           no_args=True, normalized=True)
-                    self.add_symbol(name, alias_node, tree)
-            elif self.found_incomplete_ref(tag):
-                # Built-in class target may not ready yet -- defer.
+            self.create_alias(tree, target_name, alias, name)
+
+    def add_typing_extension_aliases(self, tree: MypyFile) -> None:
+        """Typing extensions module does contain some type aliases.
+
+        We need to analyze them as such, because in typeshed
+        they are just defined as `_Alias()` call.
+        Which is not supported natively.
+        """
+        assert tree.fullname == 'typing_extensions'
+
+        for alias, target_name in typing_extensions_aliases.items():
+            name = alias.split('.')[-1]
+            if name in tree.names and isinstance(tree.names[name].node, TypeAlias):
+                continue  # Do not reset TypeAliases on the second pass.
+
+            # We need to remove any node that is there at the moment. It is invalid.
+            tree.names.pop(name, None)
+
+            # Now, create a new alias.
+            self.create_alias(tree, target_name, alias, name)
+
+    def create_alias(self, tree: MypyFile, target_name: str, alias: str, name: str) -> None:
+        tag = self.track_incomplete_refs()
+        n = self.lookup_fully_qualified_or_none(target_name)
+        if n:
+            if isinstance(n.node, PlaceholderNode):
                 self.mark_incomplete(name, tree)
             else:
-                # Test fixtures may be missing some builtin classes, which is okay.
-                # Kill the placeholder if there is one.
-                if name in tree.names:
-                    assert isinstance(tree.names[name].node, PlaceholderNode)
-                    del tree.names[name]
+                # Found built-in class target. Create alias.
+                target = self.named_type_or_none(target_name, [])
+                assert target is not None
+                # Transform List to List[Any], etc.
+                fix_instance_types(target, self.fail, self.note, self.options.python_version)
+                alias_node = TypeAlias(target, alias,
+                                       line=-1, column=-1,  # there is no context
+                                       no_args=True, normalized=True)
+                self.add_symbol(name, alias_node, tree)
+        elif self.found_incomplete_ref(tag):
+            # Built-in class target may not ready yet -- defer.
+            self.mark_incomplete(name, tree)
+        else:
+            # Test fixtures may be missing some builtin classes, which is okay.
+            # Kill the placeholder if there is one.
+            if name in tree.names:
+                assert isinstance(tree.names[name].node, PlaceholderNode)
+                del tree.names[name]
 
     def adjust_public_exports(self) -> None:
         """Adjust the module visibility of globals due to __all__."""


Test patch:
diff --git a/test-data/unit/pythoneval.test b/test-data/unit/pythoneval.test
--- a/test-data/unit/pythoneval.test
+++ b/test-data/unit/pythoneval.test
@@ -1557,3 +1557,19 @@ _testNoPython3StubAvailable.py:1: note: See https://mypy.readthedocs.io/en/stabl
 _testNoPython3StubAvailable.py:3: error: Library stubs not installed for "maxminddb" (or incompatible with Python 3.6)
 _testNoPython3StubAvailable.py:3: note: Hint: "python3 -m pip install types-maxminddb"
 _testNoPython3StubAvailable.py:3: note: (or run "mypy --install-types" to install all missing stub packages)
+
+
+[case testTypingOrderedDictAlias]
+# flags: --python-version 3.7
+from typing import OrderedDict
+x: OrderedDict[str, int] = OrderedDict({})
+reveal_type(x)
+[out]
+_testTypingOrderedDictAlias.py:4: note: Revealed type is "collections.OrderedDict[builtins.str, builtins.int]"
+
+[case testTypingExtensionsOrderedDictAlias]
+from typing_extensions import OrderedDict
+x: OrderedDict[str, str] = OrderedDict({})
+reveal_type(x)  # Revealed type is "collections.OrderedDict[builtins.str, builtins.int]"
+[out]
+_testTypingExtensionsOrderedDictAlias.py:3: note: Revealed type is "collections.OrderedDict[builtins.str, builtins.str]"