bpo-32873: Treat type variables and special typing forms as immutable by copy and pickle (GH-6216)


This also fixes python/typingGH-512
This also fixes python/typingGH-511

As was discussed in both issues, some typing forms deserve to be treated
as immutable by copy and pickle modules, so that:
* copy(X) is X
* deepcopy(X) is X
* loads(dumps(X)) is X  GH- pickled by reference

This PR adds such behaviour to:
* Type variables
* Special forms like Union, Any, ClassVar
* Unsubscripted generic aliases to containers like List, Mapping, Iterable

This not only resolves inconsistencies mentioned in the issues, but also
improves backwards compatibility with previous versions of Python
(including 3.6).

Note that this requires some dances with __module__ for type variables
(similar to NamedTuple) because the class TypeVar itself is define in typing,
while type variables should get module where they were defined.

https://bugs.python.org/issue32873
(cherry picked from commit 834940375ae88bc95794226dd8eff1f25fba1cf9)

Co-authored-by: Ivan Levkivskyi <levkivskyi@gmail.com>
diff --git a/Lib/typing.py b/Lib/typing.py
index 56126cf..510574c 100644
--- a/Lib/typing.py
+++ b/Lib/typing.py
@@ -285,8 +285,17 @@
         if '_root' not in kwds:
             raise TypeError("Cannot subclass special typing classes")
 
+class _Immutable:
+    """Mixin to indicate that object should not be copied."""
 
-class _SpecialForm(_Final, _root=True):
+    def __copy__(self):
+        return self
+
+    def __deepcopy__(self, memo):
+        return self
+
+
+class _SpecialForm(_Final, _Immutable, _root=True):
     """Internal indicator of special typing constructs.
     See _doc instance attribute for specific docs.
     """
@@ -328,8 +337,8 @@
     def __repr__(self):
         return 'typing.' + self._name
 
-    def __copy__(self):
-        return self  # Special forms are immutable.
+    def __reduce__(self):
+        return self._name
 
     def __call__(self, *args, **kwds):
         raise TypeError(f"Cannot instantiate {self!r}")
@@ -496,7 +505,11 @@
         return f'ForwardRef({self.__forward_arg__!r})'
 
 
-class TypeVar(_Final, _root=True):
+def _find_name(mod, name):
+    return getattr(sys.modules[mod], name)
+
+
+class TypeVar(_Final, _Immutable, _root=True):
     """Type variable.
 
     Usage::
@@ -536,10 +549,12 @@
       T.__covariant__ == False
       T.__contravariant__ = False
       A.__constraints__ == (str, bytes)
+
+    Note that only type variables defined in global scope can be pickled.
     """
 
     __slots__ = ('__name__', '__bound__', '__constraints__',
-                 '__covariant__', '__contravariant__')
+                 '__covariant__', '__contravariant__', '_def_mod')
 
     def __init__(self, name, *constraints, bound=None,
                  covariant=False, contravariant=False):
@@ -558,6 +573,7 @@
             self.__bound__ = _type_check(bound, "Bound must be a type.")
         else:
             self.__bound__ = None
+        self._def_mod = sys._getframe(1).f_globals['__name__']  # for pickling
 
     def __getstate__(self):
         return {'name': self.__name__,
@@ -582,6 +598,9 @@
             prefix = '~'
         return prefix + self.__name__
 
+    def __reduce__(self):
+        return (_find_name, (self._def_mod, self.__name__))
+
 
 # Special typing constructs Union, Optional, Generic, Callable and Tuple
 # use three special attributes for internal bookkeeping of generic types:
@@ -724,6 +743,11 @@
         raise TypeError("Subscripted generics cannot be used with"
                         " class and instance checks")
 
+    def __reduce__(self):
+        if self._special:
+            return self._name
+        return super().__reduce__()
+
 
 class _VariadicGenericAlias(_GenericAlias, _root=True):
     """Same as _GenericAlias above but for variadic aliases. Currently,