Repository: pydantic/pydantic

Issue:
AttributeError: name with frozendict[..., ...] and subclass (maybe Mapping+GenericAlias issue?)
# Bug

Using a [`frozendict`](https://github.com/Marco-Sulla/python-frozendict) as a model field works normally/correctly but when subscripted (eg: `frozendict[str, int]`), pydantic errors in some weird ways:
- the output has a `dict` (similar to #2311 and others, except even converting to `frozendict` explicitly or in a `@validator` doesn't work)
- on model subclasses, the `field.outer_type_` seems to be botched and missing attributes (but works fine on the original defining class)

```py
# pip3 install frozendict
from frozendict import frozendict
from pydantic import BaseModel


class Base(BaseModel):
    a: frozendict
    b: frozendict[str, int]


class Sub(Base):
    pass


def inspect(m: BaseModel):
    print(f"\t{m.__name__}\n")
    print(m(a=frozendict(a=1), b=frozendict(b=1)))
    print(m.__fields__)
    print(m.__fields__["b"].outer_type_)


# This doesn't error, but the sub-typed variant is replaced with a dict and the Field is a Mapping
inspect(Base)
# This errors when inspecting `b` ModelField.outer_type_, seemingly trying to access an uninitialized .name
inspect(Sub)
```
outputs
```
        Base

a=frozendict({'a': 1}) b={'b': 1}
{'a': ModelField(name='a', type=frozendict, required=True), 'b': ModelField(name='b', type=Mapping[str, int], required=True)}
frozendict.core.frozendict[str, int]
        Sub

a=frozendict({'a': 1}) b={'b': 1}
{'a': ModelField(name='a', type=frozendict, required=True), 'b': ModelField(name='b', type=Mapping[str, int], required=True)}
Traceback (most recent call last):
  File "/tmp/pydantic_error.py", line 25, in <module>
    inspect(Sub)
  File "/tmp/pydantic_error.py", line 19, in inspect
    print(m.__fields__["b"].outer_type_)
  File "pydantic/utils.py", line 396, in pydantic.utils.Representation.__repr__
  File "pydantic/utils.py", line 375, in genexpr
  File "pydantic/utils.py", line 375, in genexpr
  File "pydantic/fields.py", line 951, in pydantic.fields.ModelField.__repr_args__
AttributeError: name
```

The `type(frozendict[str, int])` is a `GenericAlias` and otherwise seems to be normal:

```
>>> type(frozendict[str, str])
<class 'types.GenericAlias'>
>>> get_origin(frozendict[str, str])
<class 'frozendict.core.frozendict'>
>>> get_args(frozendict[str, str])
(<class 'str'>, <class 'str'>)
>>>
>>>
>>> type(dict[str, str])
<class 'types.GenericAlias'>
>>> get_origin(dict[str, str])
<class 'dict'>
>>> get_args(dict[str, str])
(<class 'str'>, <class 'str'>)
```

### Checks

* [x] I added a descriptive title to this issue
* [x] I have searched (google, github) for similar issues and couldn't find anything
* [x] I have read and followed [the docs](https://pydantic-docs.helpmanual.io/) and still think this is a bug

Output of `python -c "import pydantic.utils; print(pydantic.utils.version_info())"`:
```
             pydantic version: 1.8.2
            pydantic compiled: True
                 install path: /Users/jacobhayes/src/github.com/replicahq/artigraph/.wt/type-dispatch/.direnv/python-3.9.6/lib/python3.9/site-packages/pydantic
               python version: 3.9.6 (default, Jul 20 2021, 13:51:27)  [Clang 12.0.5 (clang-1205.0.22.9)]
                     platform: macOS-11.4-x86_64-i386-64bit
     optional deps. installed: ['typing-extensions']
```


Discussion:

Resolving patch:
diff --git a/pydantic/fields.py b/pydantic/fields.py
--- a/pydantic/fields.py
+++ b/pydantic/fields.py
@@ -554,13 +554,13 @@ def _type_analysis(self) -> None:  # noqa: C901 (ignore complexity)
             if isinstance(self.type_, type) and isinstance(None, self.type_):
                 self.allow_none = True
             return
-        if origin is Annotated:
+        elif origin is Annotated:
             self.type_ = get_args(self.type_)[0]
             self._type_analysis()
             return
-        if origin is Callable:
+        elif origin is Callable:
             return
-        if is_union(origin):
+        elif is_union(origin):
             types_ = []
             for type_ in get_args(self.type_):
                 if type_ is NoneType:
@@ -580,8 +580,7 @@ def _type_analysis(self) -> None:  # noqa: C901 (ignore complexity)
             else:
                 self.sub_fields = [self._create_sub_type(t, f'{self.name}_{display_as_type(t)}') for t in types_]
             return
-
-        if issubclass(origin, Tuple):  # type: ignore
+        elif issubclass(origin, Tuple):  # type: ignore
             # origin == Tuple without item type
             args = get_args(self.type_)
             if not args:  # plain tuple
@@ -599,8 +598,7 @@ def _type_analysis(self) -> None:  # noqa: C901 (ignore complexity)
                 self.shape = SHAPE_TUPLE
                 self.sub_fields = [self._create_sub_type(t, f'{self.name}_{i}') for i, t in enumerate(args)]
             return
-
-        if issubclass(origin, List):
+        elif issubclass(origin, List):
             # Create self validators
             get_validators = getattr(self.type_, '__get_validators__', None)
             if get_validators:
@@ -636,6 +634,11 @@ def _type_analysis(self) -> None:  # noqa: C901 (ignore complexity)
         elif issubclass(origin, Sequence):
             self.type_ = get_args(self.type_)[0]
             self.shape = SHAPE_SEQUENCE
+        # priority to most common mapping: dict
+        elif origin is dict or origin is Dict:
+            self.key_field = self._create_sub_type(get_args(self.type_)[0], 'key_' + self.name, for_keys=True)
+            self.type_ = get_args(self.type_)[1]
+            self.shape = SHAPE_DICT
         elif issubclass(origin, DefaultDict):
             self.key_field = self._create_sub_type(get_args(self.type_)[0], 'key_' + self.name, for_keys=True)
             self.type_ = get_args(self.type_)[1]
@@ -644,10 +647,6 @@ def _type_analysis(self) -> None:  # noqa: C901 (ignore complexity)
             self.key_field = self._create_sub_type(get_args(self.type_)[0], 'key_' + self.name, for_keys=True)
             self.type_ = int
             self.shape = SHAPE_COUNTER
-        elif issubclass(origin, Dict):
-            self.key_field = self._create_sub_type(get_args(self.type_)[0], 'key_' + self.name, for_keys=True)
-            self.type_ = get_args(self.type_)[1]
-            self.shape = SHAPE_DICT
         elif issubclass(origin, Mapping):
             self.key_field = self._create_sub_type(get_args(self.type_)[0], 'key_' + self.name, for_keys=True)
             self.type_ = get_args(self.type_)[1]


Test patch:
diff --git a/tests/test_main.py b/tests/test_main.py
--- a/tests/test_main.py
+++ b/tests/test_main.py
@@ -2,7 +2,20 @@
 from collections import defaultdict
 from copy import deepcopy
 from enum import Enum
-from typing import Any, Callable, ClassVar, Counter, DefaultDict, Dict, List, Mapping, Optional, Type, get_type_hints
+from typing import (
+    Any,
+    Callable,
+    ClassVar,
+    Counter,
+    DefaultDict,
+    Dict,
+    List,
+    Mapping,
+    Optional,
+    Type,
+    TypeVar,
+    get_type_hints,
+)
 from uuid import UUID, uuid4
 
 import pytest
@@ -1976,6 +1989,27 @@ class Model(BaseModel):
     assert repr(m) == "Model(x={'one': 1, 'two': 2})"
 
 
+@pytest.mark.skipif(sys.version_info < (3, 7), reason='generic classes need 3.7')
+def test_typing_non_coercion_of_dict_subclasses():
+    KT = TypeVar('KT')
+    VT = TypeVar('VT')
+
+    class MyDict(Dict[KT, VT]):
+        def __repr__(self):
+            return f'MyDict({super().__repr__()})'
+
+    class Model(BaseModel):
+        a: MyDict
+        b: MyDict[str, int]
+        c: Dict[str, int]
+        d: Mapping[str, int]
+
+    assert (
+        repr(Model(a=MyDict({'a': 1}), b=MyDict({'a': '1'}), c=MyDict({'a': '1'}), d=MyDict({'a': '1'})))
+        == "Model(a=MyDict({'a': 1}), b=MyDict({'a': 1}), c={'a': 1}, d=MyDict({'a': 1}))"
+    )
+
+
 def test_typing_coercion_defaultdict():
     class Model(BaseModel):
         x: DefaultDict[int, str]