plum icon indicating copy to clipboard operation
plum copied to clipboard

Alternative for Union Aliases in Py3.14+

Open nstarman opened this issue 1 month ago • 1 comments

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:

  1. Look up each annotated type in the existing union alias registry (populated by set_union_alias).
  2. If an annotation is a Union[...] (or equivalent X | Y) that is registered under some alias name AliasName, dynamically create a TypeAliasType object:
from typing import TypeAliasType

AliasName = TypeAliasType(
    name="AliasName",
    value=original_union_type,  # e.g. int | str
    type_params=(),
)
  1. Replace the Union annotation in the ingested signature with this TypeAliasType instance.

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 a TypeAliasType("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 to Union[...] 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 avatar Nov 25 '25 03:11 nstarman

@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?

wesselb avatar Nov 26 '25 14:11 wesselb