Repository: pallets/click (python)

Issue:
`ParamType` missing `name` errors on `to_info_dict`
When calling `to_info_dict` on a `ParamType` instance which doesn't have a `name` attribute, an exception is raised. While this is a bug in the downstream code, only `to_info_dict` is affected, so the bug often goes unnoticed.

```py3
class TestType(click.ParamType):
    pass

TestType().to_info_dict()
```

```
>       return {"param_type": param_type, "name": self.name}
E       AttributeError: 'TestType' object has no attribute 'name'

src/click/types.py:66: AttributeError
```

It would be nice to have some sort of reasonable fallback. I've put coded such possibility at https://github.com/pallets/click/compare/main...phy1729:fix-ParamType-no-name . I also fixed where the docs neglected to specify `name` in an example.

Environment:

- Python version: Python 3.9.9
- Click version: 8.0.3


Pull request description:
fix ParamType.to_info_dict() with no name
fixes #2168

Patch:
diff --git a/CHANGES.rst b/CHANGES.rst
index 0a9661a..91b9ec1 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -48,6 +48,8 @@ Version 8.1.0
 -   A ``Group`` with ``invoke_without_command=True`` and ``chain=False``
     will invoke its result callback with the group function's return
     value. :issue:`2124`
+-   ``to_info_dict`` will not fail if a ``ParamType`` doesn't define a
+    ``name``. :issue:`2168`
 
 
 Version 8.0.4
diff --git a/docs/shell-completion.rst b/docs/shell-completion.rst
index 699d25d..ebf73c3 100644
--- a/docs/shell-completion.rst
+++ b/docs/shell-completion.rst
@@ -132,6 +132,8 @@ with the incomplete value.
 .. code-block:: python
 
     class EnvVarType(ParamType):
+        name = "envvar"
+
         def shell_complete(self, ctx, param, incomplete):
             return [
                 CompletionItem(name)
diff --git a/src/click/types.py b/src/click/types.py
index aab0656..3a78a3a 100644
--- a/src/click/types.py
+++ b/src/click/types.py
@@ -63,7 +63,14 @@ class ParamType:
         # The class name without the "ParamType" suffix.
         param_type = type(self).__name__.partition("ParamType")[0]
         param_type = param_type.partition("ParameterType")[0]
-        return {"param_type": param_type, "name": self.name}
+
+        # Custom subclasses might not remember to set a name.
+        if hasattr(self, "name"):
+            name = self.name
+        else:
+            name = param_type
+
+        return {"param_type": param_type, "name": name}
 
     def __call__(
         self,


Test patch:
diff --git a/tests/test_info_dict.py b/tests/test_info_dict.py
index b58ad6e..79d39ee 100644
--- a/tests/test_info_dict.py
+++ b/tests/test_info_dict.py
@@ -266,3 +266,10 @@ def test_context():
         "ignore_unknown_options": False,
         "auto_envvar_prefix": None,
     }
+
+
+def test_paramtype_no_name():
+    class TestType(click.ParamType):
+        pass
+
+    assert TestType().to_info_dict()["name"] == "TestType"