Alternative for Union Aliases in Py3.14+
Right now, plum has the concept of “union aliases” managed via set_union_alias, activate_union_aliases, and deactivate_union_aliases. These are essentially a runtime mechanism to treat a particular Union[...] as if it had a stable, named alias for dispatch and introspection, without that alias necessarily appearing in the function’s source-level annotations.
In Python 3.14+ this is broken (#224).
There might be a workable alternative. With Python 3.13’s typing.TypeAliasType, we can define custom aliases and swap them for Union objects. This will work, however we might lose the ability to have activation/deactivation toggles.
When plum.dispatch is called, plum already walks the annotations to build its internal signature representation. At that ingestion point, plum can:
- Look up each annotated type in the existing union alias registry (populated by
set_union_alias). - If an annotation is a
Union[...](or equivalentX | Y) that is registered under some alias nameAliasName, dynamically create aTypeAliasTypeobject:
from typing import TypeAliasType
AliasName = TypeAliasType(
name="AliasName",
value=original_union_type, # e.g. int | str
type_params=(),
)
- Replace the
Unionannotation in the ingested signature with thisTypeAliasTypeinstance.
From plum’s perspective, the function now genuinely appears to be annotated with a named alias type, even though the original source used Union[...] directly. Dispatch and introspection can key off that alias type instead of pattern-matching on the underlying union structure.
This transformation is essentially one-way:
- Once plum has rewritten a
Union[...]annotation into aTypeAliasType("AliasName", union_value, ()), the stored signature no longer contains the original raw Union anywhere; it only knows about the alias. - The alias is still semantically tied to the union via
AliasName.__value__, but “going back” to a state where the signature refers toUnion[...]instead of the alias would require explicitly undoing the rewrite and re-injecting the bare union. That might be possible, but it's not guaranteed to be a safe reverse mapping.
@nstarman This is very clever! I like this a lot. This actually sounds much better than attempting to override Union.__repr__.
I'm thinking that this transformation could happen inside resolve_type_hint. Since this transformation should be safe, we could even do this by default and not require an explicit activation of the functionality. It's a shame TypeAliasType is only available for the newer Python versions. We could even consider dropping support for union aliasing for earlier Python versions than 3.12. How would that sound?