{"record":{"id":"a4f02bff76e52781","repo":"NationalSecurityAgency/ghidra","slug":"unions-not-allowed-except-with-none-for-optional","errorCode":null,"errorMessage":"Unions not allowed except with None (for Optional)","messagePattern":"Unions not allowed except with None \\(for Optional\\)","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"Ghidra/Debug/Debugger-rmi-trace/src/main/py/src/ghidratrace/client.py","lineNumber":594,"sourceCode":"    action: str\n    display: Optional[str]\n    icon: Optional[str]\n    ok_text: Optional[str]\n    description: Optional[str]\n    parameters: List[RemoteParameter]\n    return_schema: sch.Schema\n    callback: Callable\n\n\nC = TypeVar('C', bound=Callable)\n\n\ndef unopt_type(t: type) -> type:\n    if not get_origin(t) is Union:\n        return t\n    sub = [a for a in get_args(t) if a is not type(None)]\n    if len(sub) != 1:\n        raise TypeError(\"Unions not allowed except with None (for Optional)\")\n    return unopt_type(sub[0])\n\n\ndef find_metadata(annotation: Any, cls: type[T]) -> Tuple[Any, Optional[T]]:\n    if not hasattr(annotation, '__metadata__'):\n        return unopt_type(annotation), None\n    for m in annotation.__metadata__:\n        if isinstance(m, cls):\n            return unopt_type(annotation.__origin__), m\n    return unopt_type(annotation.__origin__), None\n\n\nclass MethodRegistry(object):\n\n    def __init__(self, executor: Executor) -> None:\n        self._methods: Dict[str, RemoteMethod] = {}\n        self._executor: Executor = executor\n","sourceCodeStart":576,"sourceCodeEnd":612,"githubUrl":"https://github.com/NationalSecurityAgency/ghidra/blob/d5f144c24d6bc53c9cbf4448c6d11143e7696206/Ghidra/Debug/Debugger-rmi-trace/src/main/py/src/ghidratrace/client.py#L576-L612","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Narrow the annotation to a single type (e.g. accept int and resolve paths separately), or use Optional[T].","Use a dedicated type like TraceObject/Union[int,str]-free wrapper that the schema layer can map (see _to_schema's supported types).","Re-register the method after correcting the annotation; the error is raised once at registration."],"exampleFix":"# before\n@method\ndef thing(self, ref: Union[int, str]) -> None: ...\n# after\n@method\ndef thing(self, ref: int) -> None: ...  # or use Optional[int]","handlingStrategy":"validation","validationCode":"from typing import get_args, get_origin, Union\nif get_origin(t) is Union:\n    non_none = [a for a in get_args(t) if a is not type(None)]\n    if len(non_none) != 1:\n        raise TypeError(f'Union with multiple non-None types not allowed: {t}')","typeGuard":"def is_optional_only(t) -> bool:\n    from typing import get_args, get_origin, Union\n    if get_origin(t) is not Union:\n        return True\n    return len([a for a in get_args(t) if a is not type(None)]) == 1","tryCatchPattern":null,"preventionTips":["Use Optional[T] or a single concrete type for registered method params.","Run a smoke registration test for new methods."],"tags":["type-system","trace-rmi","method-registration","validation"],"backgroundTag":null,"analyzedSha":"d5f144c24d6bc53c9cbf4448c6d11143e7696206","analyzedAt":"2026-08-14T01:00:57.564Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}