enumerate.__next__ needs more context than is available to have a return type more specific than Tuple[int, Any], so I believe mypy itself would need to be modified to make the inference that enumerate(actions) produces Tuple[int,Union[str,int]] values.
Until that happens, you can explicitly cast the value of action before passing it to act.
from typing import Union, cast
StrOrInt = Union[str, int]
def process(actions: Union[list[str], list[int]]) -> None:
for pos, action in enumerate(actions):
act(cast(StrOrInt, action))
def act(action: Union[str, int]) -> None:
print(action)
You can also make process generic (which now that I've thought of it, is probably a better idea, as it avoids the overhead of calling cast at runtime).
from typing import Union, cast, Iterable, TypeVar
T = TypeVar("T", str, int)
def process(actions: Iterable[T]) -> None:
for pos, action in enumerate(actions):
act(action)
def act(action: T) -> None:
print(action)
Here, T is not a union of types, but a single concrete type whose identity is fixed by the call to process. Iterable[T] is either Iterable[str] or Iterable[int], depending on which type you pass to process. That fixes T for the rest of the call to process, which every call to act must take the same type of argument.
An Iterable[str] or an Iterable[int] is a valid argument, binding T to int or str in the process. Now enumerate.__next__ apparently can have a specific return type Tuple[int, T].