NationalSecurityAgency/ghidra · error · TypeError

Unions not allowed except with None (for Optional)

Error message

Unions not allowed except with None (for Optional)

What it means

Raised as TypeError by unopt_type() when a method annotation is a typing.Union that contains more than one non-None member. The RMI method-registration machinery only allows Optional[T] (i.e. Union[T, None]) so each parameter maps to exactly one wire schema; a genuine multi-type Union like Union[int,str] is ambiguous and rejected at registration time.

Source

Thrown at Ghidra/Debug/Debugger-rmi-trace/src/main/py/src/ghidratrace/client.py:594

    action: str
    display: Optional[str]
    icon: Optional[str]
    ok_text: Optional[str]
    description: Optional[str]
    parameters: List[RemoteParameter]
    return_schema: sch.Schema
    callback: Callable


C = TypeVar('C', bound=Callable)


def unopt_type(t: type) -> type:
    if not get_origin(t) is Union:
        return t
    sub = [a for a in get_args(t) if a is not type(None)]
    if len(sub) != 1:
        raise TypeError("Unions not allowed except with None (for Optional)")
    return unopt_type(sub[0])


def find_metadata(annotation: Any, cls: type[T]) -> Tuple[Any, Optional[T]]:
    if not hasattr(annotation, '__metadata__'):
        return unopt_type(annotation), None
    for m in annotation.__metadata__:
        if isinstance(m, cls):
            return unopt_type(annotation.__origin__), m
    return unopt_type(annotation.__origin__), None


class MethodRegistry(object):

    def __init__(self, executor: Executor) -> None:
        self._methods: Dict[str, RemoteMethod] = {}
        self._executor: Executor = executor

View on GitHub (pinned to d5f144c24d)

Solutions

  1. Narrow the annotation to a single type (e.g. accept int and resolve paths separately), or use Optional[T].
  2. Use a dedicated type like TraceObject/Union[int,str]-free wrapper that the schema layer can map (see _to_schema's supported types).
  3. Re-register the method after correcting the annotation; the error is raised once at registration.

Example fix

# before
@method
def thing(self, ref: Union[int, str]) -> None: ...
# after
@method
def thing(self, ref: int) -> None: ...  # or use Optional[int]
Defensive patterns

Strategy: validation

Validate before calling

from typing import get_args, get_origin, Union
if get_origin(t) is Union:
    non_none = [a for a in get_args(t) if a is not type(None)]
    if len(non_none) != 1:
        raise TypeError(f'Union with multiple non-None types not allowed: {t}')

Type guard

def is_optional_only(t) -> bool:
    from typing import get_args, get_origin, Union
    if get_origin(t) is not Union:
        return True
    return len([a for a in get_args(t) if a is not type(None)]) == 1

Prevention

When it happens

Trigger: Decorating/registering a method whose signature uses Union[int, str] or Union[A, B] (with two real types). The error surfaces when the method registry inspects annotations, not at call time.

Common situations: Writing a custom TraceRmi method that accepts 'either an id or a path' and annotating it as Union[int,str] instead of using a single normalized type; copying a general-purpose helper signature into a registered method.

Related errors


AI-assisted analysis of NationalSecurityAgency/ghidra@d5f144c24d (2026-08-14). Data as JSON: /api/errors/a4f02bff76e52781. Report an issue: GitHub.