Further 3.12 compatibility fixes (#164)

Make our TypeAliasType behave exactly like the 3.12 one
diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py
index f7820ec..8d0ed9d 100644
--- a/src/test_typing_extensions.py
+++ b/src/test_typing_extensions.py
@@ -4745,30 +4745,64 @@
         self.assertEqual(Variadic.__type_params__, (Ts,))
         self.assertEqual(Variadic.__parameters__, tuple(iter(Ts)))
 
-    def test_immutable(self):
+    def test_cannot_set_attributes(self):
         Simple = TypeAliasType("Simple", int)
-        with self.assertRaisesRegex(AttributeError, "Can't set attribute"):
+        with self.assertRaisesRegex(AttributeError, "readonly attribute"):
             Simple.__name__ = "NewName"
-        with self.assertRaisesRegex(AttributeError, "Can't set attribute"):
+        with self.assertRaisesRegex(
+            AttributeError,
+            "attribute '__value__' of 'typing.TypeAliasType' objects is not writable",
+        ):
             Simple.__value__ = str
-        with self.assertRaisesRegex(AttributeError, "Can't set attribute"):
+        with self.assertRaisesRegex(
+            AttributeError,
+            "attribute '__type_params__' of 'typing.TypeAliasType' objects is not writable",
+        ):
             Simple.__type_params__ = (T,)
-        with self.assertRaisesRegex(AttributeError, "Can't set attribute"):
+        with self.assertRaisesRegex(
+            AttributeError,
+            "attribute '__parameters__' of 'typing.TypeAliasType' objects is not writable",
+        ):
             Simple.__parameters__ = (T,)
-        with self.assertRaisesRegex(AttributeError, "Can't set attribute"):
+        with self.assertRaisesRegex(
+            AttributeError,
+            "attribute '__module__' of 'typing.TypeAliasType' objects is not writable",
+        ):
+            Simple.__module__ = 42
+        with self.assertRaisesRegex(
+            AttributeError,
+            "'typing.TypeAliasType' object has no attribute 'some_attribute'",
+        ):
             Simple.some_attribute = "not allowed"
-        with self.assertRaisesRegex(AttributeError, "Can't delete attribute"):
+
+    def test_cannot_delete_attributes(self):
+        Simple = TypeAliasType("Simple", int)
+        with self.assertRaisesRegex(AttributeError, "readonly attribute"):
             del Simple.__name__
-        with self.assertRaisesRegex(AttributeError, "Can't delete attribute"):
-            del Simple.nonexistent_attribute
+        with self.assertRaisesRegex(
+            AttributeError,
+            "attribute '__value__' of 'typing.TypeAliasType' objects is not writable",
+        ):
+            del Simple.__value__
+        with self.assertRaisesRegex(
+            AttributeError,
+            "'typing.TypeAliasType' object has no attribute 'some_attribute'",
+        ):
+            del Simple.some_attribute
 
     def test_or(self):
         Alias = TypeAliasType("Alias", int)
         if sys.version_info >= (3, 10):
-            self.assertEqual(Alias | "Ref", Union[Alias, typing.ForwardRef("Ref")])
+            self.assertEqual(Alias | int, Union[Alias, int])
+            self.assertEqual(Alias | None, Union[Alias, None])
+            self.assertEqual(Alias | (int | str), Union[Alias, int | str])
+            self.assertEqual(Alias | list[float], Union[Alias, list[float]])
         else:
             with self.assertRaises(TypeError):
-                Alias | "Ref"
+                Alias | int
+        # Rejected on all versions
+        with self.assertRaises(TypeError):
+            Alias | "Ref"
 
     def test_getitem(self):
         ListOrSetT = TypeAliasType("ListOrSetT", Union[List[T], Set[T]], type_params=(T,))
diff --git a/src/typing_extensions.py b/src/typing_extensions.py
index ff5aefe..2a635ba 100644
--- a/src/typing_extensions.py
+++ b/src/typing_extensions.py
@@ -1061,9 +1061,6 @@
 if hasattr(typing, "Required"):
     get_type_hints = typing.get_type_hints
 else:
-    import functools
-    import types
-
     # replaces _strip_annotations()
     def _strip_extras(t):
         """Strips Annotated, Required and NotRequired from a given type."""
@@ -1076,12 +1073,12 @@
             if stripped_args == t.__args__:
                 return t
             return t.copy_with(stripped_args)
-        if hasattr(types, "GenericAlias") and isinstance(t, types.GenericAlias):
+        if hasattr(_types, "GenericAlias") and isinstance(t, _types.GenericAlias):
             stripped_args = tuple(_strip_extras(a) for a in t.__args__)
             if stripped_args == t.__args__:
                 return t
-            return types.GenericAlias(t.__origin__, stripped_args)
-        if hasattr(types, "UnionType") and isinstance(t, types.UnionType):
+            return _types.GenericAlias(t.__origin__, stripped_args)
+        if hasattr(_types, "UnionType") and isinstance(t, _types.UnionType):
             stripped_args = tuple(_strip_extras(a) for a in t.__args__)
             if stripped_args == t.__args__:
                 return t
@@ -2691,6 +2688,15 @@
 if hasattr(typing, "TypeAliasType"):
     TypeAliasType = typing.TypeAliasType
 else:
+    def _is_unionable(obj):
+        """Corresponds to is_unionable() in unionobject.c in CPython."""
+        return obj is None or isinstance(obj, (
+            type,
+            _types.GenericAlias,
+            _types.UnionType,
+            TypeAliasType,
+        ))
+
     class TypeAliasType:
         """Create named, parameterized type aliases.
 
@@ -2740,15 +2746,25 @@
 
         def __setattr__(self, __name: str, __value: object) -> None:
             if hasattr(self, "__name__"):
-                raise AttributeError(
-                    f"Can't set attribute {__name!r} on an instance of TypeAliasType"
-                )
+                self._raise_attribute_error(__name)
             super().__setattr__(__name, __value)
 
-        def __delattr__(self, __name: str) -> None:
-            raise AttributeError(
-                f"Can't delete attribute {__name!r} on an instance of TypeAliasType"
-            )
+        def __delattr__(self, __name: str) -> Never:
+            self._raise_attribute_error(__name)
+
+        def _raise_attribute_error(self, name: str) -> Never:
+            # Match the Python 3.12 error messages exactly
+            if name == "__name__":
+                raise AttributeError("readonly attribute")
+            elif name in {"__value__", "__type_params__", "__parameters__", "__module__"}:
+                raise AttributeError(
+                    f"attribute '{name}' of 'typing.TypeAliasType' objects "
+                    "is not writable"
+                )
+            else:
+                raise AttributeError(
+                    f"'typing.TypeAliasType' object has no attribute '{name}'"
+                )
 
         def __repr__(self) -> str:
             return self.__name__
@@ -2779,7 +2795,13 @@
 
         if sys.version_info >= (3, 10):
             def __or__(self, right):
+                # For forward compatibility with 3.12, reject Unions
+                # that are not accepted by the built-in Union.
+                if not _is_unionable(right):
+                    return NotImplemented
                 return typing.Union[self, right]
 
             def __ror__(self, left):
+                if not _is_unionable(left):
+                    return NotImplemented
                 return typing.Union[left, self]